github.com/cs3org/reva/v2@v2.27.7/pkg/storage/fs/owncloudsql/filecache/filecache.go (about)

     1  // Copyright 2018-2021 CERN
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  //
    15  // In applying this license, CERN does not waive the privileges and immunities
    16  // granted to it by virtue of its status as an Intergovernmental Organization
    17  // or submit itself to any jurisdiction.
    18  
    19  package filecache
    20  
    21  import (
    22  	"context"
    23  	"crypto/md5"
    24  	"database/sql"
    25  	"encoding/hex"
    26  	"fmt"
    27  	"path/filepath"
    28  	"regexp"
    29  	"strconv"
    30  	"strings"
    31  	"time"
    32  
    33  	provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
    34  	"github.com/cs3org/reva/v2/pkg/conversions"
    35  
    36  	"github.com/pkg/errors"
    37  	"github.com/rs/zerolog/log"
    38  
    39  	// Provides mysql drivers
    40  	_ "github.com/go-sql-driver/mysql"
    41  )
    42  
    43  // Cache represents a oc10-style file cache
    44  type Cache struct {
    45  	driver string
    46  	db     *sql.DB
    47  }
    48  
    49  // Storage represents a storage entry in the database
    50  type Storage struct {
    51  	ID        string
    52  	NumericID int
    53  }
    54  
    55  // File represents an entry of the file cache
    56  type File struct {
    57  	ID              int
    58  	Storage         int
    59  	Parent          int
    60  	MimePart        int
    61  	MimeType        int
    62  	MimeTypeString  string
    63  	Size            int
    64  	MTime           int
    65  	StorageMTime    int
    66  	UnencryptedSize int
    67  	Permissions     int
    68  	Encrypted       bool
    69  	Path            string
    70  	Name            string
    71  	Etag            string
    72  	Checksum        string
    73  }
    74  
    75  // TrashItem represents a trash item of the file cache
    76  type TrashItem struct {
    77  	ID        int
    78  	Name      string
    79  	User      string
    80  	Path      string
    81  	Timestamp int
    82  }
    83  
    84  // Scannable describes the interface providing a Scan method
    85  type Scannable interface {
    86  	Scan(...interface{}) error
    87  }
    88  
    89  // NewMysql returns a new Cache instance connecting to a MySQL database
    90  func NewMysql(dsn string) (*Cache, error) {
    91  	sqldb, err := sql.Open("mysql", dsn)
    92  	if err != nil {
    93  		return nil, errors.Wrap(err, "error connecting to the database")
    94  	}
    95  
    96  	// FIXME make configurable
    97  	sqldb.SetConnMaxLifetime(time.Minute * 3)
    98  	sqldb.SetConnMaxIdleTime(time.Second * 30)
    99  	sqldb.SetMaxOpenConns(100)
   100  	sqldb.SetMaxIdleConns(10)
   101  
   102  	err = sqldb.Ping()
   103  	if err != nil {
   104  		return nil, errors.Wrap(err, "error connecting to the database")
   105  	}
   106  
   107  	return New("mysql", sqldb)
   108  }
   109  
   110  // New returns a new Cache instance connecting to the given sql.DB
   111  func New(driver string, sqldb *sql.DB) (*Cache, error) {
   112  	return &Cache{
   113  		driver: driver,
   114  		db:     sqldb,
   115  	}, nil
   116  }
   117  
   118  // ListStorages returns the list of numeric ids of all storages
   119  // Optionally only home storages are considered
   120  func (c *Cache) ListStorages(ctx context.Context, onlyHome bool) ([]*Storage, error) {
   121  	query := ""
   122  	if onlyHome {
   123  		mountPointConcat := ""
   124  		if c.driver == "mysql" {
   125  			mountPointConcat = "m.mount_point = CONCAT('/', m.user_id, '/')"
   126  		} else { // sqlite3
   127  			mountPointConcat = "m.mount_point = '/' || m.user_id || '/'"
   128  		}
   129  
   130  		query = "SELECT s.id, s.numeric_id FROM oc_storages s JOIN oc_mounts m ON s.numeric_id = m.storage_id WHERE " + mountPointConcat
   131  	} else {
   132  		query = "SELECT id, numeric_id FROM oc_storages"
   133  	}
   134  	rows, err := c.db.QueryContext(ctx, query)
   135  	if err != nil {
   136  		return nil, err
   137  	}
   138  	defer rows.Close()
   139  
   140  	storages := []*Storage{}
   141  	for rows.Next() {
   142  		storage := &Storage{}
   143  		err := rows.Scan(&storage.ID, &storage.NumericID)
   144  		if err != nil {
   145  			return nil, err
   146  		}
   147  		storages = append(storages, storage)
   148  	}
   149  	return storages, nil
   150  }
   151  
   152  // GetStorage returns the storage with the given numeric id
   153  func (c *Cache) GetStorage(ctx context.Context, numeridID interface{}) (*Storage, error) {
   154  	numericID, err := toIntID(numeridID)
   155  	if err != nil {
   156  		return nil, err
   157  	}
   158  	row := c.db.QueryRowContext(ctx, "SELECT id, numeric_id FROM oc_storages WHERE numeric_id = ?", numericID)
   159  	s := &Storage{}
   160  	switch err := row.Scan(&s.ID, &s.NumericID); err {
   161  	case nil:
   162  		return s, nil
   163  	default:
   164  		return nil, err
   165  	}
   166  }
   167  
   168  // GetNumericStorageID returns the database id for the given storage
   169  func (c *Cache) GetNumericStorageID(ctx context.Context, id string) (int, error) {
   170  	row := c.db.QueryRowContext(ctx, "SELECT numeric_id FROM oc_storages WHERE id = ?", id)
   171  	var nid int
   172  	switch err := row.Scan(&nid); err {
   173  	case nil:
   174  		return nid, nil
   175  	default:
   176  		return -1, err
   177  	}
   178  }
   179  
   180  // CreateStorage creates a new storage and returns its numeric id
   181  func (c *Cache) CreateStorage(ctx context.Context, id string) (int, error) {
   182  	tx, err := c.db.Begin()
   183  	if err != nil {
   184  		return -1, err
   185  	}
   186  	defer func() { _ = tx.Rollback() }()
   187  
   188  	stmt, err := tx.PrepareContext(ctx, "INSERT INTO oc_storages(id) VALUES(?)")
   189  	if err != nil {
   190  		return -1, err
   191  	}
   192  	defer stmt.Close()
   193  
   194  	res, err := stmt.ExecContext(ctx, id)
   195  	if err != nil {
   196  		return -1, err
   197  	}
   198  	insertedID, err := res.LastInsertId()
   199  	if err != nil {
   200  		return -1, err
   201  	}
   202  
   203  	data := map[string]interface{}{
   204  		"path":     "",
   205  		"etag":     "",
   206  		"mimetype": "httpd/unix-directory",
   207  	}
   208  	_, err = c.doInsertOrUpdate(ctx, tx, int(insertedID), data, true)
   209  	if err != nil {
   210  		return -1, err
   211  	}
   212  
   213  	err = tx.Commit()
   214  	if err != nil {
   215  		return -1, err
   216  	}
   217  
   218  	return int(insertedID), err
   219  }
   220  
   221  // GetStorageOwner returns the username of the owner of the given storage
   222  func (c *Cache) GetStorageOwner(ctx context.Context, numericID interface{}) (string, error) {
   223  	numericID, err := toIntID(numericID)
   224  	if err != nil {
   225  		return "", err
   226  	}
   227  	row := c.db.QueryRowContext(ctx, "SELECT id FROM oc_storages WHERE numeric_id = ?", numericID)
   228  	var id string
   229  	switch err := row.Scan(&id); err {
   230  	case nil:
   231  		return strings.TrimPrefix(id, "home::"), nil
   232  	default:
   233  		return "", err
   234  	}
   235  }
   236  
   237  // GetStorageOwnerByFileID returns the username of the owner of the given entry
   238  func (c *Cache) GetStorageOwnerByFileID(ctx context.Context, numericID interface{}) (string, error) {
   239  	numericID, err := toIntID(numericID)
   240  	if err != nil {
   241  		return "", err
   242  	}
   243  	row := c.db.QueryRowContext(ctx, "SELECT id FROM oc_storages storages, oc_filecache cache WHERE storages.numeric_id = cache.storage AND cache.fileid = ?", numericID)
   244  	var id string
   245  	switch err := row.Scan(&id); err {
   246  	case nil:
   247  		return strings.TrimPrefix(id, "home::"), nil
   248  	default:
   249  		return "", err
   250  	}
   251  }
   252  
   253  func (c *Cache) rowToFile(row Scannable) (*File, error) {
   254  	var fileid, storage, parent, mimetype, mimepart, size, mtime, storageMtime, encrypted, unencryptedSize int
   255  	var permissions sql.NullInt32
   256  	var path, name, etag, checksum, mimetypestring sql.NullString
   257  	err := row.Scan(&fileid, &storage, &path, &parent, &permissions, &mimetype, &mimepart, &mimetypestring, &size, &mtime, &storageMtime, &encrypted, &unencryptedSize, &name, &etag, &checksum)
   258  	if err != nil {
   259  		return nil, err
   260  	}
   261  
   262  	return &File{
   263  		ID:              fileid,
   264  		Storage:         storage,
   265  		Path:            path.String,
   266  		Parent:          parent,
   267  		Permissions:     int(permissions.Int32),
   268  		MimeType:        mimetype,
   269  		MimeTypeString:  mimetypestring.String,
   270  		MimePart:        mimepart,
   271  		Size:            size,
   272  		MTime:           mtime,
   273  		StorageMTime:    storageMtime,
   274  		Encrypted:       encrypted == 1,
   275  		UnencryptedSize: unencryptedSize,
   276  		Name:            name.String,
   277  		Etag:            etag.String,
   278  		Checksum:        checksum.String,
   279  	}, nil
   280  }
   281  
   282  // Get returns the cache entry for the specified storage/path
   283  func (c *Cache) Get(ctx context.Context, s interface{}, p string) (*File, error) {
   284  	storageID, err := toIntID(s)
   285  	if err != nil {
   286  		return nil, err
   287  	}
   288  
   289  	phashBytes := md5.Sum([]byte(p))
   290  	phash := hex.EncodeToString(phashBytes[:])
   291  
   292  	row := c.db.QueryRowContext(ctx, `
   293  		SELECT
   294  			fc.fileid, fc.storage, fc.path, fc.parent, fc.permissions, fc.mimetype, fc.mimepart,
   295  			mt.mimetype, fc.size, fc.mtime, fc.storage_mtime, fc.encrypted, fc.unencrypted_size,
   296  			fc.name, fc.etag, fc.checksum
   297  		FROM oc_filecache fc
   298  		LEFT JOIN oc_mimetypes mt ON fc.mimetype = mt.id
   299  		WHERE path_hash = ? AND storage = ?`, phash, storageID)
   300  	return c.rowToFile(row)
   301  }
   302  
   303  // Path returns the path for the specified entry
   304  func (c *Cache) Path(ctx context.Context, id interface{}) (string, error) {
   305  	id, err := toIntID(id)
   306  	if err != nil {
   307  		return "", err
   308  	}
   309  
   310  	row := c.db.QueryRowContext(ctx, "SELECT path FROM oc_filecache WHERE fileid = ?", id)
   311  	var path string
   312  	err = row.Scan(&path)
   313  	if err != nil {
   314  		return "", err
   315  	}
   316  	return path, nil
   317  }
   318  
   319  // List returns the list of entries below the given path
   320  func (c *Cache) List(ctx context.Context, storage interface{}, p string) ([]*File, error) {
   321  	storageID, err := toIntID(storage)
   322  	if err != nil {
   323  		return nil, err
   324  	}
   325  
   326  	var rows *sql.Rows
   327  	phash := fmt.Sprintf("%x", md5.Sum([]byte(strings.Trim(p, "/"))))
   328  	rows, err = c.db.QueryContext(ctx, `
   329  		SELECT
   330  			fc.fileid, fc.storage, fc.path, fc.parent, fc.permissions, fc.mimetype, fc.mimepart,
   331  			mt.mimetype, fc.size, fc.mtime, fc.storage_mtime, fc.encrypted, fc.unencrypted_size,
   332  			fc.name, fc.etag, fc.checksum
   333  		FROM oc_filecache fc
   334  		LEFT JOIN oc_mimetypes mt ON fc.mimetype = mt.id
   335  		WHERE storage = ? AND parent = (SELECT fileid FROM oc_filecache WHERE storage = ? AND path_hash=?) AND name IS NOT NULL
   336  	`, storageID, storageID, phash)
   337  	if err != nil {
   338  		return nil, err
   339  	}
   340  	defer rows.Close()
   341  	entries := []*File{}
   342  	for rows.Next() {
   343  		entry, err := c.rowToFile(rows)
   344  		if err != nil {
   345  			return nil, err
   346  		}
   347  		entries = append(entries, entry)
   348  	}
   349  
   350  	return entries, nil
   351  }
   352  
   353  // Permissions returns the permissions for the specified storage/path
   354  func (c *Cache) Permissions(ctx context.Context, storage interface{}, p string) (*provider.ResourcePermissions, error) {
   355  	entry, err := c.Get(ctx, storage, p)
   356  	if err != nil {
   357  		return nil, err
   358  	}
   359  
   360  	perms, err := conversions.NewPermissions(entry.Permissions)
   361  	if err != nil {
   362  		return nil, err
   363  	}
   364  
   365  	return conversions.RoleFromOCSPermissions(perms, nil).CS3ResourcePermissions(), nil
   366  }
   367  
   368  // InsertOrUpdate creates or updates a cache entry
   369  func (c *Cache) InsertOrUpdate(ctx context.Context, storage interface{}, data map[string]interface{}, allowEmptyParent bool) (int, error) {
   370  	tx, err := c.db.Begin()
   371  	if err != nil {
   372  		return -1, err
   373  	}
   374  	defer func() { _ = tx.Rollback() }()
   375  
   376  	id, err := c.doInsertOrUpdate(ctx, tx, storage, data, allowEmptyParent)
   377  	if err != nil {
   378  		return -1, err
   379  	}
   380  
   381  	err = tx.Commit()
   382  	if err != nil {
   383  		return -1, err
   384  	}
   385  
   386  	return id, err
   387  }
   388  
   389  func (c *Cache) doInsertOrUpdate(ctx context.Context, tx *sql.Tx, storage interface{}, data map[string]interface{}, allowEmptyParent bool) (int, error) {
   390  	storageID, err := toIntID(storage)
   391  	if err != nil {
   392  		return -1, err
   393  	}
   394  
   395  	columns := []string{"storage"}
   396  	placeholders := []string{"?"}
   397  	values := []interface{}{storage}
   398  
   399  	for _, key := range []string{"path", "mimetype", "etag"} {
   400  		if _, exists := data[key]; !exists {
   401  			return -1, fmt.Errorf("missing required data")
   402  		}
   403  	}
   404  
   405  	path := data["path"].(string)
   406  	data["name"] = filepath.Base(path)
   407  	if data["name"] == "." {
   408  		data["name"] = ""
   409  	}
   410  
   411  	parentPath := strings.TrimRight(filepath.Dir(path), "/")
   412  	if parentPath == "." {
   413  		parentPath = ""
   414  	}
   415  	if path == "" {
   416  		data["parent"] = -1
   417  	} else {
   418  		parent, err := c.Get(ctx, storageID, parentPath)
   419  		if err == nil {
   420  			data["parent"] = parent.ID
   421  		} else {
   422  			if allowEmptyParent {
   423  				data["parent"] = -1
   424  			} else {
   425  				return -1, fmt.Errorf("could not find parent %s, %s, %v, %w", parentPath, path, parent, err)
   426  			}
   427  		}
   428  	}
   429  
   430  	if _, exists := data["checksum"]; !exists {
   431  		data["checksum"] = ""
   432  	}
   433  
   434  	for k, v := range data {
   435  		switch k {
   436  		case "path":
   437  			phashBytes := md5.Sum([]byte(v.(string)))
   438  			phash := hex.EncodeToString(phashBytes[:])
   439  			columns = append(columns, "path_hash")
   440  			values = append(values, phash)
   441  			placeholders = append(placeholders, "?")
   442  		case "storage_mtime":
   443  			if _, exists := data["mtime"]; !exists {
   444  				columns = append(columns, "mtime")
   445  				values = append(values, v)
   446  				placeholders = append(placeholders, "?")
   447  			}
   448  		case "mimetype":
   449  			parts := strings.Split(v.(string), "/")
   450  			columns = append(columns, "mimetype")
   451  			values = append(values, v)
   452  			placeholders = append(placeholders, "(SELECT id FROM oc_mimetypes WHERE mimetype=?)")
   453  			columns = append(columns, "mimepart")
   454  			values = append(values, parts[0])
   455  			placeholders = append(placeholders, "(SELECT id FROM oc_mimetypes WHERE mimetype=?)")
   456  			continue
   457  		}
   458  
   459  		columns = append(columns, k)
   460  		values = append(values, v)
   461  		placeholders = append(placeholders, "?")
   462  	}
   463  
   464  	err = c.insertMimetype(ctx, tx, data["mimetype"].(string))
   465  	if err != nil {
   466  		return -1, err
   467  	}
   468  
   469  	query := "INSERT INTO oc_filecache( " + strings.Join(columns, ", ") + ") VALUES(" + strings.Join(placeholders, ",") + ")"
   470  
   471  	updates := []string{}
   472  	for i, column := range columns {
   473  		if column != "path" && column != "path_hash" && column != "storage" {
   474  			updates = append(updates, column+"="+placeholders[i])
   475  			values = append(values, values[i])
   476  		}
   477  	}
   478  	if c.driver == "mysql" { // mysql upsert
   479  		query += " ON DUPLICATE KEY UPDATE "
   480  	} else { // sqlite3 upsert
   481  		query += " ON CONFLICT(storage,path_hash) DO UPDATE SET "
   482  	}
   483  	query += strings.Join(updates, ",")
   484  
   485  	stmt, err := tx.PrepareContext(ctx, query)
   486  	if err != nil {
   487  		return -1, err
   488  	}
   489  
   490  	res, err := stmt.ExecContext(ctx, values...)
   491  	if err != nil {
   492  		log.Err(err).Msg("could not store filecache item")
   493  		return -1, err
   494  	}
   495  	id, err := res.LastInsertId()
   496  	if err != nil {
   497  		return -1, err
   498  	}
   499  	return int(id), nil
   500  }
   501  
   502  // Copy creates a copy of the specified entry at the target path
   503  func (c *Cache) Copy(ctx context.Context, storage interface{}, sourcePath, targetPath string) (int, error) {
   504  	storageID, err := toIntID(storage)
   505  	if err != nil {
   506  		return -1, err
   507  	}
   508  	source, err := c.Get(ctx, storageID, sourcePath)
   509  	if err != nil {
   510  		return -1, errors.Wrap(err, "could not find source")
   511  	}
   512  
   513  	row := c.db.QueryRowContext(ctx, "SELECT mimetype FROM oc_mimetypes WHERE id=?", source.MimeType)
   514  	var mimetype string
   515  	err = row.Scan(&mimetype)
   516  	if err != nil {
   517  		return -1, errors.Wrap(err, "could not find source mimetype")
   518  	}
   519  
   520  	data := map[string]interface{}{
   521  		"path":             targetPath,
   522  		"checksum":         source.Checksum,
   523  		"mimetype":         mimetype,
   524  		"permissions":      source.Permissions,
   525  		"etag":             source.Etag,
   526  		"size":             source.Size,
   527  		"mtime":            source.MTime,
   528  		"storage_mtime":    source.StorageMTime,
   529  		"encrypted":        source.Encrypted,
   530  		"unencrypted_size": source.UnencryptedSize,
   531  	}
   532  	return c.InsertOrUpdate(ctx, storage, data, false)
   533  }
   534  
   535  // Move moves the specified entry to the target path
   536  func (c *Cache) Move(ctx context.Context, storage interface{}, sourcePath, targetPath string) error {
   537  	storageID, err := toIntID(storage)
   538  	if err != nil {
   539  		return err
   540  	}
   541  	source, err := c.Get(ctx, storageID, sourcePath)
   542  	if err != nil {
   543  		return errors.Wrap(err, "could not find source")
   544  	}
   545  	newParentPath := strings.TrimRight(filepath.Dir(targetPath), "/")
   546  	newParent, err := c.Get(ctx, storageID, newParentPath)
   547  	if err != nil {
   548  		return errors.Wrap(err, "could not find new parent")
   549  	}
   550  
   551  	tx, err := c.db.Begin()
   552  	if err != nil {
   553  		return err
   554  	}
   555  	defer func() { _ = tx.Rollback() }()
   556  	stmt, err := tx.Prepare("UPDATE oc_filecache SET parent=?, path=?, name=?, path_hash=? WHERE storage = ? AND fileid=?")
   557  	if err != nil {
   558  		return err
   559  	}
   560  	defer stmt.Close()
   561  	phashBytes := md5.Sum([]byte(targetPath))
   562  	_, err = stmt.ExecContext(ctx, newParent.ID, targetPath, filepath.Base(targetPath), hex.EncodeToString(phashBytes[:]), storageID, source.ID)
   563  	if err != nil {
   564  		return err
   565  	}
   566  
   567  	childRows, err := tx.QueryContext(ctx, "SELECT fileid, path FROM oc_filecache WHERE parent = ?", source.ID)
   568  	if err != nil {
   569  		return err
   570  	}
   571  	defer childRows.Close()
   572  	children := map[int]string{}
   573  	for childRows.Next() {
   574  		var (
   575  			id   int
   576  			path string
   577  		)
   578  		err = childRows.Scan(&id, &path)
   579  		if err != nil {
   580  			return err
   581  		}
   582  
   583  		children[id] = path
   584  	}
   585  	for id, path := range children {
   586  		path = strings.ReplaceAll(path, sourcePath, targetPath)
   587  		phashBytes = md5.Sum([]byte(path))
   588  		_, err = stmt.ExecContext(ctx, source.ID, path, filepath.Base(path), hex.EncodeToString(phashBytes[:]), storageID, id)
   589  		if err != nil {
   590  			return err
   591  		}
   592  	}
   593  
   594  	return tx.Commit()
   595  }
   596  
   597  // Purge removes the specified storage/path from the cache without putting it into the trash
   598  func (c *Cache) Purge(ctx context.Context, storage interface{}, path string) error {
   599  	storageID, err := toIntID(storage)
   600  	if err != nil {
   601  		return err
   602  	}
   603  	phashBytes := md5.Sum([]byte(path))
   604  	phash := hex.EncodeToString(phashBytes[:])
   605  	_, err = c.db.ExecContext(ctx, "DELETE FROM oc_filecache WHERE storage = ? and path_hash = ?", storageID, phash)
   606  	return err
   607  }
   608  
   609  // Delete removes the specified storage/path from the cache
   610  func (c *Cache) Delete(ctx context.Context, storage interface{}, user, path, trashPath string) error {
   611  	err := c.Move(ctx, storage, path, trashPath)
   612  	if err != nil {
   613  		return err
   614  	}
   615  
   616  	re := regexp.MustCompile(`(.*)\.d(\d+)$`)
   617  	parts := re.FindStringSubmatch(filepath.Base(trashPath))
   618  
   619  	query := "INSERT INTO oc_files_trash(user,id,timestamp,location) VALUES(?,?,?,?)"
   620  	stmt, err := c.db.PrepareContext(ctx, query)
   621  	if err != nil {
   622  		return err
   623  	}
   624  
   625  	relativeLocation, err := filepath.Rel("files/", filepath.Dir(path))
   626  	if err != nil {
   627  		return err
   628  	}
   629  	_, err = stmt.ExecContext(ctx, user, filepath.Base(parts[1]), parts[2], relativeLocation)
   630  	if err != nil {
   631  		log.Err(err).Msg("could not store filecache item")
   632  		return err
   633  	}
   634  
   635  	return nil
   636  }
   637  
   638  // GetRecycleItem returns the specified recycle item
   639  func (c *Cache) GetRecycleItem(ctx context.Context, user, path string, timestamp int) (*TrashItem, error) {
   640  	row := c.db.QueryRowContext(ctx, "SELECT auto_id, id, location FROM oc_files_trash WHERE id = ? AND user = ? AND timestamp = ?", path, user, timestamp)
   641  	var autoID int
   642  	var id, location string
   643  	err := row.Scan(&autoID, &id, &location)
   644  	if err != nil {
   645  		return nil, err
   646  	}
   647  
   648  	return &TrashItem{
   649  		ID:        autoID,
   650  		Name:      id,
   651  		User:      user,
   652  		Path:      location,
   653  		Timestamp: timestamp,
   654  	}, nil
   655  }
   656  
   657  // EmptyRecycle clears the recycle bin for the given user
   658  func (c *Cache) EmptyRecycle(ctx context.Context, user string) error {
   659  	_, err := c.db.ExecContext(ctx, "DELETE FROM oc_files_trash WHERE user = ?", user)
   660  	if err != nil {
   661  		return err
   662  	}
   663  
   664  	storage, err := c.GetNumericStorageID(ctx, "home::"+user)
   665  	if err != nil {
   666  		return err
   667  	}
   668  
   669  	_, err = c.db.ExecContext(ctx, "DELETE FROM oc_filecache WHERE storage = ? AND PATH LIKE ?", storage, "files_trashbin/%")
   670  	return err
   671  }
   672  
   673  // DeleteRecycleItem deletes the specified item from the trash
   674  func (c *Cache) DeleteRecycleItem(ctx context.Context, user, path string, timestamp int) error {
   675  	_, err := c.db.ExecContext(ctx, "DELETE FROM oc_files_trash WHERE id = ? AND user = ? AND timestamp = ?", path, user, timestamp)
   676  	return err
   677  }
   678  
   679  // PurgeRecycleItem deletes the specified item from the filecache and the trash
   680  func (c *Cache) PurgeRecycleItem(ctx context.Context, user, path string, timestamp int, isVersionFile bool) error {
   681  	row := c.db.QueryRowContext(ctx, "SELECT auto_id, location FROM oc_files_trash WHERE id = ? AND user = ? AND timestamp = ?", path, user, timestamp)
   682  	var autoID int
   683  	var location string
   684  	err := row.Scan(&autoID, &location)
   685  	if err != nil {
   686  		return err
   687  	}
   688  
   689  	_, err = c.db.ExecContext(ctx, "DELETE FROM oc_files_trash WHERE auto_id=?", autoID)
   690  	if err != nil {
   691  		return err
   692  	}
   693  
   694  	storage, err := c.GetNumericStorageID(ctx, "home::"+user)
   695  	if err != nil {
   696  		return err
   697  	}
   698  	trashType := "files"
   699  	if isVersionFile {
   700  		trashType = "versions"
   701  	}
   702  	item, err := c.Get(ctx, storage, filepath.Join("files_trashbin", trashType, path+".d"+strconv.Itoa(timestamp)))
   703  	if err != nil {
   704  		return err
   705  	}
   706  	_, err = c.db.ExecContext(ctx, "DELETE FROM oc_filecache WHERE fileid=? OR parent=?", item.ID, item.ID)
   707  
   708  	return err
   709  }
   710  
   711  // SetEtag set a new etag for the specified item
   712  func (c *Cache) SetEtag(ctx context.Context, storage interface{}, path, etag string) error {
   713  	storageID, err := toIntID(storage)
   714  	if err != nil {
   715  		return err
   716  	}
   717  	source, err := c.Get(ctx, storageID, path)
   718  	if err != nil {
   719  		return errors.Wrap(err, "could not find source")
   720  	}
   721  	stmt, err := c.db.PrepareContext(ctx, "UPDATE oc_filecache SET etag=? WHERE storage = ? AND fileid=?")
   722  	if err != nil {
   723  		return err
   724  	}
   725  	_, err = stmt.ExecContext(ctx, etag, storageID, source.ID)
   726  	return err
   727  }
   728  
   729  func (c *Cache) insertMimetype(ctx context.Context, tx *sql.Tx, mimetype string) error {
   730  	insertPart := func(v string) error {
   731  		stmt, err := tx.PrepareContext(ctx, "INSERT INTO oc_mimetypes(mimetype) VALUES(?)")
   732  		if err != nil {
   733  			return err
   734  		}
   735  		_, err = stmt.ExecContext(ctx, v)
   736  		if err != nil {
   737  			if strings.Contains(err.Error(), "UNIQUE") || strings.Contains(err.Error(), "Error 1062") {
   738  				return nil // Already exists
   739  			}
   740  			return err
   741  		}
   742  		return nil
   743  	}
   744  	parts := strings.Split(mimetype, "/")
   745  	err := insertPart(parts[0])
   746  	if err != nil {
   747  		return err
   748  	}
   749  	return insertPart(mimetype)
   750  }
   751  
   752  func toIntID(rid interface{}) (int, error) {
   753  	switch t := rid.(type) {
   754  	case int:
   755  		return t, nil
   756  	case string:
   757  		return strconv.Atoi(t)
   758  	default:
   759  		return -1, fmt.Errorf("invalid type")
   760  	}
   761  }