From dcd8e6e8f658b50c3075f858a4b1db0e4a8d4ca6 Mon Sep 17 00:00:00 2001
From: Timm Fitschen <t.fitschen@indiscale.com>
Date: Fri, 24 Jun 2022 21:43:38 +0200
Subject: [PATCH] Add import and recursive import flags

---
 CHANGELOG.md                |   9 +++
 src/caosdb/common/models.py | 110 +++++++++++++-----------------------
 2 files changed, 48 insertions(+), 71 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5ad9b14d..9e15263b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,12 +9,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ### Added ###
 
+* Directory (EXPERIMENTAL)
+
 ### Changed ###
 
+* It is not possible anymore to upload complete directories in one go by just
+  using a File entity with `file=/my/dir/`. The functionality of creating
+  directory entities is handled be the new Directory class.
+
 ### Deprecated ###
 
+* Dropoffbox functionality is deprecated in the caosdb server as well.
+
 ### Removed ###
 
+* Anything thumbnail-related (File entities)
 * Support for Python 3.6 and Python 3.7
 
 ### Fixed ###
diff --git a/src/caosdb/common/models.py b/src/caosdb/common/models.py
index baed2928..ee7a69e0 100644
--- a/src/caosdb/common/models.py
+++ b/src/caosdb/common/models.py
@@ -301,17 +301,6 @@ class Entity(object):
     def path(self, new_path):
         self.__path = new_path
 
-    @property
-    def thumbnail(self):
-        if self.__thumbnail is not None or self._wrapped_entity is None:
-            return self.__thumbnail
-
-        return self._wrapped_entity.thumbnail
-
-    @thumbnail.setter
-    def thumbnail(self, new_thumbnail):
-        self.__thumbnail = new_thumbnail
-
     @property
     def file(self):
         if self.__file is not None or self._wrapped_entity is None:
@@ -1824,7 +1813,8 @@ class Record(Entity):
 class Directory(Record):
     """This class represents CaosDB's directory entities."""
 
-    def __init__(self, name=None, id=None, description=None, path=None):
+    def __init__(self, name=None, id=None, description=None, path=None,
+                 import_file=False, recursive_import=False):
         Record.__init__(self, id=id, name=name, description=description)
         self.role = "Directory"
         self.datatype = None
@@ -1832,6 +1822,9 @@ class Directory(Record):
         # location in the fileserver
         self.path = path
 
+        self.recursive_import = recursive_import
+        self.import_file = import_file or recursive_import
+
     def to_xml(self, xml=None, add_properties=ALL, local_serialization=False):
         """Convert this file to an xml element.
 
@@ -1845,7 +1838,7 @@ class Directory(Record):
                              local_serialization=local_serialization)
 
     def add_property(self, property=None, id=None, name=None, description=None, datatype=None,
-                     value=None, unit=None, importance=FIX, inheritance=FIX):  # @ReservedAssignment
+                     value=None, unit=None, importance=FIX, inheritance=FIX):
 
         return super().add_property(
             property=property, id=id, name=name, description=description, datatype=datatype,
@@ -1865,26 +1858,30 @@ class File(Record):
     look at `test_files.py` in the Python integration tests of the
     `load_files.py` script in the advanced user tools.
 
-    @param name: A name for this file record (That's an entity name - not to be
-        confused with the last segment of the files path).
-    @param id: An ID.
-    @param description: A description for this file record.
-    @param path: The complete path, including the file name, of the file in the
-        server's "caosroot" file system.
-    @param file: A local path or python file object.  The file designated by
-        this argument will be uploaded to the server via HTTP.
-    @param pickup: A file/folder in the DropOffBox (the server will move that
-        file into its "caosroot" file system).
-    @param thumbnail: (Local) filename to a thumbnail for this file.
-    @param properties: A list of properties for this file record. @todo is this
-        implemented?
-    @param from_location: Deprecated, use `pickup` instead.
-
+    Parameters
+    ----------
+    name : str
+        A name for this file record (That's an entity name - not to be confused
+        with the last segment of the files path).
+    id : int
+        An ID.
+    description : str
+        A description for this file record.
+    path : str
+        The complete path, including the file name, of the file in the server's
+        virtual file system.
+    file : str or readable
+        A local path or python file object.  The file designated by this
+        argument will be uploaded to the server via HTTP.
+    pickup : str
+        A file/folder in the DropOffBox (the server will move that file into
+        its "caosroot" file system). (DEPRECATED, use import feature)
+    import_file : bool
+        Import the file (don't upload it, its already there). Default: `False`
     """
 
