9 from apiclient import errors as apiclient_errors
13 from fusefile import StringFile, ObjectFile, FuncToJSONFile, FuseArvadosFile
14 from fresh import FreshBase, convertTime, use_counter, check_update
16 import arvados.collection
17 from arvados.util import portable_data_hash_pattern, uuid_pattern, collection_uuid_pattern, group_uuid_pattern, user_uuid_pattern, link_uuid_pattern
19 _logger = logging.getLogger('arvados.arvados_fuse')
22 # Match any character which FUSE or Linux cannot accommodate as part
23 # of a filename. (If present in a collection filename, they will
24 # appear as underscores in the fuse mount.)
25 _disallowed_filename_characters = re.compile('[\x00/]')
27 # '.' and '..' are not reachable if API server is newer than #6277
28 def sanitize_filename(dirty):
29 """Replace disallowed filename characters with harmless "_"."""
39 return _disallowed_filename_characters.sub('_', dirty)
42 class Directory(FreshBase):
43 """Generic directory object, backed by a dict.
45 Consists of a set of entries with the key representing the filename
46 and the value referencing a File or Directory object.
49 def __init__(self, parent_inode, inodes):
50 """parent_inode is the integer inode number"""
52 super(Directory, self).__init__()
55 if not isinstance(parent_inode, int):
56 raise Exception("parent_inode should be an int")
57 self.parent_inode = parent_inode
60 self._mtime = time.time()
62 # Overriden by subclasses to implement logic to update the entries dict
63 # when the directory is stale
68 # Only used when computing the size of the disk footprint of the directory
76 def checkupdate(self):
80 except apiclient.errors.HttpError as e:
85 def __getitem__(self, item):
86 return self._entries[item]
91 return list(self._entries.items())
95 def __contains__(self, k):
96 return k in self._entries
101 return len(self._entries)
104 self.inodes.touch(self)
105 super(Directory, self).fresh()
107 def merge(self, items, fn, same, new_entry):
108 """Helper method for updating the contents of the directory.
110 Takes a list describing the new contents of the directory, reuse
111 entries that are the same in both the old and new lists, create new
112 entries, and delete old entries missing from the new list.
114 :items: iterable with new directory contents
116 :fn: function to take an entry in 'items' and return the desired file or
117 directory name, or None if this entry should be skipped
119 :same: function to compare an existing entry (a File or Directory
120 object) with an entry in the items list to determine whether to keep
123 :new_entry: function to create a new directory entry (File or Directory
124 object) from an entry in the items list.
128 oldentries = self._entries
132 name = sanitize_filename(fn(i))
134 if name in oldentries and same(oldentries[name], i):
135 # move existing directory entry over
136 self._entries[name] = oldentries[name]
139 _logger.debug("Adding entry '%s' to inode %i", name, self.inode)
140 # create new directory entry
143 self._entries[name] = self.inodes.add_entry(ent)
146 # delete any other directory entries that were not in found in 'items'
148 _logger.debug("Forgetting about entry '%s' on inode %i", i, self.inode)
149 self.inodes.invalidate_entry(self.inode, i.encode(self.inodes.encoding))
150 self.inodes.del_entry(oldentries[i])
154 self.inodes.invalidate_inode(self.inode)
155 self._mtime = time.time()
159 def clear(self, force=False):
160 """Delete all entries"""
162 if not self.in_use() or force:
163 oldentries = self._entries
166 if not oldentries[n].clear(force):
167 self._entries = oldentries
170 self.inodes.invalidate_entry(self.inode, n.encode(self.inodes.encoding))
171 self.inodes.del_entry(oldentries[n])
172 self.inodes.invalidate_inode(self.inode)
187 def want_event_subscribe(self):
188 raise NotImplementedError()
190 def create(self, name):
191 raise NotImplementedError()
193 def mkdir(self, name):
194 raise NotImplementedError()
196 def unlink(self, name):
197 raise NotImplementedError()
199 def rmdir(self, name):
200 raise NotImplementedError()
202 def rename(self, name_old, name_new, src):
203 raise NotImplementedError()
206 class CollectionDirectoryBase(Directory):
207 """Represent an Arvados Collection as a directory.
209 This class is used for Subcollections, and is also the base class for
210 CollectionDirectory, which implements collection loading/saving on
213 Most operations act only the underlying Arvados `Collection` object. The
214 `Collection` object signals via a notify callback to
215 `CollectionDirectoryBase.on_event` that an item was added, removed or
216 modified. FUSE inodes and directory entries are created, deleted or
217 invalidated in response to these events.
221 def __init__(self, parent_inode, inodes, collection):
222 super(CollectionDirectoryBase, self).__init__(parent_inode, inodes)
223 self.collection = collection
225 def new_entry(self, name, item, mtime):
226 name = sanitize_filename(name)
227 if hasattr(item, "fuse_entry") and item.fuse_entry is not None:
228 if item.fuse_entry.dead is not True:
229 raise Exception("Can only reparent dead inode entry")
230 if item.fuse_entry.inode is None:
231 raise Exception("Reparented entry must still have valid inode")
232 item.fuse_entry.dead = False
233 self._entries[name] = item.fuse_entry
234 elif isinstance(item, arvados.collection.RichCollectionBase):
235 self._entries[name] = self.inodes.add_entry(CollectionDirectoryBase(self.inode, self.inodes, item))
236 self._entries[name].populate(mtime)
238 self._entries[name] = self.inodes.add_entry(FuseArvadosFile(self.inode, item, mtime))
239 item.fuse_entry = self._entries[name]
241 def on_event(self, event, collection, name, item):
242 if collection == self.collection:
243 name = sanitize_filename(name)
244 _logger.debug("collection notify %s %s %s %s", event, collection, name, item)
246 if event == arvados.collection.ADD:
247 self.new_entry(name, item, self.mtime())
248 elif event == arvados.collection.DEL:
249 ent = self._entries[name]
250 del self._entries[name]
251 self.inodes.invalidate_entry(self.inode, name.encode(self.inodes.encoding))
252 self.inodes.del_entry(ent)
253 elif event == arvados.collection.MOD:
254 if hasattr(item, "fuse_entry") and item.fuse_entry is not None:
255 self.inodes.invalidate_inode(item.fuse_entry.inode)
256 elif name in self._entries:
257 self.inodes.invalidate_inode(self._entries[name].inode)
259 def populate(self, mtime):
261 self.collection.subscribe(self.on_event)
262 for entry, item in self.collection.items():
263 self.new_entry(entry, item, self.mtime())
266 return self.collection.writable()
270 with llfuse.lock_released:
271 self.collection.root_collection().save()
275 def create(self, name):
276 with llfuse.lock_released:
277 self.collection.open(name, "w").close()
281 def mkdir(self, name):
282 with llfuse.lock_released:
283 self.collection.mkdirs(name)
287 def unlink(self, name):
288 with llfuse.lock_released:
289 self.collection.remove(name)
294 def rmdir(self, name):
295 with llfuse.lock_released:
296 self.collection.remove(name)
301 def rename(self, name_old, name_new, src):
302 if not isinstance(src, CollectionDirectoryBase):
303 raise llfuse.FUSEError(errno.EPERM)
308 if isinstance(ent, FuseArvadosFile) and isinstance(tgt, FuseArvadosFile):
310 elif isinstance(ent, CollectionDirectoryBase) and isinstance(tgt, CollectionDirectoryBase):
312 raise llfuse.FUSEError(errno.ENOTEMPTY)
313 elif isinstance(ent, CollectionDirectoryBase) and isinstance(tgt, FuseArvadosFile):
314 raise llfuse.FUSEError(errno.ENOTDIR)
315 elif isinstance(ent, FuseArvadosFile) and isinstance(tgt, CollectionDirectoryBase):
316 raise llfuse.FUSEError(errno.EISDIR)
318 with llfuse.lock_released:
319 self.collection.rename(name_old, name_new, source_collection=src.collection, overwrite=True)
323 def clear(self, force=False):
324 r = super(CollectionDirectoryBase, self).clear(force)
325 self.collection = None
329 class CollectionDirectory(CollectionDirectoryBase):
330 """Represents the root of a directory tree representing a collection."""
332 def __init__(self, parent_inode, inodes, api, num_retries, collection_record=None, explicit_collection=None):
333 super(CollectionDirectory, self).__init__(parent_inode, inodes, None)
335 self.num_retries = num_retries
336 self.collection_record_file = None
337 self.collection_record = None
340 self._poll_time = (api._rootDesc.get('blobSignatureTtl', 60*60*2)/2)
342 _logger.debug("Error getting blobSignatureTtl from discovery document: %s", sys.exc_info()[0])
343 self._poll_time = 60*60
345 if isinstance(collection_record, dict):
346 self.collection_locator = collection_record['uuid']
347 self._mtime = convertTime(collection_record.get('modified_at'))
349 self.collection_locator = collection_record
351 self._manifest_size = 0
352 if self.collection_locator:
353 self._writable = (uuid_pattern.match(self.collection_locator) is not None)
354 self._updating_lock = threading.Lock()
357 return i['uuid'] == self.collection_locator or i['portable_data_hash'] == self.collection_locator
360 return self.collection.writable() if self.collection is not None else self._writable
362 def want_event_subscribe(self):
363 return (uuid_pattern.match(self.collection_locator) is not None)
365 # Used by arv-web.py to switch the contents of the CollectionDirectory
366 def change_collection(self, new_locator):
367 """Switch the contents of the CollectionDirectory.
369 Must be called with llfuse.lock held.
372 self.collection_locator = new_locator
373 self.collection_record = None
376 def new_collection(self, new_collection_record, coll_reader):
378 self.clear(force=True)
380 self.collection_record = new_collection_record
382 if self.collection_record:
383 self._mtime = convertTime(self.collection_record.get('modified_at'))
384 self.collection_locator = self.collection_record["uuid"]
385 if self.collection_record_file is not None:
386 self.collection_record_file.update(self.collection_record)
388 self.collection = coll_reader
389 self.populate(self.mtime())
392 return self.collection_locator
395 def update(self, to_record_version=None):
397 if self.collection_record is not None and portable_data_hash_pattern.match(self.collection_locator):
400 if self.collection_locator is None:
405 with llfuse.lock_released:
406 self._updating_lock.acquire()
410 _logger.debug("Updating %s", to_record_version)
411 if self.collection is not None:
412 if self.collection.known_past_version(to_record_version):
413 _logger.debug("%s already processed %s", self.collection_locator, to_record_version)
415 self.collection.update()
417 if uuid_pattern.match(self.collection_locator):
418 coll_reader = arvados.collection.Collection(
419 self.collection_locator, self.api, self.api.keep,
420 num_retries=self.num_retries)
422 coll_reader = arvados.collection.CollectionReader(
423 self.collection_locator, self.api, self.api.keep,
424 num_retries=self.num_retries)
425 new_collection_record = coll_reader.api_response() or {}
426 # If the Collection only exists in Keep, there will be no API
427 # response. Fill in the fields we need.
428 if 'uuid' not in new_collection_record:
429 new_collection_record['uuid'] = self.collection_locator
430 if "portable_data_hash" not in new_collection_record:
431 new_collection_record["portable_data_hash"] = new_collection_record["uuid"]
432 if 'manifest_text' not in new_collection_record:
433 new_collection_record['manifest_text'] = coll_reader.manifest_text()
435 if self.collection_record is None or self.collection_record["portable_data_hash"] != new_collection_record.get("portable_data_hash"):
436 self.new_collection(new_collection_record, coll_reader)
438 self._manifest_size = len(coll_reader.manifest_text())
439 _logger.debug("%s manifest_size %i", self, self._manifest_size)
440 # end with llfuse.lock_released, re-acquire lock
445 self._updating_lock.release()
446 except arvados.errors.NotFoundError as e:
447 _logger.error("Error fetching collection '%s': %s", self.collection_locator, e)
448 except arvados.errors.ArgumentError as detail:
449 _logger.warning("arv-mount %s: error %s", self.collection_locator, detail)
450 if self.collection_record is not None and "manifest_text" in self.collection_record:
451 _logger.warning("arv-mount manifest_text is: %s", self.collection_record["manifest_text"])
453 _logger.exception("arv-mount %s: error", self.collection_locator)
454 if self.collection_record is not None and "manifest_text" in self.collection_record:
455 _logger.error("arv-mount manifest_text is: %s", self.collection_record["manifest_text"])
461 def __getitem__(self, item):
462 if item == '.arvados#collection':
463 if self.collection_record_file is None:
464 self.collection_record_file = ObjectFile(self.inode, self.collection_record)
465 self.inodes.add_entry(self.collection_record_file)
466 return self.collection_record_file
468 return super(CollectionDirectory, self).__getitem__(item)
470 def __contains__(self, k):
471 if k == '.arvados#collection':
474 return super(CollectionDirectory, self).__contains__(k)
476 def invalidate(self):
477 self.collection_record = None
478 self.collection_record_file = None
479 super(CollectionDirectory, self).invalidate()
482 return (self.collection_locator is not None)
485 # This is an empirically-derived heuristic to estimate the memory used
486 # to store this collection's metadata. Calculating the memory
487 # footprint directly would be more accurate, but also more complicated.
488 return self._manifest_size * 128
491 if self.collection is not None:
493 self.collection.save()
494 self.collection.stop_threads()
497 class TmpCollectionDirectory(CollectionDirectoryBase):
498 """A directory backed by an Arvados collection that never gets saved.
500 This supports using Keep as scratch space. A userspace program can
501 read the .arvados#collection file to get a current manifest in
502 order to save a snapshot of the scratch data or use it as a crunch
506 class UnsaveableCollection(arvados.collection.Collection):
512 def __init__(self, parent_inode, inodes, api_client, num_retries):
513 collection = self.UnsaveableCollection(
514 api_client=api_client,
515 keep_client=api_client.keep,
516 num_retries=num_retries)
517 super(TmpCollectionDirectory, self).__init__(
518 parent_inode, inodes, collection)
519 self.collection_record_file = None
520 self.populate(self.mtime())
522 def on_event(self, *args, **kwargs):
523 super(TmpCollectionDirectory, self).on_event(*args, **kwargs)
524 if self.collection_record_file:
526 self.collection_record_file.invalidate()
527 self.inodes.invalidate_inode(self.collection_record_file.inode)
528 _logger.debug("%s invalidated collection record", self)
530 def collection_record(self):
531 with llfuse.lock_released:
534 "manifest_text": self.collection.manifest_text(),
535 "portable_data_hash": self.collection.portable_data_hash(),
538 def __contains__(self, k):
539 return (k == '.arvados#collection' or
540 super(TmpCollectionDirectory, self).__contains__(k))
543 def __getitem__(self, item):
544 if item == '.arvados#collection':
545 if self.collection_record_file is None:
546 self.collection_record_file = FuncToJSONFile(
547 self.inode, self.collection_record)
548 self.inodes.add_entry(self.collection_record_file)
549 return self.collection_record_file
550 return super(TmpCollectionDirectory, self).__getitem__(item)
558 def want_event_subscribe(self):
562 self.collection.stop_threads()
564 def invalidate(self):
565 if self.collection_record_file:
566 self.collection_record_file.invalidate()
567 super(TmpCollectionDirectory, self).invalidate()
570 class MagicDirectory(Directory):
571 """A special directory that logically contains the set of all extant keep locators.
573 When a file is referenced by lookup(), it is tested to see if it is a valid
574 keep locator to a manifest, and if so, loads the manifest contents as a
575 subdirectory of this directory with the locator as the directory name.
576 Since querying a list of all extant keep locators is impractical, only
577 collections that have already been accessed are visible to readdir().
582 This directory provides access to Arvados collections as subdirectories listed
583 by uuid (in the form 'zzzzz-4zz18-1234567890abcde') or portable data hash (in
584 the form '1234567890abcdef0123456789abcdef+123').
586 Note that this directory will appear empty until you attempt to access a
587 specific collection subdirectory (such as trying to 'cd' into it), at which
588 point the collection will actually be looked up on the server and the directory
589 will appear if it exists.
593 def __init__(self, parent_inode, inodes, api, num_retries, pdh_only=False):
594 super(MagicDirectory, self).__init__(parent_inode, inodes)
596 self.num_retries = num_retries
597 self.pdh_only = pdh_only
599 def __setattr__(self, name, value):
600 super(MagicDirectory, self).__setattr__(name, value)
601 # When we're assigned an inode, add a README.
602 if ((name == 'inode') and (self.inode is not None) and
603 (not self._entries)):
604 self._entries['README'] = self.inodes.add_entry(
605 StringFile(self.inode, self.README_TEXT, time.time()))
606 # If we're the root directory, add an identical by_id subdirectory.
607 if self.inode == llfuse.ROOT_INODE:
608 self._entries['by_id'] = self.inodes.add_entry(MagicDirectory(
609 self.inode, self.inodes, self.api, self.num_retries, self.pdh_only))
611 def __contains__(self, k):
612 if k in self._entries:
615 if not portable_data_hash_pattern.match(k) and (self.pdh_only or not uuid_pattern.match(k)):
619 e = self.inodes.add_entry(CollectionDirectory(
620 self.inode, self.inodes, self.api, self.num_retries, k))
623 if k not in self._entries:
626 self.inodes.del_entry(e)
629 self.inodes.invalidate_entry(self.inode, k)
630 self.inodes.del_entry(e)
632 except Exception as ex:
633 _logger.debug('arv-mount exception keep %s', ex)
634 self.inodes.del_entry(e)
637 def __getitem__(self, item):
639 return self._entries[item]
641 raise KeyError("No collection with id " + item)
643 def clear(self, force=False):
646 def want_event_subscribe(self):
647 return not self.pdh_only
650 class RecursiveInvalidateDirectory(Directory):
651 def invalidate(self):
653 super(RecursiveInvalidateDirectory, self).invalidate()
654 for a in self._entries:
655 self._entries[a].invalidate()
660 class TagsDirectory(RecursiveInvalidateDirectory):
661 """A special directory that contains as subdirectories all tags visible to the user."""
663 def __init__(self, parent_inode, inodes, api, num_retries, poll_time=60):
664 super(TagsDirectory, self).__init__(parent_inode, inodes)
666 self.num_retries = num_retries
668 self._poll_time = poll_time
670 def want_event_subscribe(self):
675 with llfuse.lock_released:
676 tags = self.api.links().list(
677 filters=[['link_class', '=', 'tag']],
678 select=['name'], distinct=True
679 ).execute(num_retries=self.num_retries)
681 self.merge(tags['items'],
683 lambda a, i: a.tag == i['name'],
684 lambda i: TagDirectory(self.inode, self.inodes, self.api, self.num_retries, i['name'], poll=self._poll, poll_time=self._poll_time))
687 class TagDirectory(Directory):
688 """A special directory that contains as subdirectories all collections visible
689 to the user that are tagged with a particular tag.
692 def __init__(self, parent_inode, inodes, api, num_retries, tag,
693 poll=False, poll_time=60):
694 super(TagDirectory, self).__init__(parent_inode, inodes)
696 self.num_retries = num_retries
699 self._poll_time = poll_time
701 def want_event_subscribe(self):
706 with llfuse.lock_released:
707 taggedcollections = self.api.links().list(
708 filters=[['link_class', '=', 'tag'],
709 ['name', '=', self.tag],
710 ['head_uuid', 'is_a', 'arvados#collection']],
712 ).execute(num_retries=self.num_retries)
713 self.merge(taggedcollections['items'],
714 lambda i: i['head_uuid'],
715 lambda a, i: a.collection_locator == i['head_uuid'],
716 lambda i: CollectionDirectory(self.inode, self.inodes, self.api, self.num_retries, i['head_uuid']))
719 class ProjectDirectory(Directory):
720 """A special directory that contains the contents of a project."""
722 def __init__(self, parent_inode, inodes, api, num_retries, project_object,
723 poll=False, poll_time=60):
724 super(ProjectDirectory, self).__init__(parent_inode, inodes)
726 self.num_retries = num_retries
727 self.project_object = project_object
728 self.project_object_file = None
729 self.project_uuid = project_object['uuid']
731 self._poll_time = poll_time
732 self._updating_lock = threading.Lock()
733 self._current_user = None
735 def want_event_subscribe(self):
738 def createDirectory(self, i):
739 if collection_uuid_pattern.match(i['uuid']):
740 return CollectionDirectory(self.inode, self.inodes, self.api, self.num_retries, i)
741 elif group_uuid_pattern.match(i['uuid']):
742 return ProjectDirectory(self.inode, self.inodes, self.api, self.num_retries, i, self._poll, self._poll_time)
743 elif link_uuid_pattern.match(i['uuid']):
744 if i['head_kind'] == 'arvados#collection' or portable_data_hash_pattern.match(i['head_uuid']):
745 return CollectionDirectory(self.inode, self.inodes, self.api, self.num_retries, i['head_uuid'])
748 elif uuid_pattern.match(i['uuid']):
749 return ObjectFile(self.parent_inode, i)
754 return self.project_uuid
758 if self.project_object_file == None:
759 self.project_object_file = ObjectFile(self.inode, self.project_object)
760 self.inodes.add_entry(self.project_object_file)
764 if i['name'] is None or len(i['name']) == 0:
766 elif collection_uuid_pattern.match(i['uuid']) or group_uuid_pattern.match(i['uuid']):
767 # collection or subproject
769 elif link_uuid_pattern.match(i['uuid']) and i['head_kind'] == 'arvados#collection':
772 elif 'kind' in i and i['kind'].startswith('arvados#'):
774 return "{}.{}".format(i['name'], i['kind'][8:])
779 if isinstance(a, CollectionDirectory) or isinstance(a, ProjectDirectory):
780 return a.uuid() == i['uuid']
781 elif isinstance(a, ObjectFile):
782 return a.uuid() == i['uuid'] and not a.stale()
786 with llfuse.lock_released:
787 self._updating_lock.acquire()
791 if group_uuid_pattern.match(self.project_uuid):
792 self.project_object = self.api.groups().get(
793 uuid=self.project_uuid).execute(num_retries=self.num_retries)
794 elif user_uuid_pattern.match(self.project_uuid):
795 self.project_object = self.api.users().get(
796 uuid=self.project_uuid).execute(num_retries=self.num_retries)
798 contents = arvados.util.list_all(self.api.groups().contents,
799 self.num_retries, uuid=self.project_uuid)
801 # end with llfuse.lock_released, re-acquire lock
806 self.createDirectory)
808 self._updating_lock.release()
812 def __getitem__(self, item):
813 if item == '.arvados#project':
814 return self.project_object_file
816 return super(ProjectDirectory, self).__getitem__(item)
818 def __contains__(self, k):
819 if k == '.arvados#project':
822 return super(ProjectDirectory, self).__contains__(k)
827 with llfuse.lock_released:
828 if not self._current_user:
829 self._current_user = self.api.users().current().execute(num_retries=self.num_retries)
830 return self._current_user["uuid"] in self.project_object["writable_by"]
837 def mkdir(self, name):
839 with llfuse.lock_released:
840 self.api.collections().create(body={"owner_uuid": self.project_uuid,
842 "manifest_text": ""}).execute(num_retries=self.num_retries)
844 except apiclient_errors.Error as error:
846 raise llfuse.FUSEError(errno.EEXIST)
850 def rmdir(self, name):
852 raise llfuse.FUSEError(errno.ENOENT)
853 if not isinstance(self[name], CollectionDirectory):
854 raise llfuse.FUSEError(errno.EPERM)
855 if len(self[name]) > 0:
856 raise llfuse.FUSEError(errno.ENOTEMPTY)
857 with llfuse.lock_released:
858 self.api.collections().delete(uuid=self[name].uuid()).execute(num_retries=self.num_retries)
863 def rename(self, name_old, name_new, src):
864 if not isinstance(src, ProjectDirectory):
865 raise llfuse.FUSEError(errno.EPERM)
869 if not isinstance(ent, CollectionDirectory):
870 raise llfuse.FUSEError(errno.EPERM)
873 # POSIX semantics for replacing one directory with another is
874 # tricky (the target directory must be empty, the operation must be
875 # atomic which isn't possible with the Arvados API as of this
876 # writing) so don't support that.
877 raise llfuse.FUSEError(errno.EPERM)
879 self.api.collections().update(uuid=ent.uuid(),
880 body={"owner_uuid": self.uuid(),
881 "name": name_new}).execute(num_retries=self.num_retries)
883 # Acually move the entry from source directory to this directory.
884 del src._entries[name_old]
885 self._entries[name_new] = ent
886 self.inodes.invalidate_entry(src.inode, name_old.encode(self.inodes.encoding))
889 class SharedDirectory(Directory):
890 """A special directory that represents users or groups who have shared projects with me."""
892 def __init__(self, parent_inode, inodes, api, num_retries, exclude,
893 poll=False, poll_time=60):
894 super(SharedDirectory, self).__init__(parent_inode, inodes)
896 self.num_retries = num_retries
897 self.current_user = api.users().current().execute(num_retries=num_retries)
899 self._poll_time = poll_time
903 with llfuse.lock_released:
904 all_projects = arvados.util.list_all(
905 self.api.groups().list, self.num_retries,
906 filters=[['group_class','=','project']])
908 for ob in all_projects:
909 objects[ob['uuid']] = ob
913 for ob in all_projects:
914 if ob['owner_uuid'] != self.current_user['uuid'] and ob['owner_uuid'] not in objects:
916 root_owners[ob['owner_uuid']] = True
918 lusers = arvados.util.list_all(
919 self.api.users().list, self.num_retries,
920 filters=[['uuid','in', list(root_owners)]])
921 lgroups = arvados.util.list_all(
922 self.api.groups().list, self.num_retries,
923 filters=[['uuid','in', list(root_owners)]])
929 objects[l["uuid"]] = l
931 objects[l["uuid"]] = l
934 for r in root_owners:
938 contents[obr["name"]] = obr
939 #elif obr.get("username"):
940 # contents[obr["username"]] = obr
941 elif "first_name" in obr:
942 contents[u"{} {}".format(obr["first_name"], obr["last_name"])] = obr
946 if r['owner_uuid'] not in objects:
947 contents[r['name']] = r
949 # end with llfuse.lock_released, re-acquire lock
952 self.merge(contents.items(),
954 lambda a, i: a.uuid() == i[1]['uuid'],
955 lambda i: ProjectDirectory(self.inode, self.inodes, self.api, self.num_retries, i[1], poll=self._poll, poll_time=self._poll_time))
959 def want_event_subscribe(self):