1
# Copyright 2009 Canonical Ltd.
2
# Copyright 2015-2018 Chicharreros (https://launchpad.net/~chicharreros)
4
# This program is free software: you can redistribute it and/or modify it
5
# under the terms of the GNU General Public License version 3, as published
6
# by the Free Software Foundation.
8
# This program is distributed in the hope that it will be useful, but
9
# WITHOUT ANY WARRANTY; without even the implied warranties of
10
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11
# PURPOSE. See the GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License along
14
# with this program. If not, see <http://www.gnu.org/licenses/>.
16
"""Routines for loading/storing u1sync mirror metadata."""
18
from __future__ import with_statement
23
from contextlib import contextmanager
24
import cPickle as pickle
25
from errno import ENOENT
27
from ubuntuone.storageprotocol.dircontent_pb2 import DIRECTORY
29
from u1sync.merge import MergeNode
30
from u1sync.utils import safe_unlink
33
class Metadata(object):
34
"""Object representing mirror metadata."""
36
def __init__(self, local_tree=None, remote_tree=None, share_uuid=None,
37
root_uuid=None, path=None):
38
"""Populate fields."""
39
self.local_tree = local_tree
40
self.remote_tree = remote_tree
41
self.share_uuid = share_uuid
42
self.root_uuid = root_uuid
46
def read(metadata_dir):
47
"""Read metadata for a mirror rooted at directory."""
48
index_file = os.path.join(metadata_dir, "local-index")
49
share_uuid_file = os.path.join(metadata_dir, "share-uuid")
50
root_uuid_file = os.path.join(metadata_dir, "root-uuid")
51
path_file = os.path.join(metadata_dir, "path")
53
index = read_pickle_file(index_file, {})
54
share_uuid = read_uuid_file(share_uuid_file)
55
root_uuid = read_uuid_file(root_uuid_file)
56
path = read_string_file(path_file, '/')
58
local_tree = index.get("tree", None)
59
remote_tree = index.get("remote_tree", None)
61
if local_tree is None:
62
local_tree = MergeNode(node_type=DIRECTORY, children={})
63
if remote_tree is None:
64
remote_tree = MergeNode(node_type=DIRECTORY, children={})
66
return Metadata(local_tree=local_tree, remote_tree=remote_tree,
67
share_uuid=share_uuid, root_uuid=root_uuid,
71
def write(metadata_dir, info):
72
"""Writes all metadata for the mirror rooted at directory."""
73
share_uuid_file = os.path.join(metadata_dir, "share-uuid")
74
root_uuid_file = os.path.join(metadata_dir, "root-uuid")
75
index_file = os.path.join(metadata_dir, "local-index")
76
path_file = os.path.join(metadata_dir, "path")
77
if info.share_uuid is not None:
78
write_uuid_file(share_uuid_file, info.share_uuid)
80
safe_unlink(share_uuid_file)
81
if info.root_uuid is not None:
82
write_uuid_file(root_uuid_file, info.root_uuid)
84
safe_unlink(root_uuid_file)
85
write_string_file(path_file, info.path)
86
write_pickle_file(index_file, {"tree": info.local_tree,
87
"remote_tree": info.remote_tree})
90
def write_pickle_file(filename, value):
91
"""Writes a pickled python object to a file."""
92
with atomic_update_file(filename) as stream:
93
pickle.dump(value, stream, 2)
96
def write_string_file(filename, value):
97
"""Writes a string to a file with an added line feed, or
98
deletes the file if value is None.
100
if value is not None:
101
with atomic_update_file(filename) as stream:
105
safe_unlink(filename)
108
def write_uuid_file(filename, value):
109
"""Writes a UUID to a file."""
110
write_string_file(filename, str(value))
113
def read_pickle_file(filename, default_value=None):
114
"""Reads a pickled python object from a file."""
116
with open(filename, "rb") as stream:
117
return pickle.load(stream)
119
if e.errno != ENOENT:
124
def read_string_file(filename, default_value=None):
125
"""Reads a string from a file, discarding the final character."""
127
with open(filename, "r") as stream:
128
return stream.read()[:-1]
130
if e.errno != ENOENT:
135
def read_uuid_file(filename, default_value=None):
136
"""Reads a UUID from a file."""
138
with open(filename, "r") as stream:
139
return uuid.UUID(stream.read()[:-1])
141
if e.errno != ENOENT:
147
def atomic_update_file(filename):
148
"""Returns a context manager for atomically updating a file."""
149
temp_filename = "%s.%s" % (filename, uuid.uuid4())
151
with open(temp_filename, "w") as stream:
153
os.rename(temp_filename, filename)
155
safe_unlink(temp_filename)