def create_remote_file_folder(self, local_file_folder, local_node): logger.debug('create_remote_file_folder') assert local_file_folder is not None assert isinstance(local_file_folder, File) assert local_node is not None assert isinstance(local_node, Node) assert local_file_folder.locally_created if local_file_folder.is_folder: remote_file_folder = yield from self.osf_query.upload_folder( local_file_folder) elif local_file_folder.is_file: try: remote_file_folder = yield from self.osf_query.upload_file( local_file_folder) except FileNotFoundError: logger.warning( 'file not created on remote server because does not exist locally: {}' .format(local_file_folder.name)) return local_file_folder.osf_id = remote_file_folder.id local_file_folder.osf_path = remote_file_folder.id local_file_folder.locally_created = False save(session, local_file_folder) return remote_file_folder
def create_remote_file_folder(self, local_file_folder, local_node): logger.debug('create_remote_file_folder') assert local_file_folder is not None assert isinstance(local_file_folder, File) assert local_node is not None assert isinstance(local_node, Node) assert local_file_folder.locally_created if local_file_folder.is_folder: remote_file_folder = yield from self.osf_query.upload_folder(local_file_folder) elif local_file_folder.is_file: try: remote_file_folder = yield from self.osf_query.upload_file(local_file_folder) except FileNotFoundError: logger.warning('file not created on remote server because does not exist locally: {}'.format( local_file_folder.name)) return local_file_folder.osf_id = remote_file_folder.id local_file_folder.osf_path = remote_file_folder.id local_file_folder.locally_created = False save(session, local_file_folder) return remote_file_folder
def update_sync_nodes(self): user = session.query(User).filter(User.logged_in).one() guid_list = self.get_guid_list() # FIXME: This needs a try-except block but is waiting on a preferences refactor to be merged user.guid_for_top_level_nodes_to_sync = guid_list save(session, user) self.checked_items = guid_list self.close()
def delete_local_node(self, local_node): logger.debug('delete_local_node') assert isinstance(local_node, Node) path = local_node.path # delete model session.delete(local_node) save(session) yield from self.queue.put(DeleteFolder(path))
def on_deleted(self, event): """Called when a file or directory is deleted. :param event: Event representing file/directory deletion. :type event: :class:`DirDeletedEvent` or :class:`FileDeletedEvent` """ try: src_path = ProperPath(event.src_path, event.is_directory) except Exception: logging.exception('Exception caught: Invalid path') AlertHandler.warn( 'invalid path specified. {} will not be synced'.format( os.path.basename(event.src_path))) else: if not self._already_exists(src_path): return # get item try: item = self._get_item_by_path(src_path) except ItemNotInDB: logging.exception( 'Exception caught: Tried to delete item {}, but it was not found in DB' .format(src_path.name)) else: # put item in delete state after waiting a second and # checking to make sure the file was actually deleted yield from asyncio.sleep(1) if not os.path.exists(item.path): item.locally_deleted = True # nodes cannot be deleted online. THUS, delete it inside database. It will be recreated locally. if isinstance(item, Node): session.delete(item) try: save(session) except SQLAlchemyError as e: logging.exception( 'Exception caught: Error deleting node {} from database.' .format(item.name)) return try: save(session, item) except SQLAlchemyError as e: logging.exception( 'Exception caught: Error deleting {} {} from database.' .format('folder' if event.is_directory else 'file', item.name)) else: logging.info('{} set to be deleted'.format( src_path.full_path))
def logout(self): user = session.query(User).filter(User.logged_in).one() user.logged_in = False try: save(session, user) except SQLAlchemyError: session.query(User).delete() self.tray.tray_icon.hide() if self.preferences.isVisible(): self.preferences.close() self.start_screen.open_window()
def modify_local_node(self, local_node, remote_node): logger.debug('modify_local_node') assert isinstance(local_node, Node) assert isinstance(remote_node, RemoteNode) assert remote_node.id == local_node.osf_id old_path = local_node.path local_node.title = remote_node.name local_node.category = remote_node.category save(session, local_node) yield from self.queue.put(RenameFolder(old_path, local_node.path))
def on_modified(self, event): """Called when a file or directory is modified. :param event: Event representing file/directory modification. :type event: :class:`DirModifiedEvent` or :class:`FileModifiedEvent` """ if isinstance(event, DirModifiedEvent): return try: src_path = ProperPath(event.src_path, event.is_directory) except Exception: logging.exception('Exception caught: Invalid path') AlertHandler.warn( 'invalid path specified. {} will not be synced'.format( os.path.basename(event.src_path))) else: # get item try: item = self._get_item_by_path(src_path) except ItemNotInDB: # todo: create file folder logging.warning( 'file {} was modified but not already in db. create it in db.' .format(src_path)) return # todo: remove this once above is implemented # update hash try: item.update_hash() except OSError: logging.exception('File inaccessible during update_hash') AlertHandler.warn( 'Error updating {}. {} inaccessible, will stop syncing.'. format('Folder' if event.is_directory else 'File', item.name)) return # save try: save(session, item) except SQLAlchemyError: logging.exception( 'Exception caught: Could not save data for {}'.format( item)) AlertHandler.warn( 'Error updating {}. {} will stop syncing.'.format( 'folder' if event.is_directory else 'file', item.name))
def on_deleted(self, event): """Called when a file or directory is deleted. :param event: Event representing file/directory deletion. :type event: :class:`DirDeletedEvent` or :class:`FileDeletedEvent` """ try: src_path = ProperPath(event.src_path, event.is_directory) except Exception: logging.exception("Exception caught: Invalid path") AlertHandler.warn("invalid path specified. {} will not be synced".format(os.path.basename(event.src_path))) else: if not self._already_exists(src_path): return # get item try: item = self._get_item_by_path(src_path) except ItemNotInDB: logging.exception( "Exception caught: Tried to delete item {}, but it was not found in DB".format(src_path.name) ) else: # put item in delete state after waiting a second and # checking to make sure the file was actually deleted yield from asyncio.sleep(1) if not os.path.exists(item.path): item.locally_deleted = True # nodes cannot be deleted online. THUS, delete it inside database. It will be recreated locally. if isinstance(item, Node): session.delete(item) try: save(session) except SQLAlchemyError as e: logging.exception( "Exception caught: Error deleting node {} from database.".format(item.name) ) return try: save(session, item) except SQLAlchemyError as e: logging.exception( "Exception caught: Error deleting {} {} from database.".format( "folder" if event.is_directory else "file", item.name ) ) else: logging.info("{} set to be deleted".format(src_path.full_path))
def delete_local_file_folder(self, local_file_folder): logger.debug('delete_local_file_folder') assert isinstance(local_file_folder, File) path = local_file_folder.path is_folder = local_file_folder.is_folder # delete model session.delete(local_file_folder) save(session) # delete from local if is_folder: yield from self.queue.put(DeleteFolder(path)) else: yield from self.queue.put(DeleteFile(path))
def delete_remote_file_folder(self, local_file_folder, remote_file_folder): logger.debug('delete_remote_file_folder') assert local_file_folder is not None assert remote_file_folder is not None assert local_file_folder.osf_id == self.get_id(remote_file_folder) assert local_file_folder.locally_deleted if local_file_folder.is_file: yield from self.osf_query.delete_remote_file(remote_file_folder) elif local_file_folder.is_folder: yield from self.osf_query.delete_remote_folder(remote_file_folder) local_file_folder.deleted = False session.delete(local_file_folder) save(session)
def on_modified(self, event): """Called when a file or directory is modified. :param event: Event representing file/directory modification. :type event: :class:`DirModifiedEvent` or :class:`FileModifiedEvent` """ if isinstance(event, DirModifiedEvent): return try: src_path = ProperPath(event.src_path, event.is_directory) except Exception: logging.exception("Exception caught: Invalid path") AlertHandler.warn("invalid path specified. {} will not be synced".format(os.path.basename(event.src_path))) else: # get item try: item = self._get_item_by_path(src_path) except ItemNotInDB: # todo: create file folder logging.warning("file {} was modified but not already in db. create it in db.".format(src_path)) return # todo: remove this once above is implemented # update hash try: item.update_hash() except OSError: logging.exception("File inaccessible during update_hash") AlertHandler.warn( "Error updating {}. {} inaccessible, will stop syncing.".format( "Folder" if event.is_directory else "File", item.name ) ) return # save try: save(session, item) except SQLAlchemyError: logging.exception("Exception caught: Could not save data for {}".format(item)) AlertHandler.warn( "Error updating {}. {} will stop syncing.".format( "folder" if event.is_directory else "file", item.name ) )
def quit(self): try: if self.background_worker.is_alive(): logger.info('Stopping background worker') self.background_worker.stop() try: user = session.query(User).filter(User.logged_in).one() except NoResultFound: pass else: logger.info('Saving user data') save(session, user) session.close() finally: logger.info('Quitting application') QApplication.instance().quit()
def rename_local_file_folder(self, local_file_folder, remote_file_folder): logger.debug('rename_local_file_folder') assert isinstance(local_file_folder, File) assert isinstance(remote_file_folder, RemoteFileFolder) assert remote_file_folder.id == local_file_folder.osf_path # handle renaming local file and local folder old_path = local_file_folder.path # update model local_file_folder.name = remote_file_folder.name save(session, local_file_folder) if local_file_folder.is_folder: yield from self.queue.put(RenameFolder(old_path, local_file_folder.path)) elif local_file_folder.is_file: yield from self.queue.put(RenameFile(old_path, local_file_folder.path))
def rename_local_file_folder(self, local_file_folder, remote_file_folder): logger.debug('rename_local_file_folder') assert isinstance(local_file_folder, File) assert isinstance(remote_file_folder, RemoteFileFolder) assert remote_file_folder.id == local_file_folder.osf_path # handle renaming local file and local folder old_path = local_file_folder.path # update model local_file_folder.name = remote_file_folder.name save(session, local_file_folder) if local_file_folder.is_folder: yield from self.queue.put( RenameFolder(old_path, local_file_folder.path)) elif local_file_folder.is_file: yield from self.queue.put( RenameFile(old_path, local_file_folder.path))
def start(self): logger.debug('Start in main called.') try: user = session.query(User).filter(User.logged_in).one() except MultipleResultsFound: session.query(User).delete() self.login_signal.emit() return except NoResultFound: self.login_signal.emit() return try: # Simple request to ensure user logged in with valid oauth_token user = asyncio.get_event_loop().run_until_complete( AuthClient().populate_user_data(user)) except AuthError as e: logging.exception(e.message) self.login_signal.emit() containing_folder = os.path.dirname(user.osf_local_folder_path) while not validate_containing_folder(containing_folder): logger.warning( 'Invalid containing folder: {}'.format(containing_folder)) AlertHandler.warn( 'Invalid containing folder. Please choose another.') containing_folder = os.path.abspath( self.set_containing_folder_initial()) user.osf_local_folder_path = os.path.join(containing_folder, 'OSF') save(session, user) self.tray.set_containing_folder(containing_folder) if not os.path.isdir(user.osf_local_folder_path): os.makedirs(user.osf_local_folder_path) self.start_tray_signal.emit() logger.debug('starting background worker from main.start') self.background_worker = BackgroundWorker() self.background_worker.start()
def _create_file_or_folder(self, event, src_path): # assert: whats being created is a file folder try: containing_item = self._get_parent_item_from_path(src_path) except ItemNotInDB: logging.error( "tried to create item {} for parent {} but parent does not exist".format( src_path.full_path, src_path.parent.full_path ) ) return if isinstance(containing_item, Node): node = containing_item else: # file node = containing_item.node new_item = File( name=src_path.name, type=File.FOLDER if event.is_directory else File.FILE, user=self.user, locally_created=True, provider=File.DEFAULT_PROVIDER, node=node, ) containing_item.files.append(new_item) if new_item.is_file: try: new_item.update_hash() except FileNotFoundError: # if file doesnt exist just as we create it, then file is likely temp file. thus don't put it in db. return try: save(session, new_item, containing_item) except SQLAlchemyError: logging.exception("Exception caught: Could not save data for {} in {}".format(new_item, containing_item)) AlertHandler.warn( "Error creating {}: {} will not be synced.".format( "file" if new_item.is_file else "folder", new_item.name ) ) else: logging.info("created new {} {}".format("folder" if event.is_directory else "file", src_path.full_path))
def _create_file_or_folder(self, event, src_path): # assert: whats being created is a file folder try: containing_item = self._get_parent_item_from_path(src_path) except ItemNotInDB: logging.error( 'tried to create item {} for parent {} but parent does not exist' .format(src_path.full_path, src_path.parent.full_path)) return if isinstance(containing_item, Node): node = containing_item else: # file node = containing_item.node new_item = File(name=src_path.name, type=File.FOLDER if event.is_directory else File.FILE, user=self.user, locally_created=True, provider=File.DEFAULT_PROVIDER, node=node) containing_item.files.append(new_item) if new_item.is_file: try: new_item.update_hash() except FileNotFoundError: # if file doesnt exist just as we create it, then file is likely temp file. thus don't put it in db. return try: save(session, new_item, containing_item) except SQLAlchemyError: logging.exception( 'Exception caught: Could not save data for {} in {}'.format( new_item, containing_item)) AlertHandler.warn( 'Error creating {}: {} will not be synced.'.format( 'file' if new_item.is_file else 'folder', new_item.name)) else: logging.info("created new {} {}".format( 'folder' if event.is_directory else 'file', src_path.full_path))
def log_in(self, user=None, username=None, password=None): """ Takes standard auth credentials, returns authenticated user or raises AuthError. """ if not username or not password: raise AuthError('Username and password required for login.') if user: user.oauth_token = yield from self._authenticate(username, password) if user.osf_login != username: #Different user authenticated, drop old user and allow login clear_models() user = yield from self._create_user(username, password) else: user = yield from self._create_user(username, password) user.logged_in = True try: save(session, user) except SQLAlchemyError as e: raise AuthError('Unable to save user data. Please try again later') else: return user
def create_local_node(self, remote_node, local_parent_node): logger.debug('create_local_node') assert isinstance(remote_node, RemoteNode) assert isinstance(local_parent_node, Node) or local_parent_node is None # create local node in db category = Node.PROJECT if remote_node.category == 'project' else Node.COMPONENT new_node = Node(title=remote_node.name, category=category, osf_id=remote_node.id, user=self.user, parent=local_parent_node) save(session, new_node) if local_parent_node: yield from self._ensure_components_folder(local_parent_node) yield from self.queue.put(CreateFolder(new_node.path)) yield from self._ensure_components_folder(new_node) assert local_parent_node is None or (new_node in local_parent_node.child_nodes) return new_node
def start(self): logger.debug('Start in main called.') try: user = session.query(User).filter(User.logged_in).one() except MultipleResultsFound: session.query(User).delete() self.login_signal.emit() return except NoResultFound: self.login_signal.emit() return try: # Simple request to ensure user logged in with valid oauth_token user = asyncio.get_event_loop().run_until_complete(AuthClient().populate_user_data(user)) except AuthError as e: logging.exception(e.message) self.login_signal.emit() containing_folder = os.path.dirname(user.osf_local_folder_path) while not validate_containing_folder(containing_folder): logger.warning('Invalid containing folder: {}'.format(containing_folder)) AlertHandler.warn('Invalid containing folder. Please choose another.') containing_folder = os.path.abspath(self.set_containing_folder_initial()) user.osf_local_folder_path = os.path.join(containing_folder, 'OSF') save(session, user) self.tray.set_containing_folder(containing_folder) if not os.path.isdir(user.osf_local_folder_path): os.makedirs(user.osf_local_folder_path) self.start_tray_signal.emit() logger.debug('starting background worker from main.start') self.background_worker = BackgroundWorker() self.background_worker.start()
def log_in(self, user=None, username=None, password=None): """ Takes standard auth credentials, returns authenticated user or raises AuthError. """ if not username or not password: raise AuthError('Username and password required for login.') if user: user.oauth_token = yield from self._authenticate( username, password) if user.osf_login != username: #Different user authenticated, drop old user and allow login clear_models() user = yield from self._create_user(username, password) else: user = yield from self._create_user(username, password) user.logged_in = True try: save(session, user) except SQLAlchemyError as e: raise AuthError('Unable to save user data. Please try again later') else: return user
def create_local_node(self, remote_node, local_parent_node): logger.debug('create_local_node') assert isinstance(remote_node, RemoteNode) assert isinstance(local_parent_node, Node) or local_parent_node is None # create local node in db category = Node.PROJECT if remote_node.category == 'project' else Node.COMPONENT new_node = Node( title=remote_node.name, category=category, osf_id=remote_node.id, user=self.user, parent=local_parent_node ) save(session, new_node) if local_parent_node: yield from self._ensure_components_folder(local_parent_node) yield from self.queue.put(CreateFolder(new_node.path)) yield from self._ensure_components_folder(new_node) assert local_parent_node is None or (new_node in local_parent_node.child_nodes) return new_node
def create_local_file_folder(self, remote_file_folder, local_parent_folder, local_node): logger.debug('creating local file folder') assert remote_file_folder is not None assert isinstance(remote_file_folder, RemoteFileFolder) assert isinstance(local_parent_folder, File) or local_parent_folder is None assert local_parent_folder is None or (local_parent_folder.is_folder) assert isinstance(local_node, Node) # NOTE: develop is not letting me download files. dont know why. # create local file folder in db file_type = File.FILE if isinstance(remote_file_folder, RemoteFile) else File.FOLDER new_file_folder = File( name=remote_file_folder.name, type=file_type, osf_id=remote_file_folder.id, provider=remote_file_folder.provider, osf_path=remote_file_folder.id, user=self.user, parent=local_parent_folder, node=local_node ) save(session, new_file_folder) if file_type == File.FILE: event = CreateFile( path=new_file_folder.path, download_url=remote_file_folder.download_url, osf_query=self.osf_query ) yield from self.queue.put(event) elif file_type == File.FOLDER: yield from self.queue.put(CreateFolder(new_file_folder.path)) else: raise ValueError('file type is unknown') return new_file_folder
def create_local_file_folder(self, remote_file_folder, local_parent_folder, local_node): logger.debug('creating local file folder') assert remote_file_folder is not None assert isinstance(remote_file_folder, RemoteFileFolder) assert isinstance(local_parent_folder, File) or local_parent_folder is None assert local_parent_folder is None or (local_parent_folder.is_folder) assert isinstance(local_node, Node) # NOTE: develop is not letting me download files. dont know why. # create local file folder in db file_type = File.FILE if isinstance(remote_file_folder, RemoteFile) else File.FOLDER new_file_folder = File(name=remote_file_folder.name, type=file_type, osf_id=remote_file_folder.id, provider=remote_file_folder.provider, osf_path=remote_file_folder.id, user=self.user, parent=local_parent_folder, node=local_node) save(session, new_file_folder) if file_type == File.FILE: event = CreateFile(path=new_file_folder.path, download_url=remote_file_folder.download_url, osf_query=self.osf_query) yield from self.queue.put(event) elif file_type == File.FOLDER: yield from self.queue.put(CreateFolder(new_file_folder.path)) else: raise ValueError('file type is unknown') return new_file_folder
def on_moved(self, event): """Called when a file or a directory is moved or renamed. :param event: Event representing file/directory movement. :type event: :class:`DirMovedEvent` or :class:`FileMovedEvent` """ try: src_path = ProperPath(event.src_path, event.is_directory) dest_path = ProperPath(event.dest_path, event.is_directory) except Exception: logging.exception("Exception caught: Invalid path specified") AlertHandler.warn( "Error moving {}. {} will not be synced.".format( "folder" if event.is_directory else "file", os.path.basename(event.src_path) ) ) else: # determine and get what moved if not self._already_exists(src_path): try: self._get_parent_item_from_path(src_path) except ItemNotInDB: # This means it was put into a place on the hierarchy being watched but otherwise not attached to a # node, so it needs to be added just like a new event rather than as a move. new_event = ( DirCreatedEvent(event.dest_path) if event.is_directory else FileCreatedEvent(event.dest_path) ) yield from self._create_file_or_folder(new_event, src_path=dest_path) return logging.warning("Tried to move item that does not exist: {}".format(src_path.name)) return try: item = self._get_item_by_path(src_path) except ItemNotInDB: logging.exception( "Exception caught: Tried to move or rename item {}, but it could not be found in DB".format( src_path.name ) ) AlertHandler.warn("Could not find item to manipulate. {} will not be synced".format(src_path.name)) else: if isinstance(item, Node): AlertHandler.warn("Cannot manipulate components locally. {} will stop syncing".format(item.title)) return # File # rename if item.name != dest_path.name: item.name = dest_path.name item.locally_renamed = True try: save(session, item) except SQLAlchemyError: logging.exception("Exception caught: Could not save data for {}".format(item)) AlertHandler.warn("Error renaming file. {} will stop syncing.".format(item.name)) else: logging.info("renamed a file {}".format(dest_path.full_path)) # move elif src_path != dest_path: # check if file already exists in this moved location. If so, delete it from db. try: item_to_replace = self._get_item_by_path(dest_path) session.delete(item_to_replace) save(session) except ItemNotInDB: logging.info("file does not already exist in moved destination: {}".format(dest_path.full_path)) except SQLAlchemyError: logging.exception("Exception caught: Could not save data for {}".format(item_to_replace)) AlertHandler.warn("Error moving file. {} will stop syncing.".format(dest_path.name)) try: new_parent_item = self._get_parent_item_from_path(dest_path) except ItemNotInDB: AlertHandler.warn( "{} {} placed into invalid containing folder. It will not be synced.".format( "Folder" if event.is_directory else "File", dest_path.name ) ) else: # move item # set previous fields item.previous_provider = item.provider item.previous_node_osf_id = item.node.osf_id # update parent and node fields # NOTE: this line makes it so the file no longer exists in the database. # NOTE: item at this point is stale. Unclear why it matters though. # NOTE: fix is above: session.refresh(item) item.parent = new_parent_item if isinstance(new_parent_item, File) else None item.node = new_parent_item if isinstance(new_parent_item, Node) else new_parent_item.node # basically always osfstorage. this is just meant to be extendible in the future to other providers item.provider = ( new_parent_item.provider if isinstance(new_parent_item, File) else File.DEFAULT_PROVIDER ) # flags item.locally_moved = True try: save(session, item) except SQLAlchemyError: logging.exception("Exception caught: Could not save data for {}".format(item)) AlertHandler.warn("Error moving file. {} will stop syncing.".format(item.name)) else: logging.info("moved from {} to {}".format(src_path.full_path, dest_path.full_path))
def _check_file_folder(self, local_file_folder, remote_file_folder, local_parent_file_folder, local_node): """ VARIOUS STATES (update as neccessary): (None, None) -> Error -- (None, remote) -> if locally moved -> do nothing else -> create local (local.create, None) -> create remote -- (local.create, remote) -> ERROR -- (local.delete, None) -> ERROR -- (local.delete, remote) - > delete remote -- (local, None) -> if locally moved -> move else -> delete local (local, remote) -> check modifications -- """ assert local_file_folder or remote_file_folder # both shouldnt be None. logger.debug('checking file_folder internal') if local_file_folder is None: locally_moved = yield from self.is_locally_moved( remote_file_folder) if locally_moved: return else: local_file_folder = yield from self.create_local_file_folder( remote_file_folder, local_parent_file_folder, local_node) elif local_file_folder.locally_created and remote_file_folder is None: if not local_file_folder.is_provider: remote_file_folder = yield from self.create_remote_file_folder( local_file_folder, local_node) return elif local_file_folder.locally_created and remote_file_folder is not None: raise ValueError( 'newly created local file_folder was already on server') elif local_file_folder.locally_deleted and remote_file_folder is None: session.delete(local_file_folder) save(session) logger.warning( 'local file_folder is to be deleted, however, it was never on the server.' ) return elif local_file_folder.locally_deleted and remote_file_folder is not None: yield from self.delete_remote_file_folder(local_file_folder, remote_file_folder) return elif local_file_folder is not None and remote_file_folder is None: if local_file_folder.locally_moved: # todo: we are ignoring return value for now because to start going down new tree would require # todo: us to have the new node. we currently use the head node instead of dynamically determining # todo: node. This is problematic. And Bad. FIX IT. remote_file_folder = yield from self.move_remote_file_folder( local_file_folder) return else: logger.warning('delete_local_file_folder called on {}'.format( local_file_folder.name)) yield from self.delete_local_file_folder(local_file_folder) return elif local_file_folder is not None and remote_file_folder is not None: possibly_new_remote_file_folder = yield from self.modify_file_folder_logic( local_file_folder, remote_file_folder) # if we do not need to modify things, remote file folder and local file folder does not change # we do not need to get a new local file folder because it is updated internally by the db if possibly_new_remote_file_folder: remote_file_folder = possibly_new_remote_file_folder else: raise ValueError('in some weird state. figure it out.') assert local_file_folder is not None assert remote_file_folder is not None # recursively handle folder's children if local_file_folder.is_folder: remote_children = yield from self.osf_query.get_child_files( remote_file_folder) local_remote_file_folders = self.make_local_remote_tuple_list( local_file_folder.files, remote_children) for local, remote in local_remote_file_folders: yield from self._check_file_folder( local, remote, local_parent_file_folder=local_file_folder, local_node=local_node)
def on_moved(self, event): """Called when a file or a directory is moved or renamed. :param event: Event representing file/directory movement. :type event: :class:`DirMovedEvent` or :class:`FileMovedEvent` """ try: src_path = ProperPath(event.src_path, event.is_directory) dest_path = ProperPath(event.dest_path, event.is_directory) except Exception: logging.exception('Exception caught: Invalid path specified') AlertHandler.warn('Error moving {}. {} will not be synced.'.format( 'folder' if event.is_directory else 'file', os.path.basename(event.src_path))) else: # determine and get what moved if not self._already_exists(src_path): try: self._get_parent_item_from_path(src_path) except ItemNotInDB: # This means it was put into a place on the hierarchy being watched but otherwise not attached to a # node, so it needs to be added just like a new event rather than as a move. new_event = DirCreatedEvent( event.dest_path ) if event.is_directory else FileCreatedEvent( event.dest_path) yield from self._create_file_or_folder(new_event, src_path=dest_path) return logging.warning( 'Tried to move item that does not exist: {}'.format( src_path.name)) return try: item = self._get_item_by_path(src_path) except ItemNotInDB: logging.exception( 'Exception caught: Tried to move or rename item {}, but it could not be found in DB' .format(src_path.name)) AlertHandler.warn( 'Could not find item to manipulate. {} will not be synced'. format(src_path.name)) else: if isinstance(item, Node): AlertHandler.warn( 'Cannot manipulate components locally. {} will stop syncing' .format(item.title)) return # File # rename if item.name != dest_path.name: item.name = dest_path.name item.locally_renamed = True try: save(session, item) except SQLAlchemyError: logging.exception( 'Exception caught: Could not save data for {}'. format(item)) AlertHandler.warn( 'Error renaming file. {} will stop syncing.'. format(item.name)) else: logging.info("renamed a file {}".format( dest_path.full_path)) # move elif src_path != dest_path: # check if file already exists in this moved location. If so, delete it from db. try: item_to_replace = self._get_item_by_path(dest_path) session.delete(item_to_replace) save(session) except ItemNotInDB: logging.info( 'file does not already exist in moved destination: {}' .format(dest_path.full_path)) except SQLAlchemyError: logging.exception( 'Exception caught: Could not save data for {}'. format(item_to_replace)) AlertHandler.warn( 'Error moving file. {} will stop syncing.'.format( dest_path.name)) try: new_parent_item = self._get_parent_item_from_path( dest_path) except ItemNotInDB: AlertHandler.warn( '{} {} placed into invalid containing folder. It will not be synced.' .format('Folder' if event.is_directory else 'File', dest_path.name)) else: # move item # set previous fields item.previous_provider = item.provider item.previous_node_osf_id = item.node.osf_id # update parent and node fields # NOTE: this line makes it so the file no longer exists in the database. # NOTE: item at this point is stale. Unclear why it matters though. # NOTE: fix is above: session.refresh(item) item.parent = new_parent_item if isinstance( new_parent_item, File) else None item.node = new_parent_item if isinstance( new_parent_item, Node) else new_parent_item.node # basically always osfstorage. this is just meant to be extendible in the future to other providers item.provider = new_parent_item.provider if isinstance( new_parent_item, File) else File.DEFAULT_PROVIDER # flags item.locally_moved = True try: save(session, item) except SQLAlchemyError: logging.exception( 'Exception caught: Could not save data for {}'. format(item)) AlertHandler.warn( 'Error moving file. {} will stop syncing.'. format(item.name)) else: logging.info('moved from {} to {}'.format( src_path.full_path, dest_path.full_path))
def _check_file_folder(self, local_file_folder, remote_file_folder, local_parent_file_folder, local_node): """ VARIOUS STATES (update as neccessary): (None, None) -> Error -- (None, remote) -> if locally moved -> do nothing else -> create local (local.create, None) -> create remote -- (local.create, remote) -> ERROR -- (local.delete, None) -> ERROR -- (local.delete, remote) - > delete remote -- (local, None) -> if locally moved -> move else -> delete local (local, remote) -> check modifications -- """ assert local_file_folder or remote_file_folder # both shouldnt be None. logger.debug('checking file_folder internal') if local_file_folder is None: locally_moved = yield from self.is_locally_moved(remote_file_folder) if locally_moved: return else: local_file_folder = yield from self.create_local_file_folder( remote_file_folder, local_parent_file_folder, local_node ) elif local_file_folder.locally_created and remote_file_folder is None: if not local_file_folder.is_provider: remote_file_folder = yield from self.create_remote_file_folder(local_file_folder, local_node) return elif local_file_folder.locally_created and remote_file_folder is not None: raise ValueError('newly created local file_folder was already on server') elif local_file_folder.locally_deleted and remote_file_folder is None: session.delete(local_file_folder) save(session) logger.warning('local file_folder is to be deleted, however, it was never on the server.') return elif local_file_folder.locally_deleted and remote_file_folder is not None: yield from self.delete_remote_file_folder(local_file_folder, remote_file_folder) return elif local_file_folder is not None and remote_file_folder is None: if local_file_folder.locally_moved: # todo: we are ignoring return value for now because to start going down new tree would require # todo: us to have the new node. we currently use the head node instead of dynamically determining # todo: node. This is problematic. And Bad. FIX IT. remote_file_folder = yield from self.move_remote_file_folder(local_file_folder) return else: logger.warning('delete_local_file_folder called on {}'.format(local_file_folder.name)) yield from self.delete_local_file_folder(local_file_folder) return elif local_file_folder is not None and remote_file_folder is not None: possibly_new_remote_file_folder = yield from self.modify_file_folder_logic(local_file_folder, remote_file_folder) # if we do not need to modify things, remote file folder and local file folder does not change # we do not need to get a new local file folder because it is updated internally by the db if possibly_new_remote_file_folder: remote_file_folder = possibly_new_remote_file_folder else: raise ValueError('in some weird state. figure it out.') assert local_file_folder is not None assert remote_file_folder is not None # recursively handle folder's children if local_file_folder.is_folder: remote_children = yield from self.osf_query.get_child_files(remote_file_folder) local_remote_file_folders = self.make_local_remote_tuple_list(local_file_folder.files, remote_children) for local, remote in local_remote_file_folders: yield from self._check_file_folder( local, remote, local_parent_file_folder=local_file_folder, local_node=local_node )