Ejemplo n.º 1
0
def assert_synced(m: Maestral):
    """Asserts that the `local_folder` and `remote_folder` are synced."""

    listing = m.client.list_folder("/", recursive=True)
    local_snapshot = DirectorySnapshot(m.dropbox_path)

    # assert that all items from server are present locally
    # with the same content hash
    for e in listing.entries:
        dbx_path = e.path_display
        local_path = to_existing_cased_path(str(dbx_path), root=m.dropbox_path)

        remote_hash = e.content_hash if isinstance(e, FileMetadata) else "folder"
        assert (
            m.sync.get_local_hash(local_path) == remote_hash
        ), f'different file content for "{dbx_path}"'

    # assert that all local items are present on server
    for path in local_snapshot.paths:
        if not m.sync.is_excluded(path) and is_child(path, m.dropbox_path):
            if not m.sync.is_excluded(path):
                dbx_path = m.sync.to_dbx_path(path).lower()
                matching_items = list(
                    e for e in listing.entries if e.path_lower == dbx_path
                )
                assert (
                    len(matching_items) == 1
                ), f'local item "{path}" does not exist on dbx'

    # check that our index is correct
    for index_entry in m.sync.get_index():

        if is_child(index_entry.dbx_path_lower, "/"):
            # check that there is a match on the server
            matching_items = list(
                e for e in listing.entries if e.path_lower == index_entry.dbx_path_lower
            )
            assert (
                len(matching_items) == 1
            ), f'indexed item "{index_entry.dbx_path_lower}" does not exist on dbx'

            e = matching_items[0]
            remote_rev = e.rev if isinstance(e, FileMetadata) else "folder"

            # check if revs are equal on server and locally
            assert (
                index_entry.rev == remote_rev
            ), f'different revs for "{index_entry.dbx_path_lower}"'

            # check if casing on drive is the same as in index
            local_path_expected_casing = m.dropbox_path + index_entry.dbx_path_cased
            local_path_actual_casing = to_existing_cased_path(
                local_path_expected_casing
            )

            assert (
                local_path_expected_casing == local_path_actual_casing
            ), "casing on drive does not match index"
Ejemplo n.º 2
0
    def include_item(self, dbx_path):
        """
        Includes file or folder in sync and downloads in the background. It is safe to
        call this method with items which have already been included, they will not be
        downloaded again.

        :param str dbx_path: Dropbox path of item to include.
        :raises: :class:`ValueError` if ``dbx_path`` is not on Dropbox or lies inside
            another excluded folder.
        :raises: :class:`ConnectionError` if connection to Dropbox fails.
        """

        # input validation
        md = self.client.get_metadata(dbx_path)

        if not md:
            raise ValueError(f'"{dbx_path}" does not exist on Dropbox')

        dbx_path = dbx_path.lower().rstrip(osp.sep)

        old_excluded_items = self.sync.excluded_items

        for folder in old_excluded_items:
            if is_child(dbx_path, folder):
                raise ValueError(
                    f'"{dbx_path}" lies inside the excluded folder '
                    f'"{folder}". Please include "{folder}" first.')

        # Get items which will need to be downloaded, do not attempt to download
        # children of `dbx_path` which were already included.
        # `new_included_items` will either be empty (`dbx_path` was already
        # included), just contain `dbx_path` itself (the item was fully excluded) or
        # only contain children of `dbx_path` (`dbx_path` was partially included).
        new_included_items = tuple(x for x in old_excluded_items
                                   if x == dbx_path or is_child(x, dbx_path))

        if new_included_items:
            # remove `dbx_path` or all excluded children from the excluded list
            excluded_items = list(
                set(old_excluded_items) - set(new_included_items))
        else:
            logger.info('%s was already included', dbx_path)
            return

        self.sync.excluded_items = excluded_items

        logger.info('Included %s', dbx_path)

        # download items from Dropbox
        for folder in new_included_items:
            self.sync.queued_newly_included_downloads.put(folder)
