Committing extracted and compiled translations
[mediagoblin.git] / mediagoblin / storage / mountstorage.py
1 # GNU MediaGoblin -- federated, autonomous media hosting
2 # Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
3 #
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License as published by
6 # the Free Software Foundation, either version 3 of the License, or
7 # (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU Affero General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17 from mediagoblin.storage import StorageInterface, clean_listy_filepath
18
19
20 class MountStorage(StorageInterface):
21 """
22 Experimental "Mount" virtual Storage Interface
23
24 This isn't an interface to some real storage, instead it's a
25 redirecting interface, that redirects requests to other
26 "StorageInterface"s.
27
28 For example, say you have the paths:
29
30 1. ['user_data', 'cwebber', 'avatar.jpg']
31 2. ['user_data', 'elrond', 'avatar.jpg']
32 3. ['media_entries', '34352f304c3f4d0ad8ad0f043522b6f2', 'thumb.jpg']
33
34 You could mount media_entries under CloudFileStorage and user_data
35 under BasicFileStorage. Then 1 would be passed to
36 BasicFileStorage under the path ['cwebber', 'avatar.jpg'] and 3
37 would be passed to CloudFileStorage under
38 ['34352f304c3f4d0ad8ad0f043522b6f2', 'thumb.jpg'].
39
40 In other words, this is kind of like mounting /home/ and /etc/
41 under different filesystems on your operating system... but with
42 mediagoblin filestorages :)
43
44 To set this up, you currently need to call the mount() method with
45 the target path and a backend, that shall be available under that
46 target path. You have to mount things in a sensible order,
47 especially you can't mount ["a", "b"] before ["a"].
48 """
49 def __init__(self, **kwargs):
50 self.mounttab = {}
51
52 def mount(self, dirpath, backend):
53 """
54 Mount a new backend under dirpath
55 """
56 new_ent = clean_listy_filepath(dirpath)
57
58 print "Mounting:", repr(new_ent)
59 already, rem_1, table, rem_2 = self._resolve_to_backend(new_ent, True)
60 print "===", repr(already), repr(rem_1), repr(rem_2), len(table)
61
62 assert (len(rem_2) > 0) or (None not in table), \
63 "That path is already mounted"
64 assert (len(rem_2) > 0) or (len(table) == 0), \
65 "A longer path is already mounted here"
66
67 for part in rem_2:
68 table[part] = {}
69 table = table[part]
70 table[None] = backend
71
72 def _resolve_to_backend(self, filepath, extra_info=False):
73 """
74 extra_info = True is for internal use!
75
76 Normally, returns the backend and the filepath inside that backend.
77
78 With extra_info = True it returns the last directory node and the
79 remaining filepath from there in addition.
80 """
81 table = self.mounttab
82 filepath = filepath[:]
83 res_fp = None
84 while True:
85 new_be = table.get(None)
86 if (new_be is not None) or res_fp is None:
87 res_be = new_be
88 res_fp = filepath[:]
89 res_extra = (table, filepath[:])
90 # print "... New res: %r, %r, %r" % (res_be, res_fp, res_extra)
91 if len(filepath) == 0:
92 break
93 query = filepath.pop(0)
94 entry = table.get(query)
95 if entry is not None:
96 table = entry
97 res_extra = (table, filepath[:])
98 else:
99 break
100 if extra_info:
101 return (res_be, res_fp) + res_extra
102 else:
103 return (res_be, res_fp)
104
105 def resolve_to_backend(self, filepath):
106 backend, filepath = self._resolve_to_backend(filepath)
107 if backend is None:
108 raise Error("Path not mounted")
109 return backend, filepath
110
111 def __repr__(self, table=None, indent=[]):
112 res = []
113 if table is None:
114 res.append("MountStorage<")
115 table = self.mounttab
116 v = table.get(None)
117 if v:
118 res.append(" " * len(indent) + repr(indent) + ": " + repr(v))
119 for k, v in table.iteritems():
120 if k == None:
121 continue
122 res.append(" " * len(indent) + repr(k) + ":")
123 res += self.__repr__(v, indent + [k])
124 if table is self.mounttab:
125 res.append(">")
126 return "\n".join(res)
127 else:
128 return res
129
130 def file_exists(self, filepath):
131 backend, filepath = self.resolve_to_backend(filepath)
132 return backend.file_exists(filepath)
133
134 def get_file(self, filepath, mode='r'):
135 backend, filepath = self.resolve_to_backend(filepath)
136 return backend.get_file(filepath, mode)
137
138 def delete_file(self, filepath):
139 backend, filepath = self.resolve_to_backend(filepath)
140 return backend.delete_file(filepath)
141
142 def file_url(self, filepath):
143 backend, filepath = self.resolve_to_backend(filepath)
144 return backend.file_url(filepath)
145
146 def get_local_path(self, filepath):
147 backend, filepath = self.resolve_to_backend(filepath)
148 return backend.get_local_path(filepath)
149
150 def copy_locally(self, filepath, dest_path):
151 """
152 Need to override copy_locally, because the local_storage
153 attribute is not correct.
154 """
155 backend, filepath = self.resolve_to_backend(filepath)
156 backend.copy_locally(filepath, dest_path)