def load_config(self, parser): import ConfigParser config_name = 'config.ini' config = ConfigParser.ConfigParser() configs = [] path = os.path.join(os.path.dirname(sys.executable), config_name) if os.path.exists(path): configs.append(path) if os.path.exists(config_name): configs.append(config_name) user_ini = os.path.expanduser(os.path.join(self.default_home, config_name)) if os.path.exists(user_ini): configs.append(user_ini) if len(configs) > 0: config.read(configs) if config.has_option(ConfigParser.DEFAULTSECT, 'env'): env = config.get(ConfigParser.DEFAULTSECT, 'env') args = AbstractOSIntegration.get(None).get_system_configuration() for item in config.items(env): if item[0] == 'env': continue args[item[0].replace('-', '_')] = item[1] if len(args): parser.set_defaults(**args) else: parser.set_defaults(**AbstractOSIntegration.get(None).get_system_configuration())
def set_folder_icon(self, ref, icon): if icon is None: return if AbstractOSIntegration.is_windows(): self.set_folder_icon_win32(ref, icon) elif AbstractOSIntegration.is_mac(): self.set_folder_icon_darwin(ref, icon)
def showMessage(self, title, message, icon=QtGui.QSystemTrayIcon.Information, timeout = 10000): if AbstractOSIntegration.is_mac(): if AbstractOSIntegration.os_version_above("10.8"): from nxdrive.osi.darwin.pyNotificationCenter import notify # Use notification center return notify(title, None, message) return QtGui.QSystemTrayIcon.showMessage(self, title, message, icon, timeout)
def load_config(self, parser): import ConfigParser config_name = 'config.ini' config = ConfigParser.ConfigParser() configs = [] path = os.path.join(os.path.dirname(sys.executable), config_name) if os.path.exists(path): configs.append(path) if os.path.exists(config_name): configs.append(config_name) user_ini = os.path.expanduser( os.path.join(self.default_home, config_name)) if os.path.exists(user_ini): configs.append(user_ini) if len(configs) > 0: config.read(configs) if config.has_option(ConfigParser.DEFAULTSECT, 'env'): env = config.get(ConfigParser.DEFAULTSECT, 'env') args = AbstractOSIntegration.get(None).get_system_configuration() for item in config.items(env): if item[0] == 'env': continue args[item[0].replace('-', '_')] = item[1] if len(args): parser.set_defaults(**args) else: parser.set_defaults( **AbstractOSIntegration.get(None).get_system_configuration())
def _check_last_sync(self): from nxdrive.engine.watcher.local_watcher import WIN_MOVE_RESOLUTION_PERIOD qm_active = self._queue_manager.active() qm_size = self._queue_manager.get_overall_size() empty_polls = self._remote_watcher.get_metrics()["empty_polls"] if not AbstractOSIntegration.is_windows(): win_info = 'not Windows' else: win_info = 'Windows with win queue size = %d and win folder scan size = %d' % ( self._local_watcher.get_win_queue_size(), self._local_watcher.get_win_folder_scan_size()) log.debug('Checking sync completed: queue manager is %s, overall size = %d, empty polls count = %d' ', watchdog queue size = %d, %s', 'active' if qm_active else 'inactive', qm_size, empty_polls, self._local_watcher.get_watchdog_queue_size(), win_info) local_metrics = self._local_watcher.get_metrics() if (qm_size == 0 and not qm_active and empty_polls > 0 and self._local_watcher.empty_events() and ( not AbstractOSIntegration.is_windows() or self._local_watcher.win_queue_empty() and self._local_watcher.win_folder_scan_empty())): self._dao.update_config("last_sync_date", datetime.datetime.utcnow()) if local_metrics['last_event'] == 0: log.warn("No watchdog event detected but sync is completed") if self._sync_started: self._sync_started = False log.debug('Emitting syncCompleted for engine %s', self.get_uid()) self.syncCompleted.emit()
def remove_remote_id(self, ref, name='ndrive'): # Can be move to another class path = self._abspath(ref) log.trace('Removing xattr %s from %s', name, path) locker = self.unlock_path(path, False) if AbstractOSIntegration.is_windows(): pathAlt = path + ":" + name try: if os.path.exists(pathAlt): os.remove(pathAlt) except WindowsError as e: if e.errno == os.errno.EACCES: self.unset_path_readonly(path) os.remove(pathAlt) self.set_path_readonly(path) else: raise e finally: self.lock_path(path, locker) else: try: import xattr if AbstractOSIntegration.is_mac(): xattr.removexattr(path, name) else: xattr.removexattr(path, 'user.' + name) except IOError as e: # Ignore IOError: [Errno 93] Attribute not found ( Mac ) # IOError: [Errno 61] No data available ( Linux ) if e.errno == 93 or e.errno == 61: pass else: raise finally: self.lock_path(path, locker)
def remove_remote_id(self, ref, name='ndrive'): # Can be move to another class path = self._abspath(ref) log.trace('Removing xattr %s from %s', name, path) locker = self.unlock_path(path, False) if AbstractOSIntegration.is_windows(): pathAlt = path + ":" + name try: os.remove(pathAlt) except WindowsError as e: if e.errno == os.errno.EACCES: self.unset_path_readonly(path) os.remove(pathAlt) self.set_path_readonly(path) else: raise e finally: self.lock_path(path, locker) else: try: import xattr if AbstractOSIntegration.is_mac(): xattr.removexattr(path, name) else: xattr.removexattr(path, 'user.' + name) finally: self.lock_path(path, locker)
def get_local_client(self, path): if AbstractOSIntegration.is_windows(): from nxdrive.tests.win_local_client import WindowsLocalClient return WindowsLocalClient(path) if AbstractOSIntegration.is_mac(): from nxdrive.tests.mac_local_client import MacLocalClient return MacLocalClient(path) return LocalClient(path)
def showMessage(self, title, message, icon=QtGui.QSystemTrayIcon.Information, timeout = 10000): if AbstractOSIntegration.is_mac(): if AbstractOSIntegration.os_version_above("10.8"): from nxdrive.osi.darwin.pyNotificationCenter import notify # Use notification center log.trace("Display systray message: %s | %s | %s", QtCore.QCoreApplication.applicationName(), title, message) return notify(title, None, message) return QtGui.QSystemTrayIcon.showMessage(self, title, message, icon, timeout)
def has_folder_icon(self, ref): target_folder = self._abspath(ref) if AbstractOSIntegration.is_mac(): meta_file = os.path.join(target_folder, "Icon\r") return os.path.exists(meta_file) if AbstractOSIntegration.is_windows(): meta_file = os.path.join(target_folder, "desktop.ini") return os.path.exists(meta_file) return False
def unset_folder_icon(self, ref): ''' Unset the red icon ''' if AbstractOSIntegration.is_windows(): # TODO Clean version desktop_ini_file_path = os.path.join(self._abspath(ref), "desktop.ini") if AbstractOSIntegration.is_mac(): desktop_ini_file_path = os.path.join(self._abspath(ref), "Icon\r") if os.path.exists(desktop_ini_file_path): os.remove(desktop_ini_file_path)
def get_local_client(self, path): if AbstractOSIntegration.is_mac() and ( self._testMethodName == 'test_local_delete_readonly_folder' or self._testMethodName == 'test_local_rename_readonly_folder'): return LocalClient(path) # Old mac dont handle case rename if AbstractOSIntegration.is_mac() and AbstractOSIntegration.os_version_below("10.10") and ( self._testMethodName == 'test_local_rename_file_uppercase_stopped' or self._testMethodName == 'test_local_rename_file_uppercase'): return LocalClient(path) return super(TestLocalMoveAndRename, self).get_local_client(path)
def __init__(self, manager, options, argv=()): super(Application, self).__init__(list(argv)) self.setApplicationName(manager.get_appname()) self.setQuitOnLastWindowClosed(False) self._delegator = None self.manager = manager from nxdrive.scripting import DriveUiScript self.manager.set_script_object(DriveUiScript(manager, self)) self.options = options self.mainEngine = None self.filters_dlg = None self._conflicts_modals = dict() self.current_notification = None # Make dialog unique self.uniqueDialogs = dict() # Init translator self._init_translator() for _, engine in self.manager.get_engines().iteritems(): self.mainEngine = engine break if self.mainEngine is not None and options.debug: from nxdrive.engine.engine import EngineLogger self.engineLogger = EngineLogger(self.mainEngine) self.engineWidget = None self.aboutToQuit.connect(self.manager.stop) self.manager.dropEngine.connect(self.dropped_engine) # Timer to spin the transferring icon self.icon_spin_timer = QtCore.QTimer() self.icon_spin_timer.timeout.connect(self.spin_transferring_icon) self.icon_spin_count = 0 # Application update self.manager.get_updater().appUpdated.connect(self.app_updated) self.updated_version = None # This is a windowless application mostly using the system tray self.setQuitOnLastWindowClosed(False) self.setup_systray() # Direct Edit conflict self.manager.get_drive_edit().driveEditConflict.connect(self._direct_edit_conflict) # Check if actions is required, separate method so it can be override self.init_checks() self.engineWidget = None # Setup notification center for Mac if AbstractOSIntegration.is_mac(): if AbstractOSIntegration.os_version_above("10.8"): self._setup_notification_center()
def showMessage(self, title, message, icon=QtGui.QSystemTrayIcon.Information, timeout=10000): if AbstractOSIntegration.is_mac(): if AbstractOSIntegration.os_version_above("10.8"): from nxdrive.osi.darwin.pyNotificationCenter import notify # Use notification center return notify(title, None, message) return QtGui.QSystemTrayIcon.showMessage(self, title, message, icon, timeout)
def current_locale(self): """ Detect the OS default language. """ encoding = locale.getdefaultlocale()[1] if AbstractOSIntegration.is_windows(): l10n_code = ctypes.windll.kernel32.GetUserDefaultUILanguage() l10n = locale.windows_locale[l10n_code] elif AbstractOSIntegration.is_mac(): l10n_code = NSLocale.currentLocale() l10n = NSLocale.localeIdentifier(l10n_code) encoding = 'UTF-8' else: l10n = locale.getdefaultlocale()[0] return '.'.join([l10n, encoding])
def __init__(self, manager, *args): super(Application, self).__init__(manager, *args) self.setQuitOnLastWindowClosed(False) self._delegator = None from nxdrive.scripting import DriveUiScript self.manager.set_script_object(DriveUiScript(manager, self)) self.mainEngine = None self.filters_dlg = None self._conflicts_modals = dict() self.current_notification = None self.default_tooltip = self.manager.app_name for _, engine in self.manager.get_engines().iteritems(): self.mainEngine = engine break if self.mainEngine is not None and Options.debug: from nxdrive.engine.engine import EngineLogger self.engineLogger = EngineLogger(self.mainEngine) self.engineWidget = None self.aboutToQuit.connect(self.manager.stop) self.manager.dropEngine.connect(self.dropped_engine) # Timer to spin the transferring icon self.icon_spin_timer = QtCore.QTimer() self.icon_spin_timer.timeout.connect(self.spin_transferring_icon) self.icon_spin_count = 0 # Application update self.manager.get_updater().appUpdated.connect(self.app_updated) self.updated_version = None # This is a windowless application mostly using the system tray self.setQuitOnLastWindowClosed(False) self.setup_systray() # Direct Edit conflict self.manager.direct_edit.directEditConflict.connect(self._direct_edit_conflict) # Check if actions is required, separate method so it can be override self.init_checks() self.engineWidget = None # Setup notification center for macOS if (AbstractOSIntegration.is_mac() and AbstractOSIntegration.os_version_above('10.8')): self._setup_notification_center()
def _check_last_sync(self): empty_events = self._local_watcher.empty_events() blacklist_size = self._queue_manager.get_errors_count() qm_size = self._queue_manager.get_overall_size() qm_active = self._queue_manager.active() empty_polls = self._remote_watcher.get_metrics()["empty_polls"] if not AbstractOSIntegration.is_windows(): win_info = 'not Windows' else: win_info = 'Windows with win queue size = %d and win folder scan size = %d' % ( self._local_watcher.get_win_queue_size(), self._local_watcher.get_win_folder_scan_size()) log.debug('Checking sync completed: queue manager is %s, overall size = %d, empty polls count = %d' ', local watcher empty events = %d, blacklist = %d, %s', 'active' if qm_active else 'inactive', qm_size, empty_polls, empty_events, blacklist_size, win_info) local_metrics = self._local_watcher.get_metrics() if (qm_size == 0 and not qm_active and empty_polls > 0 and empty_events): if blacklist_size != 0: self.syncPartialCompleted.emit() return self._dao.update_config("last_sync_date", datetime.datetime.utcnow()) if local_metrics['last_event'] == 0: log.warn("No watchdog event detected but sync is completed") if self._sync_started: self._sync_started = False log.debug('Emitting syncCompleted for engine %s', self.get_uid()) self.syncCompleted.emit()
def _check_last_sync(self): from nxdrive.engine.watcher.local_watcher import WIN_MOVE_RESOLUTION_PERIOD qm_active = self._queue_manager.active() qm_size = self._queue_manager.get_overall_size() empty_polls = self._remote_watcher.get_metrics()["empty_polls"] log.debug( "Checking sync completed: queue manager is %s, overall size = %d, empty polls count = %d", "active" if qm_active else "inactive", qm_size, empty_polls, ) local_metrics = self._local_watcher.get_metrics() if ( qm_size == 0 and not qm_active and empty_polls > 0 and self._local_watcher.empty_events() and ( not AbstractOSIntegration.is_windows() or self._local_watcher.win_queue_empty() and self._local_watcher.win_folder_scan_empty() ) ): self._dao.update_config("last_sync_date", datetime.datetime.utcnow()) if local_metrics["last_event"] == 0: log.warn("No watchdog event detected but sync is completed") if self._sync_started: self._sync_started = False log.debug("Emitting syncCompleted for engine %s", self.get_uid()) self.syncCompleted.emit()
def test_finder_in_use(self): if not AbstractOSIntegration.is_mac(): raise SkipTest('Not relevant on Linux or Windows') self.engine_1.start() self.wait_sync(wait_for_async=True) self.local_client_1.make_file('/', u'File.txt', content=u'Some Content 1'.encode('utf-8')) # Emulate the Finder in use flag OSX_FINDER_INFO_ENTRY_SIZE = 32 key = (OSX_FINDER_INFO_ENTRY_SIZE)*[0] key[0] = 0x62 key[1] = 0x72 key[2] = 0x6F key[3] = 0x6B key[4] = 0x4D key[5] = 0x41 key[6] = 0x43 key[7] = 0x53 import xattr xattr.setxattr(self.local_client_1._abspath(u'/File.txt'), xattr.XATTR_FINDERINFO_NAME, bytes(bytearray(key))) # The file should not be synced and there have no remote id self.wait_sync(wait_for_async=True, fail_if_timeout=False, timeout=10) info = self.local_client_1.get_remote_id(u'/File.txt') self.assertIsNone(info) # Remove the Finder flag self.local_client_1.remove_remote_id(u'/File.txt', xattr.XATTR_FINDERINFO_NAME) # The sync process should now handle the file and sync it self.wait_sync(wait_for_async=True, fail_if_timeout=False, timeout=10) info = self.local_client_1.get_remote_id(u'/File.txt') self.assertIsNotNone(info)
def load_config(self, parser): import ConfigParser config_name = 'config.ini' config = ConfigParser.ConfigParser() configs = [] path = os.path.join(os.path.dirname(sys.executable), config_name) if os.path.exists(path): configs.append(path) if os.path.exists(config_name): configs.append(config_name) user_ini = os.path.expanduser(os.path.join(Options.nxdrive_home, config_name)) if os.path.exists(user_ini): configs.append(user_ini) if configs: config.read(configs) args = AbstractOSIntegration.get(None).get_system_configuration() if config.has_option(ConfigParser.DEFAULTSECT, 'env'): env = config.get(ConfigParser.DEFAULTSECT, 'env') for item in config.items(env): if item[0] == 'env': continue value = item[1] if value == '': continue if '\n' in item[1]: # Treat multiline option as a set value = tuple(sorted(item[1].split())) args[item[0].replace('-', '_')] = value if args: Options.update(args, setter='local') parser.set_defaults(**args)
def _handle_watchdog_event_on_known_acquired_pair(self, doc_pair, evt, rel_path): if evt.event_type == 'deleted': # Delay on Windows the delete event if self._windows: self._win_lock.acquire() log.debug('Add pair to delete events: %r', doc_pair) try: self._delete_events[doc_pair.remote_ref] = (current_milli_time(), doc_pair) finally: self._win_lock.release() else: # In case of case sensitive can be an issue if self.client.exists(doc_pair.local_path): remote_id = self.client.get_remote_id(doc_pair.local_path) if remote_id == doc_pair.remote_ref or remote_id is None: # This happens on update don't do anything return self._handle_watchdog_delete(doc_pair) return if evt.event_type == 'created': # NXDRIVE-471 case maybe remote_ref = self.client.get_remote_id(rel_path) if remote_ref is None: log.debug("Created event on a known pair with no remote_ref," " this should only happen in case of a quick move and copy-paste: %r", doc_pair) return else: # NXDRIVE-509 log.debug("Created event on a known pair with a remote_ref: %r", doc_pair) local_info = self.client.get_info(rel_path, raise_if_missing=False) if local_info is not None: # Unchanged folder if doc_pair.folderish: log.debug('Unchanged folder %s (watchdog event [%s]), only update last_local_updated', rel_path, evt.event_type) self._dao.update_local_modification_time(doc_pair, local_info) return if doc_pair.local_state == 'synchronized': digest = local_info.get_digest() # Unchanged digest, can be the case if only the last modification time or file permissions # have been updated if doc_pair.local_digest == digest: log.debug('Digest has not changed for %s (watchdog event [%s]), only update last_local_updated', rel_path, evt.event_type) if local_info.remote_ref is None: self.client.set_remote_id(rel_path, doc_pair.remote_ref) self._dao.update_local_modification_time(doc_pair, local_info) return doc_pair.local_digest = digest doc_pair.local_state = 'modified' if AbstractOSIntegration.is_mac() and evt.event_type == 'modified' and doc_pair.remote_ref is not None and doc_pair.remote_ref != local_info.remote_ref: original_pair = self._dao.get_normal_state_from_remote(local_info.remote_ref) original_info = None if original_pair is not None: original_info = self.client.get_info(original_pair.local_path, raise_if_missing=False) if original_info is not None and original_info.remote_ref == local_info.remote_ref: log.debug("MacOSX has postponed overwriting of xattr, need to reset remote_ref for %r", doc_pair) # We are in a copy/paste situation with OS overriding the xattribute self.client.set_remote_id(doc_pair.local_path, doc_pair.remote_ref) self._dao.update_local_state(doc_pair, local_info)
def __init__(self, root, path, folderish, last_modification_time, size=0, digest_func='md5', check_suspended=None, remote_ref=None): # Function to check during long-running processing like digest # computation if the synchronization thread needs to be suspended self.check_suspended = check_suspended self.size = size filepath = os.path.join(root, path[1:].replace(u'/', os.path.sep)) root = unicodedata.normalize('NFC', root) path = unicodedata.normalize('NFC', path) normalized_filepath = os.path.join(root, path[1:].replace(u'/', os.path.sep)) self.filepath = normalized_filepath # Normalize name on the file system if not normalized # See https://jira.nuxeo.com/browse/NXDRIVE-188 if os.path.exists(filepath) and normalized_filepath != filepath and not AbstractOSIntegration.is_mac(): log.debug('Forcing normalization of %r to %r', filepath, normalized_filepath) os.rename(filepath, normalized_filepath) self.root = root # the sync root folder local path self.path = path # the truncated path (under the root) self.folderish = folderish # True if a Folder self.remote_ref = remote_ref # Last OS modification date of the file self.last_modification_time = last_modification_time # Function to use self._digest_func = digest_func.lower() # Precompute base name once and for all are it's often useful in # practice self.name = os.path.basename(path)
def load_config(self, parser): import ConfigParser config_name = 'config.ini' config = ConfigParser.ConfigParser() configs = [] path = os.path.join(os.path.dirname(sys.executable), config_name) if os.path.exists(path): configs.append(path) if os.path.exists(config_name): configs.append(config_name) user_ini = os.path.expanduser( os.path.join(Options.nxdrive_home, config_name)) if os.path.exists(user_ini): configs.append(user_ini) if configs: config.read(configs) args = AbstractOSIntegration.get(None).get_system_configuration() if config.has_option(ConfigParser.DEFAULTSECT, 'env'): env = config.get(ConfigParser.DEFAULTSECT, 'env') for item in config.items(env): if item[0] == 'env': continue value = item[1] if value == '': continue if '\n' in item[1]: # Treat multiline option as a set value = tuple(sorted(item[1].split())) args[item[0].replace('-', '_')] = value if args: Options.update(args, setter='local') parser.set_defaults(**args)
def test_untrash_file_with_rename(self): self.local_client_1.make_file('/', 'File_To_Delete.txt', 'This is a content') self.wait_sync() self.assertTrue(self.remote_document_client_1.exists('/File_To_Delete.txt')) uid = self.local_client_1.get_remote_id('/File_To_Delete.txt') old_info = self.remote_document_client_1.get_info('/File_To_Delete.txt', use_trash=True) abs_path = self.local_client_1.abspath('/File_To_Delete.txt') # Pretend we had trash the file shutil.move(abs_path, os.path.join(self.local_test_folder_1, 'File_To_Delete2.txt')) self.wait_sync(wait_for_async=True) self.assertFalse(self.remote_document_client_1.exists('/File_To_Delete.txt')) self.assertFalse(self.local_client_1.exists('/File_To_Delete.txt')) with open(os.path.join(self.local_test_folder_1, 'File_To_Delete2.txt'), 'w') as f: f.write('New content') if AbstractOSIntegration.is_windows(): # Python API overwrite the tag by default with open(os.path.join(self.local_test_folder_1, 'File_To_Delete2.txt:ndrive'), 'w') as f: f.write(uid) # See if it untrash or recreate shutil.move(os.path.join(self.local_test_folder_1, 'File_To_Delete2.txt'), self.local_client_1.abspath('/')) self.wait_sync(wait_for_async=True) self.assertTrue(self.remote_document_client_1.exists(old_info.uid)) self.assertTrue(self.local_client_1.exists('/File_To_Delete2.txt')) self.assertFalse(self.local_client_1.exists('/File_To_Delete.txt')) self.assertEqual(self.local_client_1.get_content('/File_To_Delete2.txt'), 'New content')
def is_ignored(self, parent_ref, file_name): # Add parent_ref to be able to filter on size if needed ignore = False # Office temp file # http://support.microsoft.com/kb/211632 if file_name.startswith("~") and file_name.endswith(".tmp"): return True # Emacs auto save file # http://www.emacswiki.org/emacs/AutoSave if file_name.startswith("#") and file_name.endswith("#") and len(file_name) > 2: return True for suffix in self.ignored_suffixes: if file_name.endswith(suffix): ignore = True break for prefix in self.ignored_prefixes: if file_name.startswith(prefix): ignore = True break if ignore: return True if AbstractOSIntegration.is_windows(): # NXDRIVE-465 ref = self.get_children_ref(parent_ref, file_name) path = self._abspath(ref) if not os.path.exists(path): return False import win32con import win32api attrs = win32api.GetFileAttributes(path) if attrs & win32con.FILE_ATTRIBUTE_SYSTEM == win32con.FILE_ATTRIBUTE_SYSTEM: return True if attrs & win32con.FILE_ATTRIBUTE_HIDDEN == win32con.FILE_ATTRIBUTE_HIDDEN: return True return False
def copy_with_xattr(src, dst): """ Custom copy tree method that also copies xattr along with shutil.copytree functionality. """ if AbstractOSIntegration.is_windows(): # Make a copy of file1 using shell (to copy including xattr) shell.SHFileOperation((0, shellcon.FO_COPY, src, dst, shellcon.FOF_NOCONFIRMATION, None, None)) elif AbstractOSIntegration.is_mac(): fm = Cocoa.NSFileManager.defaultManager() fm.copyItemAtPath_toPath_error_(src, dst, None) else: shutil.copy2(src, dst) remote_id = xattr.getxattr(src, 'user.ndrive') xattr.setxattr(dst, 'user.ndrive', remote_id)
def test_move_and_copy_paste_folder_original_location_stopped(self): if AbstractOSIntegration.is_linux(): raise SkipTest( "NXDRIVE-471: Not handled under Linux as creation time is not stored" ) self._move_and_copy_paste_folder(self.folder_path_1, self.folder_path_2, os.path.dirname(self.folder_path_1))
def test_local_delete_readonly_folder(self): local_client = self.local_client_1 remote_client = self.remote_document_client_1 # Check local folder self.assertTrue(local_client.exists(u'/Original Folder 1')) folder_1_state = self.get_dao_state_from_engine_1( u'/Original Folder 1') self.assertTrue(folder_1_state.remote_can_delete) # Set remote folder as readonly for test user folder_1_path = TEST_WORKSPACE_PATH + u'/Original Folder 1' op_input = "doc:" + folder_1_path self.root_remote_client.execute("Document.SetACE", op_input=op_input, user=self.user_1, permission="Read") self.root_remote_client.block_inheritance(folder_1_path, overwrite=False) self.wait_sync(wait_for_async=True) # Check can_delete flag in pair state folder_1_state = self.get_dao_state_from_engine_1( u'/Original Folder 1') self.assertFalse(folder_1_state.remote_can_delete) # Delete local folder local_client.delete(u'/Original Folder 1') self.assertFalse(local_client.exists(u'/Original Folder 1')) self.wait_sync(wait_for_async=True) self.assertEqual(self.engine_1.get_dao().get_sync_count(), 6) # Check remote folder and its children have not been deleted folder_1_remote_info = remote_client.get_info(u'/Original Folder 1') self.assertEqual(folder_1_remote_info.name, u'Original Folder 1') file_1_1_remote_info = remote_client.get_info( u'/Original Folder 1/Original File 1.1.txt') self.assertEqual(file_1_1_remote_info.name, u'Original File 1.1.txt') folder_1_1_remote_info = remote_client.get_info( u'/Original Folder 1/Sub-Folder 1.1') self.assertEqual(folder_1_1_remote_info.name, u'Sub-Folder 1.1') folder_1_2_remote_info = remote_client.get_info( u'/Original Folder 1/Sub-Folder 1.2') self.assertEqual(folder_1_2_remote_info.name, u'Sub-Folder 1.2') if not AbstractOSIntegration.is_windows(): # Check filter has been created self.assertTrue(self.engine_1.get_dao().is_filter( folder_1_state.remote_parent_path + '/' + folder_1_state.remote_ref)) # Check local folder haven't been re-created self.assertFalse(local_client.exists(u'/Original Folder 1'))
def test_binding_initialization_and_first_sync(self): local = self.local_client_1 remote = self.remote_document_client_1 # Create some documents in a Nuxeo workspace and bind this server to a # Nuxeo Drive local folder self.make_server_tree() # The root binding operation does not create the local folder yet. self.assertFalse(local.exists('/')) # Launch ndrive and check synchronization self.engine_1.start() self.wait_sync(wait_for_async=True) self.assertTrue(local.exists('/')) self.assertTrue(local.exists('/Folder 1')) self.assertEquals(local.get_content('/Folder 1/File 1.txt'), "aaa") self.assertTrue(local.exists('/Folder 1/Folder 1.1')) self.assertEquals(local.get_content('/Folder 1/Folder 1.1/File 2.txt'), "bbb") self.assertTrue(local.exists('/Folder 1/Folder 1.2')) self.assertEquals(local.get_content('/Folder 1/Folder 1.2/File 3.txt'), "ccc") self.assertTrue(local.exists('/Folder 2')) # Cannot predict the resolution in advance self.assertTrue(remote.get_content(self._duplicate_file_1), "Some content.") self.assertTrue(remote.get_content(self._duplicate_file_2), "Other content.") if local.get_content( '/Folder 2/Duplicated File.txt') == "Some content.": self.assertEquals( local.get_content('/Folder 2/Duplicated File__1.txt'), "Other content.") else: self.assertEquals( local.get_content('/Folder 2/Duplicated File.txt'), "Other content.") self.assertEquals( local.get_content('/Folder 2/Duplicated File__1.txt'), "Some content.") self.assertEquals(local.get_content('/Folder 2/File 4.txt'), "ddd") self.assertEquals(local.get_content('/File 5.txt'), "eee") # Unbind root and resynchronize remote.unregister_as_root(self.workspace) # Since errors are generated by the deletion events sent by Watchdog for the workspace children under UNIX, # don't enforce errors and increase timeout if not AbstractOSIntegration.is_windows(): timeout = 60 enforce_errors = False else: timeout = DEFAULT_WAIT_SYNC_TIMEOUT enforce_errors = True self.wait_sync(wait_for_async=True, timeout=timeout, enforce_errors=enforce_errors) self.assertFalse(local.exists('/'))
def get_path_remote_id(path, name="ndrive"): if AbstractOSIntegration.is_windows(): path = path + ":" + name try: with open(path, "r") as f: return unicode(f.read(), 'utf-8') except: return None else: import xattr try: if AbstractOSIntegration.is_mac(): value = xattr.getxattr(path, name) else: value = xattr.getxattr(path, 'user.' + name) return unicode(value, 'utf-8') except: return None
def testLogs(self): from nxdrive.osi import AbstractOSIntegration if AbstractOSIntegration.is_windows(): raise SkipTest("Temporarily skipped, need to investigate") # NXDRIVE-358 report = Report(self.manager, os.path.join(self.folder, "report.zip")) log.debug("Strange encoding \xe9") log.debug(u"Unicode encoding \xe8") report.generate()
def test_synchronize_deletion(self): local = self.local_client_1 remote = self.remote_document_client_1 self.engine_1.start() # Create a remote folder with 2 children then synchronize remote.make_folder('/', 'Remote folder') remote.make_file('/Remote folder', 'Remote file 1.odt', 'Some content.') remote.make_file('/Remote folder', 'Remote file 2.odt', 'Other content.') self.wait_sync(wait_for_async=True) self.assertTrue(local.exists('/Remote folder')) self.assertTrue(local.exists('/Remote folder/Remote file 1.odt')) self.assertTrue(local.exists('/Remote folder/Remote file 2.odt')) # Delete remote folder then synchronize remote.delete('/Remote folder') self.wait_sync(wait_for_async=True) self.assertFalse(local.exists('/Remote folder')) self.assertFalse(local.exists('/Remote folder/Remote file 1.odt')) self.assertFalse(local.exists('/Remote folder/Remote file 2.odt')) # Create a local folder with 2 children then synchronize local.make_folder('/', 'Local folder') local.make_file('/Local folder', 'Local file 1.odt', 'Some content.') local.make_file('/Local folder', 'Local file 2.odt', 'Other content.') self.wait_sync() self.assertTrue(remote.exists('/Local folder')) self.assertTrue(remote.exists('/Local folder/Local file 1.odt')) self.assertTrue(remote.exists('/Local folder/Local file 2.odt')) # Delete local folder then synchronize time.sleep(OS_STAT_MTIME_RESOLUTION) local.delete('/Local folder') # Since errors are generated by the deletion events sent by Watchdog for the folder children under UNIX, # don't enforce errors and increase timeout if not AbstractOSIntegration.is_windows(): timeout = 60 enforce_errors = False else: timeout = DEFAULT_WAIT_SYNC_TIMEOUT enforce_errors = True self.wait_sync(timeout=timeout, enforce_errors=enforce_errors) self.assertFalse(remote.exists('/Local folder')) # Wait for async completion as recursive deletion of children is done # by the BulkLifeCycleChangeListener which is asynchronous self.wait() self.assertFalse(remote.exists('/Local folder/Local file 1.odt')) self.assertFalse(remote.exists('/Local folder/Local file 2.odt'))
def _new_notification(self, notification): if not notification.is_bubble(): return if AbstractOSIntegration.is_mac(): if AbstractOSIntegration.os_version_above("10.8"): from nxdrive.osi.darwin.pyNotificationCenter import notify, NotificationDelegator if self._delegator is None: self._delegator = NotificationDelegator.alloc().init() self._delegator._manager = self.manager # Use notification center userInfo = dict() userInfo["uuid"] = notification.get_uid() return notify(notification.get_title(), None, notification.get_description(), userInfo=userInfo) self.current_notification = notification icon = QtGui.QSystemTrayIcon.Information if (notification.get_level() == Notification.LEVEL_WARNING): icon = QtGui.QSystemTrayIcon.Warning elif (notification.get_level() == Notification.LEVEL_ERROR): icon = QtGui.QSystemTrayIcon.Critical self.show_message(notification.get_title(), notification.get_description(), icon=icon)
def get_creation_time(self, child_full_path): if self._windows: return os.path.getctime(child_full_path) else: stat = os.stat(child_full_path) # Try inode number as on HFS seems to be increasing if AbstractOSIntegration.is_mac() and hasattr(stat, "st_ino"): return stat.st_ino if hasattr(stat, "st_birthtime"): return stat.st_birthtime return 0
def rename(self, ref, to_name): """Rename a local file or folder Return the actualized info object. """ new_name = safe_filename(to_name) source_os_path = self._abspath(ref) parent = ref.rsplit(u'/', 1)[0] old_name = ref.rsplit(u'/', 1)[1] parent = u'/' if parent == '' else parent locker = self.unlock_ref(ref) try: # Check if only case renaming if (old_name != new_name and old_name.lower() == new_name.lower() and not self.is_case_sensitive()): # Must use a temp rename as FS is not case sensitive temp_path = os.tempnam(self._abspath(parent), '.ren_' + old_name + '_') if AbstractOSIntegration.is_windows(): import ctypes ctypes.windll.kernel32.SetFileAttributesW( unicode(temp_path), 2) os.rename(source_os_path, temp_path) source_os_path = temp_path # Try the os rename part target_os_path = self._abspath(os.path.join(parent, new_name)) else: target_os_path, new_name = self._abspath_deduped(parent, new_name, old_name) if old_name != new_name: os.rename(source_os_path, target_os_path) if AbstractOSIntegration.is_windows(): import ctypes # See http://msdn.microsoft.com/en-us/library/aa365535%28v=vs.85%29.aspx ctypes.windll.kernel32.SetFileAttributesW( unicode(target_os_path), 128) new_ref = self.get_children_ref(parent, new_name) return self.get_info(new_ref) finally: self.lock_ref(ref, locker & 2)
def get_path_remote_id(path, name="ndrive"): if AbstractOSIntegration.is_windows(): path = path + ":" + name try: with open(path, "r") as f: return f.read() except: return None else: import xattr try: if AbstractOSIntegration.is_mac(): value = xattr.getxattr(path, name) else: value = xattr.getxattr(path, "user." + name) if type(value).__name__ == "unicode": value = unicode(value) return value except: return None
def _set_root_icon(self): local_client = self.get_local_client() if not local_client.exists('/') or local_client.has_folder_icon('/'): return if AbstractOSIntegration.is_mac(): if AbstractOSIntegration.os_version_below("10.10"): icon = find_icon("NuxeoDrive_Mac_Folder.dat") else: icon = find_icon("NuxeoDrive_Mac_Yosemite_Folder.dat") elif AbstractOSIntegration.is_windows(): icon = find_icon("NuxeoDrive_Windows_Folder.ico") else: # No implementation on Linux return if icon is None: return locker = local_client.unlock_ref('/', unlock_parent=False) try: local_client.set_folder_icon('/', icon) finally: local_client.lock_ref('/', locker)
def set_remote_id(self, ref, remote_id, name="ndrive"): if type(remote_id).__name__ == "unicode": remote_id = unicodedata.normalize("NFC", remote_id).encode("utf-8") # Can be move to another class path = self._abspath(ref) log.trace("Setting xattr %s with value %r on %r", name, remote_id, path) locker = self.unlock_path(path, False) if AbstractOSIntegration.is_windows(): pathAlt = path + ":" + name try: if not os.path.exists(path): raise NotFound() stat = os.stat(path) with open(pathAlt, "w") as f: f.write(remote_id) # Avoid time modified change os.utime(path, (stat.st_atime, stat.st_mtime)) except IOError as e: # Should not happen if e.errno == os.errno.EACCES: self.unset_path_readonly(path) with open(pathAlt, "w") as f: f.write(remote_id) self.set_path_readonly(path) else: raise e finally: self.lock_path(path, locker) else: try: import xattr stat = os.stat(path) if AbstractOSIntegration.is_mac(): xattr.setxattr(path, name, remote_id) else: xattr.setxattr(path, "user." + name, remote_id) os.utime(path, (stat.st_atime, stat.st_mtime)) finally: self.lock_path(path, locker)
def test_fileinfo_normalization(self): import os from nxdrive.client.local_client import FileInfo from nose.plugins.skip import SkipTest from nxdrive.osi import AbstractOSIntegration if AbstractOSIntegration.is_mac(): raise SkipTest("Normalization dont work on Mac") self.engine_1.stop() name = u'Teste\u0301' self.local_client.make_file('/', name, 'Test') info = FileInfo(self.local_client.base_folder, '/' + name, False, 0) # The encoding should be different - cannot trust the get_children as they use FileInfo children = os.listdir(self.local_client._abspath('/')) children.sort() self.assertNotEqual(children[0], name)