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: ¤tTime, 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 }