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"
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)
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)
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)
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'
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
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")
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')