Commit | Line | Data |
---|---|---|
a2468d18 | 1 | # GNU MediaGoblin -- federated, autonomous media hosting |
cf29e8a8 | 2 | # Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. |
a2468d18 JW |
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 | ||
dd1756ee | 17 | from mediagoblin.storage import StorageInterface, clean_listy_filepath |
a2468d18 JW |
18 | |
19 | ||
a855e92a WKG |
20 | class MountError(Exception): |
21 | pass | |
22 | ||
23 | ||
a2468d18 JW |
24 | class MountStorage(StorageInterface): |
25 | """ | |
26 | Experimental "Mount" virtual Storage Interface | |
27 | ||
28 | This isn't an interface to some real storage, instead it's a | |
29 | redirecting interface, that redirects requests to other | |
30 | "StorageInterface"s. | |
31 | ||
32 | For example, say you have the paths: | |
33 | ||
34 | 1. ['user_data', 'cwebber', 'avatar.jpg'] | |
35 | 2. ['user_data', 'elrond', 'avatar.jpg'] | |
36 | 3. ['media_entries', '34352f304c3f4d0ad8ad0f043522b6f2', 'thumb.jpg'] | |
37 | ||
38 | You could mount media_entries under CloudFileStorage and user_data | |
39 | under BasicFileStorage. Then 1 would be passed to | |
40 | BasicFileStorage under the path ['cwebber', 'avatar.jpg'] and 3 | |
41 | would be passed to CloudFileStorage under | |
42 | ['34352f304c3f4d0ad8ad0f043522b6f2', 'thumb.jpg']. | |
43 | ||
44 | In other words, this is kind of like mounting /home/ and /etc/ | |
45 | under different filesystems on your operating system... but with | |
46 | mediagoblin filestorages :) | |
47 | ||
48 | To set this up, you currently need to call the mount() method with | |
49 | the target path and a backend, that shall be available under that | |
50 | target path. You have to mount things in a sensible order, | |
51 | especially you can't mount ["a", "b"] before ["a"]. | |
52 | """ | |
53 | def __init__(self, **kwargs): | |
54 | self.mounttab = {} | |
55 | ||
56 | def mount(self, dirpath, backend): | |
57 | """ | |
58 | Mount a new backend under dirpath | |
59 | """ | |
60 | new_ent = clean_listy_filepath(dirpath) | |
61 | ||
62 | print "Mounting:", repr(new_ent) | |
63 | already, rem_1, table, rem_2 = self._resolve_to_backend(new_ent, True) | |
64 | print "===", repr(already), repr(rem_1), repr(rem_2), len(table) | |
65 | ||
66 | assert (len(rem_2) > 0) or (None not in table), \ | |
67 | "That path is already mounted" | |
68 | assert (len(rem_2) > 0) or (len(table) == 0), \ | |
69 | "A longer path is already mounted here" | |
70 | ||
71 | for part in rem_2: | |
72 | table[part] = {} | |
73 | table = table[part] | |
74 | table[None] = backend | |
75 | ||
76 | def _resolve_to_backend(self, filepath, extra_info=False): | |
77 | """ | |
78 | extra_info = True is for internal use! | |
79 | ||
80 | Normally, returns the backend and the filepath inside that backend. | |
81 | ||
82 | With extra_info = True it returns the last directory node and the | |
83 | remaining filepath from there in addition. | |
84 | """ | |
85 | table = self.mounttab | |
86 | filepath = filepath[:] | |
87 | res_fp = None | |
88 | while True: | |
89 | new_be = table.get(None) | |
90 | if (new_be is not None) or res_fp is None: | |
91 | res_be = new_be | |
92 | res_fp = filepath[:] | |
93 | res_extra = (table, filepath[:]) | |
94 | # print "... New res: %r, %r, %r" % (res_be, res_fp, res_extra) | |
95 | if len(filepath) == 0: | |
96 | break | |
97 | query = filepath.pop(0) | |
98 | entry = table.get(query) | |
99 | if entry is not None: | |
100 | table = entry | |
101 | res_extra = (table, filepath[:]) | |
102 | else: | |
103 | break | |
104 | if extra_info: | |
105 | return (res_be, res_fp) + res_extra | |
106 | else: | |
107 | return (res_be, res_fp) | |
108 | ||
109 | def resolve_to_backend(self, filepath): | |
110 | backend, filepath = self._resolve_to_backend(filepath) | |
111 | if backend is None: | |
a855e92a | 112 | raise MountError("Path not mounted") |
a2468d18 JW |
113 | return backend, filepath |
114 | ||
115 | def __repr__(self, table=None, indent=[]): | |
116 | res = [] | |
117 | if table is None: | |
118 | res.append("MountStorage<") | |
119 | table = self.mounttab | |
120 | v = table.get(None) | |
121 | if v: | |
122 | res.append(" " * len(indent) + repr(indent) + ": " + repr(v)) | |
123 | for k, v in table.iteritems(): | |
124 | if k == None: | |
125 | continue | |
126 | res.append(" " * len(indent) + repr(k) + ":") | |
127 | res += self.__repr__(v, indent + [k]) | |
128 | if table is self.mounttab: | |
129 | res.append(">") | |
130 | return "\n".join(res) | |
131 | else: | |
132 | return res | |
133 | ||
134 | def file_exists(self, filepath): | |
135 | backend, filepath = self.resolve_to_backend(filepath) | |
136 | return backend.file_exists(filepath) | |
137 | ||
138 | def get_file(self, filepath, mode='r'): | |
139 | backend, filepath = self.resolve_to_backend(filepath) | |
140 | return backend.get_file(filepath, mode) | |
141 | ||
142 | def delete_file(self, filepath): | |
143 | backend, filepath = self.resolve_to_backend(filepath) | |
144 | return backend.delete_file(filepath) | |
145 | ||
146 | def file_url(self, filepath): | |
147 | backend, filepath = self.resolve_to_backend(filepath) | |
148 | return backend.file_url(filepath) | |
149 | ||
150 | def get_local_path(self, filepath): | |
151 | backend, filepath = self.resolve_to_backend(filepath) | |
152 | return backend.get_local_path(filepath) | |
153 | ||
154 | def copy_locally(self, filepath, dest_path): | |
155 | """ | |
156 | Need to override copy_locally, because the local_storage | |
157 | attribute is not correct. | |
158 | """ | |
159 | backend, filepath = self.resolve_to_backend(filepath) | |
160 | backend.copy_locally(filepath, dest_path) |