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 }