Skip to content

Commit

Permalink
add rosetta
Browse files Browse the repository at this point in the history
  • Loading branch information
doronz88 committed Aug 20, 2024
1 parent e942ec7 commit 9ed0cfa
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 51 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/python-app.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,4 @@ jobs:
python -m pip install -U .
- name: Test query
run: |
python -m applecatalog list -q
python -m applecatalog macos list -q
16 changes: 9 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ python3 -m pip install -U -e .
# Usage

```
Usage: python -m applecatalog [OPTIONS] COMMAND [ARGS]...
Usage: applecatalog [OPTIONS] {macos|rosetta} COMMAND [ARGS]...
CLI util for downloading updates from either macos/rosetta seeds
Options:
--help Show this message and exit.
Expand All @@ -41,20 +43,20 @@ Commands:

```shell
# List CommandLineTools
python3 -m applecatalog list -q | grep CLTools
python3 -m applecatalog macos list -q | grep CLTools

# List macOS
python3 -m applecatalog list --macos
python3 -m applecatalog macos list --macos

# Download single component (by given PRODUCT_ID)
python3 -m applecatalog download PRODUCT_ID /tmp
python3 -m applecatalog macos download PRODUCT_ID /tmp

```

## Example output

```
➜ dev applecatalog products list
➜ dev applecatalog macos products list
Product(id='031-17335', version='11.0.0.0', title='Photo Content Catalogs', date=datetime.datetime(2015, 2, 23, 18, 1, 41), basename='PhotoContentCatalogs.smd')
Product(id='031-17334', version='11.0.0.0', title='Photo Content Themes', date=datetime.datetime(2015, 2, 23, 18, 1, 41), basename='PhotoContentThemes.smd')
Product(id='031-18981', version='17.0.0.0', title='Photo Content Catalogs', date=datetime.datetime(2015, 3, 25, 17, 12, 1), basename='PhotoContentCatalogs.smd')
Expand All @@ -78,7 +80,7 @@ Product(id='041-88172', version='2.0.1', title='Dictation Language Update - Chin
Listing macOS images can be done as follows:

```
➜ apple-catalog git:(master) ✗ applecatalog products list --macos
➜ apple-catalog git:(master) ✗ applecatalog macos products list --macos
MacOsProduct(product='061-26578', name=None, build='18F2059', version='10.14.5')
MacOsProduct(product='061-26589', name=None, build='18G103', version='10.14.6')
MacOsProduct(product='041-91758', name=None, build='17G66', version='10.13.6')
Expand All @@ -103,7 +105,7 @@ MacOsProduct(product='071-59953', name='macOS Monterey beta', build='21A5268h',
For Downloading each one you just download their packages as you would for any other product:

```
➜ apple-catalog git:(master) ✗ applecatalog products download 071-59953 ~/Downloads/macos
➜ apple-catalog git:(master) ✗ applecatalog macos products download 071-59953 ~/Downloads/macos
2021-07-08 10:48:27 DoronZ.local root[21530] DEBUG downloading: UpdateBrain.zip
100%|██████████████████████████████████████████████████████████████████████████████████████████| 2.75M/2.75M [00:00<00:00, 44.7MiB/s]
2021-07-08 10:48:27 DoronZ.local root[21530] DEBUG downloading: Info.plist
Expand Down
55 changes: 21 additions & 34 deletions applecatalog/__main__.py
Original file line number Diff line number Diff line change
@@ -1,57 +1,42 @@
import logging
import os
from pathlib import Path
from pprint import pprint

import click
import coloredlogs

from applecatalog.catalog import Catalog, ProductInfo
from applecatalog.catalog import Catalog, MacOsCatalog, RosettaCatalog

coloredlogs.install(level=logging.DEBUG)

logging.getLogger('urllib3.connectionpool').disabled = True
logging.getLogger('charset_normalizer').disabled = True


def extract_package(filename: Path, out_dir: Path) -> None:
assert 0 == os.system(f'pkgutil --expand "{filename}" "{out_dir}"')
payload = os.path.join(out_dir, 'Payload')
assert 0 == os.system(f'tar xf "{payload}" -C "{out_dir}"')


def get_unique_product(match: str) -> ProductInfo:
products = []
for product in Catalog().products(detailed=False):
if product.basename and match in product.basename:
products.append(product)
assert len(products) == 1
return products[0]


def get_xprotect_product() -> ProductInfo:
return get_unique_product('XProtectPayloads')


@click.group()
def cli():
pass
@click.argument('catalog', type=click.Choice(['macos', 'rosetta']))
@click.pass_context
def cli(ctx: click.Context, catalog: str) -> None:
""" CLI util for downloading updates from either macos/rosetta seeds """
ctx.ensure_object(Catalog)
ctx.obj = MacOsCatalog() if catalog == 'macos' else RosettaCatalog()


@cli.command()
def date():
@click.pass_context
def date(ctx: click.Context) -> None:
""" last update date """
print(Catalog().date)
print(ctx.obj.date)


@cli.command('list')
@click.option('--macos', is_flag=True)
@click.pass_context
@click.option('--os', is_flag=True)
@click.option('-q', '--quick', is_flag=True, help='don\'t require extended information')
def list(macos: bool, quick: bool):
def list(ctx: click.Context, os: bool, quick: bool) -> None:
""" list all products """

catalog = Catalog()
if macos:
catalog = ctx.obj
if os:
for k in catalog.macos_products:
print(k)
return
Expand All @@ -60,20 +45,22 @@ def list(macos: bool, quick: bool):


@cli.command('download')
@click.pass_context
@click.argument('product_id')
@click.argument('out_dir', type=click.Path(dir_okay=True, exists=False))
def download(product_id: str, out_dir: str):
def download(ctx: click.Context, product_id: str, out_dir: str) -> None:
""" download a single product packages """
out_dir = Path(out_dir)
out_dir.mkdir(exist_ok=True, parents=True)
Catalog().download(product_id, out_dir)
ctx.obj.download(product_id, out_dir)


@cli.command('info')
@click.argument('product_id')
def info(product_id: str):
@click.pass_context
def info(ctx: click.Context, product_id: str) -> None:
""" query info for a single product """
pprint(Catalog().get_product(product_id))
pprint(ctx.obj.get_product(product_id))


if __name__ == '__main__':
Expand Down
58 changes: 49 additions & 9 deletions applecatalog/catalog.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@

urllib3.disable_warnings()

APPLE_SEED_URL = ('https://swscan.apple.com/content/catalogs/others/index-15seed-15-14-13-12-10.16-10.15-10.14-10.13-'
'10.12-10.11-10.10-10.9-mountainlion-lion-snowleopard-leopard.merged-1.sucatalog.gz')
MacOsProductInfo = namedtuple('MacOsProduct', 'product name build version')
ProductInfo = namedtuple('ProductInfo', 'id version title date basename')

Expand All @@ -36,12 +34,15 @@ def download_file(url: str, out_dir: Path) -> Path:


class Catalog:
URL = ('https://swscan.apple.com/content/catalogs/others/index-15seed-15-14-13-12-10.16-10.15-10.14-10.13-'
'10.12-10.11-10.10-10.9-mountainlion-lion-snowleopard-leopard.merged-1.sucatalog.gz')

def __init__(self):
self._catalog = {}
self.reload()

def reload(self) -> None:
self._catalog = plistlib.loads(requests.get(APPLE_SEED_URL, verify=False).content)
self._catalog = plistlib.loads(requests.get(self.URL, verify=False).content)

@property
def date(self) -> datetime.datetime:
Expand Down Expand Up @@ -72,6 +73,19 @@ def products(self, detailed=True) -> Generator[ProductInfo, None, None]:
for product_id, product in self._catalog['Products'].items():
yield self.get_product_info(product_id, detailed=detailed)

def download(self, product_id: str, out_dir: Path) -> List[Path]:
results = []
product = self._catalog['Products'][product_id]
for package in product['Packages']:
results.append(download_file(package['URL'], out_dir))
return results


class MacOsCatalog(Catalog):
def __init__(self):
self._catalog = {}
self.reload()

@property
def macos_products(self) -> Generator[MacOsProductInfo, None, None]:
for product_id, product in self._catalog['Products'].items():
Expand All @@ -97,9 +111,35 @@ def macos_products(self) -> Generator[MacOsProductInfo, None, None]:
yield MacOsProductInfo(product=product_id, name=name, build=auxinfo.get('BUILD'),
version=auxinfo.get('VERSION'))

def download(self, product_id: str, out_dir: Path) -> List[Path]:
results = []
product = self._catalog['Products'][product_id]
for package in product['Packages']:
results.append(download_file(package['URL'], out_dir))
return results

class RosettaCatalog(Catalog):
URL = 'https://swscan.apple.com/content/catalogs/others/index-rosettaupdateauto-1.sucatalog.gz'

def __init__(self):
self._catalog = {}
self.reload()

@property
def macos_products(self) -> Generator[MacOsProductInfo, None, None]:
for product_id, product in self._catalog['Products'].items():
extended_meta_info = product.get('ExtendedMetaInfo')
if extended_meta_info is None:
continue

if 'InstallAssistantPackageIdentifiers' not in extended_meta_info:
continue

metadata = requests.get(product['Distributions']['English']).text
if 'auxinfo' not in metadata:
continue

name = metadata.split('<title>')[1].split('<')[0]

if name == 'SU_TITLE':
name = None

auxinfo = metadata.split('<auxinfo>')[1].split('</auxinfo>')[0].encode()
auxinfo = plistlib.loads(b'<plist version="1.0">' + auxinfo + b'</plist>')

yield MacOsProductInfo(product=product_id, name=name, build=auxinfo.get('BUILD'),
version=auxinfo.get('VERSION'))

0 comments on commit 9ed0cfa

Please sign in to comment.