nc-py-api 0.12.1__py3-none-any.whl → 0.15.0__py3-none-any.whl

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.
nc_py_api/files/files.py CHANGED
@@ -9,7 +9,7 @@ from httpx import Headers
9
9
 
10
10
  from .._exceptions import NextcloudException, NextcloudExceptionNotFound, check_error
11
11
  from .._misc import random_string, require_capabilities
12
- from .._session import AsyncNcSessionBasic, NcSessionBasic
12
+ from .._session import NcSessionBasic
13
13
  from . import FsNode, LockType, SystemTag
14
14
  from ._files import (
15
15
  PROPFIND_PROPERTIES,
@@ -21,6 +21,7 @@ from ._files import (
21
21
  build_listdir_req,
22
22
  build_listdir_response,
23
23
  build_setfav_req,
24
+ build_tags_ids_for_object,
24
25
  build_update_tag_req,
25
26
  dav_get_obj_path,
26
27
  element_tree_as_str,
@@ -28,7 +29,7 @@ from ._files import (
28
29
  get_propfind_properties,
29
30
  lf_parse_webdav_response,
30
31
  )
31
- from .sharing import _AsyncFilesSharingAPI, _FilesSharingAPI
32
+ from .sharing import _FilesSharingAPI
32
33
 
33
34
 
34
35
  class FilesAPI:
@@ -344,6 +345,17 @@ class FilesAPI:
344
345
  response = self._session.adapter_dav.request("PROPFIND", "/systemtags", content=element_tree_as_str(root))
345
346
  return build_list_tags_response(response)
346
347
 
348
+ def get_tags(self, file_id: FsNode | int) -> list[SystemTag]:
349
+ """Returns list of Tags assigned to the File or Directory."""
350
+ fs_object = file_id.info.fileid if isinstance(file_id, FsNode) else file_id
351
+ url_to_fetch = f"/systemtags-relations/files/{fs_object}/"
352
+ response = self._session.adapter_dav.request("PROPFIND", url_to_fetch)
353
+ object_tags_ids = build_tags_ids_for_object(self._session.cfg.dav_url_suffix + url_to_fetch, response)
354
+ if not object_tags_ids:
355
+ return []
356
+ all_tags = self.list_tags()
357
+ return [tag for tag in all_tags if tag.tag_id in object_tags_ids]
358
+
347
359
  def create_tag(self, name: str, user_visible: bool = True, user_assignable: bool = True) -> None:
348
360
  """Creates a new Tag.
349
361
 
@@ -500,483 +512,3 @@ class FilesAPI:
500
512
  return FsNode(full_path.strip("/"), **etag_fileid_from_response(response))
501
513
  finally:
502
514
  self._session.adapter_dav.delete(_dav_path)
503
-
504
-
505
- class AsyncFilesAPI:
506
- """Class that encapsulates async file system and file sharing API."""
507
-
508
- sharing: _AsyncFilesSharingAPI
509
- """API for managing Files Shares"""
510
-
511
- def __init__(self, session: AsyncNcSessionBasic):
512
- self._session = session
513
- self.sharing = _AsyncFilesSharingAPI(session)
514
-
515
- async def listdir(self, path: str | FsNode = "", depth: int = 1, exclude_self=True) -> list[FsNode]:
516
- """Returns a list of all entries in the specified directory.
517
-
518
- :param path: path to the directory to get the list.
519
- :param depth: how many directory levels should be included in output. Default = **1** (only specified directory)
520
- :param exclude_self: boolean value indicating whether the `path` itself should be excluded from the list or not.
521
- Default = **True**.
522
- """
523
- if exclude_self and not depth:
524
- raise ValueError("Wrong input parameters, query will return nothing.")
525
- properties = get_propfind_properties(await self._session.capabilities)
526
- path = path.user_path if isinstance(path, FsNode) else path
527
- return await self._listdir(
528
- await self._session.user, path, properties=properties, depth=depth, exclude_self=exclude_self
529
- )
530
-
531
- async def by_id(self, file_id: int | str | FsNode) -> FsNode | None:
532
- """Returns :py:class:`~nc_py_api.files.FsNode` by file_id if any.
533
-
534
- :param file_id: can be full file ID with Nextcloud instance ID or only clear file ID.
535
- """
536
- file_id = file_id.file_id if isinstance(file_id, FsNode) else file_id
537
- result = await self.find(req=["eq", "fileid", file_id])
538
- return result[0] if result else None
539
-
540
- async def by_path(self, path: str | FsNode) -> FsNode | None:
541
- """Returns :py:class:`~nc_py_api.files.FsNode` by exact path if any."""
542
- path = path.user_path if isinstance(path, FsNode) else path
543
- result = await self.listdir(path, depth=0, exclude_self=False)
544
- return result[0] if result else None
545
-
546
- async def find(self, req: list, path: str | FsNode = "") -> list[FsNode]:
547
- """Searches a directory for a file or subdirectory with a name.
548
-
549
- :param req: list of conditions to search for. Detailed description here...
550
- :param path: path where to search from. Default = **""**.
551
- """
552
- # `req` possible keys: "name", "mime", "last_modified", "size", "favorite", "fileid"
553
- root = build_find_request(req, path, await self._session.user, await self._session.capabilities)
554
- webdav_response = await self._session.adapter_dav.request(
555
- "SEARCH", "", content=element_tree_as_str(root), headers={"Content-Type": "text/xml"}
556
- )
557
- request_info = f"find: {await self._session.user}, {req}, {path}"
558
- return lf_parse_webdav_response(self._session.cfg.dav_url_suffix, webdav_response, request_info)
559
-
560
- async def download(self, path: str | FsNode) -> bytes:
561
- """Downloads and returns the content of a file."""
562
- path = path.user_path if isinstance(path, FsNode) else path
563
- response = await self._session.adapter_dav.get(quote(dav_get_obj_path(await self._session.user, path)))
564
- check_error(response, f"download: user={await self._session.user}, path={path}")
565
- return response.content
566
-
567
- async def download2stream(self, path: str | FsNode, fp, **kwargs) -> None:
568
- """Downloads file to the given `fp` object.
569
-
570
- :param path: path to download file.
571
- :param fp: filename (string), pathlib.Path object or a file object.
572
- The object must implement the ``file.write`` method and be able to write binary data.
573
- :param kwargs: **chunk_size** an int value specifying chunk size to write. Default = **5Mb**
574
- """
575
- path = quote(dav_get_obj_path(await self._session.user, path.user_path if isinstance(path, FsNode) else path))
576
- await self._session.download2stream(path, fp, dav=True, **kwargs)
577
-
578
- async def download_directory_as_zip(
579
- self, path: str | FsNode, local_path: str | Path | None = None, **kwargs
580
- ) -> Path:
581
- """Downloads a remote directory as zip archive.
582
-
583
- :param path: path to directory to download.
584
- :param local_path: relative or absolute file path to save zip file.
585
- :returns: Path to the saved zip archive.
586
-
587
- .. note:: This works only for directories, you should not use this to download a file.
588
- """
589
- path = path.user_path if isinstance(path, FsNode) else path
590
- result_path = local_path if local_path else os.path.basename(path)
591
- with open(result_path, "wb") as fp:
592
- await self._session.download2fp(
593
- "/index.php/apps/files/ajax/download.php", fp, dav=False, params={"dir": path}, **kwargs
594
- )
595
- return Path(result_path)
596
-
597
- async def upload(self, path: str | FsNode, content: bytes | str) -> FsNode:
598
- """Creates a file with the specified content at the specified path.
599
-
600
- :param path: file's upload path.
601
- :param content: content to create the file. If it is a string, it will be encoded into bytes using UTF-8.
602
- """
603
- path = path.user_path if isinstance(path, FsNode) else path
604
- full_path = dav_get_obj_path(await self._session.user, path)
605
- response = await self._session.adapter_dav.put(quote(full_path), content=content)
606
- check_error(response, f"upload: user={await self._session.user}, path={path}, size={len(content)}")
607
- return FsNode(full_path.strip("/"), **etag_fileid_from_response(response))
608
-
609
- async def upload_stream(self, path: str | FsNode, fp, **kwargs) -> FsNode:
610
- """Creates a file with content provided by `fp` object at the specified path.
611
-
612
- :param path: file's upload path.
613
- :param fp: filename (string), pathlib.Path object or a file object.
614
- The object must implement the ``file.read`` method providing data with str or bytes type.
615
- :param kwargs: **chunk_size** an int value specifying chunk size to read. Default = **5Mb**
616
- """
617
- path = path.user_path if isinstance(path, FsNode) else path
618
- chunk_size = kwargs.get("chunk_size", 5 * 1024 * 1024)
619
- if isinstance(fp, str | Path):
620
- with builtins.open(fp, "rb") as f:
621
- return await self.__upload_stream(path, f, chunk_size)
622
- elif hasattr(fp, "read"):
623
- return await self.__upload_stream(path, fp, chunk_size)
624
- else:
625
- raise TypeError("`fp` must be a path to file or an object with `read` method.")
626
-
627
- async def mkdir(self, path: str | FsNode) -> FsNode:
628
- """Creates a new directory.
629
-
630
- :param path: path of the directory to be created.
631
- """
632
- path = path.user_path if isinstance(path, FsNode) else path
633
- full_path = dav_get_obj_path(await self._session.user, path)
634
- response = await self._session.adapter_dav.request("MKCOL", quote(full_path))
635
- check_error(response)
636
- full_path += "/" if not full_path.endswith("/") else ""
637
- return FsNode(full_path.lstrip("/"), **etag_fileid_from_response(response))
638
-
639
- async def makedirs(self, path: str | FsNode, exist_ok=False) -> FsNode | None:
640
- """Creates a new directory and subdirectories.
641
-
642
- :param path: path of the directories to be created.
643
- :param exist_ok: ignore error if any of pathname components already exists.
644
- :returns: `FsNode` if directory was created or ``None`` if it was already created.
645
- """
646
- _path = ""
647
- path = path.user_path if isinstance(path, FsNode) else path
648
- path = path.lstrip("/")
649
- result = None
650
- for i in Path(path).parts:
651
- _path = os.path.join(_path, i)
652
- if not exist_ok:
653
- result = await self.mkdir(_path)
654
- else:
655
- try:
656
- result = await self.mkdir(_path)
657
- except NextcloudException as e:
658
- if e.status_code != 405:
659
- raise e from None
660
- return result
661
-
662
- async def delete(self, path: str | FsNode, not_fail=False) -> None:
663
- """Deletes a file/directory (moves to trash if trash is enabled).
664
-
665
- :param path: path to delete.
666
- :param not_fail: if set to ``True`` and the object is not found, it does not raise an exception.
667
- """
668
- path = path.user_path if isinstance(path, FsNode) else path
669
- response = await self._session.adapter_dav.delete(quote(dav_get_obj_path(await self._session.user, path)))
670
- if response.status_code == 404 and not_fail:
671
- return
672
- check_error(response)
673
-
674
- async def move(self, path_src: str | FsNode, path_dest: str | FsNode, overwrite=False) -> FsNode:
675
- """Moves an existing file or a directory.
676
-
677
- :param path_src: path of an existing file/directory.
678
- :param path_dest: name of the new one.
679
- :param overwrite: if ``True`` and the destination object already exists, it gets overwritten.
680
- Default = **False**.
681
- """
682
- path_src = path_src.user_path if isinstance(path_src, FsNode) else path_src
683
- full_dest_path = dav_get_obj_path(
684
- await self._session.user, path_dest.user_path if isinstance(path_dest, FsNode) else path_dest
685
- )
686
- dest = self._session.cfg.dav_endpoint + quote(full_dest_path)
687
- headers = Headers({"Destination": dest, "Overwrite": "T" if overwrite else "F"}, encoding="utf-8")
688
- response = await self._session.adapter_dav.request(
689
- "MOVE",
690
- quote(dav_get_obj_path(await self._session.user, path_src)),
691
- headers=headers,
692
- )
693
- check_error(response, f"move: user={await self._session.user}, src={path_src}, dest={dest}, {overwrite}")
694
- return (await self.find(req=["eq", "fileid", response.headers["OC-FileId"]]))[0]
695
-
696
- async def copy(self, path_src: str | FsNode, path_dest: str | FsNode, overwrite=False) -> FsNode:
697
- """Copies an existing file/directory.
698
-
699
- :param path_src: path of an existing file/directory.
700
- :param path_dest: name of the new one.
701
- :param overwrite: if ``True`` and the destination object already exists, it gets overwritten.
702
- Default = **False**.
703
- """
704
- path_src = path_src.user_path if isinstance(path_src, FsNode) else path_src
705
- full_dest_path = dav_get_obj_path(
706
- await self._session.user, path_dest.user_path if isinstance(path_dest, FsNode) else path_dest
707
- )
708
- dest = self._session.cfg.dav_endpoint + quote(full_dest_path)
709
- headers = Headers({"Destination": dest, "Overwrite": "T" if overwrite else "F"}, encoding="utf-8")
710
- response = await self._session.adapter_dav.request(
711
- "COPY",
712
- quote(dav_get_obj_path(await self._session.user, path_src)),
713
- headers=headers,
714
- )
715
- check_error(response, f"copy: user={await self._session.user}, src={path_src}, dest={dest}, {overwrite}")
716
- return (await self.find(req=["eq", "fileid", response.headers["OC-FileId"]]))[0]
717
-
718
- async def list_by_criteria(
719
- self, properties: list[str] | None = None, tags: list[int | SystemTag] | None = None
720
- ) -> list[FsNode]:
721
- """Returns a list of all files/directories for the current user filtered by the specified values.
722
-
723
- :param properties: List of ``properties`` that should have been set for the file.
724
- Supported values: **favorite**
725
- :param tags: List of ``tags ids`` or ``SystemTag`` that should have been set for the file.
726
- """
727
- root = build_list_by_criteria_req(properties, tags, await self._session.capabilities)
728
- webdav_response = await self._session.adapter_dav.request(
729
- "REPORT", dav_get_obj_path(await self._session.user), content=element_tree_as_str(root)
730
- )
731
- request_info = f"list_files_by_criteria: {await self._session.user}"
732
- check_error(webdav_response, request_info)
733
- return lf_parse_webdav_response(self._session.cfg.dav_url_suffix, webdav_response, request_info)
734
-
735
- async def setfav(self, path: str | FsNode, value: int | bool) -> None:
736
- """Sets or unsets favourite flag for specific file.
737
-
738
- :param path: path to the object to set the state.
739
- :param value: value to set for the ``favourite`` state.
740
- """
741
- path = path.user_path if isinstance(path, FsNode) else path
742
- root = build_setfav_req(value)
743
- webdav_response = await self._session.adapter_dav.request(
744
- "PROPPATCH", quote(dav_get_obj_path(await self._session.user, path)), content=element_tree_as_str(root)
745
- )
746
- check_error(webdav_response, f"setfav: path={path}, value={value}")
747
-
748
- async def trashbin_list(self) -> list[FsNode]:
749
- """Returns a list of all entries in the TrashBin."""
750
- properties = PROPFIND_PROPERTIES
751
- properties += ["nc:trashbin-filename", "nc:trashbin-original-location", "nc:trashbin-deletion-time"]
752
- return await self._listdir(
753
- await self._session.user,
754
- "",
755
- properties=properties,
756
- depth=1,
757
- exclude_self=False,
758
- prop_type=PropFindType.TRASHBIN,
759
- )
760
-
761
- async def trashbin_restore(self, path: str | FsNode) -> None:
762
- """Restore a file/directory from the TrashBin.
763
-
764
- :param path: path to delete, e.g., the ``user_path`` field from ``FsNode`` or the **FsNode** class itself.
765
- """
766
- restore_name = path.name if isinstance(path, FsNode) else path.split("/", maxsplit=1)[-1]
767
- path = path.user_path if isinstance(path, FsNode) else path
768
-
769
- dest = self._session.cfg.dav_endpoint + f"/trashbin/{await self._session.user}/restore/{restore_name}"
770
- headers = Headers({"Destination": dest}, encoding="utf-8")
771
- response = await self._session.adapter_dav.request(
772
- "MOVE",
773
- quote(f"/trashbin/{await self._session.user}/{path}"),
774
- headers=headers,
775
- )
776
- check_error(response, f"trashbin_restore: user={await self._session.user}, src={path}, dest={dest}")
777
-
778
- async def trashbin_delete(self, path: str | FsNode, not_fail=False) -> None:
779
- """Deletes a file/directory permanently from the TrashBin.
780
-
781
- :param path: path to delete, e.g., the ``user_path`` field from ``FsNode`` or the **FsNode** class itself.
782
- :param not_fail: if set to ``True`` and the object is not found, it does not raise an exception.
783
- """
784
- path = path.user_path if isinstance(path, FsNode) else path
785
- response = await self._session.adapter_dav.delete(quote(f"/trashbin/{await self._session.user}/{path}"))
786
- if response.status_code == 404 and not_fail:
787
- return
788
- check_error(response)
789
-
790
- async def trashbin_cleanup(self) -> None:
791
- """Empties the TrashBin."""
792
- check_error(await self._session.adapter_dav.delete(f"/trashbin/{await self._session.user}/trash"))
793
-
794
- async def get_versions(self, file_object: FsNode) -> list[FsNode]:
795
- """Returns a list of all file versions if any."""
796
- require_capabilities("files.versioning", await self._session.capabilities)
797
- return await self._listdir(
798
- await self._session.user,
799
- str(file_object.info.fileid) if file_object.info.fileid else file_object.file_id,
800
- properties=PROPFIND_PROPERTIES,
801
- depth=1,
802
- exclude_self=False,
803
- prop_type=PropFindType.VERSIONS_FILEID if file_object.info.fileid else PropFindType.VERSIONS_FILE_ID,
804
- )
805
-
806
- async def restore_version(self, file_object: FsNode) -> None:
807
- """Restore a file with specified version.
808
-
809
- :param file_object: The **FsNode** class from :py:meth:`~nc_py_api.files.files.FilesAPI.get_versions`.
810
- """
811
- require_capabilities("files.versioning", await self._session.capabilities)
812
- dest = self._session.cfg.dav_endpoint + f"/versions/{await self._session.user}/restore/{file_object.name}"
813
- headers = Headers({"Destination": dest}, encoding="utf-8")
814
- response = await self._session.adapter_dav.request(
815
- "MOVE",
816
- quote(f"/versions/{await self._session.user}/{file_object.user_path}"),
817
- headers=headers,
818
- )
819
- check_error(response, f"restore_version: user={await self._session.user}, src={file_object.user_path}")
820
-
821
- async def list_tags(self) -> list[SystemTag]:
822
- """Returns list of the avalaible Tags."""
823
- root = build_list_tag_req()
824
- response = await self._session.adapter_dav.request("PROPFIND", "/systemtags", content=element_tree_as_str(root))
825
- return build_list_tags_response(response)
826
-
827
- async def create_tag(self, name: str, user_visible: bool = True, user_assignable: bool = True) -> None:
828
- """Creates a new Tag.
829
-
830
- :param name: Name of the tag.
831
- :param user_visible: Should be Tag visible in the UI.
832
- :param user_assignable: Can Tag be assigned from the UI.
833
- """
834
- response = await self._session.adapter_dav.post(
835
- "/systemtags",
836
- json={
837
- "name": name,
838
- "userVisible": user_visible,
839
- "userAssignable": user_assignable,
840
- },
841
- )
842
- check_error(response, info=f"create_tag({name})")
843
-
844
- async def update_tag(
845
- self,
846
- tag_id: int | SystemTag,
847
- name: str | None = None,
848
- user_visible: bool | None = None,
849
- user_assignable: bool | None = None,
850
- ) -> None:
851
- """Updates the Tag information."""
852
- tag_id = tag_id.tag_id if isinstance(tag_id, SystemTag) else tag_id
853
- root = build_update_tag_req(name, user_visible, user_assignable)
854
- response = await self._session.adapter_dav.request(
855
- "PROPPATCH", f"/systemtags/{tag_id}", content=element_tree_as_str(root)
856
- )
857
- check_error(response)
858
-
859
- async def delete_tag(self, tag_id: int | SystemTag) -> None:
860
- """Deletes the tag."""
861
- tag_id = tag_id.tag_id if isinstance(tag_id, SystemTag) else tag_id
862
- response = await self._session.adapter_dav.delete(f"/systemtags/{tag_id}")
863
- check_error(response)
864
-
865
- async def tag_by_name(self, tag_name: str) -> SystemTag:
866
- """Returns Tag info by its name if found or ``None`` otherwise."""
867
- r = [i for i in await self.list_tags() if i.display_name == tag_name]
868
- if not r:
869
- raise NextcloudExceptionNotFound(f"Tag with name='{tag_name}' not found.")
870
- return r[0]
871
-
872
- async def assign_tag(self, file_id: FsNode | int, tag_id: SystemTag | int) -> None:
873
- """Assigns Tag to a file/directory."""
874
- await self._file_change_tag_state(file_id, tag_id, True)
875
-
876
- async def unassign_tag(self, file_id: FsNode | int, tag_id: SystemTag | int) -> None:
877
- """Removes Tag from a file/directory."""
878
- await self._file_change_tag_state(file_id, tag_id, False)
879
-
880
- async def lock(self, path: FsNode | str, lock_type: LockType = LockType.MANUAL_LOCK) -> None:
881
- """Locks the file.
882
-
883
- .. note:: Exception codes: 423 - existing lock present.
884
- """
885
- require_capabilities("files.locking", await self._session.capabilities)
886
- full_path = dav_get_obj_path(await self._session.user, path.user_path if isinstance(path, FsNode) else path)
887
- response = await self._session.adapter_dav.request(
888
- "LOCK",
889
- quote(full_path),
890
- headers={"X-User-Lock": "1", "X-User-Lock-Type": str(lock_type.value)},
891
- )
892
- check_error(response, f"lock: user={self._session.user}, path={full_path}")
893
-
894
- async def unlock(self, path: FsNode | str) -> None:
895
- """Unlocks the file.
896
-
897
- .. note:: Exception codes: 412 - the file is not locked, 423 - the lock is owned by another user.
898
- """
899
- require_capabilities("files.locking", await self._session.capabilities)
900
- full_path = dav_get_obj_path(await self._session.user, path.user_path if isinstance(path, FsNode) else path)
901
- response = await self._session.adapter_dav.request(
902
- "UNLOCK",
903
- quote(full_path),
904
- headers={"X-User-Lock": "1"},
905
- )
906
- check_error(response, f"unlock: user={self._session.user}, path={full_path}")
907
-
908
- async def _file_change_tag_state(self, file_id: FsNode | int, tag_id: SystemTag | int, tag_state: bool) -> None:
909
- fs_object = file_id.info.fileid if isinstance(file_id, FsNode) else file_id
910
- tag = tag_id.tag_id if isinstance(tag_id, SystemTag) else tag_id
911
- response = await self._session.adapter_dav.request(
912
- "PUT" if tag_state else "DELETE", f"/systemtags-relations/files/{fs_object}/{tag}"
913
- )
914
- check_error(
915
- response,
916
- info=f"({'Adding' if tag_state else 'Removing'} `{tag}` {'to' if tag_state else 'from'} {fs_object})",
917
- )
918
-
919
- async def _listdir(
920
- self,
921
- user: str,
922
- path: str,
923
- properties: list[str],
924
- depth: int,
925
- exclude_self: bool,
926
- prop_type: PropFindType = PropFindType.DEFAULT,
927
- ) -> list[FsNode]:
928
- root, dav_path = build_listdir_req(user, path, properties, prop_type)
929
- webdav_response = await self._session.adapter_dav.request(
930
- "PROPFIND",
931
- quote(dav_path),
932
- content=element_tree_as_str(root),
933
- headers={"Depth": "infinity" if depth == -1 else str(depth)},
934
- )
935
- return build_listdir_response(
936
- self._session.cfg.dav_url_suffix, webdav_response, user, path, properties, exclude_self, prop_type
937
- )
938
-
939
- async def __upload_stream(self, path: str, fp, chunk_size: int) -> FsNode:
940
- _tmp_path = "nc-py-api-" + random_string(56)
941
- _dav_path = quote(dav_get_obj_path(await self._session.user, _tmp_path, root_path="/uploads"))
942
- _v2 = bool(self._session.cfg.options.upload_chunk_v2 and chunk_size >= 5 * 1024 * 1024)
943
- full_path = dav_get_obj_path(await self._session.user, path)
944
- headers = Headers({"Destination": self._session.cfg.dav_endpoint + quote(full_path)}, encoding="utf-8")
945
- if _v2:
946
- response = await self._session.adapter_dav.request("MKCOL", _dav_path, headers=headers)
947
- else:
948
- response = await self._session.adapter_dav.request("MKCOL", _dav_path)
949
- check_error(response)
950
- try:
951
- start_bytes = end_bytes = chunk_number = 0
952
- while True:
953
- piece = fp.read(chunk_size)
954
- if not piece:
955
- break
956
- end_bytes = start_bytes + len(piece)
957
- if _v2:
958
- response = await self._session.adapter_dav.put(
959
- _dav_path + "/" + str(chunk_number), content=piece, headers=headers
960
- )
961
- else:
962
- _filename = str(start_bytes).rjust(15, "0") + "-" + str(end_bytes).rjust(15, "0")
963
- response = await self._session.adapter_dav.put(_dav_path + "/" + _filename, content=piece)
964
- check_error(
965
- response,
966
- f"upload_stream(v={_v2}): user={await self._session.user}, path={path}, cur_size={end_bytes}",
967
- )
968
- start_bytes = end_bytes
969
- chunk_number += 1
970
-
971
- response = await self._session.adapter_dav.request(
972
- "MOVE",
973
- _dav_path + "/.file",
974
- headers=headers,
975
- )
976
- check_error(
977
- response,
978
- f"upload_stream(v={_v2}): user={await self._session.user}, path={path}, total_size={end_bytes}",
979
- )
980
- return FsNode(full_path.strip("/"), **etag_fileid_from_response(response))
981
- finally:
982
- await self._session.adapter_dav.delete(_dav_path)