def _create_empty_file_or_folder(self): self._fullname = FilePath(self._get_full_name()) if not self._fullname: return data_dir = self._cfg.sync_directory if self._cfg else get_data_dir() self._in_data_dir = self._fullname in FilePath(data_dir) logger.debug("Adding special file %s", self._fullname) if self._in_data_dir: self._sync.add_special_file(self._fullname, self._on_special_file_event) special_file_created = False try: if self._is_folder: make_dirs(self._fullname, is_folder=True) else: create_empty_file(self._fullname) special_file_created = True self._update_spec_files(self._fullname, self._is_folder) except Exception as e: logger.warning("Can't create file or folder %s. Reason %s", self._fullname, e) if not self._in_data_dir and special_file_created: self._sync.add_special_file(self._fullname, self._on_special_file_event) elif self._in_data_dir and not special_file_created: self._sync.remove_special_file(self._fullname)
def default_config(): return dict( autologin=not is_portable(), upload_limit=0, download_limit=0, fs_events_processing_delay=1, fs_events_processing_period=1, fs_folder_timeout=2, sync_directory=FilePath(get_data_dir()), conflict_file_suffix='Conflicted copy from {}'.format( get_device_name()), node_hash=sha512(uuid4().bytes).hexdigest(), user_hash=None, send_statistics=True, license_type=UNKNOWN_LICENSE, # unknown licence by default lang=None, # System language user_email=None, last_user_email=None, user_password_hash=None, http_downloader_timeout=3, excluded_dirs=(), # List of directories excluded from sync max_log_size=100, # Max log file size Mb download_backups=False, remote_events_max_total=5000, max_remote_events_per_request=100, max_relpath_len=3096, sync_dir_size=0, copies_logging=True, excluded_dirs_applied=(), # List of excluded dirs, applied in DB host=REGULAR_URI, tracking_address='https://tracking.pvtbox.net:443/1/', smart_sync=True, )
def _init_connectivity(self): self._connectivity_service = ConnectivityService( self._ss_client, self._network_speed_calculator) self._connectivity_service_thread = QThread() self._connectivity_service.moveToThread( self._connectivity_service_thread) self._connectivity_service_thread.started.connect( self._connectivity_service.init.emit) self._connectivity_service.connected_nodes_outgoing_changed.connect( self._on_connected_nodes_changed, Qt.QueuedConnection) self._download_manager = DownloadManager( connectivity_service=self._connectivity_service, ss_client=self._ss_client, upload_enabled=False, tracker=self._tracker) self._download_manager.moveToThread(self._connectivity_service_thread) data_dir = self._cfg.sync_directory if self._cfg else get_data_dir() downloads_dir = get_downloads_dir(data_dir=data_dir, create=True) self._connectivity_service_thread.started.connect( lambda: self._download_manager.prepare_cleanup([downloads_dir])) self._connectivity_service_thread.start() self._download_manager.idle.connect(self._sync.on_share_idle) self._download_manager.working.connect(self._sync.on_share_downloading) self._download_manager.error.connect( self._sync.on_share_downloading_error) self._download_manager.progress.connect( self._sync.send_download_progress) self._download_manager.downloads_status.connect( self._sync.send_downloads_status) self._download_manager.signal_info_tx.connect(self._on_info_tx) self._download_manager.signal_info_rx.connect(self._on_info_rx)
def _clear_share_download(self): data_dir = self._cfg.sync_directory if self._cfg else get_data_dir() downloads_dir = get_downloads_dir(data_dir=data_dir, create=True) download_name = op.join(downloads_dir, self._current_share_hash) if self._is_folder: remove_dir(download_name) else: remove_file(download_name)
def get_url(): filename = config.get_main_option("filename") if filename is None: filename = ensure_unicode( join(get_patches_dir(get_data_dir(), create=True), 'patches.db')) url = config.get_main_option("sqlalchemy.url") url = ensure_unicode(url) url = url.format(filename=FilePath(filename)) return url
def _get_full_name(self, cancel=True, existing_file=False): data_dir = self._cfg.sync_directory if self._cfg else get_data_dir() dest_dir = self._dest_dirs.get(self._current_share_hash, data_dir) if FilePath(dest_dir) in FilePath(data_dir): if not self._renew_dest_dir(cancel, wait=False): return "" dest_dir = self._dest_dirs.get(self._current_share_hash, data_dir) fullname = op.join(dest_dir, self._current_share_name + '.download') fullname = FilePath(fullname).longpath if not existing_file: fullname = get_next_name(fullname) return fullname
def _relpath(self, path, data_dir=None): rel_path = None if not data_dir: data_dir = FilePath( self._cfg.sync_directory if self._cfg else get_data_dir()) if not path: path = FilePath(data_dir) if path in data_dir: rel_path = op.relpath(path, data_dir) if rel_path == os.curdir: rel_path = "" rel_path = ensure_unicode(rel_path) return rel_path
def _get_path_relative_to_share(self, file_info): data_path = self._cfg.sync_directory if self._cfg else get_data_dir() share_dir = get_downloads_dir(data_path) rel_path = FilePath(op.relpath(file_info.fullname, share_dir)) rel_path_list = rel_path.split('/') assert rel_path_list, "Must have relative path for share" # replace share hash with share name in rel path rel_path_list[0] = self._current_share_name rel_path = '/'.join(rel_path_list) logger.debug("Relative path for shared file %s is %s", file_info, rel_path) return rel_path
def start_file_download(self, file_info): data_path = self._cfg.sync_directory if self._cfg else get_data_dir() download_path = op.join(get_copies_dir(data_path), file_info.file_hash) logger.info("Initiating downloading of file '%s' to '%s'...", file_info.fullname, download_path) def on_success(task): self.signals.download_success.emit(task.id, file_info, download_path) def on_failure(task): self.signals.download_failure.emit(task.id, file_info) if not file_info.size: create_empty_file(file_info.fullname) self._tasks[self._current_share_hash].remove(file_info.event_uuid) self._finish_task_download() return elif not self._cfg.download_backups: self._sync.make_copy_from_existing_files(file_info.file_hash) files_info = [{ "target_file_path": self._get_path_relative_to_share(file_info), "mtime": 0, # mtime == 0 => shared file "is_created": None, "is_deleted": None }] self._download_manager.add_file_download( DOWNLOAD_PRIORITY_FILE, file_info.event_uuid, file_info.size, file_info.file_hash, download_path, 'Downloading shared file {}'.format(file_info.name), on_success, on_failure, files_info=files_info, )
def read_config_file(self): need_sync = False with open(self.config_file_name, 'rb') as f: data = f.read() try: decrypted_data = xor_with_key(data) config = loads(decrypted_data) secret = UUID(int=158790876260364472748646807733425668096 + getnode()).bytes if is_portable(): config['user_hash'] = None config['user_password_hash'] = None config['sync_directory'] = FilePath(get_data_dir()) config['conflict_file_suffix'] = 'Conflicted copy from {}'\ .format(get_device_name()) else: try: if config.get('user_hash', None): config['user_hash'] = xor_with_key( bytes.fromhex(config['user_hash']), secret).decode() except Exception as e: logger.debug("Error decoding user hash: %s", e) config["user_hash"] = None try: if config.get('user_password_hash', None): config['user_password_hash'] = xor_with_key( bytes.fromhex(config['user_password_hash']), secret).decode() except Exception as e: logger.debug("Error decoding user password hash: %s", e) config["user_password_hash"] = None except ValueError as e: logger.warning("Error: %s", e) config = loads(data) config["user_hash"] = None config["user_password_hash"] = None need_sync = True return config, need_sync
def _move(self): data_dir = self._cfg.sync_directory if self._cfg else get_data_dir() downloads_dir = get_downloads_dir(data_dir=data_dir, create=True) download_name = op.join(downloads_dir, self._current_share_hash) if not self._renew_dest_dir(): return dest_dir = self._dest_dirs.get(self._current_share_hash, data_dir) dest_name = op.join(dest_dir, self._current_share_name) dest_name = FilePath(dest_name).longpath dest_name = get_next_name(dest_name) logger.debug("Move '%s' to '%s'", download_name, dest_name) try: if FilePath(dest_dir) not in FilePath(data_dir): make_dirs(dest_name) shutil.move(download_name, dest_name) except IOError as e: logger.warning( "Can't move downloaded shared file to %s. " "Reason: %s", dest_name, e) self.cancel_share_download(self._current_share_name, folder_deleted=True)
def _renew_dest_dir(self, cancel=True, wait=True): logger.debug("Waiting for folder uuid") if wait: self._folder_uuid_ready.wait() folder_uuid = self._dest_uuids[self._current_share_hash] logger.debug("Folder uuid got %s", folder_uuid) if not folder_uuid: return True try: with self._db.soft_lock(): path, deleted, excluded = self._db \ .get_folder_path_deleted_excluded_by_uuid(folder_uuid) except EventsDbBusy: logger.debug("Events db busy") path = deleted = excluded = None if path is None or deleted or excluded: reason_str = 'not synced' if path is None \ else 'deleted' if deleted else 'excluded' logger.warning( "Can't download shared file '%s' because " "dir %s is %s", self._current_share_name, self._dest_dirs[self._current_share_hash], reason_str) if cancel: if deleted: self.cancel_share_download(self._current_share_name, folder_deleted=True) else: self.cancel_share_download(self._current_share_name, folder_excluded=True) return False data_dir = self._cfg.sync_directory if self._cfg else get_data_dir() self._dest_dirs[self._current_share_hash] = op.join(data_dir, path) return True
def _get_folder_uuid(self, dest_dir): data_dir = FilePath( self._cfg.sync_directory if self._cfg else get_data_dir()) if not dest_dir: dest_dir = data_dir dest_dir = FilePath(dest_dir) if dest_dir in data_dir: rel_path = self._relpath(dest_dir, data_dir) if not rel_path: folder_uuid = None else: try: folder_uuid = self._sync.get_file_uuid(rel_path) if folder_uuid is None: raise FileNotFound("") logger.debug("Folder uuid for path %s is %s", rel_path, folder_uuid) except (FileNotFound, EventsDbBusy): logger.warning("No folder uuid for folder %s", dest_dir) raise WebshareHandlerPathNotFoundError( "No folder uuid for folder") else: folder_uuid = "" return folder_uuid
def _on_sync_folder_location_button_clicked(self): selected_folder = QFileDialog.getExistingDirectory( self._dialog, tr('Choose Pvtbox folder location'), get_parent_dir(FilePath(self._cfg.sync_directory))) selected_folder = ensure_unicode(selected_folder) try: if not selected_folder: raise self._MigrationFailed("Folder is not selected") if len(selected_folder + "/Pvtbox") > self._max_root_len: if not self._migrate: msgbox(tr("Destination path too long. " "Please select shorter path."), tr("Path too long"), parent=self._dialog) raise self._MigrationFailed("Destination path too long") free_space = get_free_space(selected_folder) selected_folder = get_data_dir(dir_parent=selected_folder, create=False) if FilePath(selected_folder) == FilePath(self._cfg.sync_directory): raise self._MigrationFailed("Same path selected") if FilePath(selected_folder) in FilePath(self._cfg.sync_directory): msgbox(tr("Can't migrate into existing Pvtbox folder.\n" "Please choose other location"), tr("Invalid Pvtbox folder location"), parent=self._dialog) raise self._MigrationFailed( "Can't migrate into existing Pvtbox folder") if self._size and free_space < self._size: logger.debug( "No disk space in %s. Free space: %s. Needed: %s.", selected_folder, free_space, self._size) msgbox(tr( "Insufficient disk space for migration to\n{}.\n" "Please clean disk", selected_folder), tr("No disk space"), parent=self._dialog) raise self._MigrationFailed( "Insufficient disk space for migration") self._migration_cancelled = False dialog = QProgressDialog(self._dialog) dialog.setWindowTitle(tr('Migrating to new Pvtbox folder')) dialog.setWindowIcon(QIcon(':/images/icon.svg')) dialog.setModal(True) dialog.setMinimum(0) dialog.setMaximum(100) dialog.setMinimumSize(400, 80) dialog.setAutoClose(False) def progress(value): logger.debug("Migration dialog progress received: %s", value) dialog.setValue(value) def migration_failed(error): logger.warning("Migration failed with error: %s", error) msgbox(error, tr('Migration to new Pvtbox folder error'), parent=dialog) dialog.cancel() self._migration_cancelled = True done() def cancel(): logger.debug("Migration dialog cancelled") self._migration_cancelled = True self._migration.cancel() def done(): logger.debug("Migration done") try: self._migration.progress.disconnect(progress) self._migration.failed.disconnect(migration_failed) self._migration.done.disconnect(done) dialog.canceled.disconnect(cancel) except Exception as e: logger.warning("Can't disconnect signal %s", e) dialog.hide() dialog.done(QDialog.Accepted) dialog.close() self._migration = SyncDirMigration(self._cfg, parent=self._dialog) self._migration.progress.connect(progress, Qt.QueuedConnection) self._migration.failed.connect(migration_failed, Qt.QueuedConnection) self._migration.done.connect(done, Qt.QueuedConnection) dialog.canceled.connect(cancel) self._exit_service() old_dir = self._cfg.sync_directory self._migration.migrate(old_dir, selected_folder) def on_finished(): logger.info("Migration dialog closed") if not self._migration_cancelled: logger.debug("Setting new location") self._ui.location_edit.setText(FilePath(selected_folder)) disable_file_logging(logger) shutil.rmtree(op.join(old_dir, '.pvtbox'), ignore_errors=True) set_root_directory(FilePath(selected_folder)) enable_file_logging(logger) make_dir_hidden(get_patches_dir(selected_folder)) self._start_service() dialog.finished.connect(on_finished) dialog.show() except self._MigrationFailed as e: logger.warning("Sync dir migration failed. Reason: %s", e) finally: if self._migrate: self._dialog.accept()
args = vars(parser.parse_args(new_argv)) else: args = vars(namespace) return args if __name__ == "__main__": # for multiprocessing under build pyinstaller multiprocessing.freeze_support() from common import utils utils.is_daemon = True utils.get_cfg_dir(create=True) utils.get_patches_dir(utils.get_data_dir(create=True), create=True) from common.application import Application from daemon.application_impl import ApplicationImpl args = sys.argv[1:] # Parse command line arguments args = parseArgs(args) # To terminate from console with Ctrl+C signal.signal(signal.SIGINT, signal.SIG_DFL) Application.set_instance_class(ApplicationImpl) Application.start(args) print('Exited')