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  }