Ejemplo n.º 3
0
    def include_folder(self, dbx_path):
        """
        Includes folder in sync and downloads in the background. It is safe to call this
        method with folders which have already been included, they will not be downloaded
        again.

        :param str dbx_path: Dropbox folder to include.
        :raises: :class:`ValueError` if ``dbx_path`` is not on Dropbox or lies inside
            another excluded folder.
        :raises: :class:`ConnectionError` if connection to Dropbox fails.
        """

        dbx_path = dbx_path.lower().rstrip(osp.sep)
        md = self.client.get_metadata(dbx_path)

        old_excluded_folders = self.sync.excluded_folders

        if not isinstance(md, files.FolderMetadata):
            raise ValueError(
                "No such folder on Dropbox: '{0}'".format(dbx_path))
        for folder in old_excluded_folders:
            if is_child(dbx_path, folder):
                raise ValueError(
                    "'{0}' lies inside the excluded folder '{1}'. "
                    "Please include '{1}' first.".format(dbx_path, folder))

        # Get folders which will need to be downloaded, do not attempt to download
        # subfolders of `dbx_path` which were already included.
        # `new_included_folders` will either be empty (`dbx_path` was already
        # included), just contain `dbx_path` itself (the whole folder was excluded) or
        # only contain subfolders of `dbx_path` (`dbx_path` was partially included).
        new_included_folders = tuple(x for x in old_excluded_folders
                                     if x == dbx_path or is_child(x, dbx_path))

        if new_included_folders:
            # remove `dbx_path` or all excluded children from the excluded list
            excluded_folders = list(
                set(old_excluded_folders) - set(new_included_folders))
        else:
            logger.info("Folder was already included, nothing to do.")
            return

        self.sync.excluded_folders = excluded_folders

        # download folder contents from Dropbox
        logger.info(f"Downloading added folder '{dbx_path}'.")
        for folder in new_included_folders:
            self.sync.queued_folder_downloads.put(folder)
Ejemplo n.º 4
0
    def __init__(
        self,
        async_loader,
        unchecked,
        path_display="/",
        path_lower="/",
        is_folder=True,
        parent=None,
    ):
        super().__init__(parent=parent)
        if is_folder:
            self.icon = native_folder_icon()
            self._children = [MessageTreeItem(self, "Loading...")]
        else:
            self.icon = native_file_icon()
            self._children = []
        self.is_folder = is_folder
        self.can_have_children = is_folder
        self._path_display = path_display
        self._path_lower = path_lower
        self._basename = os.path.basename(self._path_display)
        self._async_loader = async_loader
        self._unchecked = unchecked

        self._checkStateChanged = False

        # get info from our own excluded list
        if path_lower in unchecked:
            # item is excluded
            self._originalCheckState = 0
        elif self._parent is not None and self._parent._originalCheckState == 0:
            # item's parent is excluded
            self._originalCheckState = 0
        elif any(is_child(f, path_lower) for f in unchecked):
            # some of item's children are excluded
            self._originalCheckState = 1
        else:
            # item is fully included
            self._originalCheckState = 2

        # overwrite original state if the parent was modified
        if (
            self._parent is not None
            and self._parent._checkStateChanged
            and not self._parent.checkState == 1
        ):
            # inherit from parent
            self._checkState = self._parent.checkState
            self._checkStateChanged = self._parent._checkStateChanged
        else:
            self._checkStateChanged = False
            self._checkState = int(self._originalCheckState)
Ejemplo n.º 5
0
    def excluded_status(self, dbx_path):
        """
        Returns 'excluded', 'partially excluded' or 'included'. This function will not
        check if the item actually exists on Dropbox.

        :param str dbx_path: Path to item on Dropbox.
        :returns: Excluded status.
        :rtype: str
        """

        dbx_path = dbx_path.lower().rstrip(osp.sep)

        excluded_items = self._conf.get('main', 'excluded_items')

        if dbx_path in excluded_items:
            return 'excluded'
        elif any(is_child(f, dbx_path) for f in excluded_items):
            return 'partially excluded'
        else:
            return 'included'
Ejemplo n.º 6
0
    def _init_selected(self) -> None:

        excluded_items = getattr(self._mdbx, "excluded_items", [])

        # Get included state from current list.
        if self.path_lower in excluded_items:
            # Item is excluded.
            self._original_state = OFF
        elif self._parent is not None and self._parent._original_state == OFF:
            # Item's parent is excluded.
            self._original_state = OFF
        elif any(is_child(e, self.path_lower) for e in excluded_items):
            # Some of item's children are excluded.
            self._original_state = MIXED
        else:
            self._original_state = ON

        # Get included state from parent if it has been user modified.
        if (self.parent is not None and self.parent.is_selection_modified()
                and self.parent.included.state is not MIXED):
            self.included.state = self.parent.included.state
        else:
            self.included.state = self._original_state
Ejemplo n.º 7
0
def test_is_child():
    assert is_child("/parent/path/child", "/parent/path/")
    assert is_child("/parent/path/child/", "/parent/path")
    assert not is_child("/parent/path", "/parent/path")
    assert not is_child("/path1", "/path2")
Ejemplo n.º 8
0
def test_is_child():
    assert is_child('/parent/path/child', '/parent/path/')
    assert is_child('/parent/path/child/', '/parent/path')
    assert not is_child('/parent/path', '/parent/path')
    assert not is_child('/path1', '/path2')