1
# ubuntuone.u1sync.metadata
3
# u1sync metadata routines
5
# Author: Tim Cole <tim.cole@canonical.com>
7
# Copyright 2009 Canonical Ltd.
9
# This program is free software: you can redistribute it and/or modify it
10
# under the terms of the GNU General Public License version 3, as published
11
# by the Free Software Foundation.
13
# This program is distributed in the hope that it will be useful, but
14
# WITHOUT ANY WARRANTY; without even the implied warranties of
15
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
16
# PURPOSE. See the GNU General Public License for more details.
18
# You should have received a copy of the GNU General Public License along
19
# with this program. If not, see <http://www.gnu.org/licenses/>.
20
"""Routines for loading/storing u1sync mirror metadata."""
22
from __future__ import with_statement
25
import cPickle as pickle
26
from errno import ENOENT
27
from contextlib import contextmanager
28
from ubuntuone.storageprotocol.dircontent_pb2 import DIRECTORY
29
from ubuntuone.u1sync.merge import MergeNode
30
from ubuntuone.u1sync.utils import safe_unlink
33
class Metadata(object):
34
"""Object representing mirror metadata."""
35
def __init__(self, local_tree=None, remote_tree=None, share_uuid=None,
36
root_uuid=None, path=None):
37
"""Populate fields."""
38
self.local_tree = local_tree
39
self.remote_tree = remote_tree
40
self.share_uuid = share_uuid
41
self.root_uuid = root_uuid
44
def read(metadata_dir):
45
"""Read metadata for a mirror rooted at directory."""
46
index_file = os.path.join(metadata_dir, "local-index")
47
share_uuid_file = os.path.join(metadata_dir, "share-uuid")
48
root_uuid_file = os.path.join(metadata_dir, "root-uuid")
49
path_file = os.path.join(metadata_dir, "path")
51
index = read_pickle_file(index_file, {})
52
share_uuid = read_uuid_file(share_uuid_file)
53
root_uuid = read_uuid_file(root_uuid_file)
54
path = read_string_file(path_file, '/')
56
local_tree = index.get("tree", None)
57
remote_tree = index.get("remote_tree", None)
59
if local_tree is None:
60
local_tree = MergeNode(node_type=DIRECTORY, children={})
61
if remote_tree is None:
62
remote_tree = MergeNode(node_type=DIRECTORY, children={})
64
return Metadata(local_tree=local_tree, remote_tree=remote_tree,
65
share_uuid=share_uuid, root_uuid=root_uuid,
68
def write(metadata_dir, info):
69
"""Writes all metadata for the mirror rooted at directory."""
70
share_uuid_file = os.path.join(metadata_dir, "share-uuid")
71
root_uuid_file = os.path.join(metadata_dir, "root-uuid")
72
index_file = os.path.join(metadata_dir, "local-index")
73
path_file = os.path.join(metadata_dir, "path")
74
if info.share_uuid is not None:
75
write_uuid_file(share_uuid_file, info.share_uuid)
77
safe_unlink(share_uuid_file)
78
if info.root_uuid is not None:
79
write_uuid_file(root_uuid_file, info.root_uuid)
81
safe_unlink(root_uuid_file)
82
write_string_file(path_file, info.path)
83
write_pickle_file(index_file, {"tree": info.local_tree,
84
"remote_tree": info.remote_tree})
86
def write_pickle_file(filename, value):
87
"""Writes a pickled python object to a file."""
88
with atomic_update_file(filename) as stream:
89
pickle.dump(value, stream, 2)
91
def write_string_file(filename, value):
92
"""Writes a string to a file with an added line feed, or
93
deletes the file if value is None.
96
with atomic_update_file(filename) as stream:
100
safe_unlink(filename)
102
def write_uuid_file(filename, value):
103
"""Writes a UUID to a file."""
104
write_string_file(filename, str(value))
106
def read_pickle_file(filename, default_value=None):
107
"""Reads a pickled python object from a file."""
109
with open(filename, "rb") as stream:
110
return pickle.load(stream)
112
if e.errno != ENOENT:
116
def read_string_file(filename, default_value=None):
117
"""Reads a string from a file, discarding the final character."""
119
with open(filename, "r") as stream:
120
return stream.read()[:-1]
122
if e.errno != ENOENT:
126
def read_uuid_file(filename, default_value=None):
127
"""Reads a UUID from a file."""
129
with open(filename, "r") as stream:
130
return uuid.UUID(stream.read()[:-1])
132
if e.errno != ENOENT:
137
def atomic_update_file(filename):
138
"""Returns a context manager for atomically updating a file."""
139
temp_filename = "%s.%s" % (filename, uuid.uuid4())
141
with open(temp_filename, "w") as stream:
143
os.rename(temp_filename, filename)
145
safe_unlink(temp_filename)