gitlab.nesad.fit.vutbr.cz/blended/libblended@v0.0.0-20221202124402-2bee159339df/catalog/catalog.go (about) 1 package catalog 2 3 import ( 4 shell "github.com/ipfs/go-ipfs-api" 5 "github.com/jinzhu/gorm" 6 "gitlab.nesad.fit.vutbr.cz/blended/libblended/catalog/models" 7 "gitlab.nesad.fit.vutbr.cz/blended/libblended/errors" 8 "gitlab.nesad.fit.vutbr.cz/blended/libblended/ipfs" 9 "path/filepath" 10 "strings" 11 ) 12 13 type Catalog struct { 14 db Database 15 ipfs ipfs.IpfsClient 16 } 17 18 func NewCatalogClient(db Database, ipfs ipfs.IpfsClient) *Catalog { 19 c := new(Catalog) 20 c.db = db 21 c.ipfs = ipfs 22 23 return c 24 } 25 func (c *Catalog) Add(catalogPath string, filesystemPath string, override bool, ignoreLock bool, progressCallback func(event ipfs.ProgressEvent)) (*models.Resource, error) { 26 if catalogPath != "" { 27 if !override { 28 exists, err := c.catalogPathExists(catalogPath) 29 if err != nil { 30 return nil, err 31 } 32 if exists { 33 return nil, errors.Newf("Path %s already exists. Use override flag to replace it.", catalogPath) 34 } 35 } 36 37 // lock resource if it is already in catalog. 38 name, _ := c.splitPathToNameAndPath(catalogPath) 39 exists, err := c.resourceExists(name) 40 if err != nil { 41 return nil, err 42 } 43 44 if exists { 45 err = c.tryLockResource(name, ignoreLock) 46 if err != nil { 47 return nil, err 48 } 49 defer c.unlockResource(name) 50 } 51 } 52 53 // upload file/dir to IPFS and get its hash 54 result, err := c.ipfs.Add(filesystemPath, progressCallback) 55 if err != nil { 56 return nil, err 57 } 58 59 var resource *models.Resource 60 // adding to catalog subfolder 61 if catalogPath != "" { 62 resource, err = c.move(catalogPath, result.Hash, override) 63 if err != nil { 64 return nil, err 65 } 66 } else { 67 // adding to the catalog root 68 // create new resource or update old one if exists 69 exists, err := c.resourceExists(result.Name) 70 if err != nil { 71 return nil, err 72 } 73 74 if exists { 75 resource, err = c.getDbModelByName(result.Name) 76 if err != nil { 77 return nil, err 78 } 79 resource.Hash = result.Hash 80 resource.Size = result.Size 81 err = c.db.Update(&resource) 82 } else { 83 stat, err := c.ipfs.Stat(result.Hash) 84 if err != nil { 85 return nil, err 86 } 87 88 resource = &models.Resource{Name: result.Name, Hash: result.Hash, Size: result.Size, Type: stat.GetType()} 89 err = c.db.Create(&resource) 90 } 91 92 if err != nil { 93 return nil, err 94 } 95 } 96 err = c.ipfs.Shell.Pin(resource.Hash) 97 if err != nil { 98 return nil, err 99 } 100 101 return resource, nil 102 } 103 104 func (c *Catalog) Link(catalogPath string, ipfsHash string, override bool, ignoreLock bool) (*models.Resource, error) { 105 // lock resource if it is already in catalog. 106 name, _ := c.splitPathToNameAndPath(catalogPath) 107 exists, err := c.resourceExists(name) 108 if err != nil { 109 return nil, err 110 } 111 112 if exists { 113 err = c.tryLockResource(name, ignoreLock) 114 if err != nil { 115 return nil, err 116 } 117 defer c.unlockResource(name) 118 } 119 120 return c.move(catalogPath, ipfsHash, override) 121 } 122 123 func (c *Catalog) move(catalogPath string, ipfsHash string, override bool) (*models.Resource, error) { 124 name, path := c.splitPathToNameAndPath(catalogPath) 125 126 exists, err := c.resourceExists(name) 127 if err != nil { 128 return nil, err 129 } 130 131 var resource *models.Resource 132 // nothing with that name is in catalog yet 133 if !exists { 134 emptyFolderHash, err := c.ipfs.CreateEmptyFolder() 135 if err != nil { 136 return nil, err 137 } 138 139 hash, err := c.ipfs.Move(emptyFolderHash, path, ipfsHash) 140 if err != nil { 141 return nil, err 142 } 143 144 stat, err := c.ipfs.Stat(ipfsHash) 145 if err != nil { 146 return nil, err 147 } 148 149 // create new resource 150 if name == "" { 151 name = hash 152 } 153 resource = &models.Resource{Name: name, Hash: hash, Size: int64(stat.CumulativeSize), Type: stat.GetType()} 154 err = c.db.Create(resource) 155 } else { 156 resource, err = c.getDbModelByName(name) 157 if err != nil { 158 return nil, err 159 } 160 161 // resource with that name already exists. Add path to its hash. 162 if !override { 163 exists, err := c.catalogPathExists(catalogPath) 164 if err != nil { 165 return nil, err 166 } 167 if exists { 168 return nil, errors.Newf("Path %s already exists. Use override flag to replace it.", catalogPath) 169 } 170 } 171 172 hash, err := c.ipfs.Move(resource.Hash, path, ipfsHash) 173 if err != nil { 174 return nil, err 175 } 176 177 _ = c.ipfs.Shell.Unpin(resource.Hash) 178 179 // update the resource hash. Do not create new resource 180 resource.Hash = hash 181 err = c.db.Update(resource) 182 } 183 184 return resource, err 185 186 } 187 188 func (c *Catalog) Remove(path string, ignoreLock bool) (*models.Resource, error) { 189 ipfsPath, err := c.getIpfsPathFromCatalogPath(path) 190 if err != nil { 191 return nil, err 192 } 193 194 // lock resource 195 name, _ := c.splitPathToNameAndPath(path) 196 exists, err := c.resourceExists(name) 197 if err != nil { 198 return nil, err 199 } 200 201 if exists { 202 err = c.tryLockResource(name, ignoreLock) 203 if err != nil { 204 return nil, err 205 } 206 defer c.unlockResource(name) 207 } 208 209 newHash, err := c.ipfs.Remove(ipfsPath) 210 if err != nil { 211 return nil, err 212 } 213 214 resource, err := c.getDbModelByName(name) 215 if err != nil { 216 return nil, err 217 } 218 219 _ = c.ipfs.Shell.Unpin(resource.Hash) 220 221 // removed from catalog root - need to delete whole resource record 222 if newHash == "" { 223 err = c.db.Delete(resource) 224 return nil, err 225 } else { 226 // removed from catalog subfolder - need to update resource hash 227 228 err = c.ipfs.Shell.Pin(newHash) 229 if err != nil { 230 return nil, err 231 } 232 233 resource.Hash = newHash 234 err = c.db.Update(resource) 235 return resource, err 236 } 237 } 238 239 func (c *Catalog) Download(name string, outdir string, progressCallback func(event ipfs.ProgressEvent)) error { 240 hash, err := c.getIpfsPathFromCatalogPath(name) 241 if err != nil { 242 return err 243 } 244 return c.ipfs.Download(hash, outdir, progressCallback) 245 } 246 247 func (c *Catalog) Ls(name string) ([]*shell.LsLink, error) { 248 if name == "" { 249 var results []*shell.LsLink 250 _, err := c.db.Query(func(db *gorm.DB) *gorm.DB { 251 return db.Find(&[]models.Resource{}).Scan(&results) 252 }) 253 if err != nil { 254 return nil, err 255 } 256 257 return results, nil 258 259 } else { 260 hash, err := c.getIpfsPathFromCatalogPath(name) 261 if err != nil { 262 return nil, err 263 } 264 links, err := c.ipfs.Ls(hash) 265 if err != nil { 266 return nil, err 267 } 268 269 return links, nil 270 } 271 } 272 273 func (c *Catalog) getIpfsPathFromCatalogPath(path string) (string, error) { 274 if path == "" { 275 return "", errors.New("Empty catalog path") 276 } 277 catalogName, restOfIpfsPath := c.splitPathToNameAndPath(path) 278 279 resource, err := c.getDbModelByName(catalogName) 280 if err != nil { 281 return "", err 282 } 283 284 return filepath.ToSlash(c.ipfs.JoinPaths(resource.Hash, restOfIpfsPath)), nil 285 } 286 287 func (c *Catalog) getDbModelByName(name string) (*models.Resource, error) { 288 var result models.Resource 289 _, err := c.db.Query(func(db *gorm.DB) *gorm.DB { 290 return db.Where("name = ?", name).First(&result) 291 }) 292 293 if err != nil { 294 return nil, err 295 } 296 297 return &result, nil 298 } 299 300 func (c *Catalog) resourceExists(name string) (bool, error) { 301 _, err := c.getDbModelByName(name) 302 if err != nil { 303 if gorm.IsRecordNotFoundError(err) { 304 return false, nil 305 } 306 return false, err 307 } 308 309 return true, nil 310 } 311 312 func (c *Catalog) catalogPathExists(path string) (bool, error) { 313 // check if path already exists 314 name, _ := c.splitPathToNameAndPath(path) 315 exists, err := c.resourceExists(name) 316 if err != nil { 317 return false, err 318 } 319 320 if exists { 321 ipfsPath, err := c.getIpfsPathFromCatalogPath(path) 322 if err != nil { 323 return false, err 324 } 325 326 _, err = c.ipfs.Stat(ipfsPath) 327 if err == nil { 328 return true, nil 329 } 330 } 331 return false, nil 332 } 333 334 // set locked flag to the resource 335 func (c *Catalog) tryLockResource(name string, ignoreAlreadyLockError bool) error { 336 locked, err := c.isResourceLocked(name) 337 if err != nil { 338 return err 339 } 340 if locked && !ignoreAlreadyLockError { 341 return errors.Newf("Resource %s is locked. Some other user is currently modifying it. You can use ignore-lock flag to suppress this error.", name) 342 } 343 _, err = c.db.Query(func(db *gorm.DB) *gorm.DB { 344 return db.Model(models.Resource{}).Where("name = ?", name).Update(models.Resource{Locked: true}) 345 }) 346 if err != nil { 347 return err 348 } 349 return nil 350 } 351 352 // remove locked flag from the resource 353 func (c *Catalog) unlockResource(name string) error { 354 _, err := c.db.Query(func(db *gorm.DB) *gorm.DB { 355 // WARNING when update with struct, GORM will only update those fields that with non blank value 356 // If we use models.Resource{Locked: false}, nothing will be updated as false is blank value of bool 357 return db.Model(models.Resource{}).Where("name = ?", name).Update("locked", false) 358 }) 359 if err != nil { 360 return err 361 } 362 return nil 363 } 364 365 // determines if the resource is locked 366 func (c *Catalog) isResourceLocked(name string) (bool, error) { 367 var result models.Resource 368 _, err := c.db.Query(func(db *gorm.DB) *gorm.DB { 369 return db.Where("name = ?", name).First(&result) 370 }) 371 372 if err != nil { 373 return false, err 374 } 375 376 return result.Locked, nil 377 } 378 379 // Splits catalog path to catalog name and rest of the path 380 // Example: /a/b/c/d -> a , b/c/d 381 // /a/ -> a , "" 382 func (c *Catalog) splitPathToNameAndPath(catalogPath string) (string, string) { 383 nameAndPath := strings.SplitN(catalogPath, "/", 2) 384 if len(nameAndPath) > 1 { 385 return nameAndPath[0], nameAndPath[1] 386 } else { 387 return nameAndPath[0], "" 388 } 389 }