Files
drivefs/internal/folder.go
Matheus Sampaio Queiroga 81bea3fc9a Refactor core logic into internal package
Move core Drive FS implementation details to the `internal` package,
simplifying the public API.

Introduce a resource pool (`pool.Pool`) for managing Google Drive
service clients to potentially improve performance and handle rate
limits more effectively. Refine error handling and path manipulation.

Remove old example and deprecated top-level files (`file.go`,
`gdrive.go`, `ro.go`, `rw.go`).
2025-05-03 21:46:50 -03:00

288 lines
8.0 KiB
Go

package drivefs
import (
"errors"
"fmt"
"io/fs"
"iter"
"net/http"
"path"
"slices"
"strings"
"time"
"google.golang.org/api/drive/v3"
"google.golang.org/api/googleapi"
)
func (gdrive *Gdrive) CreateFolderRecursive(path string) (*drive.File, error) {
if cacheNode, err := gdrive.Cache.Get(path); err == nil && cacheNode != nil {
return cacheNode, nil
}
current, err, seq := gdrive.RootDrive, error(nil), GdrivePath(path).SplitPathSeq()
for folderPath := range seq {
previus := current // storage previus Node
if current, err = gdrive.Cache.Get(folderPath); err == nil && current != nil {
continue // continue to next node
} else if current, err = gdrive.ResolveNode(previus.Id, folderPath); err != nil {
if !errors.Is(err, fs.ErrNotExist) {
return nil, err // return drive error
}
// Base to create folder
nodeCreate := &drive.File{
MimeType: GoogleDriveMimeFolder, // folder mime
Parents: []string{previus.Id}, // previus to folder to create
}
// Create recursive folder
for folderPath, name := range seq {
nodeCreate.Name = name // folder name
driveService, err := gdrive.PoolServervice.Get()
if err != nil {
return nil, err
}
if current, err = driveService.Files.Create(nodeCreate).Fields("*").Do(); err != nil {
return nil, ProcessErr(nil, err)
}
gdrive.Cache.Set(DefaultTTL, folderPath, current) // Cache folder path
nodeCreate.Parents[0] = current.Id // Set new root
}
// Break loop and return current node
break
}
// cache folder if not seted in cache
gdrive.Cache.Set(DefaultTTL, folderPath, current)
}
return current, nil
}
func (gdrive *Gdrive) ResolveNode(folderID, name string) (*drive.File, error) {
driveService, err := gdrive.PoolServervice.Get()
if err != nil {
return nil, err
}
name = strings.ReplaceAll(strings.ReplaceAll(name, `\`, `\\`), `'`, `\'`)
file, err := driveService.Files.List().Fields("*").PageSize(300).Q(fmt.Sprintf(GoogleListQueryWithName, folderID, name)).Do()
if err != nil {
return nil, ProcessErr(nil, err)
}
if len(file.Files) != 1 {
return nil, fs.ErrNotExist
} else if file.Files[0].Trashed {
return file.Files[0], fs.ErrNotExist
}
return file.Files[0], nil
}
func (gdrive *Gdrive) GetNode(name string) (current *drive.File, err error) {
if GdrivePath(name).IsRoot() {
return gdrive.RootDrive, nil
}
if current, err = gdrive.Cache.Get(name); err == nil && current != nil {
return current, nil
}
current = gdrive.RootDrive // root
nodes := GdrivePath(name).SplitPath() // split node
for _, currentNode := range nodes {
previus := current // storage previus Node
if current, err = gdrive.Cache.Get(currentNode[0]); err == nil && current != nil {
continue // continue to next node
}
var err error
// Check if ared exist in folder
if current, err = gdrive.ResolveNode(previus.Id, currentNode[1]); err != nil {
return nil, err // return drive error
}
gdrive.Cache.Set(DefaultTTL, currentNode[0], current)
}
return current, nil
}
func (gdrive *Gdrive) CreateNodeFolder(path string) (folderNode *drive.File, err error) {
if cacheNode, _ := gdrive.Cache.Get(path); cacheNode != nil {
return cacheNode, nil
}
nodes := GdrivePath(path).SplitPath()
previusNode, lastNode := nodes.At(-2), nodes.At(-1)
if folderNode, _ = gdrive.Cache.Get(previusNode.Path()); folderNode == nil {
if folderNode, err = gdrive.ResolveNode(gdrive.RootDrive.Id, previusNode.Path()); err != nil {
return
}
gdrive.Cache.Set(DefaultTTL, previusNode.Path(), folderNode)
}
previus := folderNode
if folderNode, err = gdrive.ResolveNode(gdrive.RootDrive.Id, lastNode.Path()); err != nil {
if !errors.Is(err, fs.ErrNotExist) {
return nil, err // return drive error
}
nodeCreate := &drive.File{
Name: lastNode.Name(), // Folder name
MimeType: GoogleDriveMimeFolder, // folder mime
Parents: []string{previus.Id}, // previus to folder to create
}
driveService, err := gdrive.PoolServervice.Get()
if err != nil {
return nil, err
}
if folderNode, err = driveService.Files.Create(nodeCreate).Fields("*").Do(); err != nil {
return nil, ProcessErr(nil, err)
}
}
gdrive.Cache.Set(DefaultTTL, lastNode.Path(), folderNode)
return
}
func (gdrive *Gdrive) ForwardPathResolve(nodeID string) (string, error) {
pathNodes, fistNode, currentNode, err := []string{}, (*drive.File)(nil), (*drive.File)(nil), error(nil)
for {
driveService, err := gdrive.PoolServervice.Get()
if err != nil {
return "", err
} else if currentNode, err = driveService.Files.Get(nodeID).Fields("*").Do(); err != nil {
break
}
// Loop to check if is shortcut
for limit := 200_000; limit > 0 && currentNode.MimeType == GoogleDriveMimeSyslink; limit-- {
if driveService, err = gdrive.PoolServervice.Get(); err != nil {
return "", err
} else if currentNode, err = driveService.Files.Get(currentNode.ShortcutDetails.TargetId).Fields("*").Do(); err != nil {
break
}
}
parents := len(currentNode.Parents)
if parents == 0 {
break // Stop count
} else if parents > 1 {
parentsNode, node := []*drive.File{}, (*drive.File)(nil)
for _, parentID := range currentNode.Parents {
if driveService, err = gdrive.PoolServervice.Get(); err != nil {
return "", err
} else if node, err = driveService.Files.Get(parentID).Fields("*").Do(); err != nil {
break
}
parentsNode = append(parentsNode, node)
}
slices.SortFunc(parentsNode, func(i, j *drive.File) int {
ia, _ := time.Parse(time.RFC3339, i.CreatedTime)
ja, _ := time.Parse(time.RFC3339, j.CreatedTime)
return ia.Compare(ja)
})
currentNode = parentsNode[0]
}
if currentNode.Parents[0] == gdrive.RootDrive.Id {
break // Break loop
}
nodeID = currentNode.Parents[0] // set new nodeID
pathNodes = append(pathNodes, currentNode.Name) // Append name to path
if fistNode == nil {
fistNode = currentNode
}
// Save path to cache
gdrive.Cache.Set(DefaultTTL, path.Join(Reverse(pathNodes)...), currentNode)
}
nodePath := path.Join(Reverse(pathNodes)...)
gdrive.Cache.Set(DefaultTTL, nodePath, fistNode) // Save path to cache
return nodePath, err
}
func Reverse[S ~[]E, E any](s S) S {
n := len(s)
reversedSlice := make([]E, n)
if n == 0 {
return reversedSlice // Return the empty slice right away
}
lastIndex := n - 1
for i, element := range s {
reversedSlice[lastIndex-i] = element
}
return reversedSlice
}
// Get file stream, if error check if is http2 error to make new request
func (gdrive *Gdrive) FileRequest(id string, s ...googleapi.Field) (*http.Response, error) {
driveService, err := gdrive.PoolServervice.Get()
if err != nil {
return nil, err
}
quota := 0
node := driveService.Files.Get(id).Fields(s...).AcknowledgeAbuse(true)
res, err := node.Download()
for i := 0; i < 3 && err != nil; i++ {
err = ProcessErr(res, err)
switch err {
case fs.ErrNotExist, fs.ErrPermission:
return nil, err
case ErrQuota:
if quota++; quota > 15 {
<-time.After(time.Second * 20)
quota = 0
}
if driveService, err = gdrive.PoolServervice.Next(); err != nil {
return nil, err
}
node = driveService.Files.Get(id).Fields(s...).AcknowledgeAbuse(true)
res, err = node.Download()
case ErrHttp2:
<-time.After(time.Microsecond * 2) // Wait seconds to retry download, to google server close connection
res, err = node.Download()
default:
return res, err
}
}
return res, err
}
func (gdrive *Gdrive) ListFiles(folderID string) iter.Seq2[*drive.File, error] {
return func(yield func(*drive.File, error) bool) {
driveService, err := gdrive.PoolServervice.Get()
if err != nil {
yield(nil, ProcessErr(nil, err))
return
}
folder := driveService.Files.List().Fields("*").Q(fmt.Sprintf(GoogleListQuery, folderID)).PageSize(100_000)
for {
nodes, err := folder.Do()
if err != nil {
yield(nil, ProcessErr(nil, err))
return
}
for _, node := range nodes.Files {
if !yield(node, nil) {
return
}
}
if folder.PageToken(nodes.NextPageToken); nodes.NextPageToken == "" {
break
}
}
}
}