func (s *sqlDatabase) CreateFileObject(ctx context.Context, name string, size int64, tags []string, reader io.Reader) (fileObjParam params.FileObject, err error) {
(ctx context.Context, param params.CreateFileObjectParams, reader io.Reader)
| 22 | |
| 23 | // func (s *sqlDatabase) CreateFileObject(ctx context.Context, name string, size int64, tags []string, reader io.Reader) (fileObjParam params.FileObject, err error) { |
| 24 | func (s *sqlDatabase) CreateFileObject(ctx context.Context, param params.CreateFileObjectParams, reader io.Reader) (fileObjParam params.FileObject, err error) { |
| 25 | // Save the file to temporary storage first. This allows us to accept the entire file, even over |
| 26 | // a slow connection, without locking the database as we stream the file to the DB. |
| 27 | // SQLite will lock the entire database (including for readers) when the data is being committed. |
| 28 | tmpFile, err := util.GetTmpFileHandle("") |
| 29 | if err != nil { |
| 30 | return params.FileObject{}, fmt.Errorf("failed to create tmp file: %w", err) |
| 31 | } |
| 32 | defer func() { |
| 33 | tmpFile.Close() |
| 34 | os.Remove(tmpFile.Name()) //nolint:gosec // G703 - path from os.CreateTemp, not user input |
| 35 | }() |
| 36 | if _, err := io.Copy(tmpFile, reader); err != nil { |
| 37 | return params.FileObject{}, fmt.Errorf("failed to copy data: %w", err) |
| 38 | } |
| 39 | if err := tmpFile.Sync(); err != nil { |
| 40 | return params.FileObject{}, fmt.Errorf("failed to flush data to disk: %w", err) |
| 41 | } |
| 42 | // File has been transferred. We need to seek to the beginning of the file. This same handler will be used |
| 43 | // to streab the data to the database. |
| 44 | if _, err := tmpFile.Seek(0, 0); err != nil { |
| 45 | return params.FileObject{}, fmt.Errorf("failed to seek to beginning: %w", err) |
| 46 | } |
| 47 | // Read first 8KB for type detection |
| 48 | buffer := make([]byte, 8192) |
| 49 | n, _ := io.ReadFull(tmpFile, buffer) |
| 50 | fileType := util.DetectFileType(buffer[:n]) |
| 51 | |
| 52 | fileObj := FileObject{ |
| 53 | Name: param.Name, |
| 54 | Description: param.Description, |
| 55 | FileType: fileType, |
| 56 | Size: param.Size, |
| 57 | } |
| 58 | |
| 59 | defer func() { |
| 60 | if err == nil { |
| 61 | s.sendNotify(common.FileObjectEntityType, common.CreateOperation, fileObjParam) |
| 62 | } |
| 63 | }() |
| 64 | |
| 65 | var fileBlob FileBlob |
| 66 | err = s.objectsConn.Transaction(func(tx *gorm.DB) error { |
| 67 | // Create the file first |
| 68 | if err := tx.Create(&fileObj).Error; err != nil { |
| 69 | return fmt.Errorf("failed to create file object: %w", err) |
| 70 | } |
| 71 | |
| 72 | // create the file blob, without any space allocated for the blob. |
| 73 | fileBlob = FileBlob{ |
| 74 | FileObjectID: fileObj.ID, |
| 75 | } |
| 76 | if err := tx.Create(&fileBlob).Error; err != nil { |
| 77 | return fmt.Errorf("failed to create file blob object: %w", err) |
| 78 | } |
| 79 | |
| 80 | // allocate space for the blob using the zeroblob() function. This will allow us to avoid |
| 81 | // having to allocate potentially huge byte arrays in memory and writing that huge blob to |
nothing calls this directly
no test coverage detected