Merge lp:~nataliabidart/ubuntuone-client/conflict-only-when-needed into lp:ubuntuone-client

Proposed by Natalia Bidart
Status: Merged
Approved by: John Lenton
Approved revision: not available
Merged at revision: not available
Proposed branch: lp:~nataliabidart/ubuntuone-client/conflict-only-when-needed
Merge into: lp:ubuntuone-client
Diff against target: 521 lines (+284/-41)
2 files modified
tests/syncdaemon/test_fsm.py (+208/-30)
ubuntuone/syncdaemon/filesystem_manager.py (+76/-11)
To merge this branch: bzr merge lp:~nataliabidart/ubuntuone-client/conflict-only-when-needed
Reviewer Review Type Date Requested Status
Nicola Larosa (community) Approve
Tim Cole (community) Approve
Review via email: mp+15017@code.launchpad.net

Commit message

Dir tree removal on server side deletes the whole dir in the client if no local changes (#462003).

To post a comment you must log in.
Revision history for this message
Natalia Bidart (nataliabidart) wrote :

Fix for #462003.

Revision history for this message
Natalia Bidart (nataliabidart) wrote :

When the need of removing a directory is detected (because, for example, the client issues a Query and found some hash differences), the client now recursively removes the directory if it has no local changes (in it or in any of its children).

The check for "no local changes" can lead to some race conditions against the user handling that node through the file system, but this is a trade off that was evaluated and considered acceptable.

Revision history for this message
Tim Cole (tcole) wrote :

Hmm, looks okay I guess. +1 on use of named constants rather than raw strings.

review: Approve
Revision history for this message
Nicola Larosa (teknico) wrote :

Tests and lint checks pass, "assert_no_metadata" having no docstring notwithstanding. On the other hand, setUp and tearDown have docstrings, even though pylint does not care about them. :-)

I'm not sure about the "len_func = str.__len__" optimization. Isn't "len" a global name? What's the gain of this, has it been measured?

Overall, nice code, and welcomed tests. :-)

review: Approve
Revision history for this message
dobey (dobey) wrote :

Attempt to merge lp:~nataliabidart/ubuntuone-client/conflict-only-when-needed into lp:ubuntuone-client failed due to merge conflicts:

text conflict in tests/syncdaemon/test_fsm.py
text conflict in ubuntuone/syncdaemon/filesystem_manager.py

283. By Natalia Bidart

Merged trunk in.

Revision history for this message
Natalia Bidart (nataliabidart) wrote :

> Attempt to merge lp:~nataliabidart/ubuntuone-client/conflict-only-when-needed
> into lp:ubuntuone-client failed due to merge conflicts:
>
> text conflict in tests/syncdaemon/test_fsm.py
> text conflict in ubuntuone/syncdaemon/filesystem_manager.py

