Пример #1
0
    def __apply_change(self, change_id, change_tuple):
        """Apply changes to our filesystem reported by GD. All we do is remove 
        the current record components, if it's valid, and then reload it with 
        what we were given. Note that since we don't necessarily know
        about the entries that have been changed, this also allows us to slowly
        increase our knowledge of the filesystem (of, obviously, only those 
        things that change).
        """

        (entry_id, was_deleted, entry) = change_tuple

        is_visible = entry.is_visible if entry else None

        _logger.debug(
            "Applying change with change-ID (%d), entry-ID [%s], "
            "and is-visible of [%s]", change_id, entry_id, is_visible)

        # First, remove any current knowledge from the system.

        _logger.debug(
            "Removing all trace of entry with ID [%s] "
            "(apply_change).", entry_id)

        PathRelations.get_instance().remove_entry_all(entry_id)

        # If it wasn't deleted, add it back.

        _logger.debug("Registering changed entry with ID [%s].", entry_id)

        if is_visible:
            path_relations = PathRelations.get_instance()
            path_relations.register_entry(entry)
Пример #2
0
    def __apply_change(self, change_id, change_tuple):
        """Apply changes to our filesystem reported by GD. All we do is remove 
        the current record components, if it's valid, and then reload it with 
        what we were given. Note that since we don't necessarily know
        about the entries that have been changed, this also allows us to slowly
        increase our knowledge of the filesystem (of, obviously, only those 
        things that change).
        """

        (entry_id, was_deleted, entry) = change_tuple
        
        is_visible = entry.is_visible if entry else None

        _logger.debug("Applying change with change-ID (%d), entry-ID [%s], "
                      "and is-visible of [%s]",
                      change_id, entry_id, is_visible)

        # First, remove any current knowledge from the system.

        _logger.debug("Removing all trace of entry with ID [%s] "
                      "(apply_change).", entry_id)

        PathRelations.get_instance().remove_entry_all(entry_id)

        # If it wasn't deleted, add it back.

        _logger.debug("Registering changed entry with ID [%s].", entry_id)

        if is_visible:
            path_relations = PathRelations.get_instance()
            path_relations.register_entry(entry)
Пример #3
0
    def rename(self, filepath_old, filepath_new):
        # Make sure the old filepath exists.
        (entry, path, filename_old) = get_entry_or_raise(filepath_old)

        # At this point, decorations, the is-hidden prefix, etc.. haven't been
        # stripped.
        (path, filename_new_raw) = split(filepath_new)

        # Make sure the new filepath doesn't exist.

        try:
            get_entry_or_raise(filepath_new, True)
        except GdNotFoundError:
            pass

        gd = get_gdrive()

        try:
            entry = gd.rename(entry, filename_new_raw)
        except:
            _logger.exception("Could not update entry [%s] for rename.", entry)
            raise FuseOSError(EIO)

        # Update our knowledge of the entry.

        path_relations = PathRelations.get_instance()

        try:
            path_relations.register_entry(entry)
        except:
            _logger.exception("Could not register renamed entry: %s", entry)
            raise FuseOSError(EIO)
Пример #4
0
    def rmdir(self, filepath):
        """Remove a directory."""

        path_relations = PathRelations.get_instance()

        try:
            entry_clause = path_relations.get_clause_from_path(filepath)
        except GdNotFoundError:
            _logger.exception("Could not process [%s] (rmdir).", filepath)
            raise FuseOSError(ENOENT)
        except:
            _logger.exception("Could not get clause from file-path [%s] "
                              "(rmdir).", filepath)
            raise FuseOSError(EIO)

        if not entry_clause:
            _logger.error("Path [%s] does not exist for rmdir().", filepath)
            raise FuseOSError(ENOENT)

        entry_id = entry_clause[CLAUSE_ID]
        normalized_entry = entry_clause[CLAUSE_ENTRY]

        # Check if not a directory.

        if not normalized_entry.is_directory:
            _logger.error("Can not rmdir() non-directory [%s] with ID [%s].", 
                          filepath, entry_id)

            raise FuseOSError(ENOTDIR)

        # Ensure the folder is empty.

        gd = get_gdrive()

        try:
            found = gd.get_children_under_parent_id(
                        entry_id,
                        max_results=1)
        except:
            _logger.exception("Could not determine if directory to be removed "
                              "has children.", entry_id)

            raise FuseOSError(EIO)

        if found:
            raise FuseOSError(ENOTEMPTY)

        try:
            gd.remove_entry(normalized_entry)
        except (NameError):
            raise FuseOSError(ENOENT)
        except:
            _logger.exception("Could not remove directory [%s] with ID [%s].",
                              filepath, entry_id)

            raise FuseOSError(EIO)
