Skip to content

Jumpaku/go-drivefs

Repository files navigation

drivefs

Go Reference License: BSD-2-Clause

A Go module that provides a file system-like interface for Google Drive operations.

Overview

drivefs provides a convenient and intuitive Go API for managing files and directories in Google Drive. It wraps the google.golang.org/api/drive/v3 package and offers familiar filesystem operations such as creating, reading, writing, copying, renaming, moving, and deleting files and directories. The package fully supports both My Drive and Shared Drives.

Installation

go get github.com/Jumpaku/go-drivefs

Requirements:

  • Go 1.24.10 or later
  • An authenticated Google Drive API *drive.Service

Quick Start

package main

import (
    "fmt"
    "log"
    "github.com/Jumpaku/go-drivefs"
    "google.golang.org/api/drive/v3"
)

func main() {
    // Assuming you have an authenticated drive.Service
    // See Authentication section for setup details
    var service *drive.Service // Your authenticated service
    
    // Create DriveFS instance
    driveFS := drivefs.New(service)
    
    // Get the root folder (My Drive)
    rootID := drivefs.FileID("root")
    
    // Create a directory
    dirInfo, err := driveFS.MkdirAll(rootID, "/my-project/data")
    if err != nil {
        log.Fatal(err)
    }
    
    // Create and write a file
    fileInfo, err := driveFS.Create(dirInfo.ID, "notes.txt")
    if err != nil {
        log.Fatal(err)
    }
    
    err = driveFS.WriteFile(fileInfo.ID, []byte("Hello from drivefs!"))
    if err != nil {
        log.Fatal(err)
    }
    
    // Read the file back
    data, err := driveFS.ReadFile(fileInfo.ID)
    if err != nil {
        log.Fatal(err)
    }
    
    fmt.Println(string(data)) // Output: Hello from drivefs!
}

Usage

package main

import (
	"context"
	"fmt"
	"log"

	"github.com/Jumpaku/go-drivefs"
	"google.golang.org/api/drive/v3"
	"google.golang.org/api/option"
)

