Commit d27c18e8 authored by Benjamin Fischer's avatar Benjamin Fischer
Browse files

Filesystem watch: watched directories now filters hidden files correctly (refs...

Filesystem watch: watched directories now filters hidden files correctly (refs #1955) and transmits small changes within the event
- for upto 10 file changes detailed informations are provided (get_file_list style) - for none existing/accessable files only the name will be transmitted (and root/ext); key: subject_infos
- for upto 25 file changes only the list filenames will be transmitted (including the names of delete/moved files); key: subject_names
- for any amount of file changes the total affected file count will be transmitted; key: subject_count
- the path of the directory is provided to reconstruckt the fill path of any given file; key: path
- also removed broken and unused recursive get_file_list (via deep=True argument)
parent edad62c4
......@@ -101,17 +101,15 @@ class FSAjaxController(AbstractController):
@cherrypy.expose
@cherrypy.tools.ajax(encoded=True)
def filelist(self, path, deep=False, filefilter=None, reverse=False, watch_id=None):
def filelist(self, path, filefilter=None, reverse=False, watch_id=None):
self.release_session()
fs = self.get('fs')
self.release_database()
deep = self.convert(deep, bool)
reverse = self.convert(reverse, bool)
# get the files with the filter
return fs.get_file_list(path, deep=deep,
filter=filefilter,
return fs.get_file_list(path, filter=filefilter,
reverse=reverse, encode_json=True,
window_id=self.get('window_id'),
view_id=self.get('view_id'),
......
......@@ -34,6 +34,37 @@ if not HAVE_PIL:
logger = logging.getLogger(__name__)
def get_file_info(base, name):
root, ext = os.path.splitext(name)
info = {
'name': name,
'root': root,
'ext': ext
}
try:
fullpath = os.path.join(base, name)
stats = os.lstat(fullpath)
if stat.S_ISLNK(stats.st_mode):
realfullpath = os.path.realpath(fullpath)
info.update({
'symlink': True,
'realpath': realfullpath
})
if os.path.exists(realfullpath):
stats = os.stat(realfullpath)
info.update({
'size': stats.st_size,
'mtime': stats.st_mtime,
'type': 'd' if stat.S_ISDIR(stats.st_mode) else 'f'
})
except:
pass
return info
class FileSystem(object):
FILE_EXTENSIONS = ["png", "jpg", "jpeg", "bmp", "ps", "eps", "pdf",
......@@ -122,68 +153,36 @@ class FileSystem(object):
else:
return -1
def get_file_list(self, path, deep=False,
def get_file_list(self, path,
filter=None, reverse=False,
hide_hidden=True, encode_json=True,
window_id=None, view_id=None, watch_id=None):
# inline watch
if window_id and view_id and watch_id:
if self.watch(path, window_id, view_id, watch_id, filter, reverse):
if self.watch(path, window_id, view_id, watch_id, filter, reverse, hide_hidden):
pass
# return "" # don't fail atm since it would not be caught on the client side
# actual function
filter = re.compile(filter) if filter else None
filelist = []
path_expand = self.expand(path)
try:
for elem in os.listdir(path_expand):
# hide hidden files?
if elem.startswith('.') and hide_hidden:
continue
# excluded by filters?
if filter and bool(filter.search(elem)) != reverse:
continue
root, ext = os.path.splitext(elem)
info = {
'name': elem,
'root': root,
'ext': ext
}
fullpath = os.path.join(path_expand, elem)
stats = os.lstat(fullpath)
is_symlink = stat.S_ISLNK(stats.st_mode)
if is_symlink:
realfullpath = os.path.realpath(fullpath)
info.update({'symlink': True, 'realpath': realfullpath})
if os.path.exists(realfullpath):
stats = os.stat(realfullpath)
info.update({
'size': stats.st_size,
'mtime': stats.st_mtime,
'type': 'd' if stat.S_ISDIR(stats.st_mode) else 'f'
})
filelist.append(info)
if deep:
filelist.extend(self.get_file_list(fullpath, deep,
filter, reverse))
except:
pass
base = self.expand(path)
filelist = [get_file_info(base, name) for name in os.listdir(base) if
not (hide_hidden and name.startswith('.')) and
(not filter or bool(filter.search(name)) == reverse)]
filelist = [i for i in filelist if 'size' in i] # ignore failed file info (e.g. access error)
# Determine the parent
# parentpath = path_expand[:-1] if path_expand.endswith(os.sep) and
# path_expand != os.sep else path_expand
parentpath = os.path.dirname(path_expand)
data = {'filelist': filelist, 'parentpath': parentpath}
parentpath = os.path.dirname(base)
data = {
'filelist': filelist,
'parentpath': parentpath,
'path': base
}
if encode_json:
return json.dumps(data)
else:
return data
def get_suggestions(
self, path, length=1, append_hidden=True, encode_json=True):
suggestions = []
......@@ -546,13 +545,13 @@ class FileSystem(object):
else:
return self.get_file_content(path)
def watch(self, path, window_id, view_id, watch_id, pattern=None, reverse=False):
def watch(self, path, window_id, view_id, watch_id, pattern=None, reverse=False, hide_hidden=True):
# fail if there is no such fie
path = self.expand(path)
if not os.path.exists(path):
return "The file does not exist"
self.watchservice.subscribe((window_id, view_id, watch_id), path, pattern, reverse)
self.watchservice.subscribe((window_id, view_id, watch_id), path, pattern, reverse, hide_hidden)
return ""
def unwatch(self, window_id, view_id, watch_id=None):
......@@ -636,7 +635,7 @@ class WatchService(object):
self.thread = Thread(target=self._worker)
self.thread.start()
def subscribe(self, id, path, pattern=None, reverse=False):
def subscribe(self, id, path, pattern=None, reverse=False, hide_hidden=True):
if not path:
return self.unsubscribe(id)
......@@ -646,7 +645,7 @@ class WatchService(object):
if id not in self.subscribers:
WatchSubscriber(self, id)
self.subscribers[id].update(path, pattern, reverse)
self.subscribers[id].update(path, pattern, reverse, hide_hidden)
def unsubscribe(self, id):
with self.lock:
......@@ -699,6 +698,8 @@ class WatchSubscriber(object): # this should never be instanced manually
'change': [1.0,0.1],
'modify': [1.0,0.2]
}
MAX_INLINE_SUBJECTS = 10
MAX_SUBJECT_NAMES = 25
def __init__(self, service, id):
if not isinstance(service, WatchService):
......@@ -711,7 +712,9 @@ class WatchSubscriber(object): # this should never be instanced manually
self.watch = None
self.pattern = None
self.reverse = None
self.hide_hidden = None
self.event_buffer = {}
self.subject_buffer = {}
def destroy(self):
self.unbind()
......@@ -721,10 +724,19 @@ class WatchSubscriber(object): # this should never be instanced manually
del self.service
del self.watch
del self.event_buffer
del self.subject_buffer
def process(self, event, subject=""):
if self.watch.isdir and subject and self.pattern and bool(self.pattern.search(subject)) != self.reverse:
return
if self.watch.isdir and subject:
if self.hide_hidden and subject.startswith('.'):
return
if self.pattern and bool(self.pattern.search(subject)) != self.reverse:
return
if event not in self.subject_buffer:
self.subject_buffer[event] = []
if subject not in self.subject_buffer[event]:
self.subject_buffer[event].append(subject)
if event in WatchSubscriber.EVENT_DELAYS:
now = time()
......@@ -753,7 +765,16 @@ class WatchSubscriber(object): # this should never be instanced manually
'path': self.watch.path,
'watch_id': self.id[2]
}
if not self.watch.isdir:
if self.watch.isdir:
if event in self.subject_buffer and self.subject_buffer[event]:
subject_count = len(self.subject_buffer[event])
data['subject_count'] = subject_count
if subject_count <= WatchSubscriber.MAX_INLINE_SUBJECTS:
data['subject_infos'] = [get_file_info(self.watch.path, subject) for subject in self.subject_buffer[event]]
elif subject_count <= WatchSubscriber.MAX_SUBJECT_NAMES:
data['subject_names'] = self.subject_buffer[event]
self.subject_buffer[event] = []
else:
data['mtime'] = self.watch.mtime
vispa.remote.send_topic("extension.%s.socket.watch" % self.id[1], window_id=self.id[0], data=data)
elif len(self.id) == 2: # userid, workspaceid
......@@ -764,15 +785,19 @@ class WatchSubscriber(object): # this should never be instanced manually
elif hasattr(self.id, '__call__'):
self.id(event, self)
def update(self, path, pattern="", reverse=False):
def update(self, path, pattern="", reverse=False, hide_hidden=True):
self.bind(path)
if self.watch.isdir and pattern:
if not self.pattern or self.pattern.pattern != pattern:
self.pattern = re.compile(pattern)
self.reverse = reverse
self.subject_buffer = {event:[
subject for subject in subjects if bool(self.pattern.search(subject)) == self.reverse
] for event, subjects in self.subject_buffer.items()}
else:
self.pattern = None
self.reverse = None
self.hide_hidden = hide_hidden
def bind(self, path):
if self.watch:
......@@ -801,6 +826,8 @@ class WatchSubscriber(object): # this should never be instanced manually
self.watch = watch
if self not in watch.subscribers:
watch.subscribers.append(self)
self.subject_buffer = {}
def unbind(self):
if not self.watch:
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment