Merge lp:~diegosarmentero/ubuntuone-client/search-filter into lp:ubuntuone-client

Proposed by Diego Sarmentero
Status: Merged
Approved by: Diego Sarmentero
Approved revision: 1352
Merged at revision: 1350
Proposed branch: lp:~diegosarmentero/ubuntuone-client/search-filter
Merge into: lp:ubuntuone-client
Diff against target: 507 lines (+324/-1)
14 files modified
contrib/testing/testcase.py (+2/-0)
tests/platform/ipc/test_external_interface.py (+9/-0)
tests/platform/test_tools.py (+20/-0)
tests/syncdaemon/test_files_search.py (+87/-0)
tests/syncdaemon/test_fsm.py (+107/-0)
ubuntuone/platform/ipc/ipc_client.py (+4/-0)
ubuntuone/platform/ipc/linux.py (+6/-0)
ubuntuone/platform/ipc/perspective_broker.py (+5/-0)
ubuntuone/platform/tools/__init__.py (+5/-0)
ubuntuone/syncdaemon/files_search.py (+48/-0)
ubuntuone/syncdaemon/filesystem_manager.py (+17/-1)
ubuntuone/syncdaemon/interaction_interfaces.py (+6/-0)
ubuntuone/syncdaemon/main.py (+3/-0)
ubuntuone/syncdaemon/volume_manager.py (+5/-0)
To merge this branch: bzr merge lp:~diegosarmentero/ubuntuone-client/search-filter
Reviewer Review Type Date Requested Status
Mike McCracken (community) Approve
Roberto Alsina (community) Approve
Review via email: mp+130862@code.launchpad.net

Commit message

- Search and filter for u1 files (LP: #1056189, #1056197).

Description of the change

This will be used from control panel, instead of having the data of the files inside u1 duplicated there, and this will be fix the problems to keep that data up to date, and check if the files are already in the server (as the two bug reports describe).

To post a comment you must log in.
1348. By Diego Sarmentero

adding missing files

1349. By Diego Sarmentero

delete unnecesary fake

1350. By Diego Sarmentero

removing line for testing purposes

1351. By Diego Sarmentero

docstring fixed

Revision history for this message
Roberto Alsina (ralsina) :
review: Approve
1352. By Diego Sarmentero

fix string

Revision history for this message
Mike McCracken (mikemc) wrote :

I don't really agree with replacing spaces with '.+'.

The old share_links_search does a substring search in just the
basename, while this matches the whole path, which I think is a good
idea.

However, I was expecting this to also be substring search, but you are
replacing spaces in the search string with '.+', which means the
behavior is a little unexpected wrt. spaces in the search text.

Here's a test to be added to test_fsm.py in FSMSearchTestCase that shows what I mean:

    def test_get_paths_by_pattern_with_spaces(self):
        """Test that spaces in a pattern are handled as expected."""
        mdid = 'id'
        path = '/my/path/to/a/file/deep/in/the/woods/called/a file'
        mdobj = {'server_hash': 'asdqwe123'}
        mdid2 = 'id2'
        path2 = '/my/path/to/anythingyouwant-to-defile'
        mdobj2 = {'server_hash': 'asdqwe456'}
        self.fsm._idx_path = {path: mdid, path2: mdid2}
        self.fsm.fs = {mdid: mdobj, mdid2: mdobj2}
        expected = [path]
        # expectations
        paths = "(%s|%s)" % ('/my/path/', '/home/')
        pattern = paths + ".*/.*%s.*$"
        keywords = '.+'.join(re.escape('a file').split('\\ '))
        print "keywords:", keywords
        search = pattern % keywords
        result = self.fsm.get_paths_by_pattern(search)
        print "result from get_paths_by_pattern:", result
        self.assertEqual(result, expected)

I would expect "a file" to not match "anythingyouwant-to-defile". It
matches both:

Here's what that test prints:
keywords: a.+file
searching through /my/path/to/a/file/deep/in/the/woods/called/a file id
match with p= /my/path/to/a/file/deep/in/the/woods/called/a file
searching through /my/path/to/anythingyouwant-to-defile id2
match with p= /my/path/to/anythingyouwant-to-defile
result from get_paths_by_pattern: ['/my/path/to/a/file/deep/in/the/woods/called/a file', '/my/path/to/anythingyouwant-to-defile']
Traceback (most recent call last):
  File "/Users/mmccrack/Documents/Canonical/Source/test-improve-buildout/scripts/devsetup/parts/ubuntuone-client/tests/syncdaemon/test_fsm.py", line 4361, in test_get_paths_by_pattern_with_spaces
    self.assertEqual(result, expected)
  File "/Users/mmccrack/Documents/Canonical/Source/test-improve-buildout/scripts/devsetup/eggs/Twisted-11.1.0-py2.7-macosx-10.7-x86_64.egg/twisted/trial/unittest.py", line 270, in assertEqual
    % (msg, pformat(first), pformat(second)))