func main() {
	ctx := context.Background()

	// Create a drive.Service (authentication setup required)
	// For authentication examples, see:
	// https://developers.google.com/drive/api/v3/quickstart/go
	service, err := drive.NewService(ctx /* add your auth options here */)
	if err != nil {
		log.Fatal(err)
	}

	// Create a new DriveFS instance
	driveFS := drivefs.New(service)

	// Get the root folder ID
	// Use "root" for the user's My Drive root, or provide a specific folder ID
	// for a Shared Drive or subdirectory
	rootID := drivefs.FileID("root")

	// Create a directory structure
	// MkdirAll creates all directories along the path if they don't exist
	dirInfo, err := driveFS.MkdirAll(rootID, "/path/to/directory")
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("Created directory: %s (ID: %s)\n", dirInfo.Name, dirInfo.ID)

	// Create a file in the directory
	fileInfo, err := driveFS.Create(dirInfo.ID, "example.txt")
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("Created file: %s (ID: %s)\n", fileInfo.Name, fileInfo.ID)

	// Write content to the file
	err = driveFS.WriteFile(fileInfo.ID, []byte("Hello, Google Drive!"))
	if err != nil {
		log.Fatal(err)
	}

	// Read the file content
	data, err := driveFS.ReadFile(fileInfo.ID)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("File content:", string(data))

	// Get file metadata
	info, err := driveFS.Info(fileInfo.ID)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("File: %s, Size: %d bytes, Modified: %v\n", info.Name, info.Size, info.ModTime)

	// List directory contents
	entries, err := driveFS.ReadDir(dirInfo.ID)
	if err != nil {
		log.Fatal(err)
	}
	for _, entry := range entries {
		fmt.Printf("- %s (folder: %v, ID: %s)\n", entry.Name, entry.IsFolder(), entry.ID)
	}

	// Find files by path
	// Returns all matching files (multiple if duplicates exist at any level)
	resolvedInfos, err := driveFS.FindByPath(rootID, "/path/to/directory/example.txt")
	if err != nil {
		log.Fatal(err)
	}
	for _, info := range resolvedInfos {
		fmt.Printf("Resolved: %s (ID: %s)\n", info.Name, info.ID)
	}

	// Get the full path from a file ID
	// Note: This returns a path without a root prefix
	path, err := driveFS.ResolvePath(fileInfo.ID)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("Path: %s\n", path)

	// Copy a file to a different directory
	newParentInfo, err := driveFS.MkdirAll(rootID, "/new/location")
	if err != nil {
		log.Fatal(err)
	}
	copiedFileInfo, err := driveFS.Copy(fileInfo.ID, newParentInfo.ID, "example_copy.txt")
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("Copied file: %s (ID: %s)\n", copiedFileInfo.Name, copiedFileInfo.ID)

	// Rename a file
	renamedFileInfo, err := driveFS.Rename(fileInfo.ID, "renamed_example.txt")
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("Renamed file: %s\n", renamedFileInfo.Name)

	// Move a file to a different directory
	err = driveFS.Move(fileInfo.ID, newParentInfo.ID)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("File moved successfully")

	// Walk the file tree
	// Traverses all files and directories recursively
	err = driveFS.Walk(rootID, func(path drivefs.Path, info drivefs.FileInfo) error {
		fmt.Printf("%s: %s (ID: %s)\n", path, info.Name, info.ID)
		return nil
	})
	if err != nil {
		log.Fatal(err)
	}

	// Manage permissions
	// List current permissions
	permissions, err := driveFS.PermList(fileInfo.ID)
	if err != nil {
		log.Fatal(err)
	}
	for _, perm := range permissions {
		fmt.Printf("Permission ID: %s, Role: %s\n", perm.ID(), perm.Role())
	}

	// Grant read access to a user
	_, err = driveFS.PermSet(fileInfo.ID, drivefs.UserPermission("user@example.com", drivefs.RoleReader))
	if err != nil {
		log.Fatal(err)
	}

	// Grant write access to a group
	_, err = driveFS.PermSet(fileInfo.ID, drivefs.GroupPermission("group@example.com", drivefs.RoleWriter))
	if err != nil {
		log.Fatal(err)
	}

	// Grant read access to anyone with the link
	_, err = driveFS.PermSet(fileInfo.ID, drivefs.AnyonePermission(drivefs.RoleReader, false))
	if err != nil {
		log.Fatal(err)
	}

	// Remove a user's permission
	_, err = driveFS.PermDel(fileInfo.ID, drivefs.User("user@example.com"))
	if err != nil {
		log.Fatal(err)
	}

	// Remove a file (move to trash)
	err = driveFS.Remove(fileInfo.ID, true)
	if err != nil {
		log.Fatal(err)
	}

	// Permanently delete a file (skip trash)
	err = driveFS.Remove(copiedFileInfo.ID, false)
	if err != nil {
		log.Fatal(err)
	}

	// Remove a directory and all its contents
	err = driveFS.RemoveAll(dirInfo.ID, true)
	if err != nil {
		log.Fatal(err)
	}
}

API Reference

DriveFS

The main type for interacting with Google Drive.

Constructor

func New(service *drive.Service) *DriveFS

Creates a new DriveFS instance with the given Google Drive service.

  • The service parameter should be an authenticated *drive.Service from the Google Drive API
  • This constructor does not take a root ID; root IDs are passed to individual methods as needed
  • Returns a new DriveFS instance ready to perform operations

Directory Operations

func (s *DriveFS) MkdirAll(rootID FileID, path Path) (FileInfo, error)

Creates all directories along the given path if they do not already exist.

  • rootID: The starting point folder ID (use "root" for My Drive root, or a specific folder ID)
  • path: Must be absolute (start with /)
  • Returns the FileInfo of the final directory in the path
  • If multiple directories with the same name exist at any level, returns ErrAlreadyExists
func (s *DriveFS) Mkdir(parentID FileID, name string) (FileInfo, error)