Пример #5
0
    def __create(self, filepath, mode=None):
        """Create a new file.
                
        We don't implement "mode" (permissions) because the model doesn't agree 
        with GD.
        """

# TODO: Fail if it already exists.

        try:
            result = split_path(filepath, path_resolver)
            (parent_clause, path, filename, mime_type, is_hidden) = result
        except GdNotFoundError:
            _logger.exception("Could not process [%s] (i-create).", filepath)
            raise FuseOSError(ENOENT)
        except:
            _logger.exception("Could not split path [%s] (i-create).",
                              filepath)
            raise FuseOSError(EIO)

        if mime_type is None:
            _, ext = os.path.splitext(filename)
            if ext != '':
                ext = ext[1:]

            mime_type = utility.get_first_mime_type_by_extension(ext)

        distilled_filepath = build_filepath(path, filename)

        gd = get_gdrive()

        try:
            entry = gd.create_file(
                        filename, 
                        [parent_clause[3]], 
                        mime_type,
                        is_hidden=is_hidden)
        except:
            _logger.exception("Could not create empty file [%s] under "
                              "parent with ID [%s].",
                              filename, parent_clause[3])

            raise FuseOSError(EIO)

        path_relations = PathRelations.get_instance()

        try:
            path_relations.register_entry(entry)
        except:
            _logger.exception("Could not register created file in cache.")
            raise FuseOSError(EIO)

        _logger.info("Inner-create of [%s] completed.", distilled_filepath)

        return (entry, path, filename, mime_type)
Пример #6
0
    def readdir(self, path, offset):
        """A generator returning one base filename at a time."""

        # We expect "offset" to always be (0).
        if offset != 0:
            _logger.warning(
                "readdir() has been invoked for path [%s] and "
                "non-zero offset (%d). This is not allowed.", path, offset)

# TODO: Once we start working on the cache, make sure we don't make this call,
#       constantly.

        path_relations = PathRelations.get_instance()

        try:
            entry_clause = path_relations.get_clause_from_path(path)
        except GdNotFoundError:
            _logger.exception("Could not process [%s] (readdir).")
            raise FuseOSError(ENOENT)
        except:
            _logger.exception("Could not get clause from path [%s] "
                              "(readdir)." % (path))
            raise FuseOSError(EIO)

        if not entry_clause:
            raise FuseOSError(ENOENT)

        try:
            entry_tuples = path_relations.get_children_entries_from_entry_id \
                            (entry_clause[CLAUSE_ID])
        except:
            _logger.exception(
                "Could not render list of filenames under path "
                "[%s].", path)

            raise FuseOSError(EIO)

        yield utility.translate_filename_charset('.')
        yield utility.translate_filename_charset('..')

        for (filename, entry) in entry_tuples:

            # Decorate any file that -requires- a mime-type (all files can
            # merely accept a mime-type)
            if entry.requires_mimetype:
                filename += utility.translate_filename_charset('#')

            yield (filename, self.__build_stat_from_entry(entry), 0)
Пример #7
0
    def flush(self):
        """The OS wants to effect any changes made to the file."""

        _LOGGER.debug("Flushing opened-file.")

        entry = self.__cache.get(self.__entry_id)

        if self.__is_dirty is False:
            _LOGGER.debug(
                "Flush will be skipped for [%s] because there "
                "are no changes: [%s] IS_LOADED=[%s] "
                "IS_DIRTY=[%d]", entry.id, self.file_path, self.__is_loaded,
                self.__is_dirty)
            return
        else:
            st = os.stat(self.__temp_filepath)

            _LOGGER.debug(
                "Pushing (%d) bytes for entry with ID from [%s] to "
                "GD for file-path [%s].", st.st_size, entry.id,
                self.__temp_filepath)

            # TODO: Make sure we sync the mtime to remote.
            gd = get_gdrive()
            entry = gd.update_entry(entry,
                                    filename=entry.title,
                                    data_filepath=self.__temp_filepath,
                                    mime_type=self.mime_type,
                                    parents=entry.parents,
                                    is_hidden=self.__is_hidden)

            self.__is_dirty = False

            # TODO(dustin): For now, we don't cleanup the temporary file. We need to
            #               schedule this using LRU-semantics.

            # Immediately update our current cached entry.

            _LOGGER.debug("Update successful. Updating local cache.")

            path_relations = PathRelations.get_instance()
            path_relations.register_entry(entry)

            _LOGGER.info("Update complete on entry with ID [%s].", entry.id)
