p115client 0.0.5.14.1__tar.gz → 0.0.5.14.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. {p115client-0.0.5.14.1 → p115client-0.0.5.14.2}/PKG-INFO +1 -1
  2. {p115client-0.0.5.14.1 → p115client-0.0.5.14.2}/p115client/client.py +163 -69
  3. {p115client-0.0.5.14.1 → p115client-0.0.5.14.2}/p115client/tool/download.py +8 -11
  4. {p115client-0.0.5.14.1 → p115client-0.0.5.14.2}/p115client/tool/fs_files.py +1 -1
  5. {p115client-0.0.5.14.1 → p115client-0.0.5.14.2}/p115client/tool/iterdir.py +76 -48
  6. {p115client-0.0.5.14.1 → p115client-0.0.5.14.2}/pyproject.toml +1 -1
  7. {p115client-0.0.5.14.1 → p115client-0.0.5.14.2}/LICENSE +0 -0
  8. {p115client-0.0.5.14.1 → p115client-0.0.5.14.2}/p115client/__init__.py +0 -0
  9. {p115client-0.0.5.14.1 → p115client-0.0.5.14.2}/p115client/_upload.py +0 -0
  10. {p115client-0.0.5.14.1 → p115client-0.0.5.14.2}/p115client/const.py +0 -0
  11. {p115client-0.0.5.14.1 → p115client-0.0.5.14.2}/p115client/exception.py +0 -0
  12. {p115client-0.0.5.14.1 → p115client-0.0.5.14.2}/p115client/py.typed +0 -0
  13. {p115client-0.0.5.14.1 → p115client-0.0.5.14.2}/p115client/tool/__init__.py +0 -0
  14. {p115client-0.0.5.14.1 → p115client-0.0.5.14.2}/p115client/tool/attr.py +0 -0
  15. {p115client-0.0.5.14.1 → p115client-0.0.5.14.2}/p115client/tool/auth.py +0 -0
  16. {p115client-0.0.5.14.1 → p115client-0.0.5.14.2}/p115client/tool/edit.py +0 -0
  17. {p115client-0.0.5.14.1 → p115client-0.0.5.14.2}/p115client/tool/export_dir.py +0 -0
  18. {p115client-0.0.5.14.1 → p115client-0.0.5.14.2}/p115client/tool/history.py +0 -0
  19. {p115client-0.0.5.14.1 → p115client-0.0.5.14.2}/p115client/tool/life.py +0 -0
  20. {p115client-0.0.5.14.1 → p115client-0.0.5.14.2}/p115client/tool/offline.py +0 -0
  21. {p115client-0.0.5.14.1 → p115client-0.0.5.14.2}/p115client/tool/pool.py +0 -0
  22. {p115client-0.0.5.14.1 → p115client-0.0.5.14.2}/p115client/tool/request.py +0 -0
  23. {p115client-0.0.5.14.1 → p115client-0.0.5.14.2}/p115client/tool/upload.py +0 -0
  24. {p115client-0.0.5.14.1 → p115client-0.0.5.14.2}/p115client/tool/util.py +0 -0
  25. {p115client-0.0.5.14.1 → p115client-0.0.5.14.2}/p115client/tool/xys.py +0 -0
  26. {p115client-0.0.5.14.1 → p115client-0.0.5.14.2}/p115client/type.py +0 -0
  27. {p115client-0.0.5.14.1 → p115client-0.0.5.14.2}/readme.md +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: p115client
3
- Version: 0.0.5.14.1
3
+ Version: 0.0.5.14.2
4
4
  Summary: Python 115 webdisk client.
5
5
  Home-page: https://github.com/ChenyangGao/p115client
6
6
  License: MIT
@@ -59,7 +59,7 @@ from orjson import dumps, loads
59
59
  from p115cipher.fast import (
60
60
  rsa_encode, rsa_decode, ecdh_encode_token, ecdh_aes_encode, ecdh_aes_decode, make_upload_payload,
61
61
  )
62
- from p115pickcode import get_stable_point
62
+ from p115pickcode import get_stable_point, to_id, to_pickcode
63
63
  from property import locked_cacheproperty
64
64
  from re import compile as re_compile
65
65
  from startfile import startfile, startfile_async # type: ignore
