From 2eefe1fa1cdbed3fadd983c85173d67fd49ce77a Mon Sep 17 00:00:00 2001 From: AT0myks Date: Sat, 27 May 2023 20:03:07 +0200 Subject: [PATCH] Add `pak` to info result and merge file and url --- README.md | 57 +++++++++++++++++++++---------------------- reolinkfw/__init__.py | 29 +++++++++++----------- reolinkfw/__main__.py | 2 +- 3 files changed, 44 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index 1719ded..07cf8c0 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,6 @@ * [Requirements](#requirements) * [Installation](#installation) * [Usage](#usage) -* [Example](#example) * [Notes](#notes) * [Issues](#issues) @@ -59,9 +58,36 @@ pip install reolinkfw ### Command line ``` -reolinkfw info file_or_url +$ reolinkfw info file_or_url ``` +Example: + +``` +$ reolinkfw info RLC-410-5MP_20_20052300.zip -i 2 +[ + { + "firmware_version_prefix": "v3.0.0", + "board_type": "IPC_51516M5M", + "board_name": "IPC_51516M5M", + "build_date": "200523", + "display_type_info": "RLC-410-5MP", + "detail_machine_type": "IPC_51516M5M", + "type": "IPC", + "version_file": "20_20052300", + "sha256": "6ef371a51b61d7b21d8f7016d90b5fc1ed3eaa8a3f30f1e202a3474bfb4807e5", + "file": "RLC-410-5MP_20_20052300.zip", + "pak": "IPC_51516M5M.20_20052300.RLC-410-5MP.OV05A10.5MP.REOLINK.pak" + } +] +``` + +`file` is the given argument, a file or URL. The value of `pak` depends on the +argument. If it's a local or remote ZIP file it will be the path of the PAK file +inside it. If it's a remote PAK file, it will be the value of the `name` query +parameter or `None` if not found. And finally for a local PAK file it will be +file name. + ### As a library ```py @@ -80,33 +106,6 @@ These URLs are automatically handled so that you don't have to figure out the "r However the Google Drive folder links (`drive.google.com/drive/folders`) are not handled and in these cases you must find the real URL, or you can also download the file. -## Example - -The command - -``` -reolinkfw info RLC-410-5MP_20_20052300.zip -i 2 -``` - -will give something like this: - -```json -[ - { - "firmware_version_prefix": "v3.0.0", - "board_type": "IPC_51516M5M", - "board_name": "IPC_51516M5M", - "build_date": "200523", - "display_type_info": "RLC-410-5MP", - "detail_machine_type": "IPC_51516M5M", - "type": "IPC", - "version_file": "20_20052300", - "sha256": "6ef371a51b61d7b21d8f7016d90b5fc1ed3eaa8a3f30f1e202a3474bfb4807e5", - "file": "RLC-410-5MP_20_20052300.zip" - } -] -``` - ## Notes There are at least 3 types of file systems used for Reolink firmwares: diff --git a/reolinkfw/__init__.py b/reolinkfw/__init__.py index ec3cb72..bbbd0e3 100644 --- a/reolinkfw/__init__.py +++ b/reolinkfw/__init__.py @@ -3,6 +3,7 @@ import io import re from pathlib import Path, PurePosixPath +from urllib.parse import parse_qsl, urlparse from zipfile import ZipFile, is_zipfile import aiohttp @@ -55,15 +56,15 @@ def extract_fs(pakbytes): return "No section found" -def extract_paks(zip): - """Return a list of unique PAK files found in the ZIP.""" - paks = set() +def extract_paks(zip) -> list[tuple[str, bytes]]: + """Return a list of tuples, one for each PAK file found in the ZIP.""" + paks = [] with ZipFile(zip) as myzip: for name in myzip.namelist(): with myzip.open(name) as file: if is_pak(file): - paks.add(myzip.read(name)) - return sorted(paks) # Always return in the same order. + paks.append((file.name, myzip.read(name))) + return paks def get_info_from_files(files): @@ -192,30 +193,30 @@ async def get_info(file_or_url): The file or resource may be a ZIP or a PAK. """ if is_url(file_or_url): - type_ = "url" file_or_url = await direct_download_url(file_or_url) zip_or_pak_bytes = await download(file_or_url) if isinstance(zip_or_pak_bytes, int): - return [{type_: file_or_url, "error": zip_or_pak_bytes}] + return [{"file": file_or_url, "error": zip_or_pak_bytes}] elif is_pak(zip_or_pak_bytes): - paks = [zip_or_pak_bytes] + pakname = dict(parse_qsl(urlparse(file_or_url).query)).get("name") + paks = [(pakname, zip_or_pak_bytes)] else: with io.BytesIO(zip_or_pak_bytes) as f: if is_zipfile(f): paks = await asyncio.to_thread(extract_paks, f) else: - return [{type_: file_or_url, "error": "Not a ZIP or a PAK file"}] + return [{"file": file_or_url, "error": "Not a ZIP or a PAK file"}] elif is_local_file(file_or_url): - type_ = "file" + file_or_url = Path(file_or_url) if is_zipfile(file_or_url): paks = await asyncio.to_thread(extract_paks, file_or_url) elif is_pak(file_or_url): with open(file_or_url, "rb") as f: - paks = [f.read()] + paks = [(file_or_url.name, f.read())] else: - return [{type_: file_or_url, "error": "Not a ZIP or a PAK file"}] + return [{"file": file_or_url, "error": "Not a ZIP or a PAK file"}] else: return [{"arg": file_or_url, "error": "Not a URL or file"}] if not paks: - return [{type_: file_or_url, "error": "no PAKs found in ZIP file"}] - return [{**await get_info_from_pak(pak), type_: file_or_url} for pak in paks] + return [{"file": file_or_url, "error": "no PAKs found in ZIP file"}] + return [{**await get_info_from_pak(pakbytes), "file": file_or_url, "pak": pakname} for pakname, pakbytes in paks] diff --git a/reolinkfw/__main__.py b/reolinkfw/__main__.py index 17809e3..3f4b6f6 100644 --- a/reolinkfw/__main__.py +++ b/reolinkfw/__main__.py @@ -9,7 +9,7 @@ def info(args): info = asyncio.run(get_info(args.file_or_url)) - print(json.dumps(info, indent=args.indent)) + print(json.dumps(info, indent=args.indent, default=str)) def main():