Пример #8
0
def get_entry_or_raise(raw_path, allow_normal_for_missing=False):
    try:
        result = split_path(raw_path, path_resolver)
        (parent_clause, path, filename, mime_type, is_hidden) = result
    except GdNotFoundError:
        _logger.exception("Could not retrieve clause for non-existent "
                          "file-path [%s] (parent does not exist)." % 
                          (raw_path))

        if allow_normal_for_missing is True:
            raise
        else:
            raise FuseOSError(ENOENT)
    except:
        _logger.exception("Could not process file-path [%s]." % 
                          (raw_path))
        raise FuseOSError(EIO)

    filepath = build_filepath(path, filename)
    path_relations = PathRelations.get_instance()

    try:
        entry_clause = path_relations.get_clause_from_path(filepath)
    except GdNotFoundError:
        _logger.exception("Could not retrieve clause for non-existent "
                          "file-path [%s] (parent exists)." % 
                          (filepath))

        if allow_normal_for_missing is True:
            raise
        else:
            raise FuseOSError(ENOENT)
    except:
        _logger.exception("Could not retrieve clause for path [%s]. " %
                          (filepath))
        raise FuseOSError(EIO)

    if not entry_clause:
        if allow_normal_for_missing is True:
            raise GdNotFoundError()
        else:
            raise FuseOSError(ENOENT)

    return (entry_clause[CLAUSE_ENTRY], path, filename)
Пример #9
0
    def flush(self):
        """The OS wants to effect any changes made to the file."""

        _LOGGER.debug("Flushing opened-file.")

        entry = self.__cache.get(self.__entry_id)

        if self.__is_dirty is False:
            _LOGGER.debug("Flush will be skipped for [%s] because there "
                          "are no changes: [%s] IS_LOADED=[%s] "
                          "IS_DIRTY=[%d]", 
                          entry.id, self.file_path, self.__is_loaded, 
                          self.__is_dirty)
            return
        else:
            st = os.stat(self.__temp_filepath)

            _LOGGER.debug("Pushing (%d) bytes for entry with ID from [%s] to "
                          "GD for file-path [%s].",
                          st.st_size, entry.id, self.__temp_filepath)

# TODO: Make sure we sync the mtime to remote.
            gd = get_gdrive()
            entry = gd.update_entry(
                        entry, 
                        filename=entry.title, 
                        data_filepath=self.__temp_filepath, 
                        mime_type=self.mime_type, 
                        parents=entry.parents, 
                        is_hidden=self.__is_hidden)

            self.__is_dirty = False

# TODO(dustin): For now, we don't cleanup the temporary file. We need to 
#               schedule this using LRU-semantics.

            # Immediately update our current cached entry.

            _LOGGER.debug("Update successful. Updating local cache.")

            path_relations = PathRelations.get_instance()
            path_relations.register_entry(entry)

            _LOGGER.info("Update complete on entry with ID [%s].", entry.id)
Пример #10
0
    def mkdir(self, filepath, mode):
        """Create the given directory."""

# TODO: Implement the "mode".

        try:
            result = split_path(filepath, path_resolver)
            (parent_clause, path, filename, mime_type, is_hidden) = result
        except GdNotFoundError:
            _logger.exception("Could not process [%s] (mkdir).", filepath)
            raise FuseOSError(ENOENT)
        except:
            _logger.exception("Could not split path [%s] (mkdir).", filepath)
            raise FuseOSError(EIO)

        parent_id = parent_clause[CLAUSE_ID]
        gd = get_gdrive()

        try:
            entry = gd.create_directory(
                        filename, 
                        [parent_id], 
                        is_hidden=is_hidden)
        except:
            _logger.exception("Could not create directory with name [%s] "
                              "and parent with ID [%s].",
                              filename, parent_clause[0].id)
            raise FuseOSError(EIO)

        _logger.info("Directory [%s] created as ID [%s] under parent with "
                     "ID [%s].", filepath, entry.id, parent_id)

        #parent_clause[4] = False

        path_relations = PathRelations.get_instance()

        try:
            path_relations.register_entry(entry)
        except:
            _logger.exception("Could not register new directory in cache.")
            raise FuseOSError(EIO)