-    def __init__(self, name=None, id=None, description=None,  # @ReservedAssignment
-                 path=None, file=None, pickup=None,  # @ReservedAssignment
-                 thumbnail=None, from_location=None):
+    def __init__(self, name=None, id=None, description=None,
+                 path=None, file=None, pickup=None, import_file=False):
         Record.__init__(self, id=id, name=name, description=description)
         self.role = "File"
         self.datatype = None
@@ -1894,16 +1891,9 @@ class File(Record):
 
         # local file path or pointer to local file
         self.file = file
-        self.thumbnail = thumbnail
 
         self.pickup = pickup
-
-        if from_location is not None:
-            warn(DeprecationWarning(
-                "Param `from_location` is deprecated, use `pickup instead`."))
-
-        if self.pickup is None:
-            self.pickup = from_location
+        self.import_file = import_file
 
     def to_xml(self, xml=None, add_properties=ALL, local_serialization=False):
         """Convert this file to an xml element.
@@ -1930,7 +1920,7 @@ class File(Record):
             file_ = open(target, 'wb')
         else:
             file_ = NamedTemporaryFile(mode='wb', delete=False)
-        checksum = File.download_from_path(file_, self.path)
+        checksum = File._download_from_path(file_, self.path)
 
         if check_hash and self.checksum is not None and self.checksum.lower() != checksum:
             raise ConsistencyError(
@@ -1939,7 +1929,7 @@ class File(Record):
         return file_.name
 
     @staticmethod
-    def download_from_path(target_file, path):
+    def _download_from_path(target_file, path):
 
         _log_request("GET (download): " + path)
         response = get_connection().download_file(path)
@@ -1959,29 +1949,6 @@ class File(Record):
     def sha512(file):
         return File._get_checksum_single_file(file)
 
-    @staticmethod
-    def _get_checksum(files):
-        import locale
-
-        if hasattr(files, "name"):
-            return File._get_checksum_single_file(files.name)
-        else:
-            if isdir(files):
-                checksumappend = ""
-
-                for child in sorted(listdir(files),
-                                    key=cmp_to_key(locale.strcoll)):
-
-                    if isdir(files + '/' + child):
-                        checksumappend += child
-                    checksumappend += File._get_checksum(files + "/" + child)
-                checksum = sha512()
-                checksum.update(checksumappend.encode('utf-8'))
-
-                return checksum.hexdigest()
-            else:
-                return File._get_checksum_single_file(files)
-
     @staticmethod
     def _get_checksum_single_file(single_file):
         _file = open(single_file, 'rb')
@@ -1996,7 +1963,7 @@ class File(Record):
         return checksum.hexdigest()
 
     def add_property(self, property=None, id=None, name=None, description=None, datatype=None,
-                     value=None, unit=None, importance=FIX, inheritance=FIX):  # @ReservedAssignment
+                     value=None, unit=None, importance=FIX, inheritance=FIX):
 
         return super().add_property(
             property=property, id=id, name=name, description=description, datatype=datatype,
@@ -3464,6 +3431,8 @@ class Container(list):
                 entity_xml.set("upload", entity._upload)
             elif hasattr(entity, 'pickup') and entity.pickup is not None:
                 entity_xml.set("pickup", entity.pickup)
+            elif hasattr(entity, 'import_file') and entity.import_file is True:
+                entity_xml.set("import", "true")
 
             insert_xml.append(entity_xml)
 
@@ -3504,7 +3473,7 @@ class Container(list):
     def _process_file_if_present_and_add_to_http_parts(http_parts, entity):
         if isinstance(entity, File) and hasattr(
                 entity, 'file') and entity.file is not None:
-            new_checksum = File._get_checksum(entity.file)
+            new_checksum = File.sha512(entity.file)
 
             # do not transfer unchanged files.
 
@@ -3534,11 +3503,6 @@ class Container(list):
                 part.filename = entity._upload
             http_parts.append(part)
 
-            if entity.thumbnail is not None:
-                part = MultipartParam.from_file(paramname=hex(
-                    randint(0, sys.maxsize)), filename=entity.thumbnail)
-                part.filename = entity._upload + ".thumbnail"
-                http_parts.append(part)
         elif isinstance(entity, File) and hasattr(entity, "file") and entity.file is None:
             entity._upload = None
         else:
@@ -3617,6 +3581,10 @@ class Container(list):
                 entity_xml.set("upload", entity._upload)
             elif hasattr(entity, 'pickup') and entity.pickup is not None:
                 entity_xml.set("pickup", entity.pickup)
+            elif hasattr(entity, 'recursive_import') and entity.recursive_import is True:
+                entity_xml.set("recursive_import", "true")
+            elif hasattr(entity, 'import_file') and entity.import_file is True:
+                entity_xml.set("import", "true")
             insert_xml.append(entity_xml)
 
         if len(self) > 0 and len(insert_xml) < 1:
-- 
GitLab