@@ -629,29 +629,32 @@ def check_response(resp: dict | Awaitable[dict], /) -> dict | Coroutine[Any, Any
629
629
 
630
630
  @overload
631
631
  def normalize_attr_web(
632
- info: Mapping,
632
+ info: Mapping[str, Any],
633
633
  /,
634
634
  simple: bool = False,
635
635
  keep_raw: bool = False,
636
+ default: None | Mapping[str, Any] | Iterable[tuple[str, Any]] = None,
636
637
  *,
637
638
  dict_cls: None = None,
638
639
  ) -> dict[str, Any]:
639
640
  ...
640
641
  @overload
641
642
  def normalize_attr_web[D: dict[str, Any]](
642
- info: Mapping,
643
+ info: Mapping[str, Any],
643
644
  /,
644
645
  simple: bool = False,
645
646
  keep_raw: bool = False,
647
+ default: None | Mapping[str, Any] | Iterable[tuple[str, Any]] = None,
646
648
  *,
647
649
  dict_cls: type[D],
648
650
  ) -> D:
649
651
  ...
650
652
  def normalize_attr_web[D: dict[str, Any]](
651
- info: Mapping,
653
+ info: Mapping[str, Any],
652
654
  /,
653
655
  simple: bool = False,
654
656
  keep_raw: bool = False,
657
+ default: None | Mapping[str, Any] | Iterable[tuple[str, Any]] = None,
655
658
  *,
656
659
  dict_cls: None | type[D] = None,
657
660
  ) -> dict[str, Any] | D:
@@ -660,6 +663,7 @@ def normalize_attr_web[D: dict[str, Any]](
660
663
  :param info: 原始数据
661
664
  :param simple: 只提取少量必要字段 "is_dir", "id", "parent_id", "name", "sha1", "size", "pickcode", "is_collect", "ctime", "mtime", "type"
662
665
  :param keep_raw: 是否保留原始数据,如果为 True,则保存到 "raw" 字段
666
+ :param default: 一些预设值,可被覆盖
663
667
  :param dict_cls: 字典类型
664
668
 
665
669
  :return: 翻译后的 dict 类型数据
@@ -667,6 +671,8 @@ def normalize_attr_web[D: dict[str, Any]](
667
671
  if dict_cls is None:
668
672
  dict_cls = cast(type[D], dict)
669
673
  attr: dict[str, Any] = dict_cls()
674
+ if default:
675
+ attr.update(default)
670
676
  is_dir = attr["is_dir"] = "fid" not in info
671
677
  if is_dir:
672
678
  attr["id"] = int(info["cid"]) # category_id
@@ -771,29 +777,33 @@ def normalize_attr_web[D: dict[str, Any]](
771
777
 
772
778
  @overload
773
779
  def normalize_attr_app(
774
- info: Mapping,
780
+ info: Mapping[str, Any],
775
781
  /,
776
782
  simple: bool = False,
777
783
  keep_raw: bool = False,
784
+ default: None | Mapping[str, Any] | Iterable[tuple[str, Any]] = None,
778
785
  *,
779
786
  dict_cls: None = None,
780
787
  ) -> dict[str, Any]:
781
788
  ...
782
789
  @overload
783
790
  def normalize_attr_app[D: dict[str, Any]](
784
- info: Mapping,
791
+ info: Mapping[str, Any],
785
792
  /,
786
793
  simple: bool = False,
787
794
  keep_raw: bool = False,
795
+ default: None | Mapping[str, Any] | Iterable[tuple[str, Any]] = None,
788
796
  *,
789
797
  dict_cls: type[D],
790
798
  ) -> D:
791
799
  ...
792
800
  def normalize_attr_app[D: dict[str, Any]](
793
- info: Mapping,
801
+ info: Mapping[str, Any],
794
802
  /,
795
803
  simple: bool = False,
796
804
  keep_raw: bool = False,
805
+ default: None | Mapping[str, Any] | Iterable[tuple[str, Any]] = None,
806
+ *,
797
807
  dict_cls: None | type[D] = None,
798
808
  ) -> dict[str, Any] | D:
799
809
  """翻译 `P115Client.fs_files_app` 接口响应的文件信息数据,使之便于阅读
@@ -801,6 +811,7 @@ def normalize_attr_app[D: dict[str, Any]](
801
811
  :param info: 原始数据
802
812
  :param simple: 只提取少量必要字段 "is_dir", "id", "parent_id", "name", "sha1", "size", "pickcode", "is_collect", "ctime", "mtime", "type"
803
813
  :param keep_raw: 是否保留原始数据,如果为 True,则保存到 "raw" 字段
814
+ :param default: 一些预设值,可被覆盖
804
815
  :param dict_cls: 字典类型
805
816
 
806
817
  :return: 翻译后的 dict 类型数据
@@ -808,8 +819,10 @@ def normalize_attr_app[D: dict[str, Any]](
808
819
  if dict_cls is None:
809
820
  dict_cls = cast(type[D], dict)
810
821
  attr: dict[str, Any] = dict_cls()
822
+ if default:
823
+ attr.update(default)
811
824
  is_dir = attr["is_dir"] = info["fc"] == "0" # file_category
812
- attr["id"] = int(info["fid"]) # file_id
825
+ attr["id"] = int(info["fid"]) # file_id
813
826
  attr["parent_id"] = int(info["pid"]) # parent_id
814
827
  attr["name"] = info["fn"]
815
828
  sha1 = attr["sha1"] = info.get("sha1") or ""
@@ -892,29 +905,33 @@ def normalize_attr_app[D: dict[str, Any]](
892
905
 
893
906
  @overload
894
907
  def normalize_attr_app2(
895
- info: Mapping,
908
+ info: Mapping[str, Any],
896
909
  /,
897
910
  simple: bool = False,
898
911
  keep_raw: bool = False,
912
+ default: None | Mapping[str, Any] | Iterable[tuple[str, Any]] = None,
899
913
  *,
900
914
  dict_cls: None = None,
901
915
  ) -> dict[str, Any]:
902
916
  ...
903
917
  @overload
904
918
  def normalize_attr_app2[D: dict[str, Any]](
905
- info: Mapping,
919
+ info: Mapping[str, Any],
906
920
  /,
907
921
  simple: bool = False,
908
922
  keep_raw: bool = False,
923
+ default: None | Mapping[str, Any] | Iterable[tuple[str, Any]] = None,
909
924
  *,
910
925
  dict_cls: type[D],
911
926
  ) -> D:
912
927
  ...
913
928
  def normalize_attr_app2[D: dict[str, Any]](
914
- info: Mapping,
929
+ info: Mapping[str, Any],
915
930
  /,
916
931
  simple: bool = False,
917
932
  keep_raw: bool = False,
933
+ default: None | Mapping[str, Any] | Iterable[tuple[str, Any]] = None,
934
+ *,
918
935
  dict_cls: None | type[D] = None,
919
936
  ) -> dict[str, Any] | D:
920
937
  """翻译 `P115Client.fs_files_app2` 接口响应的文件信息数据,使之便于阅读
@@ -922,6 +939,7 @@ def normalize_attr_app2[D: dict[str, Any]](
922
939
  :param info: 原始数据
923
940
  :param simple: 只提取少量必要字段 "is_dir", "id", "parent_id", "name", "sha1", "size", "pickcode", "is_collect", "ctime", "mtime", "type"
924
941
  :param keep_raw: 是否保留原始数据,如果为 True,则保存到 "raw" 字段
942
+ :param default: 一些预设值,可被覆盖
925
943
  :param dict_cls: 字典类型
926
944
 
927
945
  :return: 翻译后的 dict 类型数据
@@ -929,6 +947,8 @@ def normalize_attr_app2[D: dict[str, Any]](
929
947
  if dict_cls is None:
930
948
  dict_cls = cast(type[D], dict)
931
949
  attr: dict[str, Any] = dict_cls()
950
+ if default:
951
+ attr.update(default)
932
952
  if "file_id" in info and "parent_id" in info:
933
953
  if "file_category" in info:
934
954
  is_dir = not int(info["file_category"])
@@ -1052,29 +1072,32 @@ def normalize_attr_app2[D: dict[str, Any]](
1052
1072
 
1053
1073
  @overload
1054
1074
  def normalize_attr(
1055
- info: Mapping,
1075
+ info: Mapping[str, Any],
1056
1076
  /,
1057
1077
  simple: bool = False,
1058
1078
  keep_raw: bool = False,
1079
+ default: None | Mapping[str, Any] | Iterable[tuple[str, Any]] = None,
1059
1080
  *,
1060
1081
  dict_cls: None = None,
1061
1082
  ) -> AttrDict[str, Any]:
1062
1083
  ...
1063
1084
  @overload
1064
1085
  def normalize_attr[D: dict[str, Any]](
1065
- info: Mapping,
1086
+ info: Mapping[str, Any],
1066
1087
  /,
1067
1088
  simple: bool = False,
1068
1089
  keep_raw: bool = False,
1090
+ default: None | Mapping[str, Any] | Iterable[tuple[str, Any]] = None,
1069
1091
  *,
1070
1092
  dict_cls: type[D],
1071
1093
  ) -> D:
1072
1094
  ...
1073
1095
  def normalize_attr[D: dict[str, Any]](
1074
- info: Mapping,
1096
+ info: Mapping[str, Any],
1075
1097
  /,
1076
1098
  simple: bool = False,
1077
1099
  keep_raw: bool = False,
1100
+ default: None | Mapping[str, Any] | Iterable[tuple[str, Any]] = None,
1078
1101
  *,
1079
1102
  dict_cls: None | type[D] = None,
1080
1103
  ) -> AttrDict[str, Any] | D:
@@ -1083,55 +1106,68 @@ def normalize_attr[D: dict[str, Any]](
1083
1106
  :param info: 原始数据
1084
1107
  :param simple: 只提取少量必要字段 "is_dir", "id", "parent_id", "name", "sha1", "size", "pickcode", "is_collect", "ctime", "mtime"
1085
1108
  :param keep_raw: 是否保留原始数据,如果为 True,则保存到 "raw" 字段
1109
+ :param default: 一些预设值,可被覆盖
1086
1110
  :param dict_cls: 字典类型
1087
1111
 
1088
1112
  :return: 翻译后的 dict 类型数据
1089
1113
  """
1114
+ if "fn" in info:
1115
+ call = normalize_attr_app
1116
+ elif "file_id" in info or "category_id" in info:
1117
+ call = normalize_attr_app2
1118
+ else:
1119
+ call = normalize_attr_web
1090
1120
  if dict_cls is None:
1091
- if "fn" in info:
1092
- return normalize_attr_app(info, simple=simple, keep_raw=keep_raw, dict_cls=AttrDict)
1093
- elif "file_id" in info or "category_id" in info:
1094
- return normalize_attr_app2(info, simple=simple, keep_raw=keep_raw, dict_cls=AttrDict)
1095
- else:
1096
- return normalize_attr_web(info, simple=simple, keep_raw=keep_raw, dict_cls=AttrDict)
1121
+ return call(info, simple=simple, keep_raw=keep_raw, default=default, dict_cls=AttrDict)
1097
1122
  else:
1098
- if "fn" in info:
1099
- return normalize_attr_app(info, simple=simple, keep_raw=keep_raw, dict_cls=dict_cls)
1100
- elif "file_id" in info or "category_id" in info:
1101
- return normalize_attr_app2(info, simple=simple, keep_raw=keep_raw, dict_cls=dict_cls)
1102
- else:
1103
- return normalize_attr_web(info, simple=simple, keep_raw=keep_raw, dict_cls=dict_cls)
1123
+ return call(info, simple=simple, keep_raw=keep_raw, default=default, dict_cls=dict_cls)
1104
1124
 
1105
1125
 
1106
1126
  @overload
1107
1127
  def normalize_attr_simple(
1108
- info: Mapping,
1128
+ info: Mapping[str, Any],
1109
1129
  /,
1110
1130
  keep_raw: bool = False,
1131
+ default: None | Mapping[str, Any] | Iterable[tuple[str, Any]] = None,
1111
1132
  *,
1112
- dict_cls: None,
1113
- ) -> dict[str, Any]:
1133
+ dict_cls: None = None,
1134
+ ) -> AttrDict[str, Any]:
1114
1135
  ...
1115
1136
  @overload
1116
1137
  def normalize_attr_simple[D: dict[str, Any]](
1117
- info: Mapping,
1138
+ info: Mapping[str, Any],
1118
1139
  /,
1119
1140
  keep_raw: bool = False,
1141
+ default: None | Mapping[str, Any] | Iterable[tuple[str, Any]] = None,
1120
1142
  *,
1121
- dict_cls: type[D] = AttrDict, # type: ignore
1143
+ dict_cls: type[D],
1122
1144
  ) -> D:
1123
1145
  ...
1124
1146
  def normalize_attr_simple[D: dict[str, Any]](
1125
- info: Mapping,
1147
+ info: Mapping[str, Any],
1126
1148
  /,
1127
1149
  keep_raw: bool = False,
1150
+ default: None | Mapping[str, Any] | Iterable[tuple[str, Any]] = None,
1128
1151
  *,
1129
- dict_cls: None | type[D] = AttrDict, # type: ignore
1130
- ) -> dict[str, Any] | D:
1152
+ dict_cls: None | type[D] = None,
1153
+ ) -> AttrDict[str, Any] | D:
1154
+ """翻译获取自罗列目录、搜索、获取文件信息等接口的数据,使之便于阅读
1155
+
1156
+ .. note::
1157
+ 只提取少量必要字段 "is_dir", "id", "parent_id", "name", "sha1", "size", "pickcode", "is_collect", "ctime", "mtime"
1158
+
1159
+ :param info: 原始数据
1160
+ :param keep_raw: 是否保留原始数据,如果为 True,则保存到 "raw" 字段
1161
+ :param default: 一些预设值,可被覆盖
1162
+ :param dict_cls: 字典类型
1163
+
1164
+ :return: 翻译后的 dict 类型数据
1165
+ """
1131
1166
  return normalize_attr(
1132
1167
  info,
1133
1168
  simple=True,
1134
1169
  keep_raw=keep_raw,
1170
+ default=default,
1135
1171
  dict_cls=dict_cls,
1136
1172
  )
1137
1173
 
@@ -5407,6 +5443,26 @@ class P115OpenClient(ClientRequestMixin):
5407
5443
  upload_file_open = upload_file
5408
5444
  vip_qr_url_open = vip_qr_url
5409
5445
 
5446
+ to_id = staticmethod(to_id)
5447
+
5448
+ def to_pickcode(
5449
+ self,
5450
+ id: int | str,
5451
+ /,
5452
+ prefix: Literal["a", "b", "c", "d", "e", "fa", "fb", "fc", "fd", "fe"] = "a",
5453
+ ) -> str:
5454
+ """把可能是 id 或 pickcode 的一律转换成 pickcode
5455
+
5456
+ .. note::
5457
+ 规定:空提取码 "" 对应的 id 是 0
5458
+
5459
+ :param id: 可能是 id 或 pickcode
5460
+ :param prefix: 前缀
5461
+
5462
+ :return: pickcode
5463
+ """
5464
+ return to_pickcode(id, self.pickcode_stable_point, prefix=prefix)
5465
+
5410
5466
 
5411
5467
  class P115Client(P115OpenClient):
5412
5468
  """115 的客户端对象
@@ -18473,41 +18529,6 @@ class P115Client(P115OpenClient):
18473
18529
  payload = {"file_id": payload}
18474
18530
  return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
18475
18531
 
18476
- @overload
18477
- def offline_info(
18478
- self,
18479
- /,
18480
- base_url: bool | str | Callable[[], str] = False,
18481
- *,
18482
- async_: Literal[False] = False,
18483
- **request_kwargs,
18484
- ) -> dict:
18485
- ...
18486
- @overload
18487
- def offline_info(
18488
- self,
18489
- /,
18490
- base_url: bool | str | Callable[[], str] = False,
18491
- *,
18492
- async_: Literal[True],
18493
- **request_kwargs,
18494
- ) -> Coroutine[Any, Any, dict]:
18495
- ...
18496
- def offline_info(
18497
- self,
18498
- /,
18499
- base_url: bool | str | Callable[[], str] = False,
18500
- *,
18501
- async_: Literal[False, True] = False,
18502
- **request_kwargs,
18503
- ) -> dict | Coroutine[Any, Any, dict]:
18504
- """获取关于离线的限制的信息,以及 sign 和 time 字段(各个添加任务的接口需要)
18505
-
18506
- GET https://115.com/?ct=offline&ac=space
18507
- """
18508
- api = complete_api("/?ct=offline&ac=space", base_url=base_url)
18509
- return self.request(url=api, async_=async_, **request_kwargs)
18510
-
18511
18532
  @overload
18512
18533
  def offline_list(
18513
18534
  self,
@@ -18824,6 +18845,79 @@ class P115Client(P115OpenClient):
18824
18845
  **request_kwargs,
18825
18846
  )
18826
18847
 
18848
+ @overload
18849
+ def offline_sign(
18850
+ self,
18851
+ /,
18852
+ base_url: bool | str | Callable[[], str] = False,
18853
+ *,
18854
+ async_: Literal[False] = False,
18855
+ **request_kwargs,
18856
+ ) -> dict:
18857
+ ...
18858
+ @overload
18859
+ def offline_sign(
18860
+ self,
18861
+ /,
18862
+ base_url: bool | str | Callable[[], str] = False,
18863
+ *,
18864
+ async_: Literal[True],
18865
+ **request_kwargs,
18866
+ ) -> Coroutine[Any, Any, dict]:
18867
+ ...
18868
+ def offline_sign(
18869
+ self,
18870
+ /,
18871
+ base_url: bool | str | Callable[[], str] = False,
18872
+ *,
18873
+ async_: Literal[False, True] = False,
18874
+ **request_kwargs,
18875
+ ) -> dict | Coroutine[Any, Any, dict]:
18876
+ """获取 sign 和 time 字段(各个添加任务的接口需要),以及其它信息
18877
+
18878
+ GET https://115.com/?ct=offline&ac=space
18879
+ """
18880
+ api = complete_api("/?ct=offline&ac=space", base_url=base_url)
18881
+ return self.request(url=api, async_=async_, **request_kwargs)
18882
+
18883
+ @overload
18884
+ def offline_sign_app(
18885
+ self,
18886
+ /,
18887
+ base_url: bool | str | Callable[[], str] = False,
18888
+ app: str = "android",
18889
+ *,
18890
+ async_: Literal[False] = False,
18891
+ **request_kwargs,
18892
+ ) -> dict:
18893
+ ...
18894
+ @overload
18895
+ def offline_sign_app(
18896
+ self,
18897
+ /,
18898
+ base_url: bool | str | Callable[[], str] = False,
18899
+ app: str = "android",
18900
+ *,
18901
+ async_: Literal[True],
18902
+ **request_kwargs,
18903
+ ) -> Coroutine[Any, Any, dict]:
18904
+ ...
18905
+ def offline_sign_app(
18906
+ self,
18907
+ /,
18908
+ base_url: bool | str | Callable[[], str] = False,
18909
+ app: str = "android",
18910
+ *,
18911
+ async_: Literal[False, True] = False,
18912
+ **request_kwargs,
18913
+ ) -> dict | Coroutine[Any, Any, dict]:
18914
+ """获取 sign 和 time 字段(各个添加任务的接口需要)
18915
+
18916
+ GET https://proapi.115.com/android/files/offlinesign
18917
+ """
18918
+ api = complete_proapi("/files/offlinesign", base_url, app)
18919
+ return self.request(url=api, async_=async_, **request_kwargs)
18920
+
18827
18921
  @overload
18828
18922
  def offline_task_count(
18829
18923
  self,
@@ -41,7 +41,7 @@ from p115client import (
41
41
  P115OpenClient, P115URL,
42
42
  )
43
43
  from p115client.exception import P115Warning
44
- from p115pickcode import to_id, to_pickcode
44
+ from p115pickcode import to_id
45
45
 
46
46
  from .iterdir import (
47
47
  get_path_to_cid, iterdir, iter_files, iter_files_with_path,
@@ -94,15 +94,14 @@ def batch_get_url(
94
94
  """
95
95
  if isinstance(client, str):
96
96
  client = P115Client(client, check_for_relogin=True)
97
- stable_point = client.pickcode_stable_point
98
97
  if headers := request_kwargs.get("headers"):
99
98
  request_kwargs["headers"] = dict(headers, **{"user-agent": user_agent})
100
99
  else:
101
100
  request_kwargs["headers"] = {"user-agent": user_agent}
102
101
  if isinstance(pickcode, (int, str)):
103
- pickcode = to_pickcode(pickcode, stable_point)
102
+ pickcode = client.to_pickcode(pickcode)
104
103
  elif not isinstance(pickcode, str):
105
- pickcode = ",".join(to_pickcode(pc, stable_point) for pc in pickcode)
104
+ pickcode = ",".join(map(client.to_pickcode, pickcode))
106
105
  if not isinstance(client, P115Client) or app == "open":
107
106
  get_download_url: Callable = client.download_url_info_open
108
107
  else:
@@ -184,7 +183,6 @@ def iter_url_batches(
184
183
  """
185
184
  if isinstance(client, str):
186
185
  client = P115Client(client, check_for_relogin=True)
187
- stable_point = client.pickcode_stable_point
188
186
  if headers := request_kwargs.get("headers"):
189
187
  request_kwargs["headers"] = dict(headers, **{"user-agent": user_agent})
190
188
  else:
@@ -196,7 +194,7 @@ def iter_url_batches(
196
194
  if batch_size <= 0:
197
195
  batch_size = 1
198
196
  def gen_step():
199
- for pcs in batched((to_pickcode(pc, stable_point) for pc in pickcodes), batch_size):
197
+ for pcs in batched(map(client.to_pickcode, pickcodes), batch_size):
200
198
  resp = yield get_download_url(
201
199
  ",".join(pcs),
202
200
  async_=async_,
@@ -1210,7 +1208,6 @@ def iter_download_nodes(
1210
1208
  """
1211
1209
  if isinstance(client, str):
1212
1210
  client = P115Client(client, check_for_relogin=True)
1213
- stable_point = client.pickcode_stable_point
1214
1211
  get_base_url = cycle(("http://proapi.115.com", "https://proapi.115.com")).__next__
1215
1212
  if async_:
1216
1213
  if max_workers is None or max_workers <= 0:
@@ -1272,9 +1269,10 @@ def iter_download_nodes(
1272
1269
  async_=async_,
1273
1270
  **{"base_url": get_base_url, **request_kwargs},
1274
1271
  )
1272
+ to_pickcode = client.to_pickcode
1275
1273
  if max_workers == 1:
1276
1274
  def gen_step(pickcode: int | str, /):
1277
- pickcode = to_pickcode(pickcode, stable_point)
1275
+ pickcode = to_pickcode(pickcode)
1278
1276
  for i in count(1):
1279
1277
  payload = {"pickcode": pickcode, "page": i}
1280
1278
  resp = yield get_nodes(payload)
@@ -1324,7 +1322,7 @@ def iter_download_nodes(
1324
1322
  n = executor._max_workers
1325
1323
  submit = executor.submit
1326
1324
  shutdown = lambda: executor.shutdown(False, cancel_futures=True)
1327
- pickcode = to_pickcode(pickcode, stable_point)
1325
+ pickcode = to_pickcode(pickcode)
1328
1326
  try:
1329
1327
  sentinel = object()
1330
1328
  countdown: Callable
@@ -1449,7 +1447,6 @@ def iter_download_files(
1449
1447
  """
1450
1448
  if isinstance(client, str):
1451
1449
  client = P115Client(client, check_for_relogin=True)
1452
- stable_point = client.pickcode_stable_point
1453
1450
  if id_to_dirnode is None:
1454
1451
  id_to_dirnode = ID_TO_DIRNODE_CACHE[client.user_id]
1455
1452
  elif id_to_dirnode is ...:
@@ -1594,7 +1591,7 @@ def iter_download_files(
1594
1591
  for pickcode in pickcodes:
1595
1592
  yield YieldFrom(run_gen_step_iter(gen_step(pickcode), async_))
1596
1593
  ancestors_loaded = False
1597
- return run_gen_step_iter(gen_step(to_pickcode(cid, stable_point)), async_)
1594
+ return run_gen_step_iter(gen_step(client.to_pickcode(cid)), async_)
1598
1595
 
1599
1596
 
1600
1597
  @overload
@@ -19,7 +19,7 @@ from time import time
19
19
  from typing import cast, overload, Any, Final, Literal
20
20
  from warnings import warn
21
21
 
22
- from iterutils import as_gen_step, run_gen_step_iter, Yield
22
+ from iterutils import run_gen_step_iter, Yield
23
23
  from p115client import check_response, P115Client, P115OpenClient
24
24
  from p115client.client import get_status_code
25
25
  from p115client.exception import BusyOSError, DataError, P115Warning
@@ -20,9 +20,7 @@ __all__ = [
20
20
  __doc__ = "这个模块提供了一些和目录信息罗列有关的函数"
21
21
 
22
22
  # TODO: 再实现 2 个方法,利用 iter_download_nodes,一个有 path,一个没有,可以把某个目录下的所有节点都搞出来,导出时,先导出目录节点,再导出文件节点,但它们是并发执行的,然后必有字段:id, parent_id, pickcode, name, is_dir, sha1 等
23
-
24
23
  # TODO: 路径表示法,应该支持 / 和 > 开头,而不仅仅是 / 开头
25
- # TODO: 对于路径,增加 top_id 和 relpath 字段,表示搜素目录的 id 和相对于搜索路径的相对路径
26
24
  # TODO: get_id* 这类方法,应该放在 attr.py,用来获取某个 id 对应的值(根本还是 get_attr)
27
25
  # TODO: 创造函数 get_id, get_parent_id, get_ancestors, get_sha1, get_pickcode, get_path 等,支持多种类型的参数,目前已有的名字太长,需要改造,甚至转为私有,另外这些函数或许可以放到另一个包中,attr.py
28
26
  # TODO: 去除掉一些并不便利的办法,然后加上 traverse 和 walk 方法,通过递归拉取(支持深度和广度优先遍历)
@@ -60,7 +58,7 @@ from p115client import (
60
58
  P115Client, P115OpenClient, P115OSError, P115Warning,
61
59
  )
62
60
  from p115client.type import P115ID
63
- from p115pickcode import pickcode_to_id, to_id, to_pickcode
61
+ from p115pickcode import pickcode_to_id, to_id
64
62
  from posixpatht import path_is_dir_form, splitext, splits
65
63
 
66
64
  from .attr import type_of_attr
@@ -138,42 +136,50 @@ def _overview_attr(info: Mapping, /) -> OverviewAttr:
138
136
  return OverviewAttr(is_dir, id, pid, name, ctime, mtime)
139
137
 
140
138
 
141
- def _update_resp_2_id_to_dirnode(
139
+ def _update_resp_ancestors(
142
140
  resp: dict,
143
- id_to_dirnode: EllipsisType | MutableMapping[int, tuple[str, int] | DirNode],
141
+ id_to_dirnode: None | EllipsisType | MutableMapping[int, tuple[str, int] | DirNode] = None,
144
142
  /,
145
143
  error: None | OSError = FileNotFoundError(ENOENT, "not found"),
146
144
  ) -> dict:
145
+ list_append = list.append
146
+ need_update_id_to_dirnode = id_to_dirnode not in (..., None)
147
147
  if "path" in resp:
148
- if id_to_dirnode is not ...:
149
- for info in resp["path"][1:]:
150
- id_to_dirnode[int(info["cid"])] = DirNode(info["name"], int(info["pid"]))
148
+ ancestors = resp["ancestors"] = []
149
+ start_idx = not resp["path"][0]["cid"]
150
+ if start_idx:
151
+ list_append(ancestors, {"id": 0, "parent_id": 0, "name": ""})
152
+ for info in resp["path"][start_idx:]:
153
+ id, name, pid = int(info["cid"]), info["name"], int(info["pid"])
154
+ list_append(ancestors, {"id": id, "parent_id": pid, "name": name})
155
+ if need_update_id_to_dirnode:
156
+ id_to_dirnode[id] = DirNode(name, pid) # type: ignore
151
157
  else:
158
+ if resp and "paths" not in resp:
159
+ check_response(resp)
160
+ resp = resp["data"]
152
161
  if not resp:
153
162
  if error is None:
154
163
  return resp
155
164
  raise error
156
- if "paths" not in resp:
157
- check_response(resp)
158
- resp = resp["data"]
159
- if not resp:
160
- if error is None:
161
- return resp
162
- raise error
163
- if id_to_dirnode is not ...:
164
- paths = resp["paths"]
165
- info = paths[0]
166
- pid = int(info["file_id"])
167
- for info in paths[1:]:
168
- fid = int(info["file_id"])
169
- id_to_dirnode[fid] = DirNode(info["file_name"], pid)
170
- pid = fid
171
- if not resp["sha1"]:
172
- if "file_id" in resp:
173
- fid = int(resp["file_id"])
174
- else:
175
- fid = to_id(resp["pick_code"])
176
- id_to_dirnode[fid] = DirNode(resp["file_name"], pid)
165
+ ancestors = resp["ancestors"] = []
166
+ pid = int(resp["paths"][0]["file_id"])
167
+ for info in resp["paths"][1:]:
168
+ id = int(info["file_id"])
169
+ name = info["file_name"]
170
+ list_append(ancestors, {"id": id, "parent_id": pid, "name": name})
171
+ if need_update_id_to_dirnode:
172
+ id_to_dirnode[id] = DirNode(name, pid) # type: ignore
173
+ pid = id
174
+ if not resp["sha1"]:
175
+ if "file_id" in resp:
176
+ id = int(resp["file_id"])
177
+ else:
178
+ id = to_id(resp["pick_code"])
179
+ name = resp["file_name"]
180
+ list_append(ancestors, {"id": id, "parent_id": pid, "name": name})
181
+ if need_update_id_to_dirnode:
182
+ id_to_dirnode[id] = DirNode(name, pid) # type: ignore
177
183
  return resp
178
184
 
179
185
 
@@ -405,13 +411,11 @@ def get_file_count(
405
411
  if cid != int(resp["path"][-1]["cid"]):
406
412
  resp["cid"] = cid
407
413
  raise NotADirectoryError(ENOTDIR, resp)
408
- if id_to_dirnode is not ...:
409
- for info in resp["path"][1:]:
410
- id_to_dirnode[int(info["cid"])] = DirNode(info["name"], int(info["pid"]))
414
+ _update_resp_ancestors(resp, id_to_dirnode)
411
415
  return int(resp["count"])
412
416
  else:
413
417
  resp = yield get_resp_of_category_get(cid)
414
- resp = _update_resp_2_id_to_dirnode(resp, id_to_dirnode, FileNotFoundError(ENOENT, cid))
418
+ resp = _update_resp_ancestors(resp, id_to_dirnode, FileNotFoundError(ENOENT, cid))
415
419
  if resp["sha1"]:
416
420
  resp["cid"] = cid
417
421
  raise NotADirectoryError(ENOTDIR, resp)
@@ -577,7 +581,7 @@ def get_ancestors(
577
581
  return ancestors
578
582
  else:
579
583
  resp = yield get_resp_of_category_get(fid)
580
- resp = _update_resp_2_id_to_dirnode(resp, id_to_dirnode)
584
+ resp = _update_resp_ancestors(resp, id_to_dirnode)
581
585
  for info in resp["paths"]:
582
586
  add_ancestor({
583
587
  "parent_id": pid,
@@ -607,7 +611,7 @@ def get_ancestors(
607
611
  id_to_dirnode[ans["id"]] = DirNode(ans["name"], ans["parent_id"])
608
612
  else:
609
613
  resp = yield get_resp_of_category_get(fid)
610
- resp = _update_resp_2_id_to_dirnode(resp, id_to_dirnode)
614
+ resp = _update_resp_ancestors(resp, id_to_dirnode)
611
615
  for info in resp["paths"]:
612
616
  add_ancestor({
613
617
  "parent_id": pid,
@@ -835,7 +839,7 @@ def get_id_to_path(
835
839
  if not isinstance(client, P115Client) or app == "open":
836
840
  path = ">" + ">".join(patht)
837
841
  resp = yield client.fs_info_open(path, async_=async_, **request_kwargs)
838
- data = _update_resp_2_id_to_dirnode(resp, id_to_dirnode)
842
+ data = _update_resp_ancestors(resp, id_to_dirnode)
839
843
  return P115ID(data["file_id"], data, about="path", path=path)
840
844
  i = 0
841
845
  start_parent_id = parent_id
@@ -1312,7 +1316,7 @@ def ensure_attr_path[D: dict](
1312
1316
  pid = id_to_dirnode[pid][1]
1313
1317
  if pid and pid not in dangling_id_to_name:
1314
1318
  resp = yield get_info(pid, async_=async_, **request_kwargs)
1315
- resp = _update_resp_2_id_to_dirnode(resp, id_to_dirnode, None)
1319
+ resp = _update_resp_ancestors(resp, id_to_dirnode, None)
1316
1320
  if not resp:
1317
1321
  dangling_id_to_name[pid] = ""
1318
1322
  yield Yield(attr)
@@ -1506,6 +1510,7 @@ def _iter_fs_files(
1506
1510
  id_to_dirnode: None | EllipsisType | MutableMapping[int, tuple[str, int] | DirNode] = ...,
1507
1511
  raise_for_changed_count: bool = False,
1508
1512
  ensure_file: None | bool = None,
1513
+ hold_top: bool = True,
1509
1514
  app: str = "android",
1510
1515
  cooldown: int | float = 0,
1511
1516
  max_workers: None | int = None,
@@ -1525,6 +1530,7 @@ def _iter_fs_files(
1525
1530
  id_to_dirnode: None | EllipsisType | MutableMapping[int, tuple[str, int] | DirNode] = ...,
1526
1531
  raise_for_changed_count: bool = False,
1527
1532
  ensure_file: None | bool = None,
1533
+ hold_top: bool = True,
1528
1534
  app: str = "android",
1529
1535
  cooldown: int | float = 0,
1530
1536
  max_workers: None | int = None,
@@ -1543,6 +1549,7 @@ def _iter_fs_files(
1543
1549
  id_to_dirnode: None | EllipsisType | MutableMapping[int, tuple[str, int] | DirNode] = ...,
1544
1550
  raise_for_changed_count: bool = False,
1545
1551
  ensure_file: None | bool = None,
1552
+ hold_top: bool = True,
1546
1553
  app: str = "android",
1547
1554
  cooldown: int | float = 0,
1548
1555
  max_workers: None | int = None,
@@ -1565,6 +1572,7 @@ def _iter_fs_files(
1565
1572
  - False: 必须是目录
1566
1573
  - None: 可以是目录或文件
1567
1574
 
1575
+ :param hold_top: 保留顶层目录信息,返回字段增加 "top_id", "top_ancestors"
1568
1576
  :param app: 使用指定 app(设备)的接口
1569
1577
  :param cooldown: 冷却时间,大于 0,则使用此时间间隔执行并发
1570
1578
  :param max_workers: 最大并发数,如果为 None 或 <= 0,则自动确定
@@ -1647,12 +1655,12 @@ def _iter_fs_files(
1647
1655
  max_workers=max_workers,
1648
1656
  **request_kwargs,
1649
1657
  )
1658
+ top_id = int(payload.get("cid") or 0)
1650
1659
  with with_iter_next(it) as get_next:
1651
1660
  while True:
1652
1661
  resp = yield get_next()
1653
- if id_to_dirnode is not ...:
1654
- for info in resp["path"][1:]:
1655
- id_to_dirnode[int(info["cid"])] = DirNode(info["name"], int(info["pid"]))
1662
+ _update_resp_ancestors(resp, id_to_dirnode)
1663
+ ancestors = resp["ancestors"]
1656
1664
  for info in resp["data"]:
1657
1665
  if normalize_attr is None:
1658
1666
  attr: dict | OverviewAttr = _overview_attr(info)
@@ -1667,6 +1675,9 @@ def _iter_fs_files(
1667
1675
  continue
1668
1676
  if with_dirname:
1669
1677
  info["dirname"] = pid_to_name[attr["parent_id"]]
1678
+ if hold_top:
1679
+ info["top_id"] = top_id
1680
+ info["top_ancestors"] = ancestors
1670
1681
  yield Yield(info)
1671
1682
  return run_gen_step_iter(gen_step, async_)
1672
1683
 
@@ -2294,7 +2305,6 @@ def iter_files_with_path(
2294
2305
  raise ValueError("please set the non-zero value of suffix or type")
2295
2306
  if isinstance(client, str):
2296
2307
  client = P115Client(client, check_for_relogin=True)
2297
- stable_point = client.pickcode_stable_point
2298
2308
  if isinstance(escape, bool):
2299
2309
  if escape:
2300
2310
  from posixpatht import escape
@@ -2377,7 +2387,7 @@ def iter_files_with_path(
2377
2387
  if id:
2378
2388
  yield through(iter_download_nodes(
2379
2389
  client,
2380
- to_pickcode(id, stable_point),
2390
+ client.to_pickcode(id),
2381
2391
  files=False,
2382
2392
  id_to_dirnode=id_to_dirnode,
2383
2393
  max_workers=None,
@@ -2516,7 +2526,6 @@ def iter_files_with_path_skim(
2516
2526
  from .download import iter_download_nodes
2517
2527
  if isinstance(client, str):
2518
2528
  client = P115Client(client, check_for_relogin=True)
2519
- stable_point = client.pickcode_stable_point
2520
2529
  if isinstance(escape, bool):
2521
2530
  if escape:
2522
2531
  from posixpatht import escape
@@ -2561,6 +2570,8 @@ def iter_files_with_path_skim(
2561
2570
  dirname = id_to_path[pid] = get_path(id_to_dirnode[pid]) + "/"
2562
2571
  return dirname + name
2563
2572
  def update_path(attr: dict, /) -> dict:
2573
+ attr["top_id"] = top_id
2574
+ attr["top_ancestors"] = top_ancestors
2564
2575
  try:
2565
2576
  if with_ancestors:
2566
2577
  attr["ancestors"] = get_ancestors(attr["id"], attr)
@@ -2585,12 +2596,28 @@ def iter_files_with_path_skim(
2585
2596
  def set_path_already(*_):
2586
2597
  nonlocal _path_already
2587
2598
  _path_already = True
2599
+ top_id = cid
2600
+ if not cid:
2601
+ top_ancestors = [{"id": 0, "parent_id": 0, "name": ""}]
2602
+ else:
2603
+ top_ancestors = []
2604
+ if id_to_dirnode:
2605
+ add_ancestor = top_ancestors.append
2606
+ tid = top_id
2607
+ while tid and tid in id_to_dirnode:
2608
+ name, pid = id_to_dirnode[tid]
2609
+ add_ancestor({"id": tid, "parent_id": pid, "name": name})
2610
+ tid = pid
2611
+ if not tid:
2612
+ add_ancestor({"id": 0, "parent_id": 0, "name": ""})
2613
+ top_ancestors.reverse()
2588
2614
  @as_gen_step
2589
2615
  def fetch_dirs(id: int | str, /):
2616
+ nonlocal top_ancestors
2590
2617
  if id:
2591
2618
  if cid:
2592
2619
  do_next: Callable = anext if async_ else next
2593
- yield do_next(_iter_fs_files(
2620
+ attr = yield do_next(_iter_fs_files(
2594
2621
  client,
2595
2622
  to_id(id),
2596
2623
  page_size=1,
@@ -2598,9 +2625,10 @@ def iter_files_with_path_skim(
2598
2625
  async_=async_,
2599
2626
  **request_kwargs,
2600
2627
  ))
2628
+ top_ancestors = attr["top_ancestors"]
2601
2629
  yield through(iter_download_nodes(
2602
2630
  client,
2603
- to_pickcode(id, stable_point),
2631
+ client.to_pickcode(id),
2604
2632
  files=False,
2605
2633
  id_to_dirnode=id_to_dirnode,
2606
2634
  max_workers=max_workers,
@@ -2872,7 +2900,6 @@ def iter_nodes_by_pickcode(
2872
2900
  """
2873
2901
  if isinstance(client, str):
2874
2902
  client = P115Client(client, check_for_relogin=True)
2875
- stable_point = client.pickcode_stable_point
2876
2903
  if id_to_dirnode is None:
2877
2904
  id_to_dirnode = ID_TO_DIRNODE_CACHE[client.user_id]
2878
2905
  methods: list[Callable] = []
@@ -2910,7 +2937,7 @@ def iter_nodes_by_pickcode(
2910
2937
  project,
2911
2938
  conmap(
2912
2939
  get_response,
2913
- (to_pickcode(pc, stable_point) for pc in pickcodes),
2940
+ map(client.to_pickcode, pickcodes),
2914
2941
  max_workers=max_workers,
2915
2942
  kwargs=request_kwargs,
2916
2943
  async_=async_,
@@ -3096,7 +3123,7 @@ def iter_nodes_using_info(
3096
3123
  return None
3097
3124
  check_response(resp)
3098
3125
  if id_to_dirnode is not ...:
3099
- _update_resp_2_id_to_dirnode(resp, id_to_dirnode)
3126
+ _update_resp_ancestors(resp, id_to_dirnode)
3100
3127
  return resp
3101
3128
  return do_filter(None, do_map(
3102
3129
  project,
@@ -4214,6 +4241,7 @@ def share_search_iter(
4214
4241
 
4215
4242
  '''
4216
4243
  # TODO: 需要优化,大优化,优化不好,就删了
4244
+ # TODO: 可以在拉取的同时,检测其它待拉取目录大小,但需要设定冷却时间(例如一秒最多 10 次查询)
4217
4245
  @overload
4218
4246
  def traverse_files(
4219
4247
  client: str | P115Client,
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "p115client"
3
- version = "0.0.5.14.1"
3
+ version = "0.0.5.14.2"
4
4
  description = "Python 115 webdisk client."
5
5
  authors = ["ChenyangGao <wosiwujm@gmail.com>"]
6
6
  license = "MIT"
File without changes