Пример #11
0
def create_for_existing_filepath(filepath):
    """Process the file/path that was requested (potential export-type
    directive, dot-prefix, etc..), and build an opened-file object using
    the information.
    """

    _LOGGER.debug("Creating OpenedFile for [%s].", filepath)

    # Process/distill the requested file-path.

    try:
        result = split_path(filepath, path_resolver)
    except GdNotFoundError:
        _LOGGER.exception("Could not process [%s] (create_for_requested).",
                          filepath)

        raise fuse.FuseOSError(ENOENT)

    (parent_clause, path, filename, mime_type, is_hidden) = result
    distilled_filepath = build_filepath(path, filename)

    # Look-up the requested entry.

    path_relations = PathRelations.get_instance()

    try:
        entry_clause = path_relations.get_clause_from_path(distilled_filepath)
    except:
        _LOGGER.exception(
            "Could not try to get clause from path [%s] "
            "(OpenedFile).", distilled_filepath)

        raise fuse.FuseOSError(EIO)

    if not entry_clause:
        _LOGGER.debug("Path [%s] does not exist for stat().", path)
        raise fuse.FuseOSError(ENOENT)

    entry = entry_clause[CLAUSE_ENTRY]

    # Normalize the mime-type by considering what's available for download.
    # We're going to let the requests that didn't provide a mime-type fail
    # right here. It will give us the opportunity to try a few options to
    # get the file.

    try:
        final_mimetype = entry.normalize_download_mimetype(mime_type)
    except ExportFormatError:
        _LOGGER.exception("There was an export-format error "
                          "(create_for_requested_filesystem).")

        raise fuse.FuseOSError(ENOENT)
    except:
        _LOGGER.exception(
            "Could not normalize mime-type [%s] for entry"
            "[%s].", mime_type, entry)

        raise fuse.FuseOSError(EIO)

    if final_mimetype != mime_type:
        _LOGGER.info(
            "Entry being opened will be opened as [%s] rather "
            "than [%s].", final_mimetype, mime_type)

    # Build the object.

    return OpenedFile(entry_clause[CLAUSE_ID], path, filename, is_hidden,
                      final_mimetype)
Пример #12
0
    def unlink(self, file_path):
        """Remove a file."""
# TODO: Change to simply move to "trash". Have a FUSE option to elect this
# behavior.
        path_relations = PathRelations.get_instance()

        try:
            entry_clause = path_relations.get_clause_from_path(file_path)
        except GdNotFoundError:
            _logger.exception("Could not process [%s] (unlink).", file_path)
            raise FuseOSError(ENOENT)
        except:
            _logger.exception("Could not get clause from file-path [%s] "
                              "(unlink).", file_path)

            raise FuseOSError(EIO)

        if not entry_clause:
            _logger.error("Path [%s] does not exist for unlink().",
                          file_path)

            raise FuseOSError(ENOENT)

        entry_id = entry_clause[CLAUSE_ID]
        normalized_entry = entry_clause[CLAUSE_ENTRY]

        # Check if a directory.

        if normalized_entry.is_directory:
            _logger.error("Can not unlink() directory [%s] with ID [%s]. "
                          "Must be file.", file_path, entry_id)

            raise FuseOSError(errno.EISDIR)

        # Remove online. Complements local removal (if not found locally, a 
        # follow-up request checks online).

        gd = get_gdrive()

        try:
            gd.remove_entry(normalized_entry)
        except NameError:
            raise FuseOSError(ENOENT)
        except:
            _logger.exception("Could not remove file [%s] with ID [%s].",
                              file_path, entry_id)

            raise FuseOSError(EIO)

        # Remove from cache. Will no longer be able to be found, locally.
        PathRelations.get_instance().remove_entry_all(entry_id)

        # Remove from among opened-files.

        om = gdrivefs.opened_file.get_om()

        try:
            opened_file = om.remove_by_filepath(file_path)
        except:
            _logger.exception("There was an error while removing all "
                                 "opened-file instances for file [%s] "
                                 "(remove).", file_path)
            raise FuseOSError(EIO)
Пример #13
0
    def readdir(self, path, offset):
        """A generator returning one base filename at a time."""

        # We expect "offset" to always be (0).
        if offset != 0:
            _logger.warning("readdir() has been invoked for path [%s] and "
                            "non-zero offset (%d). This is not allowed.",
                            path, offset)

# TODO: Once we start working on the cache, make sure we don't make this call, 
#       constantly.

        path_relations = PathRelations.get_instance()

        try:
            entry_clause = path_relations.get_clause_from_path(path)
        except GdNotFoundError:
            _logger.exception("Could not process [%s] (readdir).")
            raise FuseOSError(ENOENT)
        except:
            _logger.exception("Could not get clause from path [%s] "
                              "(readdir)." % (path))
            raise FuseOSError(EIO)

        if not entry_clause:
            raise FuseOSError(ENOENT)

        try:
            entry_tuples = path_relations.get_children_entries_from_entry_id \
                            (entry_clause[CLAUSE_ID])
        except:
            _logger.exception("Could not render list of filenames under path "
                              "[%s].", path)

            raise FuseOSError(EIO)

        # Yield filenames.
        yield utility.translate_filename_charset('.')
        yield utility.translate_filename_charset('..')

        # Yield filenames with stat information.

        for (filename, entry) in entry_tuples:
            # Decorate any file that -requires- a mime-type (all files can 
            # merely accept a mime-type)
            if entry.requires_mimetype:
                filename += utility.translate_filename_charset('#')

# TODO(dustin): We get an "Input/output error" from the FUSE library when there's a slash in the name. We're not sure what we can do here. https://github.com/fusepy/fusepy/issues/133
            if '/' in filename:
                _logger.warning("Skipping entry with slash in the name: "
                                "[{}]".format(path))
                continue

            attrs = self.__build_stat_from_entry(entry)

            ye = _YIELDED_ENTRY(
                    filename=filename,
                    attrs=attrs,
                    offset=0)

            yield ye
Пример #14
0
def create_for_existing_filepath(filepath):
    """Process the file/path that was requested (potential export-type 
    directive, dot-prefix, etc..), and build an opened-file object using 
    the information.
    """

    _LOGGER.debug("Creating OpenedFile for [%s].", filepath)

    # Process/distill the requested file-path.

    try:
        result = split_path(filepath, path_resolver)
    except GdNotFoundError:
        _LOGGER.exception("Could not process [%s] (create_for_requested).",
                          filepath)

        raise fuse.FuseOSError(ENOENT)

    (parent_clause, path, filename, mime_type, is_hidden) = result
    distilled_filepath = build_filepath(path, filename)

    # Look-up the requested entry.

    path_relations = PathRelations.get_instance()

    try:
        entry_clause = path_relations.get_clause_from_path(
                        distilled_filepath)
    except:
        _LOGGER.exception("Could not try to get clause from path [%s] "
                          "(OpenedFile).", distilled_filepath)

        raise fuse.FuseOSError(EIO)

    if not entry_clause:
        _LOGGER.debug("Path [%s] does not exist for stat().", path)
        raise fuse.FuseOSError(ENOENT)

    entry = entry_clause[CLAUSE_ENTRY]

    # Normalize the mime-type by considering what's available for download. 
    # We're going to let the requests that didn't provide a mime-type fail 
    # right here. It will give us the opportunity to try a few options to 
    # get the file.

    try:
        final_mimetype = entry.normalize_download_mimetype(mime_type)
    except ExportFormatError:
        _LOGGER.exception("There was an export-format error "
                          "(create_for_requested_filesystem).")

        raise fuse.FuseOSError(ENOENT)
    except:
        _LOGGER.exception("Could not normalize mime-type [%s] for entry"
                          "[%s].", mime_type, entry)

        raise fuse.FuseOSError(EIO)

    if final_mimetype != mime_type:
        _LOGGER.info("Entry being opened will be opened as [%s] rather "
                     "than [%s].", final_mimetype, mime_type)

    # Build the object.

    return OpenedFile(
            entry_clause[CLAUSE_ID], 
            path, 
            filename, 
            is_hidden, 
            final_mimetype)