Creates a single directory with the given name under the specified parent.

  • Creates a new directory even if one with the same name already exists (Google Drive allows duplicates)

File Operations

func (s *DriveFS) Create(parentID FileID, name string) (FileInfo, error)

Creates a new empty file in the specified parent directory.

  • Creates a new file even if one with the same name already exists (Google Drive allows duplicates)
func (s *DriveFS) ReadFile(fileID FileID) ([]byte, error)

Reads and returns the entire contents of a file.

  • Returns ErrNotReadable for Google Apps files (Docs, Sheets, Slides, etc.)
  • For Google Apps files, use the Drive API export functionality instead
func (s *DriveFS) WriteFile(fileID FileID, data []byte) error

Writes data to an existing file, completely replacing its contents.

func (s *DriveFS) Shortcut(parentID FileID, name string, targetID FileID) (FileInfo, error)

Creates a shortcut (link) to another file or directory.

  • parentID: The directory where the shortcut will be created
  • name: The name of the shortcut
  • targetID: The ID of the file or directory that the shortcut points to
  • Returns the FileInfo of the created shortcut

Metadata and Navigation

func (s *DriveFS) Info(fileID FileID) (FileInfo, error)

Returns the FileInfo (metadata) for the file or directory with the given ID.

func (s *DriveFS) ReadDir(fileID FileID) ([]FileInfo, error)

Lists all files and directories directly within the specified directory.

  • Does not include trashed items
  • Returns only immediate children (not recursive)
func (s *DriveFS) FindByPath(rootID FileID, path Path) ([]FileInfo, error)

Resolves an absolute path (relative to the specified root) and returns all matching FileInfo objects.

  • rootID: The starting point folder ID (use "root" for My Drive root, or a specific folder ID)
  • path: Must be absolute (start with /)
  • Returns multiple results if there are duplicate files/folders with the same name at any level
  • Returns an empty slice if the path does not exist
func (s *DriveFS) ResolvePath(fileID FileID) (Path, error)

Returns the absolute path from the root to the file or directory with the given ID.

  • Returns ErrMultiParentsNotSupported if the file has multiple parents
  • The path is built by traversing up to the topmost parent (the file with no parents) and returns an absolute path starting with /
func (s *DriveFS) Query(query string) ([]FileInfo, error)

Executes a Google Drive API query and returns all matching files and directories.

  • query: A Google Drive API query string (see Search for files)
  • Returns a slice of FileInfo objects for all matching items
  • Useful for advanced searches that go beyond simple path-based lookups

File System Manipulation

func (s *DriveFS) Copy(fileID, newParentID FileID, newName string) (FileInfo, error)

Creates a copy of the file in the specified new parent directory with the provided new name.

  • Creates a new file with a new ID
  • Original file remains unchanged
func (s *DriveFS) Rename(fileID FileID, newName string) (FileInfo, error)

Renames the file or directory to the specified new name.

  • Does not change the file's parent or location
func (s *DriveFS) Move(fileID, newParentID FileID) error

Moves a file or directory to a new parent directory.

  • Removes all existing parents and sets the new parent
  • Does not change the file's name
func (s *DriveFS) Remove(fileID FileID, moveToTrash bool) error

Removes a file or directory.

  • If moveToTrash is true, the item is moved to Google Drive trash
  • If moveToTrash is false, the item is permanently deleted
  • Returns ErrNotRemovable if attempting to remove a non-empty directory
  • For non-empty directories, use RemoveAll instead
func (s *DriveFS) RemoveAll(fileID FileID, moveToTrash bool) error

Removes a file or directory and all its contents recursively.

  • If moveToTrash is true, items are moved to Google Drive trash
  • If moveToTrash is false, items are permanently deleted
  • Safe to use on both files and directories

Tree Walking

func (s *DriveFS) Walk(rootID FileID, f func(Path, FileInfo) error) error

Walks the file tree rooted at the specified file or directory, calling the provided function for each item.

  • rootID: The starting point folder ID to begin walking from
  • Includes the root item itself (passed as "/" to the callback)
  • The function receives both the path (starting from "/" for the root item, then its children as "/childname", etc.) and FileInfo for each item
  • If the callback function returns an error, walking stops and that error is returned

