Web · Wiki · Activities · Blog · Lists · Chat · Meeting · Bugs · Git · Translate · Archive · People · Donate

Commit 4c8c00702c4c52dd8a5161a60c226c92da004ab3

implement ByTagsDirectory, including setting hard link count and inode number
  
2626import os.path
2727import shutil
2828import stat
29import sys
2930import tempfile
3031import time
3132
5151class DataStoreObjectStat(fuse.Stat):
5252
5353 # pylint: disable-msg=R0902,R0903
54 def __init__(self, filesystem, metadata, size):
55 fuse.Stat.__init__(self, st_mode=stat.S_IFREG | 0750, st_nlink=1,
54 def __init__(self, filesystem, metadata, size, inode):
55 fuse.Stat.__init__(self, st_mode=stat.S_IFREG | 0750, st_ino=inode,
5656 st_uid=os.getuid(), st_gid=os.getgid(), st_size=size,
5757 st_mtime=self._parse_time(metadata.get('timestamp', '')))
5858 self.st_ctime = self.st_mtime
5959 self.st_atime = self.st_mtime
60 tags = [tag for tag in metadata.get('tags', '').split()
61 if tag and '/' not in tag]
62 self.st_nlink = len(tags) + 1
6063 self.metadata = metadata
6164 self._filesystem = filesystem
6265 self.object_id = metadata['uid']
7878
7979
8080class Symlink(fuse.Stat):
81 def __init__(self, filesystem, target):
81 def __init__(self, filesystem, target, inode_nr):
8282 self._filesystem = filesystem
8383 self.target = target
8484 fuse.Stat.__init__(self, st_mode=stat.S_IFLNK | 0777, st_nlink=1,
85 st_uid=os.getuid(), st_gid=os.getgid(),
85 st_uid=os.getuid(), st_gid=os.getgid(), st_ino=inode_nr,
8686 st_mtime=time.time())
8787 self.st_ctime = self.st_mtime
8888 self.st_atime = self.st_mtime
8989
9090
9191class Directory(fuse.Stat):
92 def __init__(self, filesystem, mode):
92 def __init__(self, path, parent_path, filesystem, mode):
93 self._path = path
9394 self._filesystem = filesystem
9495 fuse.Stat.__init__(self, st_mode=stat.S_IFDIR | mode, st_nlink=2,
9596 st_uid=os.getuid(), st_gid=os.getgid(),
9697 st_mtime=time.time())
9798 self.st_ctime = self.st_mtime
9899 self.st_atime = self.st_mtime
100 self.st_ino = filesystem.get_inode_number(path)
99101
100102 def getxattr(self, name_, attribute_):
101103 # on Linux ENOATTR=ENODATA (Python errno doesn't contain ENOATTR)
116116 raise IOError(errno.EACCES, os.strerror(errno.EACCES))
117117
118118 def readdir(self, offset_):
119 for name in ['.', '..']:
120 yield fuse.Direntry(name)
119 yield fuse.Direntry('.',
120 self._filesystem.get_inode_number(self._path))
121 yield fuse.Direntry('.',
122 self._filesystem.get_inode_number(self._parent_path))
121123
122124 def readlink(self, name):
123125 entry = self.lookup(name)
132132 raise IOError(errno.EACCES, os.strerror(errno.EACCES))
133133
134134 def setxattr(self, name_, attribute_, value_, flags_):
135 raise IOError(errno.ENOTSUP, os.strerror(errno.ENOTSUP))
135 # On Linux ENOTSUP = EOPNOTSUPP
136 raise IOError(errno.EOPNOTSUPP, os.strerror(errno.EOPNOTSUPP))
136137
137138
138139class ByTitleDirectory(Directory):
139 def __init__(self, filesystem, root):
140 self.root = root
141 Directory.__init__(self, filesystem, 0750)
140 def __init__(self, path, parent_path, filesystem):
141 Directory.__init__(self, path, parent_path, filesystem, 0750)
142142
143143 def readdir(self, offset):
144144 Directory.readdir(self, offset)
145145
146 for entry in self._filesystem.find({},
147 {'metadata': ['title', 'uid', 'timestamp']}):
146 for entry in self._find_entries():
147 if 'uid' not in entry:
148 # corrupted entry
149 continue
148150
149151 name = self._filesystem.lookup_title_name(entry['uid'])
150 yield fuse.Direntry(name)
152 yield fuse.Direntry(name,
153 ino=self._filesystem.get_inode_number(entry['uid']))
151154
155 @trace()
156 def _find_entries(self):
157 return self._filesystem.find({},
158 {'metadata': ['title', 'uid', 'timestamp']})
159
152160 def getxattr(self, name, attribute):
153161 object_id = self._filesystem.resolve_title_name(name)
154162 metadata = self._filesystem.get_metadata(object_id)
174174 object_id = self._filesystem.resolve_title_name(name)
175175 metadata = self._filesystem.get_metadata(object_id)
176176 size = self._filesystem.get_data_size(object_id)
177 return DataStoreObjectStat(self._filesystem, metadata, size)
177 return DataStoreObjectStat(self._filesystem, metadata, size,
178 self._filesystem.get_inode_number(object_id))
178179
179180 def mknod(self, name):
180181 if self._filesystem.try_resolve_title_name(name):
202202
203203
204204class ByIdDirectory(Directory):
205 def __init__(self, filesystem):
206 Directory.__init__(self, filesystem, 0550)
205 def __init__(self, path, parent_path, filesystem):
206 Directory.__init__(self, path, parent_path, filesystem, 0550)
207207
208208 def getxattr(self, object_id, attribute):
209209 metadata = self._filesystem.get_metadata(object_id)
218218
219219 def lookup(self, object_id):
220220 name = self._filesystem.lookup_title_name(object_id)
221 return Symlink(self._filesystem, '../' + name)
221 path = '%s/%s' % (self._path, object_id)
222 return Symlink(self._filesystem, '../' + name,
223 self._filesystem.get_inode_number(path))
222224
223225 def readdir(self, offset):
224226 Directory.readdir(self, offset)
225227
226228 for entry in self._filesystem.find({}, {'metadata': ['uid']}):
227 yield fuse.Direntry(entry['uid'])
229 if 'uid' not in entry:
230 # corrupted entry
231 continue
228232
233 yield fuse.Direntry(entry['uid'],
234 ino=self._filesystem.get_inode_number(entry['uid']))
235
229236 def remove(self, object_id):
230237 self._filesystem.remove_entry(object_id)
231238
232239
233# TODO
240class ByTagsSubDirectory(ByTitleDirectory):
241 def __init__(self, path, parent_path, filesystem, tags):
242 self._tags = frozenset(tags)
243 ByTitleDirectory.__init__(self, path, parent_path, filesystem)
244
245 def mknod(self, name):
246 if self._filesystem.try_resolve_title_name(name):
247 raise IOError(errno.EEXIST, os.strerror(errno.EEXIST))
248
249 object_id = self._filesystem.create_new(name, '', self._tags)
250
251 @trace()
252 def _find_entries(self):
253 # The current data store doesn't support searching within the tags
254 # property, so we need to do an unspecific full text search and
255 # filter out any extra matches.
256 query = {'query': ' '.join(self._tags)}
257 for entry in self._filesystem.find(query,
258 {'metadata': ['uid', 'tags']}):
259
260 entry_tags = frozenset(entry.get('tags', '').split())
261 if self._tags - entry_tags:
262 continue
263
264 yield entry
265
266
234267class ByTagsDirectory(Directory):
235 def __init__(self, filesystem):
236 Directory.__init__(self, filesystem, 0550)
268 def __init__(self, path, parent_path, filesystem):
269 Directory.__init__(self, path, parent_path, filesystem, 0550)
270 self._tag_dirs = {}
237271
272 def readdir(self, offset):
273 Directory.readdir(self, offset)
238274
275 for tag in self._get_tags():
276 if '/' in tag:
277 continue
278
279 path = '%s/%s' % (self._path, tag)
280 yield fuse.Direntry(tag,
281 ino=self._filesystem.get_inode_number(path))
282
283 def lookup(self, tag):
284 if tag not in self._tag_dirs:
285 if not self._check_tag(tag):
286 raise IOError(errno.ENOENT, os.strerror(errno.ENOENT))
287
288 path = '%s/%s' % (self._path, tag)
289 self._tag_dirs[tag] = ByTagsSubDirectory(path, self._path,
290 self._filesystem, [tag])
291
292 return self._tag_dirs[tag]
293
294 @trace()
295 def _check_tag(self, tag):
296 # The current data store doesn't support searching within the tags
297 # property, so we need to do an unspecific full text search and
298 # filter out any extra matches.
299 query = {'query': tag}
300 for entry in self._filesystem.find(query, {'metadata': ['tags']}):
301 if tag in entry.get('tags', '').split():
302 return True
303
304 return False
305
306 @trace()
307 def _get_tags(self):
308 tags = set()
309 # The current data store doesn't support get_uniquevaluesfor('tags').
310 for entry in self._filesystem.find({}, {'metadata': ['tags']}):
311 logging.debug('entry=%r', entry)
312 tags.update(entry.get('tags', '').split())
313
314 tags.discard('')
315 return tags
316
317
239318class RootDirectory(ByTitleDirectory):
240319 def __init__(self, filesystem):
241 ByTitleDirectory.__init__(self, filesystem, self)
242 self.by_id_directory = ByIdDirectory(filesystem)
243 self.by_tags_directory = ByTagsDirectory(filesystem)
320 ByTitleDirectory.__init__(self, '/', '/', filesystem)
321 self.by_id_directory = ByIdDirectory('/by-id', '/', filesystem)
322 self.by_tags_directory = ByTagsDirectory('/by-tags', '/', filesystem)
244323 self.by_title_directory = self
245324
246325 def readdir(self, offset_):
247326 for name in ['by-id', 'by-tags']:
248 yield fuse.Direntry(name)
327 yield fuse.Direntry(name,
328 ino=self._filesystem.get_inode_number('/' + name))
249329
250 for name in ByTitleDirectory.readdir(self, offset_):
251 yield name
330 for entry in ByTitleDirectory.readdir(self, offset_):
331 yield entry
252332
253333 def lookup(self, name):
254334 if name == 'by-id':
476476 self_fs._truncate_object_ids = set()
477477 self_fs._object_id_to_title_name = {}
478478 self_fs._title_name_to_object_id = {}
479 self_fs._max_inode_number = 1
480 self_fs._object_id_to_inode_number = {}
479481
480482 fuse.Fuse.__init__(self_fs, *args, **kw)
481483
591591 def reset_truncate(self, object_id):
592592 self._truncate_object_ids.discard(object_id)
593593
594 @trace()
594595 def find(self, metadata, options):
595596 mess = metadata.copy()
596597 mess.update(options)
597598 properties = mess.pop('metadata', [])
599 logging.debug('mess=%r, properties=%r', mess, properties)
598600 return self._data_store.find(mess, properties, timeout=-1,
599601 byte_arrays=True)[0]
600602
607607 except Exception, exception:
608608 raise IOError(errno.ENOENT, str(exception))
609609
610 def create_new(self, name, path):
610 def create_new(self, name, path, tags=None):
611611 base_name = os.path.splitext(name)[0]
612612 metadata = {'title': base_name}
613613 mime_type = sugar.mime.get_from_file_name(name)
614614 if mime_type:
615615 metadata['mime_type'] = mime_type
616616
617 if tags:
618 metadata['tags'] = ' '.join(tags)
619
617620 object_id = self._data_store.create(metadata, path, False, timeout=-1,
618621 byte_arrays=True)
619622 self._add_title_name(name, object_id)
703703
704704 time_human = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(mtime))
705705 name = '%s - %s' % (title, time_human)
706 name = self._safe_name(name)
706 name = safe_name(name)
707707 extension = self._guess_extension(metadata.get('mime_type'), object_id)
708708 if extension:
709709 current_name = '%s.%s' % (name, extension)
729729 if object_id:
730730 del self._object_id_to_title_name[object_id]
731731
732 def _safe_name(self, name):
733 return name.replace('/', '_')
732 def get_inode_number(self, key):
733 if key not in self._object_id_to_inode_number:
734 inode_number = self._max_inode_number
735 self._max_inode_number += 1
736 self._object_id_to_inode_number[key] = inode_number
734737
738 return self._object_id_to_inode_number[key]
739
735740 def _guess_extension(self, mime_type, object_id):
736741 extension = None
737742
754754 return extension
755755
756756
757def safe_name(name):
758 return name.replace('/', '_')
759
760
757761def main():
758762 usage = __doc__ + fuse.Fuse.fusage
759763
764 # FIXME: figure out how to force options to on, properly.
765 sys.argv += ['-o', 'use_ino']
760766 server = DataStoreFS(version="%prog " + fuse.__version__, usage=usage,
761767 dash_s_do='setsingle')
762768 server.parse(errex=1)