Skip to content

Commit

Permalink
NXDRIVE-1138: Fix Direct Edit auto-lock on Windows 10
Browse files Browse the repository at this point in the history
  • Loading branch information
Mickaël Schoentgen committed Mar 2, 2018
1 parent 6c923a8 commit 37ab4f9
Show file tree
Hide file tree
Showing 5 changed files with 61 additions and 34 deletions.
6 changes: 6 additions & 0 deletions docs/changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@
Release date: `2018-??-??`


# 3.0.6
Release date: `2018-03-02`

### Core
- [NXDRIVE-1138](https://jira.nuxeo.com/browse/NXDRIVE-1138): Fix Direct Edit auto-lock on Windows 10


# 3.0.5
Release date: `2018-02-23`
Expand Down
2 changes: 1 addition & 1 deletion nuxeo-drive-client/nxdrive/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,4 @@
"""

__author__ = 'Nuxeo'
__version__ = '3.0.5'
__version__ = '3.0.6'
30 changes: 15 additions & 15 deletions nuxeo-drive-client/nxdrive/autolocker.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
# coding: utf-8
import psutil
from copy import deepcopy
from logging import getLogger

import psutil
from PyQt4 import QtCore
from PyQt4.QtCore import QTimer, pyqtSignal

from engine.workers import PollWorker
from nxdrive.engine.workers import ThreadInterrupt
from nxdrive.engine.workers import PollWorker, ThreadInterrupt
from nxdrive.utils import force_decode

log = getLogger(__name__)


class ProcessAutoLockerWorker(PollWorker):

orphanLocks = QtCore.pyqtSignal(object)
documentLocked = QtCore.pyqtSignal(str)
documentUnlocked = QtCore.pyqtSignal(str)
orphanLocks = pyqtSignal(object)
documentLocked = pyqtSignal(str)
documentUnlocked = pyqtSignal(str)

def __init__(self, check_interval, dao, folder):
super(ProcessAutoLockerWorker, self).__init__(check_interval)
self._dao = dao
self._folder = folder
self._folder = force_decode(folder)

self._autolocked = {}
self._lockers = {}
Expand All @@ -34,7 +34,7 @@ def set_autolock(self, filepath, locker):

self._autolocked[filepath] = 0
self._lockers[filepath] = locker
QtCore.QTimer.singleShot(2000, self.force_poll)
QTimer.singleShot(2000, self.force_poll)

def _poll(self):
try:
Expand All @@ -47,7 +47,7 @@ def _poll(self):
except ThreadInterrupt:
raise
except:
log.trace('Unhandled error', exc_info=True)
log.exception('Unhandled error')

def orphan_unlocked(self, path):
self._dao.unlock_path(path)
Expand All @@ -56,9 +56,9 @@ def _process(self):
to_unlock = deepcopy(self._autolocked)
for pid, path in get_open_files():
if path.startswith(self._folder):
log.trace('Found in watched folder: %r (PID=%r)', path, pid)
log.debug('Found in watched folder: %r (PID=%r)', path, pid)
elif path in self._autolocked:
log.trace('Found in auto-locked: %r (PID=%r)', path, pid)
log.debug('Found in auto-locked: %r (PID=%r)', path, pid)
else:
continue

Expand All @@ -82,15 +82,15 @@ def _unlock_files(self, files):

def _lock_file(self, item):
pid, path = item
log.trace('Locking file %r (PID=%r)', path, pid)
log.debug('Locking file %r (PID=%r)', path, pid)
if path in self._lockers:
locker = self._lockers[path]
locker.autolock_lock(path)
self._dao.lock_path(path, pid, '')
self._to_lock.remove(item)

def _unlock_file(self, path):
log.trace('Unlocking file %r', path)
log.debug('Unlocking file %r', path)
if path in self._lockers:
locker = self._lockers[path]
locker.autolock_unlock(path)
Expand All @@ -110,6 +110,6 @@ def get_open_files():
for proc in psutil.process_iter():
try:
for handler in proc.open_files():
yield proc.pid, handler.path
yield proc.pid, force_decode(handler.path)
except psutil.Error:
pass
55 changes: 38 additions & 17 deletions nuxeo-drive-client/nxdrive/direct_edit.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ def __init__(self, manager, folder, url):
self._folder = folder
self.url = url

self._thread.started.connect(self.run)
self.autolock = self._manager.autolock_service
self.use_autolock = self._manager.get_direct_edit_auto_lock()
self._event_handler = None
self._metrics = {'edit_files': 0}
self._observer = None
Expand All @@ -54,18 +55,20 @@ def __init__(self, manager, folder, url):
self._lock_queue = Queue()
self._error_queue = BlacklistQueue()
self._stop = False
self._manager.autolock_service.orphanLocks.connect(self._autolock_orphans)
self._last_action_timing = -1
self.watchdog_queue = Queue()

self._thread.started.connect(self.run)
self.autolock.orphanLocks.connect(self._autolock_orphans)

@pyqtSlot(object)
def _autolock_orphans(self, locks):
log.trace('Orphans lock: %r', locks)
for lock in locks:
if lock.path.startswith(self._folder):
log.debug('Should unlock %r', lock.path)
if not os.path.exists(lock.path):
self._manager.autolock_service.orphan_unlocked(lock.path)
self.autolock.orphan_unlocked(lock.path)
continue

ref = self._local_client.get_path(lock.path)
Expand Down Expand Up @@ -385,7 +388,7 @@ def _handle_lock_queue(self):
remote.lock(uid)
local.set_remote_id(dir_path, '1', name='nxdirecteditlock')
# Emit the lock signal only when the lock is really set
self._manager.autolock_service.documentLocked.emit(os.path.basename(ref))
self.autolock.documentLocked.emit(os.path.basename(ref))
continue

try:
Expand All @@ -398,13 +401,13 @@ def _handle_lock_queue(self):
if purge or action.startswith('unlock'):
path = local.abspath(ref)
log.trace('Remove orphan: %r', path)
self._manager.autolock_service.orphan_unlocked(path)
self.autolock.orphan_unlocked(path)
shutil.rmtree(path, ignore_errors=True)
continue

local.remove_remote_id(dir_path, name='nxdirecteditlock')
# Emit the signal only when the unlock is done
self._manager.autolock_service.documentUnlocked.emit(os.path.basename(ref))
self.autolock.documentUnlocked.emit(os.path.basename(ref))
except ThreadInterrupt:
raise
except:
Expand Down Expand Up @@ -563,31 +566,49 @@ def handle_watchdog_event(self, evt):
return

local = self._local_client
file_name = os.path.basename(src_path)
file_name = force_decode(os.path.basename(src_path))
if local.is_temp_file(file_name):
return

log.debug('Handling watchdog event [%s] on %r',
evt.event_type, evt.src_path)

if evt.event_type == 'moved':
src_path = evt.dest_path
file_name = os.path.basename(src_path)
src_path = normalize_event_filename(evt.dest_path)
file_name = force_decode(os.path.basename(src_path))

ref = local.get_path(src_path)
dir_path = local.get_path(os.path.dirname(src_path))
name = local.get_remote_id(dir_path, name='nxdirecteditname')

if not name or force_decode(name) != force_decode(file_name):
if (evt.event_type == 'deleted'
and self._is_lock_file(file_name)):
# Free the xattr to let _cleanup() does its work
local.remove_remote_id(dir_path, name='nxdirecteditlock')
if not name:
return

editing = local.get_remote_id(dir_path, name='nxdirecteditlock')

if force_decode(name) != file_name:
if self._is_lock_file(file_name):
if (evt.event_type == 'created'
and self.use_autolock and editing != '1'):
"""
[Windows 10] The original file is not modified until
we specifically click on the save button. Instead, it
applies changes to the temporary file.
So the auto-lock does not happen because there is no
'modified' event on the original file.
Here we try to address that by checking the lock state
and use the lock if not already done.
"""
# Recompute the path from 'dir/temp_file' -> 'dir/file'
path = os.path.join(os.path.dirname(src_path), name)
self.autolock.set_autolock(path, self)
elif evt.event_type == 'deleted':
# Free the xattr to let _cleanup() does its work
local.remove_remote_id(dir_path, name='nxdirecteditlock')
return

if (local.get_remote_id(dir_path, name='nxdirecteditlock') != '1'
and self._manager.get_direct_edit_auto_lock()):
self._manager.autolock_service.set_autolock(src_path, self)
if self.use_autolock and editing != '1':
self.autolock.set_autolock(src_path, self)

if evt.event_type != 'deleted':
self._upload_queue.put(ref)
Expand Down
2 changes: 1 addition & 1 deletion nuxeo-drive-client/nxdrive/notification.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ def get_notifications(self, engine=None, include_generic=True):
return result

def send_notification(self, notification):
log.trace('Sending %r', notification)
log.debug('Sending %r', notification)
notification._time = int(time.time())
with self._lock:
if notification.is_persistent():
Expand Down

0 comments on commit 37ab4f9

Please sign in to comment.