github.com/jstaf/onedriver@v0.14.2-0.20240420231225-f07678f9e6ef/fs/inode.go (about)

     1  package fs
     2  
     3  import (
     4  	"encoding/json"
     5  	"math/rand"
     6  	"os"
     7  	"strconv"
     8  	"strings"
     9  	"sync"
    10  	"time"
    11  
    12  	"github.com/hanwen/go-fuse/v2/fuse"
    13  	"github.com/jstaf/onedriver/fs/graph"
    14  )
    15  
    16  // Inode represents a file or folder fetched from the Graph API. All struct
    17  // fields are pointers so as to avoid including them when marshaling to JSON
    18  // if not present. The embedded DriveItem's fields should never be accessed, they
    19  // are there for JSON umarshaling/marshaling only. (They are not safe to access
    20  // concurrently.) This struct's methods are thread-safe and can be called
    21  // concurrently. Reads/writes are done directly to DriveItems instead of
    22  // implementing something like the fs.FileHandle to minimize the complexity of
    23  // operations like Flush.
    24  type Inode struct {
    25  	sync.RWMutex
    26  	graph.DriveItem
    27  	nodeID     uint64   // filesystem node id
    28  	children   []string // a slice of ids, nil when uninitialized
    29  	hasChanges bool     // used to trigger an upload on flush
    30  	subdir     uint32   // used purely by NLink()
    31  	mode       uint32   // do not set manually
    32  }
    33  
    34  // SerializeableInode is like a Inode, but can be serialized for local storage
    35  // to disk
    36  type SerializeableInode struct {
    37  	graph.DriveItem
    38  	Children []string
    39  	Subdir   uint32
    40  	Mode     uint32
    41  }
    42  
    43  // NewInode initializes a new Inode
    44  func NewInode(name string, mode uint32, parent *Inode) *Inode {
    45  	itemParent := &graph.DriveItemParent{ID: "", Path: ""}
    46  	if parent != nil {
    47  		itemParent.Path = parent.Path()
    48  		parent.RLock()
    49  		itemParent.ID = parent.DriveItem.ID
    50  		itemParent.DriveID = parent.DriveItem.Parent.DriveID
    51  		itemParent.DriveType = parent.DriveItem.Parent.DriveType
    52  		parent.RUnlock()
    53  	}
    54  
    55  	currentTime := time.Now()
    56  	return &Inode{
    57  		DriveItem: graph.DriveItem{
    58  			ID:      localID(),
    59  			Name:    name,
    60  			Parent:  itemParent,
    61  			ModTime: &currentTime,
    62  		},
    63  		children: make([]string, 0),
    64  		mode:     mode,
    65  	}
    66  }
    67  
    68  // AsJSON converts a DriveItem to JSON for use with local storage. Not used with
    69  // the API. FIXME: If implemented as MarshalJSON, this will break delta syncs
    70  // for business accounts. Don't ask me why.
    71  func (i *Inode) AsJSON() []byte {
    72  	i.RLock()
    73  	defer i.RUnlock()
    74  	data, _ := json.Marshal(SerializeableInode{
    75  		DriveItem: i.DriveItem,
    76  		Children:  i.children,
    77  		Subdir:    i.subdir,
    78  		Mode:      i.mode,
    79  	})
    80  	return data
    81  }
    82  
    83  // NewInodeJSON converts JSON to a *DriveItem when loading from local storage. Not
    84  // used with the API. FIXME: If implemented as UnmarshalJSON, this will break
    85  // delta syncs for business accounts. Don't ask me why.
    86  func NewInodeJSON(data []byte) (*Inode, error) {
    87  	var raw SerializeableInode
    88  	if err := json.Unmarshal(data, &raw); err != nil {
    89  		return nil, err
    90  	}
    91  	return &Inode{
    92  		DriveItem: raw.DriveItem,
    93  		children:  raw.Children,
    94  		mode:      raw.Mode,
    95  		subdir:    raw.Subdir,
    96  	}, nil
    97  }
    98  
    99  // NewInodeDriveItem creates a new Inode from a DriveItem
   100  func NewInodeDriveItem(item *graph.DriveItem) *Inode {
   101  	if item == nil {
   102  		return nil
   103  	}
   104  	return &Inode{
   105  		DriveItem: *item,
   106  	}
   107  }
   108  
   109  // String is only used for debugging by go-fuse
   110  func (i *Inode) String() string {
   111  	return i.Name()
   112  }
   113  
   114  // Name is used to ensure thread-safe access to the NameInternal field.
   115  func (i *Inode) Name() string {
   116  	i.RLock()
   117  	defer i.RUnlock()
   118  	return i.DriveItem.Name
   119  }
   120  
   121  // SetName sets the name of the item in a thread-safe manner.
   122  func (i *Inode) SetName(name string) {
   123  	i.Lock()
   124  	i.DriveItem.Name = name
   125  	i.Unlock()
   126  }
   127  
   128  // NodeID returns the inodes ID in the filesystem
   129  func (i *Inode) NodeID() uint64 {
   130  	i.RLock()
   131  	defer i.RUnlock()
   132  	return i.nodeID
   133  }
   134  
   135  // SetNodeID sets the inode ID for an inode if not already set. Does nothing if
   136  // the Inode already has an ID.
   137  func (i *Inode) SetNodeID(id uint64) uint64 {
   138  	i.Lock()
   139  	defer i.Unlock()
   140  	if i.nodeID == 0 {
   141  		i.nodeID = id
   142  	}
   143  	return i.nodeID
   144  }
   145  
   146  var charset = []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
   147  
   148  func randString(length int) string {
   149  	out := make([]byte, length)
   150  	for i := 0; i < length; i++ {
   151  		out[i] = charset[rand.Intn(len(charset))]
   152  	}
   153  	return string(out)
   154  }
   155  
   156  func localID() string {
   157  	return "local-" + randString(20)
   158  }
   159  
   160  func isLocalID(id string) bool {
   161  	return strings.HasPrefix(id, "local-") || id == ""
   162  }
   163  
   164  // ID returns the internal ID of the item
   165  func (i *Inode) ID() string {
   166  	i.RLock()
   167  	defer i.RUnlock()
   168  	return i.DriveItem.ID
   169  }
   170  
   171  // ParentID returns the ID of this item's parent.
   172  func (i *Inode) ParentID() string {
   173  	i.RLock()
   174  	defer i.RUnlock()
   175  	if i.DriveItem.Parent == nil {
   176  		return ""
   177  	}
   178  	return i.DriveItem.Parent.ID
   179  }
   180  
   181  // Path returns an inode's full Path
   182  func (i *Inode) Path() string {
   183  	// special case when it's the root item
   184  	name := i.Name()
   185  	if i.ParentID() == "" && name == "root" {
   186  		return "/"
   187  	}
   188  
   189  	// all paths come prefixed with "/drive/root:"
   190  	i.RLock()
   191  	defer i.RUnlock()
   192  	if i.DriveItem.Parent == nil {
   193  		return name
   194  	}
   195  	prepath := strings.TrimPrefix(i.DriveItem.Parent.Path+"/"+name, "/drive/root:")
   196  	return strings.Replace(prepath, "//", "/", -1)
   197  }
   198  
   199  // HasChanges returns true if the file has local changes that haven't been
   200  // uploaded yet.
   201  func (i *Inode) HasChanges() bool {
   202  	i.RLock()
   203  	defer i.RUnlock()
   204  	return i.hasChanges
   205  }
   206  
   207  // HasChildren returns true if the item has more than 0 children
   208  func (i *Inode) HasChildren() bool {
   209  	i.RLock()
   210  	defer i.RUnlock()
   211  	return len(i.children) > 0
   212  }
   213  
   214  // makeattr is a convenience function to create a set of filesystem attrs for
   215  // use with syscalls that use or modify attrs.
   216  func (i *Inode) makeAttr() fuse.Attr {
   217  	mtime := i.ModTime()
   218  	return fuse.Attr{
   219  		Ino:   i.NodeID(),
   220  		Size:  i.Size(),
   221  		Nlink: i.NLink(),
   222  		Ctime: mtime,
   223  		Mtime: mtime,
   224  		Atime: mtime,
   225  		Mode:  i.Mode(),
   226  		// whatever user is running the filesystem is the owner
   227  		Owner: fuse.Owner{
   228  			Uid: uint32(os.Getuid()),
   229  			Gid: uint32(os.Getgid()),
   230  		},
   231  	}
   232  }
   233  
   234  // IsDir returns if it is a directory (true) or file (false).
   235  func (i *Inode) IsDir() bool {
   236  	// 0 if the dir bit is not set
   237  	return i.Mode()&fuse.S_IFDIR > 0
   238  }
   239  
   240  // Mode returns the permissions/mode of the file.
   241  func (i *Inode) Mode() uint32 {
   242  	i.RLock()
   243  	defer i.RUnlock()
   244  	if i.mode == 0 { // only 0 if fetched from Graph API
   245  		if i.DriveItem.IsDir() {
   246  			return fuse.S_IFDIR | 0755
   247  		}
   248  		return fuse.S_IFREG | 0644
   249  	}
   250  	return i.mode
   251  }
   252  
   253  // ModTime returns the Unix timestamp of last modification (to get a time.Time
   254  // struct, use time.Unix(int64(d.ModTime()), 0))
   255  func (i *Inode) ModTime() uint64 {
   256  	i.RLock()
   257  	defer i.RUnlock()
   258  	return i.DriveItem.ModTimeUnix()
   259  }
   260  
   261  // NLink gives the number of hard links to an inode (or child count if a
   262  // directory)
   263  func (i *Inode) NLink() uint32 {
   264  	if i.IsDir() {
   265  		i.RLock()
   266  		defer i.RUnlock()
   267  		// we precompute subdir due to mutex lock contention between NLink and
   268  		// other ops. subdir is modified by cache Insert/Delete and GetChildren.
   269  		return 2 + i.subdir
   270  	}
   271  	return 1
   272  }
   273  
   274  // Size pretends that folders are 4096 bytes, even though they're 0 (since
   275  // they actually don't exist).
   276  func (i *Inode) Size() uint64 {
   277  	if i.IsDir() {
   278  		return 4096
   279  	}
   280  	i.RLock()
   281  	defer i.RUnlock()
   282  	return i.DriveItem.Size
   283  }
   284  
   285  // Octal converts a number to its octal representation in string form.
   286  func Octal(i uint32) string {
   287  	return strconv.FormatUint(uint64(i), 8)
   288  }