Permission Management

func (s *DriveFS) PermList(fileID FileID) ([]Permission, error)

Lists all permissions for the file or directory with the given ID.

  • Returns a slice of Permission objects representing all users, groups, domains, or anyone who has access
  • Each Permission contains information about the grantee, role, and whether file discovery is allowed
func (s *DriveFS) PermSet(fileID FileID, permission Permission) ([]Permission, error)

Sets or updates a permission for the file or directory with the given ID.

  • If a permission for the specified grantee already exists, it will be updated
  • If no permission exists for the grantee, a new one will be created
  • Returns the updated list of all permissions for the file
  • Use helper functions like UserPermission(), GroupPermission(), DomainPermission(), or AnyonePermission() to create Permission objects
func (s *DriveFS) PermDel(fileID FileID, grantee Grantee) ([]Permission, error)

Deletes all permissions matching the specified grantee for the file or directory.

  • Removes permissions for the specified user, group, domain, or anyone access
  • Returns the updated list of remaining permissions for the file
  • Use helper functions like User(), Group(), Domain(), or Anyone() to create Grantee objects

Types

FileID

type FileID string

A unique identifier for a file or directory in Google Drive. These IDs are assigned by Google Drive and remain stable across renames and moves.

Path

type Path string

An absolute path string representing a location in the Drive filesystem.

  • Must start with / (absolute path required)
  • Relative path components (. and ..) are not allowed
  • Path separators are forward slashes (/)
  • Example: "/folder/subfolder/file.txt"

FileInfo

type FileInfo struct {
    Name           string    // File or directory name
    ID             FileID    // Unique Google Drive ID
    Size           int64     // File size in bytes (0 for directories)
    Mime           string    // MIME type (e.g., "text/plain", "application/vnd.google-apps.folder")
    ModTime        time.Time // Last modification time
    ShortcutTarget FileID    // Target file ID (for shortcuts only, empty otherwise)
    WebViewLink    string    // URL to view the file in the Google Drive web interface
}

Contains metadata about a file or directory.

Methods:

func (i FileInfo) IsFolder() bool

Returns true if the item is a folder/directory.

func (i FileInfo) IsShortcut() bool

Returns true if the item is a shortcut to another file or directory. The target file ID can be found in the ShortcutTarget field.

func (i FileInfo) IsAppFile() bool

Returns true if the item is a Google Apps file (e.g., Google Docs, Sheets, Slides). Google Apps files cannot be read with ReadFile() and must be exported using the Drive API's export functionality.

Permission

type Permission interface {
    ID() PermissionID
    Grantee() Grantee
    Role() Role
    AllowFileDiscovery() bool
}

Represents a permission granted to a user, group, domain, or anyone for a file or directory.

Helper Functions to Create Permissions:

func UserPermission(email string, role Role) Permission

Creates a permission for a specific user identified by email address.

func GroupPermission(email string, role Role) Permission

Creates a permission for a Google Group identified by email address.

func DomainPermission(domain string, role Role, allowFileDiscovery bool) Permission

Creates a permission for an entire domain (e.g., "example.com").

func AnyonePermission(role Role, allowFileDiscovery bool) Permission

Creates a permission that grants access to anyone with the link.

Grantee

type Grantee interface {
    // Sealed interface - cannot be implemented outside the package
}

Represents the recipient of a permission (user, group, domain, or anyone).

Helper Functions to Create Grantees:

func User(email string) Grantee

Creates a grantee representing a specific user.

func Group(email string) Grantee

Creates a grantee representing a Google Group.

func Domain(domain string) Grantee

Creates a grantee representing an entire domain.

func Anyone() Grantee

Creates a grantee representing anyone with the link.

Concrete Grantee Types:

  • GranteeUser - Represents a user with an Email field
  • GranteeGroup - Represents a group with an Email field
  • GranteeDomain - Represents a domain with a Domain field
  • GranteeAnyone - Represents public access (anyone with the link)

