22import io
33import posixpath
44import re
5+ from collections .abc import Iterable , Mapping
56from pathlib import Path
6- from typing import Optional
7+ from typing import IO , Any , BinaryIO , Optional , Union
78from urllib .parse import parse_qsl , urlparse
89from zipfile import ZipFile , is_zipfile
910
1011import aiohttp
12+ from aiohttp .typedefs import StrOrURL
1113from lxml .etree import fromstring
1214from lxml .html import document_fromstring
1315from pakler import PAK , Section , is_pak_file
1416from pycramfs import Cramfs
1517from PySquashfsImage import SquashFsImage
1618
1719from reolinkfw .tmpfile import TempFile
20+ from reolinkfw .typedefs import Buffer , Files , StrPath , StrPathURL
1821from reolinkfw .ubifs import UBIFS
1922from reolinkfw .uboot import get_arch_name , get_uboot_version , get_uimage_header
2023from reolinkfw .util import (
3538FS_SECTIONS = ROOTFS_SECTIONS + ["app" ]
3639
3740
38- async def download (url ) :
41+ async def download (url : StrOrURL ) -> Union [ bytes , int ] :
3942 """Return resource as bytes.
4043
4144 Return the status code of the request if it is not 200.
@@ -45,7 +48,7 @@ async def download(url):
4548 return await resp .read () if resp .status == 200 else resp .status
4649
4750
48- def extract_paks (zip ) -> list [tuple [str , PAK ]]:
51+ def extract_paks (zip : Union [ StrPath , IO [ bytes ]] ) -> list [tuple [str , PAK ]]:
4952 """Return a list of tuples, one for each PAK file found in the ZIP.
5053
5154 It is the caller's responsibility to close the PAK files.
@@ -61,8 +64,8 @@ def extract_paks(zip) -> list[tuple[str, PAK]]:
6164 return paks
6265
6366
64- def get_info_from_files (files ) :
65- xml = dict (fromstring (files ["dvr.xml" ]).items ())
67+ def get_info_from_files (files : Mapping [ Files , Optional [ bytes ]]) -> dict [ str , Optional [ str ]] :
68+ xml : dict [ str , str ] = dict (fromstring (files ["dvr.xml" ]).items ())
6669 info = {k : xml .get (k ) for k in INFO_KEYS }
6770 info ["version_file" ] = files ["version_file" ].decode ().strip ()
6871 if not info .get ("firmware_version_prefix" ):
@@ -72,7 +75,7 @@ def get_info_from_files(files):
7275 return info
7376
7477
75- def get_files_from_squashfs (fd , offset = 0 , closefd = True ):
78+ def get_files_from_squashfs (fd : BinaryIO , offset : int = 0 , closefd : bool = True ) -> dict [ Files , Optional [ bytes ]] :
7679 # Firmwares using squashfs have either one or two file system
7780 # sections. When there is only one, the app directory is located at
7881 # /mnt/app. Otherwise it's the same as with cramfs and ubifs.
@@ -85,7 +88,7 @@ def get_files_from_squashfs(fd, offset=0, closefd=True):
8588 return files
8689
8790
88- def get_files_from_ubifs (binbytes ) :
91+ def get_files_from_ubifs (binbytes : Buffer ) -> dict [ Files , Optional [ bytes ]] :
8992 # For now all firmwares using ubifs have two file system sections.
9093 # The interesting files are in the root directory of the "app" one.
9194 # Using select() with a relative path is enough.
@@ -98,7 +101,7 @@ def get_files_from_ubifs(binbytes):
98101 return files
99102
100103
101- def get_files_from_ubi (fd , size , offset = 0 ) :
104+ def get_files_from_ubi (fd : BinaryIO , size : int , offset : int = 0 ) -> dict [ Files , Optional [ bytes ]] :
102105 fsbytes = get_fs_from_ubi (fd , size , offset )
103106 fs = FileType .from_magic (fsbytes [:4 ])
104107 if fs == FileType .UBIFS :
@@ -108,7 +111,7 @@ def get_files_from_ubi(fd, size, offset=0):
108111 raise Exception ("Unknown file system in UBI" )
109112
110113
111- def get_files_from_cramfs (fd , offset = 0 , closefd = True ):
114+ def get_files_from_cramfs (fd : BinaryIO , offset : int = 0 , closefd : bool = True ) -> dict [ Files , Optional [ bytes ]] :
112115 # For now all firmwares using cramfs have two file system sections.
113116 # The interesting files are in the root directory of the "app" one.
114117 # Using select() with a relative path is enough.
@@ -120,15 +123,15 @@ def get_files_from_cramfs(fd, offset=0, closefd=True):
120123 return files
121124
122125
123- def is_url (string ) :
126+ def is_url (string : StrOrURL ) -> bool :
124127 return str (string ).startswith ("http" )
125128
126129
127- def is_local_file (string ) :
130+ def is_local_file (string : StrPath ) -> bool :
128131 return Path (string ).is_file ()
129132
130133
131- def get_fs_info (pak : PAK , fs_sections : list [Section ]) -> list [dict [str , str ]]:
134+ def get_fs_info (pak : PAK , fs_sections : Iterable [Section ]) -> list [dict [str , str ]]:
132135 result = []
133136 for section in fs_sections :
134137 pak ._fd .seek (section .start )
@@ -143,7 +146,7 @@ def get_fs_info(pak: PAK, fs_sections: list[Section]) -> list[dict[str, str]]:
143146 return result
144147
145148
146- async def get_info_from_pak (pak : PAK ):
149+ async def get_info_from_pak (pak : PAK ) -> dict [ str , Any ] :
147150 ha = await asyncio .to_thread (sha256_pak , pak )
148151 fs_sections = [s for s in pak .sections if s .name in FS_SECTIONS ]
149152 app = fs_sections [- 1 ]
@@ -169,7 +172,7 @@ async def get_info_from_pak(pak: PAK):
169172 }
170173
171174
172- async def direct_download_url (url ) :
175+ async def direct_download_url (url : str ) -> str :
173176 if url .startswith ("https://drive.google.com/file/d/" ):
174177 return f"https://drive.google.com/uc?id={ url .split ('/' )[5 ]} &confirm=t"
175178 elif url .startswith ("https://www.mediafire.com/file/" ):
@@ -182,7 +185,7 @@ async def direct_download_url(url):
182185 return url
183186
184187
185- async def get_paks (file_or_url , use_cache : bool = True ) -> list [tuple [Optional [str ], PAK ]]:
188+ async def get_paks (file_or_url : StrPathURL , use_cache : bool = True ) -> list [tuple [Optional [str ], PAK ]]:
186189 """Return PAK files read from an on-disk file or a URL.
187190
188191 The file or resource may be a ZIP or a PAK. On success return a
@@ -191,6 +194,7 @@ async def get_paks(file_or_url, use_cache: bool = True) -> list[tuple[Optional[s
191194 be None. If the file is a ZIP the list might be empty.
192195 It is the caller's responsibility to close the PAK files.
193196 """
197+ file_or_url = str (file_or_url )
194198 if is_url (file_or_url ):
195199 if use_cache and has_cache (file_or_url ):
196200 return await get_paks (get_cache_file (file_or_url ))
@@ -219,7 +223,7 @@ async def get_paks(file_or_url, use_cache: bool = True) -> list[tuple[Optional[s
219223 raise Exception ("Not a URL or file" )
220224
221225
222- async def get_info (file_or_url , use_cache : bool = True ):
226+ async def get_info (file_or_url : StrPathURL , use_cache : bool = True ) -> list [ dict [ str , Any ]] :
223227 """Retrieve firmware info from an on-disk file or a URL.
224228
225229 The file or resource may be a ZIP or a PAK.
0 commit comments