twisted.trial.unittest.FailTest: not equal:
a = ['/my/path/to/a/file/deep/in/the/woods/called/a file',
 '/my/path/to/anythingyouwant-to-defile']
b = ['/my/path/to/a/file/deep/in/the/woods/called/a file']

review: Needs Fixing
Revision history for this message
Mike McCracken (mikemc) wrote :

Hmm, my example test is kind of dumb, since it's pointing out something in the test, not in the method it's testing… I should probably have put it in test_files_search.py, but you get the point…

Revision history for this message
Mike McCracken (mikemc) wrote :

I've changed my mind.

For reference: I'm OK with matching fuzzy things, and I was mostly concerned with the order in which they appear, I wanted to be sure the least-surprising match showed up first.

However the only example I found where that might happen was if we have a search pattern with a space, and the files list has a pattern with no space that matches that pattern because of our fuzzy changes. In this case, though, when we sort the matching paths at the end of get_paths_by_pattern, the paths with spaces are sorted first anyway.

So even though using multiple regexes to control the order of matches is only a little slower in real terms, I can't think of a case in which it's necessary.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'contrib/testing/testcase.py'
2--- contrib/testing/testcase.py 2012-10-02 19:52:03 +0000
3+++ contrib/testing/testcase.py 2012-10-23 17:07:21 +0000
4@@ -50,6 +50,7 @@
5 config,
6 action_queue,
7 event_queue,
8+ files_search,
9 filesystem_manager as fs_manager,
10 interaction_interfaces,
11 interfaces,
12@@ -254,6 +255,7 @@
13
14 self.eventlog_listener = None
15 self.status_listener = FakeStatusListener()
16+ self.search = files_search.SearchFiles(self.fs)
17
18 def _connect_aq(self, _):
19 """Connect the fake action queue."""
20
21=== modified file 'tests/platform/ipc/test_external_interface.py'
22--- tests/platform/ipc/test_external_interface.py 2012-08-10 12:49:46 +0000
23+++ tests/platform/ipc/test_external_interface.py 2012-10-23 17:07:21 +0000
24@@ -275,6 +275,15 @@
25 in_signature='s', out_signature='a{ss}')
26
27 @defer.inlineCallbacks
28+ def test_search_files(self):
29+ """Test get_metadata."""
30+ result = ['path']
31+ yield self.assert_method_called(self.service.file_system,
32+ 'search_files', result, 'file')
33+ self.assert_remote_method('search_files',
34+ in_signature='s', out_signature='as')
35+
36+ @defer.inlineCallbacks
37 def test_get_metadata_by_node(self):
38 """Test get_metadata_by_node."""
39 result = {'node_id': 'test'}
40
41=== modified file 'tests/platform/test_tools.py'
42--- tests/platform/test_tools.py 2012-08-10 12:49:46 +0000
43+++ tests/platform/test_tools.py 2012-10-23 17:07:21 +0000
44@@ -203,6 +203,26 @@
45 self.assertEqual('node_id', result['node_id'])
46
47 @defer.inlineCallbacks
48+ def test_search_files(self):
49+ """Check that get_metadata works as expected."""
50+ mdid = 'id'
51+ path = os.path.join(self.root_dir, u'path/to/file_test')
52+ mdobj = {'server_hash': 'asdqwe123'}
53+ mdid2 = 'id2'
54+ path2 = os.path.join(self.root_dir, u'path/to/my_files')
55+ mdobj2 = {'server_hash': 'asdqwe456'}
56+ mdid3 = 'id3'
57+ path3 = u'/home2/to/my_files'
58+ mdobj3 = {'server_hash': 'asdqwe456'}
59+ self.fs._idx_path = {path: mdid, path2: mdid2, path3: mdid3}
60+ self.fs.fs = {mdid: mdobj, mdid2: mdobj2, mdid3: mdobj3}
61+
62+ result = yield self.tool.search_files('file')
63+ expected = [os.path.join(self.root_dir, 'path/to/file_test'),
64+ os.path.join(self.root_dir, 'path/to/my_files')]
65+ self.assertEqual(result, expected)
66+
67+ @defer.inlineCallbacks
68 def test_quit_when_running(self):
69 """Test the quit method when the daemon is running."""
70 self.patch(tools, 'is_already_running',
71
72=== added file 'tests/syncdaemon/test_files_search.py'
73--- tests/syncdaemon/test_files_search.py 1970-01-01 00:00:00 +0000
74+++ tests/syncdaemon/test_files_search.py 2012-10-23 17:07:21 +0000
75@@ -0,0 +1,87 @@
76+# -*- coding: utf-8 *-*
77+#
78+# Copyright 2012 Canonical Ltd.
79+#
80+# This program is free software: you can redistribute it and/or modify it
81+# under the terms of the GNU General Public License version 3, as published
82+# by the Free Software Foundation.
83+#
84+# This program is distributed in the hope that it will be useful, but
85+# WITHOUT ANY WARRANTY; without even the implied warranties of
86+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
87+# PURPOSE. See the GNU General Public License for more details.
88+#
89+# You should have received a copy of the GNU General Public License along
90+# with this program. If not, see <http://www.gnu.org/licenses/>.
91+#
92+# In addition, as a special exception, the copyright holders give
93+# permission to link the code of portions of this program with the
94+# OpenSSL library under certain conditions as described in each
95+# individual source file, and distribute linked combinations
96+# including the two.
97+# You must obey the GNU General Public License in all respects
98+# for all of the code used other than OpenSSL. If you modify
99+# file(s) with this exception, you may extend this exception to your
100+# version of the file(s), but you are not obligated to do so. If you
101+# do not wish to do so, delete this exception statement from your
102+# version. If you delete this exception statement from all source
103+# files in the program, then also delete it here.
104+"""Test the files search module."""
105+
106+from twisted.internet import defer
107+from twisted.trial.unittest import TestCase
108+
109+from ubuntuone.syncdaemon import files_search
110+
111+
112+class FakeVolumeManager(object):
113+ """Fake VolumeManager."""
114+
115+ def get_folders(self):
116+ """Return the u1 folders."""
117+ return ('/home/u1/', '/home/.local/udfs/', '/home/.local/shared',
118+ '/home/.local/shares')
119+
120+
121+class FakeFilesystemManager(object):
122+ """Fake FilesystemManager."""
123+
124+ def __init__(self):
125+ self.pattern = ''
126+ self.vm = FakeVolumeManager()
127+
128+ def get_paths_by_pattern(self, pattern):
129+ """Store the pattern received."""
130+ self.pattern = pattern
131+
132+
133+class FilesystemManagerTestCase(TestCase):
134+ """Test the SyncMenu."""
135+
136+ @defer.inlineCallbacks
137+ def setUp(self):
138+ yield super(FilesystemManagerTestCase, self).setUp()
139+ self.fsm = FakeFilesystemManager()
140+ self.search = files_search.SearchFiles(self.fsm)
141+ root, udf_dir, shared_dir, _ = self.fsm.vm.get_folders()
142+ paths = "(%s|%s|%s)" % (root, udf_dir, shared_dir)
143+ self.basic_pattern = paths + ".*/.*%s.*$"
144+
145+ def test_simple_string(self):
146+ """Check the pattern is the same string."""
147+ pattern = 'filename'
148+ self.search.search(pattern)
149+ expected = self.basic_pattern % pattern
150+ self.assertEqual(expected, self.fsm.pattern)
151+
152+ def test_escaped_string(self):
153+ """Check the pattern is the same string."""
154+ self.search.search('file_name')
155+ expected = self.basic_pattern % 'file\\_name'
156+ self.assertEqual(expected, self.fsm.pattern)
157+
158+ def test_string_with_space(self):
159+ """Check the pattern is the same string."""
160+ self.search.search('file name')
161+ expected = self.basic_pattern % 'file.+name'
162+ self.assertEqual(expected, self.fsm.pattern)
163
164=== modified file 'tests/syncdaemon/test_fsm.py'
165--- tests/syncdaemon/test_fsm.py 2012-08-08 13:21:13 +0000
166+++ tests/syncdaemon/test_fsm.py 2012-10-23 17:07:21 +0000
167@@ -32,6 +32,7 @@
168
169 import errno
170 import os
171+import re
172 import time
173
174 from mocker import MockerTestCase, ANY
175@@ -4299,3 +4300,109 @@
176 self.mocker.result(path)
177 self.mocker.replay()
178 self.assertRaises(KeyError, self.fsm.dir_content, path)
179+
180+
181+class FSMSearchTestCase(BaseTwistedTestCase):
182+ """Base test case for FSM."""
183+
184+ @defer.inlineCallbacks
185+ def setUp(self):
186+ """Setup the test."""
187+ yield super(FSMSearchTestCase, self).setUp()
188+ self.shares_dir = self.mktemp('shares')
189+ self.root_dir = self.mktemp('root')
190+ self.fsmdir = self.mktemp("fsmdir")
191+ self.partials_dir = self.mktemp("partials")
192+ self.tritcask_path = self.mktemp("tritcask")
193+
194+ self.db = Tritcask(self.tritcask_path)
195+ self.addCleanup(self.db.shutdown)
196+ self.fsm = FileSystemManager(self.fsmdir, self.partials_dir,
197+ FakeVolumeManager(self.root_dir), self.db)
198+
199+ def test_get_paths_by_pattern(self):
200+ """Check that we obtain the files that correspond to the filter."""
201+ mdid = 'id'
202+ path = '/my/path/to/file_test'
203+ mdobj = {'server_hash': 'asdqwe123'}
204+ mdid2 = 'id2'
205+ path2 = '/my/path/to/my_photos'
206+ mdobj2 = {'server_hash': 'asdqwe456'}
207+ self.fsm._idx_path = {path: mdid, path2: mdid2}
208+ self.fsm.fs = {mdid: mdobj, mdid2: mdobj2}
209+ expected = ['/my/path/to/file_test']
210+ # expectations
211+ paths = "(%s|%s)" % ('/my/path/', '/home/')
212+ pattern = paths + ".*/.*%s.*$"
213+ keywords = '.+'.join(re.escape('file').split('\\ '))
214+ search = pattern % keywords
215+ result = self.fsm.get_paths_by_pattern(search)
216+ self.assertEqual(result, expected)
217+
218+ def test_get_paths_by_pattern_valid_folders(self):
219+ """Search for the pattern in the valid folders."""
220+ mdid = 'id'
221+ path = '/my/path/to/file_test'
222+ mdobj = {'server_hash': 'asdqwe123'}
223+ mdid2 = 'id2'
224+ path2 = '/home/to/my_files'
225+ mdobj2 = {'server_hash': 'asdqwe456'}
226+ mdid3 = 'id3'
227+ path3 = '/home2/to/my_files'
228+ mdobj3 = {'server_hash': 'asdqwe456'}
229+ self.fsm._idx_path = {path: mdid, path2: mdid2, path3: mdid3}
230+ self.fsm.fs = {mdid: mdobj, mdid2: mdobj2, mdid3: mdobj3}
231+ expected = ['/home/to/my_files', '/my/path/to/file_test']
232+ # expectations
233+ paths = "(%s|%s)" % ('/my/path/', '/home/')
234+ pattern = paths + ".*/.*%s.*$"
235+ keywords = '.+'.join(re.escape('file').split('\\ '))
236+ search = pattern % keywords
237+ result = self.fsm.get_paths_by_pattern(search)
238+ self.assertEqual(result, expected)
239+
240+ def test_get_paths_by_pattern_not_in_server(self):
241+ """Check that we ignore the files that are not still in the server.."""
242+ mdid = 'id'
243+ path = '/my/path/to/file_test'
244+ mdobj = {'server_hash': 'asdqwe123'}
245+ mdid2 = 'id2'
246+ path2 = '/home/to/my_files'
247+ mdobj2 = {'server_hash': ''}
248+ mdid3 = 'id3'
249+ path3 = '/home2/to/my_files'
250+ mdobj3 = {'server_hash': 'asdqwe456'}
251+ self.fsm._idx_path = {path: mdid, path2: mdid2, path3: mdid3}
252+ self.fsm.fs = {mdid: mdobj, mdid2: mdobj2, mdid3: mdobj3}
253+ expected = ['/my/path/to/file_test']
254+ # expectations
255+ paths = "(%s|%s)" % ('/my/path/', '/home/')
256+ pattern = paths + ".*/.*%s.*$"
257+ keywords = '.+'.join(re.escape('file').split('\\ '))
258+ search = pattern % keywords
259+ result = self.fsm.get_paths_by_pattern(search)
260+ self.assertEqual(result, expected)
261+
262+ def test_get_paths_by_pattern_sorted_result(self):
263+ """Search that we obtained the paths sorted.."""
264+ mdid = 'id'
265+ path = '/my/path/to/file_test'
266+ mdobj = {'server_hash': 'asdqwe123'}
267+ mdid2 = 'id2'
268+ path2 = '/home/to/my_files'
269+ mdobj2 = {'server_hash': 'asdqwe456'}
270+ mdid3 = 'id3'
271+ path3 = '/home2/to/my_files'
272+ mdobj3 = {'server_hash': 'asdqwe456'}
273+ self.fsm._idx_path = {path: mdid, path2: mdid2, path3: mdid3}
274+ self.fsm.fs = {mdid: mdobj, mdid2: mdobj2, mdid3: mdobj3}
275+ expected = ['/my/path/to/file_test', '/home/to/my_files']
276+ # expectations
277+ paths = "(%s|%s)" % ('/my/path/', '/home/')
278+ pattern = paths + ".*/.*%s.*$"
279+ keywords = '.+'.join(re.escape('file').split('\\ '))
280+ search = pattern % keywords
281+ result = self.fsm.get_paths_by_pattern(search)
282+ self.assertNotEqual(result, expected)
283+ expected = sorted(expected)
284+ self.assertEqual(result, expected)
285\ No newline at end of file
286
287=== modified file 'ubuntuone/platform/ipc/ipc_client.py'
288--- ubuntuone/platform/ipc/ipc_client.py 2012-09-06 22:19:37 +0000
289+++ ubuntuone/platform/ipc/ipc_client.py 2012-10-23 17:07:21 +0000
290@@ -213,6 +213,10 @@
291 * int: bytes written
292 """
293
294+ @remote
295+ def search_files(self, name):
296+ """Returns a list of the files that contain name in the path."""
297+
298 @signal
299 def on_content_queue_changed(self):
300 """Emit ContentQueueChanged."""
301
302=== modified file 'ubuntuone/platform/ipc/linux.py'
303--- ubuntuone/platform/ipc/linux.py 2012-08-13 14:34:36 +0000
304+++ ubuntuone/platform/ipc/linux.py 2012-10-23 17:07:21 +0000
305@@ -411,6 +411,12 @@
306 """Return a list of dirty nodes."""
307 return self.service.file_system.get_dirty_nodes()
308
309+ @dbus.service.method(DBUS_IFACE_FS_NAME,
310+ in_signature='s', out_signature='as')
311+ def search_files(self, pattern):
312+ """Return the files (as a list) that contain pattern in the path."""
313+ return self.service.file_system.search_files(pattern)
314+
315
316 class Shares(DBusExposedObject):
317 """An interface to interact with shares."""
318
319=== modified file 'ubuntuone/platform/ipc/perspective_broker.py'
320--- ubuntuone/platform/ipc/perspective_broker.py 2012-09-06 22:19:37 +0000
321+++ ubuntuone/platform/ipc/perspective_broker.py 2012-10-23 17:07:21 +0000
322@@ -491,6 +491,7 @@
323 'get_metadata_by_node',
324 'get_metadata_and_quick_tree_synced',
325 'get_dirty_nodes',
326+ 'search_files',
327 ]
328
329 def get_metadata(self, path):
330@@ -515,6 +516,10 @@
331 """Return a list of dirty nodes."""
332 return self.service.file_system.get_dirty_nodes()
333
334+ def search_files(self, pattern):
335+ """Search for the occurrence of pattern in the files names."""
336+ return self.service.file_system.search_files(pattern)
337+
338
339 class Shares(IPCExposedObject):
340 """An interface to interact with shares."""
341
342=== modified file 'ubuntuone/platform/tools/__init__.py'
343--- ubuntuone/platform/tools/__init__.py 2012-10-05 12:44:28 +0000
344+++ ubuntuone/platform/tools/__init__.py 2012-10-23 17:07:21 +0000
345@@ -482,6 +482,11 @@
346 """Get metadata for 'path'."""
347 return self.proxy.call_method('file_system', 'get_metadata', path)
348
349+ @log_call(logger.debug)
350+ def search_files(self, pattern):
351+ """Get the files that matches the pattern."""
352+ return self.proxy.call_method('file_system', 'search_files', pattern)
353+
354 @defer.inlineCallbacks
355 @log_call(logger.debug)
356 def change_public_access(self, path, is_public):
357
358=== added file 'ubuntuone/syncdaemon/files_search.py'
359--- ubuntuone/syncdaemon/files_search.py 1970-01-01 00:00:00 +0000
360+++ ubuntuone/syncdaemon/files_search.py 2012-10-23 17:07:21 +0000
361@@ -0,0 +1,48 @@
362+# -*- coding: utf-8 -*-
363+#
364+# Copyright 2011-2012 Canonical Ltd.
365+#
366+# This program is free software: you can redistribute it and/or modify it
367+# under the terms of the GNU General Public License version 3, as published
368+# by the Free Software Foundation.
369+#
370+# This program is distributed in the hope that it will be useful, but
371+# WITHOUT ANY WARRANTY; without even the implied warranties of
372+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
373+# PURPOSE. See the GNU General Public License for more details.
374+#
375+# You should have received a copy of the GNU General Public License along
376+# with this program. If not, see <http://www.gnu.org/licenses/>.
377+#
378+# In addition, as a special exception, the copyright holders give
379+# permission to link the code of portions of this program with the
380+# OpenSSL library under certain conditions as described in each
381+# individual source file, and distribute linked combinations
382+# including the two.
383+# You must obey the GNU General Public License in all respects
384+# for all of the code used other than OpenSSL. If you modify
385+# file(s) with this exception, you may extend this exception to your
386+# version of the file(s), but you are not obligated to do so. If you
387+# do not wish to do so, delete this exception statement from your
388+# version. If you delete this exception statement from all source
389+# files in the program, then also delete it here.
390+"""Searchs for files inside the U1 folder."""
391+
392+import re
393+
394+
395+class SearchFiles(object):
396+ """Seearch for specific patterns in the U1 files names."""
397+
398+ def __init__(self, fs):
399+ self.fs = fs
400+ root, udf_dir, shared_dir, _ = self.fs.vm.get_folders()
401+ paths = "(%s|%s|%s)" % (root, udf_dir, shared_dir)
402+ self.pattern = paths + ".*/.*%s.*$"
403+
404+ def search(self, pattern=''):
405+ """Search for the files that contains name."""
406+ keywords = '.+'.join(re.escape(pattern).split('\\ '))
407+ search = self.pattern % keywords
408+ results = self.fs.get_paths_by_pattern(search)
409+ return results
410\ No newline at end of file
411
412=== modified file 'ubuntuone/syncdaemon/filesystem_manager.py'
413--- ubuntuone/syncdaemon/filesystem_manager.py 2012-08-08 13:21:13 +0000
414+++ ubuntuone/syncdaemon/filesystem_manager.py 2012-10-23 17:07:21 +0000
415@@ -33,6 +33,7 @@
416 from __future__ import with_statement
417
418 import os
419+import re
420 import time
421 import functools
422 import itertools
423@@ -324,7 +325,7 @@
424
425 def __init__(self, data_dir, partials_dir, vm, db):
426 if not isinstance(data_dir, basestring):
427- raise TypeError("data_dir should be a string instead of %s" % \
428+ raise TypeError("data_dir should be a string instead of %s" %
429 type(data_dir))
430 fsmdir = os.path.join(data_dir, 'fsm')
431 self._trash_dir = os.path.join(data_dir, 'trash')
432@@ -1363,6 +1364,21 @@
433
434 return all_paths
435
436+ def get_paths_by_pattern(self, search):
437+ """Return a list of paths that match the pattern."""
438+ pattern = re.compile(search)
439+
440+ def _get_matching():
441+ """Find the paths that match"""
442+ for p, m in self._idx_path.iteritems():
443+ #basename = os.path.basename(p)
444+ if pattern.match(p):
445+ mdobj = self.fs[m]
446+ if mdobj["server_hash"]:
447+ yield p
448+
449+ return sorted(_get_matching())
450+
451 def delete_to_trash(self, mdid, parent_id):
452 """Move the node to the trash."""
453 mdobj = self.fs[mdid]
454
455=== modified file 'ubuntuone/syncdaemon/interaction_interfaces.py'
456--- ubuntuone/syncdaemon/interaction_interfaces.py 2012-09-14 22:46:21 +0000
457+++ ubuntuone/syncdaemon/interaction_interfaces.py 2012-10-23 17:07:21 +0000
458@@ -401,6 +401,12 @@
459 dirty_nodes.append(self._mdobj_dict(mdobj))
460 return dirty_nodes
461
462+ @unicode_to_bytes
463+ @log_call(logger.debug)
464+ def search_files(self, pattern):
465+ """Search for the occurrence of pattern in the files names."""
466+ return self.main.search.search(pattern)
467+
468
469 class SyncdaemonShares(SyncdaemonObject):
470 """An interface to interact with shares."""
471
472=== modified file 'ubuntuone/syncdaemon/main.py'
473--- ubuntuone/syncdaemon/main.py 2012-09-20 12:56:32 +0000
474+++ ubuntuone/syncdaemon/main.py 2012-10-23 17:07:21 +0000
475@@ -39,6 +39,7 @@
476 action_queue,
477 config,
478 event_queue,
479+ files_search,
480 filesystem_manager,
481 hash_queue,
482 events_nanny,
483@@ -156,6 +157,8 @@
484 self.mark = task.LoopingCall(self.log_mark)
485 self.mark.start(mark_interval)
486
487+ self.search = files_search.SearchFiles(self.fs)
488+
489 def start_event_logger(self):
490 """Start the event logger if it's available for this platform."""
491 self.eventlog_listener = event_logging.get_listener(self.fs, self.vm)
492
493=== modified file 'ubuntuone/syncdaemon/volume_manager.py'
494--- ubuntuone/syncdaemon/volume_manager.py 2012-08-06 19:18:57 +0000
495+++ ubuntuone/syncdaemon/volume_manager.py 2012-10-23 17:07:21 +0000
496@@ -506,6 +506,11 @@
497 """Request the list of volumes to the server."""
498 self.m.action_q.list_volumes()
499
500+ def get_folders(self):
501+ """Get the list of folders where the data is stored."""
502+ return (self.m.root_dir, self._udfs_dir, self._shared_dir,
503+ self._shares_dir)
504+
505 def handle_AQ_LIST_VOLUMES(self, volumes):
506 """Handle AQ_LIST_VOLUMES event."""
507 self.log.debug('handle_AQ_LIST_VOLUMES: handling volumes list.')

Subscribers

People subscribed via source and target branches