Role

type Role string

Represents the level of access granted by a permission.

Available Roles:

const (
    RoleOwner         Role = "owner"         // Full ownership with ability to delete
    RoleOrganizer     Role = "organizer"     // Can organize files in Shared Drives
    RoleFileOrganizer Role = "fileOrganizer" // Can organize files
    RoleWriter        Role = "writer"        // Can edit files and add comments
    RoleCommenter     Role = "commenter"     // Can view and add comments
    RoleReader        Role = "reader"        // Can only view files
)

PermissionID

type PermissionID string

A unique identifier for a permission. These IDs are assigned by Google Drive and are used to update or delete specific permissions.

Errors

The package defines the following error constants that can be checked using errors.Is():

var (
    ErrInvalidPath              error // Invalid path format
    ErrDriveError               error // Google Drive API error
    ErrIOError                  error // I/O operation error
    ErrNotFound                 error // File or directory not found
    ErrAlreadyExists            error // File or directory already exists
    ErrMultiParentsNotSupported error // File has multiple parents
    ErrNotReadable              error // File cannot be read (e.g., Google Apps files)
    ErrNotRemovable             error // Directory not empty or cannot be removed
)

Error Descriptions:

  • ErrInvalidPath - Returned when a path is invalid (e.g., not absolute, contains . or .. components, or is empty)
  • ErrDriveError - Returned when a Google Drive API call fails (wraps the underlying API error)
  • ErrIOError - Returned when an I/O operation fails (e.g., reading response body)
  • ErrNotFound - Returned when a requested file or directory is not found
  • ErrAlreadyExists - Returned when MkdirAll encounters multiple directories with the same name at any level in the path
  • ErrMultiParentsNotSupported - Returned by ResolvePath when attempting to resolve the path of a file that has multiple parents (Google Drive allows files to have multiple parents, but this library doesn't support path resolution for such files)
  • ErrNotReadable - Returned by ReadFile when attempting to read a Google Apps file (Docs, Sheets, Slides, etc.), which cannot be downloaded as raw bytes
  • ErrNotRemovable - Returned by Remove when attempting to remove a non-empty directory (use RemoveAll instead)

Error Handling Example:

import (
    "errors"
    "github.com/Jumpaku/go-drivefs"
)

data, err := driveFS.ReadFile(fileID)
if err != nil {
    if errors.Is(err, drivefs.ErrNotReadable) {
        // Handle Google Apps files differently
        fmt.Println("This is a Google Apps file, use export instead")
    } else if errors.Is(err, drivefs.ErrNotFound) {
        fmt.Println("File not found")
    } else {
        log.Fatal(err)
    }
}

Features

  • File and Directory Operations: Create, read, write, copy, rename, move, and delete files and directories
  • Permission Management: List, set, and delete permissions for users, groups, domains, and public access
  • Shortcut Support: Create shortcuts (links) to files and directories
  • Path-Based Operations: Use familiar path strings like /folder/subfolder/file.txt
  • Path Resolution: Convert between file IDs and absolute paths
  • Tree Walking: Recursively traverse directory structures with the Walk function
  • Shared Drive Support: Full support for both My Drive and Shared Drives
  • Comprehensive Error Handling: Well-defined error constants that can be checked with errors.Is()
  • Google Apps File Detection: Identify Google Docs, Sheets, Slides, and other Apps files
  • Trash Support: Choose between moving items to trash or permanently deleting them

Authentication

This package requires an authenticated *drive.Service instance from the Google Drive API.

Setup Steps

  1. Create a Google Cloud project

  2. Enable the Google Drive API

    • In your project, navigate to "APIs & Services" > "Library"
    • Search for "Google Drive API" and enable it
  3. Create credentials

    • Go to "APIs & Services" > "Credentials"
    • Create OAuth 2.0 credentials (for user access) or a Service Account (for server-to-server access)
    • Download the credentials JSON file
  4. Authenticate and create a drive.Service

See the Google Drive API Go Quickstart for detailed authentication instructions.

Required Scopes

  • Full read-write access: drive.DriveScope (https://www.googleapis.com/auth/drive)
  • Read-only access: drive.DriveReadonlyScope (https://www.googleapis.com/auth/drive.readonly)

Example Authentication (OAuth 2.0):

import (
    "context"
    "log"
    "os"
    
    "golang.org/x/oauth2/google"
    "google.golang.org/api/drive/v3"
    "google.golang.org/api/option"
)

// Load credentials from a JSON file
b, err := os.ReadFile("credentials.json")
if err != nil {
    log.Fatalf("Unable to read credentials file: %v", err)
}

config, err := google.ConfigFromJSON(b, drive.DriveScope)
if err != nil {
    log.Fatalf("Unable to parse credentials: %v", err)
}

// Get an OAuth 2.0 token (implement token retrieval as needed)
token, err := getToken(config)
if err != nil {
    log.Fatalf("Unable to retrieve token: %v", err)
}

client := config.Client(context.Background(), token)
service, err := drive.NewService(context.Background(), option.WithHTTPClient(client))
if err != nil {
    log.Fatalf("Unable to create Drive service: %v", err)
}

// Now use the service with drivefs
driveFS := drivefs.New(service)

// Use "root" as the root ID for My Drive operations
rootID := drivefs.FileID("root")
dirInfo, err := driveFS.MkdirAll(rootID, "/my-files")

Important Notes

Root ID Parameter

Unlike traditional filesystem libraries, DriveFS does not bind to a single root directory at construction time. Instead:

  • The constructor New(service) only takes a Google Drive service
  • Methods that need a starting point (MkdirAll, FindByPath, Walk) accept a rootID parameter
  • Use drivefs.FileID("root") to reference the user's My Drive root
  • Use any folder ID to start operations from that folder (useful for Shared Drives or subdirectories)

This design allows you to work with multiple roots (e.g., My Drive and Shared Drives) using a single DriveFS instance.

Path Requirements

  • Absolute Paths Only: All path strings must be absolute and start with /
  • No Relative Components: Paths cannot contain . (current directory) or .. (parent directory) components
  • Forward Slashes: Use / as the path separator (Unix-style)
  • Relative to Root: Paths in MkdirAll and FindByPath are interpreted relative to the provided rootID

Duplicate File Names

Google Drive's file system differs from traditional filesystems in that it allows multiple files or folders with the same name in the same parent directory.

  • Create() and Mkdir() will create new items even if items with the same name already exist
  • To avoid duplicates, check existing items with ReadDir() before creating
  • FindByPath() returns all matching items when duplicates exist
  • MkdirAll() returns an error if it encounters multiple directories with the same name at any level

Google Apps Files

Google Apps files (Docs, Sheets, Slides, Forms, etc.) are special:

  • They cannot be read with ReadFile() - this will return ErrNotReadable
  • They have MIME types starting with application/vnd.google-apps.
  • Use FileInfo.IsAppFile() to detect them
  • To access their content, use the Google Drive API's export functionality

Multiple Parents

Google Drive allows files to have multiple parent directories:

  • This library primarily supports single-parent files
  • ResolvePath() will return ErrMultiParentsNotSupported for files with multiple parents
  • Move() removes all existing parents and sets a single new parent
  • When resolving paths, files with multiple parents cannot have an unambiguous path

Trashed Items

  • Trashed items are automatically excluded from ReadDir() and path resolution operations
  • Use the moveToTrash parameter in Remove() and RemoveAll():
    • moveToTrash=true: Items can be restored from Google Drive trash
    • moveToTrash=false: Items are permanently deleted

Shared Drives

  • Full support for Shared Drives (formerly Team Drives)
  • All API calls use SupportsAllDrives(true) and IncludeItemsFromAllDrives(true)
  • To work within a Shared Drive, pass the Shared Drive root folder ID to methods like MkdirAll, FindByPath, or Walk

License

BSD 2-Clause License. See LICENSE for details.

About

A Go module that provides a file system-like interface for Google Drive operations.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages