def _analyze_local_item(self, local_item_name): """ Analyze what to do with a local item that isn't found remotely. Assume that the item passes ignore list. :param str local_item_name: Name of the local item. """ q = self.items_store.get_items_by_id(item_name=local_item_name, parent_path=self.remote_path) p = self.local_path + '/' + local_item_name is_dir = os.path.isdir(p) if len(q) > 0: # The item was on the server before, but now seems gone. item_id, item = _unpack_first_item(q) if item.is_folder and is_dir: # Type matched. Delete local entry. self._send_path_to_trash(local_item_name, p) else: # The record is obsolete. Upload local entry. self.items_store.delete_item(item_name=local_item_name, parent_path=self.remote_path) self._create_upload_task(local_item_name, is_dir) else: # The item has no record before. Probably new so upload it. # TODO: This can be made a little smarter by examining hash. self._create_upload_task(local_item_name, is_dir)
def _analyze_remote_item(self, remote_item, all_local_items): """ Analyze what to do with a remote item. Assume that this item passes ignore list. This function is the core algorithm and probably the ugliest code I've written so far. :param onedrived.api.items.OneDriveItem remote_item: The remote item object. :param [str] all_local_items: All remaining untouched local items. """ item_local_path = self.local_path + '/' + remote_item.name q = self.items_store.get_items_by_id(parent_path=self.remote_path, item_name=remote_item.name) exists = os.path.exists(item_local_path) has_record = len(q) > 0 if not has_record and not exists: # There is no record in database. The item is not present. Probably a new file. self._create_download_task(item_local_path, remote_item) elif has_record and not exists: # There is record but the file is gone. item_id, item_record = _unpack_first_item(q) if item_id == remote_item.id and remote_item.c_tag == item_record.c_tag \ and remote_item.e_tag == item_record.e_tag: # Same record. The file is probably deleted when daemon is off. self._create_delete_item_task(item_local_path, remote_item) else: # The record differs. For safety we download it. self.logger.info('Cannot determine actions for "%s". Non-existent and records differ.', item_local_path) self._create_download_task(item_local_path, remote_item) else: # The entry exists locally. # First solve possible type conflict. is_dir = os.path.isdir(item_local_path) if is_dir != remote_item.is_folder: self.logger.info('Type conflict on path "%s". One side is file and the other is dir.', item_local_path) self._move_existing_and_download(item_local_path, remote_item, all_local_items, q) elif is_dir: # Both sides are directories. Just update the record and sync if needed. if has_record: item_id, item_record = _unpack_first_item(q) has_record = item_id == remote_item.id if not has_record: self.logger.info('Fix database record for directory "%s".', item_local_path) self.items_store.update_item(remote_item, ItemRecordStatuses.OK) else: self.logger.debug('Directory "%s" has intact record.', item_local_path) else: # Both sides are files. Examine file attributes. file_size, file_mtime = stat_file(item_local_path) if file_size == remote_item.size \ and compare_timestamps(file_mtime, datetime_to_timestamp(remote_item.modified_time)) == 0: # Same file name. Same size. Same mtime. Guess they are the same for laziness. # Just update the record. need_update = not has_record if has_record: item_id, item_record = _unpack_first_item(q) need_update = item_record.c_tag != remote_item.c_tag or item_record.e_tag != remote_item.e_tag if need_update: self.logger.info('Fix database record for file "%s" based on file size and mtime.', item_local_path) self.items_store.update_item(remote_item, ItemRecordStatuses.OK) else: self.logger.debug('File "%s" seems fine.', item_local_path) else: if has_record: item_id, item_record = _unpack_first_item(q) if item_id == remote_item.id and item_record.c_tag == remote_item.c_tag \ or item_record.e_tag == remote_item.e_tag: # The remote item did not change since last update, but LOCAL file changed since then. self.logger.info('File "%s" changed locally since last sync. Upload.', item_local_path) self._create_upload_task(local_item_name=remote_item.name, is_dir=False) elif file_size == item_record.size and compare_timestamps( file_mtime, datetime_to_timestamp(item_record.modified_time)) == 0: # The local item did not change since last update, but REMOTE file changed since then. self.logger.info('File "%s" changed remotely since last sync. Download.', item_local_path) self._create_download_task(item_local_path, remote_item) else: # Three ends mismatch. Keep both. self.logger.info('Cannot determine file "%s". All information differ. Keep both.', item_local_path) self._move_existing_and_download(item_local_path, remote_item, all_local_items, q) else: # Examine file hash. if _have_equal_hash(item_local_path, remote_item): # Same hash means that they are the same file. Update local timestamp and database record. self._update_attr_when_hash_equal(item_local_path, remote_item) else: self.logger.info('Cannot determine file "%s". Rename and keep both.', item_local_path) self._move_existing_and_download(item_local_path, remote_item, all_local_items, q)
def _analyze_remote_item(self, remote_item, all_local_items): """ Analyze what to do with a remote item. Assume that this item passes ignore list. This function is the core algorithm and probably the ugliest code I've written so far. :param onedrived.api.items.OneDriveItem remote_item: The remote item object. :param [str] all_local_items: All remaining untouched local items. """ item_local_path = self.local_path + '/' + remote_item.name q = self.items_store.get_items_by_id(parent_path=self.remote_path, item_name=remote_item.name) exists = os.path.exists(item_local_path) has_record = len(q) > 0 if not has_record and not exists: # There is no record in database. The item is not present. Probably a new file. self._create_download_task(item_local_path, remote_item) elif has_record and not exists: # There is record but the file is gone. item_id, item_record = _unpack_first_item(q) if item_id == remote_item.id and remote_item.c_tag == item_record.c_tag \ and remote_item.e_tag == item_record.e_tag: # Same record. The file is probably deleted when daemon is off. self._create_delete_item_task(item_local_path, remote_item) else: # The record differs. For safety we download it. self.logger.info( 'Cannot determine actions for "%s". Non-existent and records differ.', item_local_path) self._create_download_task(item_local_path, remote_item) else: # The entry exists locally. # First solve possible type conflict. is_dir = os.path.isdir(item_local_path) if is_dir != remote_item.is_folder: self.logger.info( 'Type conflict on path "%s". One side is file and the other is dir.', item_local_path) self._move_existing_and_download(item_local_path, remote_item, all_local_items, q) elif is_dir: # Both sides are directories. Just update the record and sync if needed. if has_record: item_id, item_record = _unpack_first_item(q) has_record = item_id == remote_item.id if not has_record: self.logger.info('Fix database record for directory "%s".', item_local_path) self.items_store.update_item(remote_item, ItemRecordStatuses.OK) else: self.logger.debug('Directory "%s" has intact record.', item_local_path) else: # Both sides are files. Examine file attributes. file_size, file_mtime = stat_file(item_local_path) if file_size == remote_item.size \ and compare_timestamps(file_mtime, datetime_to_timestamp(remote_item.modified_time)) == 0: # Same file name. Same size. Same mtime. Guess they are the same for laziness. # Just update the record. need_update = not has_record if has_record: item_id, item_record = _unpack_first_item(q) need_update = item_record.c_tag != remote_item.c_tag or item_record.e_tag != remote_item.e_tag if need_update: self.logger.info( 'Fix database record for file "%s" based on file size and mtime.', item_local_path) self.items_store.update_item(remote_item, ItemRecordStatuses.OK) else: self.logger.debug('File "%s" seems fine.', item_local_path) else: if has_record: item_id, item_record = _unpack_first_item(q) if item_id == remote_item.id and item_record.c_tag == remote_item.c_tag \ or item_record.e_tag == remote_item.e_tag: # The remote item did not change since last update, but LOCAL file changed since then. self.logger.info( 'File "%s" changed locally since last sync. Upload.', item_local_path) self._create_upload_task( local_item_name=remote_item.name, is_dir=False) elif file_size == item_record.size and compare_timestamps( file_mtime, datetime_to_timestamp( item_record.modified_time)) == 0: # The local item did not change since last update, but REMOTE file changed since then. self.logger.info( 'File "%s" changed remotely since last sync. Download.', item_local_path) self._create_download_task(item_local_path, remote_item) else: # Three ends mismatch. Keep both. self.logger.info( 'Cannot determine file "%s". All information differ. Keep both.', item_local_path) self._move_existing_and_download( item_local_path, remote_item, all_local_items, q) else: # Examine file hash. if _have_equal_hash(item_local_path, remote_item): # Same hash means that they are the same file. Update local timestamp and database record. self._update_attr_when_hash_equal( item_local_path, remote_item) else: self.logger.info( 'Cannot determine file "%s". Rename and keep both.', item_local_path) self._move_existing_and_download( item_local_path, remote_item, all_local_items, q)