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)
324 class CollectionDirectory(CollectionDirectoryBase):
325 """Represents the root of a directory tree representing a collection."""
327 def __init__(self, parent_inode, inodes, api, num_retries, collection_record=None, explicit_collection=None):
328 super(CollectionDirectory, self).__init__(parent_inode, inodes, None)
330 self.num_retries = num_retries
331 self.collection_record_file = None
332 self.collection_record = None
335 self._poll_time = (api._rootDesc.get('blobSignatureTtl', 60*60*2)/2)
337 _logger.debug("Error getting blobSignatureTtl from discovery document: %s", sys.exc_info()[0])
338 self._poll_time = 60*60
340 if isinstance(collection_record, dict):
341 self.collection_locator = collection_record['uuid']
342 self._mtime = convertTime(collection_record.get('modified_at'))
344 self.collection_locator = collection_record
346 self._manifest_size = 0
347 if self.collection_locator:
348 self._writable = (uuid_pattern.match(self.collection_locator) is not None)
349 self._updating_lock = threading.Lock()
352 return i['uuid'] == self.collection_locator or i['portable_data_hash'] == self.collection_locator
355 return self.collection.writable() if self.collection is not None else self._writable
357 def want_event_subscribe(self):
358 return (uuid_pattern.match(self.collection_locator) is not None)
360 # Used by arv-web.py to switch the contents of the CollectionDirectory
361 def change_collection(self, new_locator):
362 """Switch the contents of the CollectionDirectory.
364 Must be called with llfuse.lock held.
367 self.collection_locator = new_locator
368 self.collection_record = None
371 def new_collection(self, new_collection_record, coll_reader):
373 self.clear(force=True)
375 self.collection_record = new_collection_record
377 if self.collection_record:
378 self._mtime = convertTime(self.collection_record.get('modified_at'))
379 self.collection_locator = self.collection_record["uuid"]
380 if self.collection_record_file is not None:
381 self.collection_record_file.update(self.collection_record)
383 self.collection = coll_reader
384 self.populate(self.mtime())
387 return self.collection_locator
390 def update(self, to_record_version=None):
392 if self.collection_record is not None and portable_data_hash_pattern.match(self.collection_locator):
395 if self.collection_locator is None:
400 with llfuse.lock_released:
401 self._updating_lock.acquire()
405 _logger.debug("Updating %s", to_record_version)
406 if self.collection is not None:
407 if self.collection.known_past_version(to_record_version):
408 _logger.debug("%s already processed %s", self.collection_locator, to_record_version)
410 self.collection.update()
412 if uuid_pattern.match(self.collection_locator):
413 coll_reader = arvados.collection.Collection(
414 self.collection_locator, self.api, self.api.keep,
415 num_retries=self.num_retries)
417 coll_reader = arvados.collection.CollectionReader(
418 self.collection_locator, self.api, self.api.keep,
419 num_retries=self.num_retries)
420 new_collection_record = coll_reader.api_response() or {}
421 # If the Collection only exists in Keep, there will be no API
422 # response. Fill in the fields we need.
423 if 'uuid' not in new_collection_record:
424 new_collection_record['uuid'] = self.collection_locator
425 if "portable_data_hash" not in new_collection_record:
426 new_collection_record["portable_data_hash"] = new_collection_record["uuid"]
427 if 'manifest_text' not in new_collection_record:
428 new_collection_record['manifest_text'] = coll_reader.manifest_text()
430 if self.collection_record is None or self.collection_record["portable_data_hash"] != new_collection_record.get("portable_data_hash"):
431 self.new_collection(new_collection_record, coll_reader)
433 self._manifest_size = len(coll_reader.manifest_text())
434 _logger.debug("%s manifest_size %i", self, self._manifest_size)
435 # end with llfuse.lock_released, re-acquire lock
440 self._updating_lock.release()
441 except arvados.errors.NotFoundError as e:
442 _logger.error("Error fetching collection '%s': %s", self.collection_locator, e)
443 except arvados.errors.ArgumentError as detail:
444 _logger.warning("arv-mount %s: error %s", self.collection_locator, detail)
445 if self.collection_record is not None and "manifest_text" in self.collection_record:
446 _logger.warning("arv-mount manifest_text is: %s", self.collection_record["manifest_text"])
448 _logger.exception("arv-mount %s: error", self.collection_locator)
449 if self.collection_record is not None and "manifest_text" in self.collection_record:
450 _logger.error("arv-mount manifest_text is: %s", self.collection_record["manifest_text"])
456 def __getitem__(self, item):
457 if item == '.arvados#collection':
458 if self.collection_record_file is None:
459 self.collection_record_file = ObjectFile(self.inode, self.collection_record)
460 self.inodes.add_entry(self.collection_record_file)
461 return self.collection_record_file
463 return super(CollectionDirectory, self).__getitem__(item)
465 def __contains__(self, k):
466 if k == '.arvados#collection':
469 return super(CollectionDirectory, self).__contains__(k)
471 def invalidate(self):
472 self.collection_record = None
473 self.collection_record_file = None
474 super(CollectionDirectory, self).invalidate()
477 return (self.collection_locator is not None)
480 # This is an empirically-derived heuristic to estimate the memory used
481 # to store this collection's metadata. Calculating the memory
482 # footprint directly would be more accurate, but also more complicated.
483 return self._manifest_size * 128
486 if self.collection is not None:
488 self.collection.save()
489 self.collection.stop_threads()
492 class TmpCollectionDirectory(CollectionDirectoryBase):
493 """A directory backed by an Arvados collection that never gets saved.
495 This supports using Keep as scratch space. A userspace program can
496 read the .arvados#collection file to get a current manifest in
497 order to save a snapshot of the scratch data or use it as a crunch
501 class UnsaveableCollection(arvados.collection.Collection):
507 def __init__(self, parent_inode, inodes, api_client, num_retries):
508 collection = self.UnsaveableCollection(
509 api_client=api_client,
510 keep_client=api_client.keep,
511 num_retries=num_retries)
512 super(TmpCollectionDirectory, self).__init__(
513 parent_inode, inodes, collection)
514 self.collection_record_file = None
515 self.populate(self.mtime())
517 def on_event(self, *args, **kwargs):
518 super(TmpCollectionDirectory, self).on_event(*args, **kwargs)
519 if self.collection_record_file:
521 self.collection_record_file.invalidate()
522 self.inodes.invalidate_inode(self.collection_record_file.inode)
523 _logger.debug("%s invalidated collection record", self)
525 def collection_record(self):
526 with llfuse.lock_released:
529 "manifest_text": self.collection.manifest_text(),
530 "portable_data_hash": self.collection.portable_data_hash(),
533 def __contains__(self, k):
534 return (k == '.arvados#collection' or
535 super(TmpCollectionDirectory, self).__contains__(k))
538 def __getitem__(self, item):
539 if item == '.arvados#collection':
540 if self.collection_record_file is None:
541 self.collection_record_file = FuncToJSONFile(
542 self.inode, self.collection_record)
543 self.inodes.add_entry(self.collection_record_file)
544 return self.collection_record_file
545 return super(TmpCollectionDirectory, self).__getitem__(item)
553 def want_event_subscribe(self):
557 self.collection.stop_threads()
559 def invalidate(self):
560 if self.collection_record_file:
561 self.collection_record_file.invalidate()
562 super(TmpCollectionDirectory, self).invalidate()
565 class MagicDirectory(Directory):
566 """A special directory that logically contains the set of all extant keep locators.
568 When a file is referenced by lookup(), it is tested to see if it is a valid
569 keep locator to a manifest, and if so, loads the manifest contents as a
570 subdirectory of this directory with the locator as the directory name.
571 Since querying a list of all extant keep locators is impractical, only
572 collections that have already been accessed are visible to readdir().
577 This directory provides access to Arvados collections as subdirectories listed
578 by uuid (in the form 'zzzzz-4zz18-1234567890abcde') or portable data hash (in
579 the form '1234567890abcdef0123456789abcdef+123').
581 Note that this directory will appear empty until you attempt to access a
582 specific collection subdirectory (such as trying to 'cd' into it), at which
583 point the collection will actually be looked up on the server and the directory
584 will appear if it exists.
588 def __init__(self, parent_inode, inodes, api, num_retries, pdh_only=False):
589 super(MagicDirectory, self).__init__(parent_inode, inodes)
591 self.num_retries = num_retries
592 self.pdh_only = pdh_only
594 def __setattr__(self, name, value):
595 super(MagicDirectory, self).__setattr__(name, value)
596 # When we're assigned an inode, add a README.
597 if ((name == 'inode') and (self.inode is not None) and
598 (not self._entries)):
599 self._entries['README'] = self.inodes.add_entry(
600 StringFile(self.inode, self.README_TEXT, time.time()))
601 # If we're the root directory, add an identical by_id subdirectory.
602 if self.inode == llfuse.ROOT_INODE:
603 self._entries['by_id'] = self.inodes.add_entry(MagicDirectory(
604 self.inode, self.inodes, self.api, self.num_retries, self.pdh_only))
606 def __contains__(self, k):
607 if k in self._entries:
610 if not portable_data_hash_pattern.match(k) and (self.pdh_only or not uuid_pattern.match(k)):
614 e = self.inodes.add_entry(CollectionDirectory(
615 self.inode, self.inodes, self.api, self.num_retries, k))
618 if k not in self._entries:
621 self.inodes.del_entry(e)
624 self.inodes.invalidate_entry(self.inode, k)
625 self.inodes.del_entry(e)
627 except Exception as ex:
628 _logger.debug('arv-mount exception keep %s', ex)
629 self.inodes.del_entry(e)
632 def __getitem__(self, item):
634 return self._entries[item]
636 raise KeyError("No collection with id " + item)
638 def clear(self, force=False):
641 def want_event_subscribe(self):
642 return not self.pdh_only
644 class RecursiveInvalidateDirectory(Directory):
645 def invalidate(self):
647 super(RecursiveInvalidateDirectory, self).invalidate()
648 for a in self._entries:
649 self._entries[a].invalidate()
654 class TagsDirectory(RecursiveInvalidateDirectory):
655 """A special directory that contains as subdirectories all tags visible to the user."""
657 def __init__(self, parent_inode, inodes, api, num_retries, poll_time=60):
658 super(TagsDirectory, self).__init__(parent_inode, inodes)
660 self.num_retries = num_retries
662 self._poll_time = poll_time
666 with llfuse.lock_released:
667 tags = self.api.links().list(
668 filters=[['link_class', '=', 'tag']],
669 select=['name'], distinct=True
670 ).execute(num_retries=self.num_retries)
672 self.merge(tags['items'],
674 lambda a, i: a.tag == i['name'],
675 lambda i: TagDirectory(self.inode, self.inodes, self.api, self.num_retries, i['name'], poll=self._poll, poll_time=self._poll_time))
678 class TagDirectory(Directory):
679 """A special directory that contains as subdirectories all collections visible
680 to the user that are tagged with a particular tag.
683 def __init__(self, parent_inode, inodes, api, num_retries, tag,
684 poll=False, poll_time=60):
685 super(TagDirectory, self).__init__(parent_inode, inodes)
687 self.num_retries = num_retries
690 self._poll_time = poll_time
692 def want_event_subscribe(self):
697 with llfuse.lock_released:
698 taggedcollections = self.api.links().list(
699 filters=[['link_class', '=', 'tag'],
700 ['name', '=', self.tag],
701 ['head_uuid', 'is_a', 'arvados#collection']],
703 ).execute(num_retries=self.num_retries)
704 self.merge(taggedcollections['items'],
705 lambda i: i['head_uuid'],
706 lambda a, i: a.collection_locator == i['head_uuid'],
707 lambda i: CollectionDirectory(self.inode, self.inodes, self.api, self.num_retries, i['head_uuid']))
710 class ProjectDirectory(Directory):
711 """A special directory that contains the contents of a project."""
713 def __init__(self, parent_inode, inodes, api, num_retries, project_object,
714 poll=False, poll_time=60):
715 super(ProjectDirectory, self).__init__(parent_inode, inodes)
717 self.num_retries = num_retries
718 self.project_object = project_object
719 self.project_object_file = None
720 self.project_uuid = project_object['uuid']
722 self._poll_time = poll_time
723 self._updating_lock = threading.Lock()
724 self._current_user = None
726 def want_event_subscribe(self):
729 def createDirectory(self, i):
730 if collection_uuid_pattern.match(i['uuid']):
731 return CollectionDirectory(self.inode, self.inodes, self.api, self.num_retries, i)
732 elif group_uuid_pattern.match(i['uuid']):
733 return ProjectDirectory(self.inode, self.inodes, self.api, self.num_retries, i, self._poll, self._poll_time)
734 elif link_uuid_pattern.match(i['uuid']):
735 if i['head_kind'] == 'arvados#collection' or portable_data_hash_pattern.match(i['head_uuid']):
736 return CollectionDirectory(self.inode, self.inodes, self.api, self.num_retries, i['head_uuid'])
739 elif uuid_pattern.match(i['uuid']):
740 return ObjectFile(self.parent_inode, i)
745 return self.project_uuid
749 if self.project_object_file == None:
750 self.project_object_file = ObjectFile(self.inode, self.project_object)
751 self.inodes.add_entry(self.project_object_file)
755 if i['name'] is None or len(i['name']) == 0:
757 elif collection_uuid_pattern.match(i['uuid']) or group_uuid_pattern.match(i['uuid']):
758 # collection or subproject
760 elif link_uuid_pattern.match(i['uuid']) and i['head_kind'] == 'arvados#collection':
763 elif 'kind' in i and i['kind'].startswith('arvados#'):
765 return "{}.{}".format(i['name'], i['kind'][8:])
770 if isinstance(a, CollectionDirectory) or isinstance(a, ProjectDirectory):
771 return a.uuid() == i['uuid']
772 elif isinstance(a, ObjectFile):
773 return a.uuid() == i['uuid'] and not a.stale()
777 with llfuse.lock_released:
778 self._updating_lock.acquire()
782 if group_uuid_pattern.match(self.project_uuid):
783 self.project_object = self.api.groups().get(
784 uuid=self.project_uuid).execute(num_retries=self.num_retries)
785 elif user_uuid_pattern.match(self.project_uuid):
786 self.project_object = self.api.users().get(
787 uuid=self.project_uuid).execute(num_retries=self.num_retries)
789 contents = arvados.util.list_all(self.api.groups().contents,
790 self.num_retries, uuid=self.project_uuid)
792 # end with llfuse.lock_released, re-acquire lock
797 self.createDirectory)
799 self._updating_lock.release()
803 def __getitem__(self, item):
804 if item == '.arvados#project':
805 return self.project_object_file
807 return super(ProjectDirectory, self).__getitem__(item)
809 def __contains__(self, k):
810 if k == '.arvados#project':
813 return super(ProjectDirectory, self).__contains__(k)
818 with llfuse.lock_released:
819 if not self._current_user:
820 self._current_user = self.api.users().current().execute(num_retries=self.num_retries)
821 return self._current_user["uuid"] in self.project_object["writable_by"]
828 def mkdir(self, name):
830 with llfuse.lock_released:
831 self.api.collections().create(body={"owner_uuid": self.project_uuid,
833 "manifest_text": ""}).execute(num_retries=self.num_retries)
835 except apiclient_errors.Error as error:
837 raise llfuse.FUSEError(errno.EEXIST)
841 def rmdir(self, name):
843 raise llfuse.FUSEError(errno.ENOENT)
844 if not isinstance(self[name], CollectionDirectory):
845 raise llfuse.FUSEError(errno.EPERM)
846 if len(self[name]) > 0:
847 raise llfuse.FUSEError(errno.ENOTEMPTY)
848 with llfuse.lock_released:
849 self.api.collections().delete(uuid=self[name].uuid()).execute(num_retries=self.num_retries)
854 def rename(self, name_old, name_new, src):
855 if not isinstance(src, ProjectDirectory):
856 raise llfuse.FUSEError(errno.EPERM)
860 if not isinstance(ent, CollectionDirectory):
861 raise llfuse.FUSEError(errno.EPERM)
864 # POSIX semantics for replacing one directory with another is
865 # tricky (the target directory must be empty, the operation must be
866 # atomic which isn't possible with the Arvados API as of this
867 # writing) so don't support that.
868 raise llfuse.FUSEError(errno.EPERM)
870 self.api.collections().update(uuid=ent.uuid(),
871 body={"owner_uuid": self.uuid(),
872 "name": name_new}).execute(num_retries=self.num_retries)
874 # Acually move the entry from source directory to this directory.
875 del src._entries[name_old]
876 self._entries[name_new] = ent
877 self.inodes.invalidate_entry(src.inode, name_old.encode(self.inodes.encoding))
880 class SharedDirectory(Directory):
881 """A special directory that represents users or groups who have shared projects with me."""
883 def __init__(self, parent_inode, inodes, api, num_retries, exclude,
884 poll=False, poll_time=60):
885 super(SharedDirectory, self).__init__(parent_inode, inodes)
887 self.num_retries = num_retries
888 self.current_user = api.users().current().execute(num_retries=num_retries)
890 self._poll_time = poll_time
894 with llfuse.lock_released:
895 all_projects = arvados.util.list_all(
896 self.api.groups().list, self.num_retries,
897 filters=[['group_class','=','project']])
899 for ob in all_projects:
900 objects[ob['uuid']] = ob
904 for ob in all_projects:
905 if ob['owner_uuid'] != self.current_user['uuid'] and ob['owner_uuid'] not in objects:
907 root_owners[ob['owner_uuid']] = True
909 lusers = arvados.util.list_all(
910 self.api.users().list, self.num_retries,
911 filters=[['uuid','in', list(root_owners)]])
912 lgroups = arvados.util.list_all(
913 self.api.groups().list, self.num_retries,
914 filters=[['uuid','in', list(root_owners)]])
920 objects[l["uuid"]] = l
922 objects[l["uuid"]] = l
925 for r in root_owners:
929 contents[obr["name"]] = obr
930 #elif obr.get("username"):
931 # contents[obr["username"]] = obr
932 elif "first_name" in obr:
933 contents[u"{} {}".format(obr["first_name"], obr["last_name"])] = obr
937 if r['owner_uuid'] not in objects:
938 contents[r['name']] = r
940 # end with llfuse.lock_released, re-acquire lock
943 self.merge(contents.items(),
945 lambda a, i: a.uuid() == i[1]['uuid'],
946 lambda i: ProjectDirectory(self.inode, self.inodes, self.api, self.num_retries, i[1], poll=self._poll, poll_time=self._poll_time))