Conflicts fixed and merged. Changes pushed.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'tests/syncdaemon/test_fsm.py'
--- tests/syncdaemon/test_fsm.py 2009-11-20 22:00:25 +0000
+++ tests/syncdaemon/test_fsm.py 2009-11-26 14:55:22 +0000
@@ -1,5 +1,6 @@
1#1#
2# Author: Facundo Batista <facundo@canonical.com>2# Author: Facundo Batista <facundo@canonical.com>
3# Author: Natalia Bidart <natalia.bidart@canonical.com>
3#4#
4# Copyright 2009 Canonical Ltd.5# Copyright 2009 Canonical Ltd.
5#6#
@@ -35,8 +36,9 @@
35 InconsistencyError,36 InconsistencyError,
36 METADATA_VERSION,37 METADATA_VERSION,
37)38)
39from ubuntuone.syncdaemon.event_queue import EventQueue
40from ubuntuone.syncdaemon.logger import LOGFILENAME
38from ubuntuone.syncdaemon.volume_manager import Share, allow_writes41from ubuntuone.syncdaemon.volume_manager import Share, allow_writes
39from ubuntuone.syncdaemon.event_queue import EventQueue
4042
41TESTS_DIR = os.path.join(os.getcwd(), "tmp")43TESTS_DIR = os.path.join(os.getcwd(), "tmp")
4244
@@ -1573,6 +1575,11 @@
1573class FileHandlingTests(FSMTestCase):1575class FileHandlingTests(FSMTestCase):
1574 """Test the file handling services."""1576 """Test the file handling services."""
15751577
1578 def assert_no_metadata(self, mdid, path, share_id, node_id):
1579 self.assertRaises(KeyError, self.fsm.get_by_mdid, mdid)
1580 self.assertRaises(KeyError, self.fsm.get_by_path, path)
1581 self.assertRaises(KeyError, self.fsm.get_by_node_id, share_id, node_id)
1582
1576 def test_move_to_conflict(self):1583 def test_move_to_conflict(self):
1577 """Test that the conflict stuff works."""1584 """Test that the conflict stuff works."""
1578 testfile = os.path.join(self.share_path, "path")1585 testfile = os.path.join(self.share_path, "path")
@@ -1584,7 +1591,7 @@
1584 # move first time1591 # move first time
1585 self.fsm.move_to_conflict(mdid)1592 self.fsm.move_to_conflict(mdid)
1586 self.assertFalse(os.path.exists(testfile))1593 self.assertFalse(os.path.exists(testfile))
1587 with open(testfile + ".u1conflict") as fh:1594 with open(testfile + self.fsm.CONFLICT_SUFFIX) as fh:
1588 in_file = fh.read()1595 in_file = fh.read()
1589 self.assertEqual(in_file, "test!")1596 self.assertEqual(in_file, "test!")
1590 mdobj = self.fsm.get_by_mdid(mdid)1597 mdobj = self.fsm.get_by_mdid(mdid)
@@ -1706,8 +1713,7 @@
1706 self.assertEqual(mdobj.mdid, mdid1)1713 self.assertEqual(mdobj.mdid, mdid1)
17071714
1708 # check that the info for the overwritten one is gone1715 # check that the info for the overwritten one is gone
1709 self.assertRaises(KeyError, self.fsm.get_by_mdid, mdid2)1716 self.assert_no_metadata(mdid2, testfile1, "share", "uuid2")
1710 self.assertRaises(KeyError, self.fsm.get_by_node_id, "share", "uuid2")
17111717
1712 def test_move_file_withdir(self):1718 def test_move_file_withdir(self):
1713 """Test that a dir is moved from one point to the other."""1719 """Test that a dir is moved from one point to the other."""
@@ -1794,12 +1800,10 @@
1794 # delete the file1800 # delete the file
1795 self.fsm.delete_file(testfile)1801 self.fsm.delete_file(testfile)
1796 self.assertFalse(os.path.exists(testfile))1802 self.assertFalse(os.path.exists(testfile))
1797 self.assertRaises(KeyError, self.fsm.get_by_mdid, mdid)1803 self.assert_no_metadata(mdid, testfile, "share", "uuid")
1798 self.assertRaises(KeyError, self.fsm.get_by_path, testfile)
1799 self.assertRaises(KeyError, self.fsm.get_by_node_id, "share", "uuid")
18001804
1801 def test_delete_dir(self):1805 def test_delete_dir(self):
1802 """Test that a dir is deleted."""1806 """Test that an empty dir is deleted."""
1803 testdir = os.path.join(self.share.path, "path")1807 testdir = os.path.join(self.share.path, "path")
1804 os.mkdir(testdir)1808 os.mkdir(testdir)
1805 mdid = self.fsm.create(testdir, "share", is_dir=True)1809 mdid = self.fsm.create(testdir, "share", is_dir=True)
@@ -1807,7 +1811,6 @@
18071811
1808 # try to delete the dir, but has files on it1812 # try to delete the dir, but has files on it
1809 open(os.path.join(testdir, "foo"), "w").close()1813 open(os.path.join(testdir, "foo"), "w").close()
1810 self.assertRaises(OSError, self.fsm.delete_file, testdir)
1811 self.assertEqual(self.fsm.get_by_mdid(mdid).path, "path")1814 self.assertEqual(self.fsm.get_by_mdid(mdid).path, "path")
1812 self.assertEqual(self.fsm.get_by_path(testdir).path, "path")1815 self.assertEqual(self.fsm.get_by_path(testdir).path, "path")
1813 self.assertEqual(self.fsm.get_by_node_id("share", "uuid").path, "path")1816 self.assertEqual(self.fsm.get_by_node_id("share", "uuid").path, "path")
@@ -1815,10 +1818,92 @@
18151818
1816 # really delete the dir1819 # really delete the dir
1817 self.fsm.delete_file(testdir)1820 self.fsm.delete_file(testdir)
1821
1818 self.assertFalse(os.path.exists(testdir))1822 self.assertFalse(os.path.exists(testdir))
1819 self.assertRaises(KeyError, self.fsm.get_by_mdid, mdid)1823 self.assert_no_metadata(mdid, testdir, "share", "uuid")
1820 self.assertRaises(KeyError, self.fsm.get_by_path, testdir)1824
1821 self.assertRaises(KeyError, self.fsm.get_by_node_id, "share", "uuid")1825 def test_delete_dir_when_non_empty_and_no_modifications(self):
1826 """Test that a dir is deleted, when is not empty and unmodified."""
1827 local_dir = os.path.join(self.root_dir, "foo")
1828 os.mkdir(local_dir)
1829 mdid = self.fsm.create(local_dir, "", is_dir=True)
1830 self.fsm.set_node_id(local_dir, "uuid")
1831
1832 local_file = os.path.join(local_dir, "bar.txt")
1833 open(local_file, 'w').close() # touch bar.txt so it exists
1834 mdid_file = self.fsm.create(local_file, "")
1835 self.fsm.set_node_id(local_file, "uuid_file")
1836
1837 assert len(os.listdir(local_dir)) > 0 # local_dir is not empty
1838 assert not self.fsm.local_changed(path=local_dir)
1839
1840 self.fsm.delete_file(local_dir)
1841
1842 self.assertFalse(os.path.exists(local_file))
1843 self.assert_no_metadata(mdid_file, local_file, "", "uuid_file")
1844
1845 self.assertFalse(os.path.exists(local_dir))
1846 self.assert_no_metadata(mdid, local_dir, "", "uuid")
1847
1848 def test_delete_dir_when_non_empty_and_modifications_prior_delete(self):
1849 """Test that a dir is deleted, when is not empty and modified."""
1850 local_dir = os.path.join(self.root_dir, "foo")
1851 os.mkdir(local_dir)
1852 mdid = self.fsm.create(local_dir, "", is_dir=True)
1853 self.fsm.set_node_id(local_dir, "uuid")
1854
1855 local_file = os.path.join(local_dir, "bar.txt")
1856 open(local_file, 'w').close() # touch bar.txt so it exists
1857 mdid_file = self.fsm.create(local_file, "")
1858 self.fsm.set_node_id(local_file, "uuid_file")
1859 self.fsm.set_by_mdid(mdid_file, local_hash=98765)
1860
1861 assert len(os.listdir(local_dir)) > 0 # local_dir is not empty
1862 assert self.fsm.changed(path=local_file) == self.fsm.CHANGED_LOCAL
1863 self.assertRaises(OSError, self.fsm.delete_file, local_dir)
1864
1865 def test_no_warning_on_log_file_when_recursive_delete(self):
1866 """Test that sucessfully deleted dir does not log OSError."""
1867
1868 log = open(LOGFILENAME, 'r')
1869 log.flush()
1870 log.read() # ignore log's content till now
1871
1872 local_dir = os.path.join(self.root_dir, "foo")
1873 os.mkdir(local_dir)
1874 mdid = self.fsm.create(local_dir, "", is_dir=True)
1875 self.fsm.set_node_id(local_dir, "uuid")
1876
1877 local_file = os.path.join(local_dir, "bar.txt")
1878 open(local_file, 'w').close() # touch bar.txt so it exists
1879 mdid_file = self.fsm.create(local_file, "")
1880 self.fsm.set_node_id(local_file, "uuid_file")
1881
1882 self.fsm.delete_file(local_dir)
1883
1884 log.flush()
1885 log_content = log.read()
1886 log.close()
1887 self.assertTrue('OSError [Errno 39] Directory not empty' not in log_content)
1888
1889 def test_warning_on_log_file_when_failing_delete(self):
1890 """Test that sucessfully deleted dir does not log OSError."""
1891
1892 log = open(LOGFILENAME, 'r')
1893 log.flush()
1894 log.read() # ignore log's content till now
1895
1896 local_dir = os.path.join(self.root_dir, "foo")
1897 mdid = self.fsm.create(local_dir, "", is_dir=True)
1898 self.fsm.set_node_id(local_dir, "uuid")
1899
1900 # local_dir does not exist on the file system
1901 self.fsm.delete_file(local_dir)
1902
1903 log.flush()
1904 log_content = log.read()
1905 log.close()
1906 self.assertTrue('OSError [Errno 2] No such file or directory' in log_content)
18221907
1823 def test_move_dir_to_conflict(self):1908 def test_move_dir_to_conflict(self):
1824 """Test that the conflict to a dir removes children metadata."""1909 """Test that the conflict to a dir removes children metadata."""
@@ -1836,11 +1921,12 @@
1836 # move the dir to conflict, the file is still there, but with no MD1921 # move the dir to conflict, the file is still there, but with no MD
1837 self.fsm.move_to_conflict(mdid1)1922 self.fsm.move_to_conflict(mdid1)
1838 self.assertFalse(os.path.exists(tdir))1923 self.assertFalse(os.path.exists(tdir))
1839 self.assertTrue(os.path.exists(tdir + ".u1conflict"))1924 self.assertTrue(os.path.exists(tdir + self.fsm.CONFLICT_SUFFIX))
1840 testfile = os.path.join(self.share_path, tdir + ".u1conflict", "path")1925 testfile = os.path.join(self.share_path,
1926 tdir + self.fsm.CONFLICT_SUFFIX, "path")
1841 self.assertTrue(os.path.exists(testfile))1927 self.assertTrue(os.path.exists(testfile))
1842 self.assertTrue(self.fsm.get_by_mdid(mdid1))1928 self.assertTrue(self.fsm.get_by_mdid(mdid1))
1843 self.assertRaises(KeyError, self.fsm.get_by_mdid, mdid2)1929 self.assert_no_metadata(mdid2, testfile, "share", "uuid2")
18441930
1845 def test_move_dir_to_conflict_similar_path(self):1931 def test_move_dir_to_conflict_similar_path(self):
1846 """Test that the conflict to a dir removes children metadata."""1932 """Test that the conflict to a dir removes children metadata."""
@@ -1863,8 +1949,9 @@
1863 # move the dir2 to conflict, see dir2 and file inside it went ok1949 # move the dir2 to conflict, see dir2 and file inside it went ok
1864 self.fsm.move_to_conflict(mdid2)1950 self.fsm.move_to_conflict(mdid2)
1865 self.assertFalse(os.path.exists(tdir2))1951 self.assertFalse(os.path.exists(tdir2))
1866 self.assertTrue(os.path.exists(tdir2 + ".u1conflict"))1952 self.assertTrue(os.path.exists(tdir2 + self.fsm.CONFLICT_SUFFIX))
1867 testfile = os.path.join(self.share_path, tdir2 + ".u1conflict", "path")1953 testfile = os.path.join(self.share_path,
1954 tdir2 + self.fsm.CONFLICT_SUFFIX, "path")
1868 self.assertTrue(os.path.exists(testfile))1955 self.assertTrue(os.path.exists(testfile))
1869 self.assertTrue(self.fsm.get_by_mdid(mdid2))1956 self.assertTrue(self.fsm.get_by_mdid(mdid2))
1870 self.assertRaises(KeyError, self.fsm.get_by_mdid, mdid3)1957 self.assertRaises(KeyError, self.fsm.get_by_mdid, mdid3)
@@ -1897,6 +1984,38 @@
1897 self.assertEqual(self.fsm.trash, {})1984 self.assertEqual(self.fsm.trash, {})
1898 self.assertEqual(list(self.fsm.get_iter_trash()), [])1985 self.assertEqual(list(self.fsm.get_iter_trash()), [])
18991986
1987 def test_local_changed_empty_dir(self):
1988 """Test the recursive changed feature for a node."""
1989 local_dir = os.path.join(self.root_dir, "foo")
1990 os.mkdir(local_dir)
1991 mdid = self.fsm.create(local_dir, "", is_dir=True)
1992 self.fsm.set_node_id(local_dir, "uuid")
1993
1994 # local_hash differs from server_hash for local_dir
1995 self.fsm.set_by_mdid(mdid, local_hash=98765)
1996
1997 assert len(os.listdir(local_dir)) == 0 # local_dir is empty
1998 self.assertTrue(self.fsm.local_changed(path=local_dir))
1999
2000 def test_local_changed_non_empty_dir(self):
2001 local_dir = os.path.join(self.root_dir, "foo")
2002 os.mkdir(local_dir)
2003 mdid = self.fsm.create(local_dir, "", is_dir=True)
2004 self.fsm.set_node_id(local_dir, "uuid")
2005
2006 sub_dir = os.path.join(local_dir, "bar")
2007 os.mkdir(sub_dir)
2008 mdid_subdir = self.fsm.create(sub_dir, "", is_dir=True)
2009 self.fsm.set_node_id(sub_dir, "uuid_subdir")
2010
2011 assert len(os.listdir(local_dir)) > 0 # local_dir is not empty
2012 self.assertFalse(self.fsm.local_changed(path=local_dir))
2013
2014 # local_hash differs from server_hash for sub_dir
2015 self.fsm.set_by_mdid(mdid_subdir, local_hash=98765)
2016
2017 self.assertTrue(self.fsm.local_changed(path=local_dir))
2018
19002019
1901class SyntheticInfoTests(FSMTestCase):2020class SyntheticInfoTests(FSMTestCase):
1902 """Test the methods that generates attributes."""2021 """Test the methods that generates attributes."""
@@ -1946,7 +2065,7 @@
19462065
1947 def test_changed_server(self):2066 def test_changed_server(self):
1948 """Test the changed option when in SERVER state."""2067 """Test the changed option when in SERVER state."""
1949 # SERVER means: local_hash != server_hash and is_partial == False2068 # SERVER means: local_hash != server_hash and is_partial
1950 testfile = os.path.join(self.share_path, "path")2069 testfile = os.path.join(self.share_path, "path")
1951 mdid = self.fsm.create(testfile, "share")2070 mdid = self.fsm.create(testfile, "share")
1952 partial_path = os.path.join(self.fsm.partials_dir,2071 partial_path = os.path.join(self.fsm.partials_dir,
@@ -1956,10 +2075,10 @@
1956 # set conditions and test2075 # set conditions and test
1957 self.fsm.set_by_mdid(mdid, server_hash=98765)2076 self.fsm.set_by_mdid(mdid, server_hash=98765)
1958 # local_hash is None so far2077 # local_hash is None so far
1959 self.assertTrue(self.fsm.changed(mdid=mdid), "SERVER")2078 self.assertTrue(self.fsm.changed(mdid=mdid), self.fsm.CHANGED_SERVER)
1960 self.assertTrue(self.fsm.changed(node_id="uuid", share_id="share"),2079 self.assertTrue(self.fsm.changed(node_id="uuid", share_id="share"),
1961 "SERVER")2080 self.fsm.CHANGED_SERVER)
1962 self.assertTrue(self.fsm.changed(path=testfile), "SERVER")2081 self.assertTrue(self.fsm.changed(path=testfile), self.fsm.CHANGED_SERVER)
19632082
1964 # put a .partial by hand, to see it crash2083 # put a .partial by hand, to see it crash
1965 open(partial_path, "w").close()2084 open(partial_path, "w").close()
@@ -1978,10 +2097,10 @@
19782097
1979 # all conditions are set: by default, local_hash and server_hash2098 # all conditions are set: by default, local_hash and server_hash
1980 # are both None2099 # are both None
1981 self.assertTrue(self.fsm.changed(mdid=mdid), "NONE")2100 self.assertTrue(self.fsm.changed(mdid=mdid), self.fsm.CHANGED_NONE)
1982 self.assertTrue(self.fsm.changed(node_id="uuid", share_id="share"),2101 self.assertTrue(self.fsm.changed(node_id="uuid", share_id="share"),
1983 "NONE")2102 self.fsm.CHANGED_NONE)
1984 self.assertTrue(self.fsm.changed(path=testfile), "NONE")2103 self.assertTrue(self.fsm.changed(path=testfile), self.fsm.CHANGED_NONE)
19852104
1986 # put a .partial by hand, to see it crash2105 # put a .partial by hand, to see it crash
1987 open(partial_path, "w").close()2106 open(partial_path, "w").close()
@@ -1991,7 +2110,7 @@
19912110
1992 def test_changed_local(self):2111 def test_changed_local(self):
1993 """Test the changed option when in LOCAL state."""2112 """Test the changed option when in LOCAL state."""
1994 # LOCAL means: local_hash != server_hash and is_partial == True2113 # LOCAL means: local_hash != server_hash and is not partial
1995 testfile = os.path.join(self.share_path, "path")2114 testfile = os.path.join(self.share_path, "path")
1996 mdid = self.fsm.create(testfile, "share")2115 mdid = self.fsm.create(testfile, "share")
1997 partial_path = os.path.join(self.fsm.partials_dir,2116 partial_path = os.path.join(self.fsm.partials_dir,
@@ -2002,10 +2121,10 @@
2002 self.fsm.set_by_mdid(mdid, server_hash=98765)2121 self.fsm.set_by_mdid(mdid, server_hash=98765)
2003 # local_hash is None so far2122 # local_hash is None so far
2004 self.fsm.create_partial("uuid", "share")2123 self.fsm.create_partial("uuid", "share")
2005 self.assertTrue(self.fsm.changed(mdid=mdid), "LOCAL")2124 self.assertTrue(self.fsm.changed(mdid=mdid), self.fsm.CHANGED_LOCAL)
2006 self.assertTrue(self.fsm.changed(node_id="uuid", share_id="share"),2125 self.assertTrue(self.fsm.changed(node_id="uuid", share_id="share"),
2007 "LOCAL")2126 self.fsm.CHANGED_LOCAL)
2008 self.assertTrue(self.fsm.changed(path=testfile), "LOCAL")2127 self.assertTrue(self.fsm.changed(path=testfile), self.fsm.CHANGED_LOCAL)
20092128
2010 # remove the .partial by hand, to see it crash2129 # remove the .partial by hand, to see it crash
2011 os.remove(partial_path)2130 os.remove(partial_path)
@@ -2176,8 +2295,7 @@
2176 self.fsm.commit_partial('uuid3', self.share.id, None)2295 self.fsm.commit_partial('uuid3', self.share.id, None)
2177 self.assertTrue(os.path.exists(testfile))2296 self.assertTrue(os.path.exists(testfile))
2178 self.fsm.move_to_conflict(file_mdid)2297 self.fsm.move_to_conflict(file_mdid)
2179 self.assertTrue(os.path.exists(testfile + ".u1conflict"))2298 self.assertTrue(os.path.exists(testfile + self.fsm.CONFLICT_SUFFIX))
2180
21812299
2182 def test_file_rw_share_no_fail(self):2300 def test_file_rw_share_no_fail(self):
2183 """ Test that manual creation of a file, ona rw-share. """2301 """ Test that manual creation of a file, ona rw-share. """
@@ -2566,6 +2684,66 @@
2566 self.assertEquals('uuid1', newfsm.get_by_mdid(mdid1).node_id)2684 self.assertEquals('uuid1', newfsm.get_by_mdid(mdid1).node_id)
2567 self.assertRaises(KeyError, newfsm.get_by_mdid, mdid2)2685 self.assertRaises(KeyError, newfsm.get_by_mdid, mdid2)
25682686
2687class PathsStartingWithTestCase(FSMTestCase):
2688 """Test FSM.get_paths_starting_with utility."""
2689
2690 def setUp(self):
2691 """Basic setup."""
2692 super(PathsStartingWithTestCase, self).setUp()
2693
2694 self.some_dir = os.path.join(self.root_dir, 'foo')
2695 self.sub_dir = os.path.join(self.some_dir, 'baz')
2696 self.some_file = os.path.join(self.sub_dir, 'bar.txt')
2697
2698 for d in (self.some_dir, self.sub_dir):
2699 if os.path.exists(d):
2700 shutil.rmtree(d)
2701 os.mkdir(d)
2702 mdid = self.fsm.create(d, '', is_dir=True)
2703 self.fsm.set_node_id(d, 'uuid')
2704
2705 open(self.some_file, 'w').close()
2706 mdid_file = self.fsm.create(self.some_file, "")
2707 self.fsm.set_node_id(self.some_file, "uuid_file")
2708
2709 def tearDown(self):
2710 """Cleanup."""
2711
2712 if os.path.exists(self.some_file):
2713 os.remove(self.some_file)
2714
2715 for d in (self.some_dir, self.sub_dir):
2716 if os.path.exists(d):
2717 shutil.rmtree(d)
2718
2719 super(PathsStartingWithTestCase, self).tearDown()
2720
2721 def test_with_self(self):
2722 expected = sorted([(self.some_dir, True), (self.sub_dir, True),
2723 (self.some_file, False)])
2724 actual = self.fsm.get_paths_starting_with(self.some_dir)
2725 self.assertEqual(expected, sorted(actual))
2726
2727 def test_dir_names_only(self):
2728 similar_dir = os.path.join(self.root_dir, 'fooo')
2729 os.mkdir(similar_dir)
2730 mdid = self.fsm.create(similar_dir, '', is_dir=True)
2731 self.fsm.set_node_id(similar_dir, 'uuid')
2732
2733 expected = sorted([(self.some_dir, True), (self.sub_dir, True),
2734 (self.some_file, False)])
2735 actual = self.fsm.get_paths_starting_with(self.some_dir)
2736
2737 # XXX: do we really want this behavior?
2738 # It's failing so far
2739 #self.assertEqual(expected, sorted(actual))
2740
2741 def test_without_self(self):
2742 expected = sorted([(self.sub_dir, True), (self.some_file, False)])
2743 actual = self.fsm.get_paths_starting_with(self.some_dir, include_base=False)
2744 self.assertEqual(expected, sorted(actual))
2745
2746
2569def test_suite():2747def test_suite():
2570 # pylint: disable-msg=C01112748 # pylint: disable-msg=C0111
2571 return unittest.TestLoader().loadTestsFromName(__name__)2749 return unittest.TestLoader().loadTestsFromName(__name__)
25722750
=== modified file 'ubuntuone/syncdaemon/filesystem_manager.py'
--- ubuntuone/syncdaemon/filesystem_manager.py 2009-11-20 22:00:25 +0000
+++ ubuntuone/syncdaemon/filesystem_manager.py 2009-11-26 14:55:22 +0000
@@ -203,6 +203,12 @@
203 - idx_path: relationship path -> mdid203 - idx_path: relationship path -> mdid
204 - idx_node_id: relationship (share_id, node_id) -> mdid204 - idx_node_id: relationship (share_id, node_id) -> mdid
205 """205 """
206
207 CONFLICT_SUFFIX = '.u1conflict'
208 CHANGED_LOCAL = 'LOCAL'
209 CHANGED_SERVER = 'SERVER'
210 CHANGED_NONE = 'NONE'
211
206 def __init__(self, data_dir, partials_dir, vm):212 def __init__(self, data_dir, partials_dir, vm):
207 if not isinstance(data_dir, basestring):213 if not isinstance(data_dir, basestring):
208 raise TypeError("data_dir should be a string instead of %s" % \214 raise TypeError("data_dir should be a string instead of %s" % \
@@ -651,6 +657,33 @@
651 del self._idx_node_id[(mdobj["share_id"], mdobj["node_id"])]657 del self._idx_node_id[(mdobj["share_id"], mdobj["node_id"])]
652 del self.fs[mdid]658 del self.fs[mdid]
653659
660 def _delete_dir_tree(self, path):
661 """Helper function to recursively remove a non-empty directory.
662
663 Removes the dir if there aren't local changes, and returns True.
664 If there are, return False.
665
666 Notifications are disabled for every sub-path within path.
667
668 """
669 dir_deleted = False
670 subtree = self.get_paths_starting_with(path)
671
672 for p, is_dir in subtree:
673 if self.changed(path=p) == self.CHANGED_LOCAL:
674 break
675 else:
676 # no local modification in the path tree
677 for p, is_dir in subtree:
678 filter_name = is_dir and "FS_DIR_DELETE" or "FS_FILE_DELETE"
679 self.eq.add_to_mute_filter(filter_name, p)
680 if p != path:
681 self.delete_metadata(p)
682 shutil.rmtree(path)
683 dir_deleted = True
684
685 return dir_deleted
686
654 def delete_file(self, path):687 def delete_file(self, path):
655 """Deletes a file/dir and the metadata."""688 """Deletes a file/dir and the metadata."""
656 # adjust the metadata689 # adjust the metadata
@@ -670,9 +703,11 @@
670 os.remove(path)703 os.remove(path)
671 except OSError, e:704 except OSError, e:
672 if e.errno == errno.ENOTEMPTY:705 if e.errno == errno.ENOTEMPTY:
673 raise706 if not self._delete_dir_tree(path=path):
674 m = "OSError %s when trying to remove file/dir %r"707 raise
675 log_warning(m, e, path)708 else:
709 m = "OSError %s when trying to remove file/dir %r"
710 log_warning(m, e, path)
676 self.delete_metadata(path)711 self.delete_metadata(path)
677712
678 def move_to_conflict(self, mdid):713 def move_to_conflict(self, mdid):
@@ -680,7 +715,7 @@
680 mdobj = self.fs[mdid]715 mdobj = self.fs[mdid]
681 path = self.get_abspath(mdobj['share_id'], mdobj['path'])716 path = self.get_abspath(mdobj['share_id'], mdobj['path'])
682 log_debug("move_to_conflict: path=%r mdid=%r" % (path, mdid))717 log_debug("move_to_conflict: path=%r mdid=%r" % (path, mdid))
683 base_to_path = to_path = path + ".u1conflict"718 base_to_path = to_path = path + self.CONFLICT_SUFFIX
684 ind = 0719 ind = 0
685 while os.path.exists(to_path):720 while os.path.exists(to_path):
686 ind += 1721 ind += 1
@@ -874,7 +909,20 @@
874 raise TypeError("Incorrect arguments for 'has_metadata': %r" % kwargs)909 raise TypeError("Incorrect arguments for 'has_metadata': %r" % kwargs)
875910
876 def changed(self, **kwargs):911 def changed(self, **kwargs):
877 """Return True if there's metadata for a given object."""912 """Return whether a given node has changed or not.
913
914 The node can be defined by any of the following:
915 * path
916 * metadata's id (mdid)
917 * node_id and share_id
918
919 Return:
920 * LOCAL if the node has local modifications that the server is
921 not aware of.
922 * SERVER if the node is not fully downloaded.
923 * NONE the node has not changed.
924
925 """
878 # get the values926 # get the values
879 mdid = self._get_mdid_from_args(kwargs, "changed")927 mdid = self._get_mdid_from_args(kwargs, "changed")
880 mdobj = self.fs[mdid]928 mdobj = self.fs[mdid]
@@ -888,13 +936,26 @@
888 return "We broke the Universe! local_hash %r, server_hash %r,"\936 return "We broke the Universe! local_hash %r, server_hash %r,"\
889 " is_partial %r" % (local_hash, server_hash, is_partial)937 " is_partial %r" % (local_hash, server_hash, is_partial)
890 else:938 else:
891 return 'NONE'939 return self.CHANGED_NONE
892 else:940 else:
893 if is_partial:941 if is_partial:
894 return 'SERVER'942 return self.CHANGED_SERVER
895 else:943 else:
896 return 'LOCAL'944 return self.CHANGED_LOCAL
897 return945
946 def local_changed(self, path):
947 """Return whether a given node have locally changed or not.
948
949 Return True if the node at `path' (or any of its children) has
950 been locally modified.
951
952 """
953 has_changed = False
954 for p, is_dir in self.get_paths_starting_with(path):
955 if self.changed(path=p) == self.CHANGED_LOCAL:
956 has_changed = True
957 break
958 return has_changed
898959
899 def dir_content(self, path):960 def dir_content(self, path):
900 """Returns the content of the directory in a server-comparable way."""961 """Returns the content of the directory in a server-comparable way."""
@@ -958,11 +1019,15 @@
958 share = self._get_share(share_id)1019 share = self._get_share(share_id)
959 return EnableShareWrite(share, path)1020 return EnableShareWrite(share, path)
9601021
961 def get_paths_starting_with(self, base_path):1022 def get_paths_starting_with(self, base_path, include_base=True):
962 """ return a list of paths that starts with: path. """1023 """ return a list of paths that starts with: path. """
1024 len_func = str.__len__ # avoid lookup
1025 base_length = len_func(base_path)
1026
963 all_paths = []1027 all_paths = []
964 for path in self._idx_path:1028 for path in self._idx_path:
965 if path.startswith(base_path):1029 candidate = include_base or len_func(path) > base_length
1030 if candidate and path.startswith(base_path):
966 mdid = self._idx_path[path]1031 mdid = self._idx_path[path]
967 mdobj = self.fs[mdid]1032 mdobj = self.fs[mdid]
968 all_paths.append((path, mdobj['is_dir']))1033 all_paths.append((path, mdobj['is_dir']))

Subscribers

People subscribed via source and target branches