Skip to content

Commit 6652c59

Browse files
authored
Fix/limit thumbnail size (#678)
* Optimize delete * Limit thumbnail size to 1MB * Set hashToBeComputed field for affected directories * Fix delete * Fix hash calculation issue * Fix sql query with sequential rather than goroutine * Add db constraint * Fix hash calculation issue for delete operation * Skip go routine for count not 0 * Uncapatalize error string
1 parent c666d22 commit 6652c59

File tree

5 files changed

+132
-84
lines changed

5 files changed

+132
-84
lines changed

code/go/0chain.net/blobbercore/allocation/deletefilechange.go

Lines changed: 64 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,14 @@ package allocation
33
import (
44
"context"
55
"encoding/json"
6-
"os"
6+
"fmt"
7+
"sync"
78

89
"github.com/0chain/blobber/code/go/0chain.net/blobbercore/datastore"
910
"github.com/0chain/blobber/code/go/0chain.net/blobbercore/filestore"
1011
"github.com/0chain/blobber/code/go/0chain.net/blobbercore/reference"
11-
"github.com/0chain/blobber/code/go/0chain.net/core/common"
12-
. "github.com/0chain/blobber/code/go/0chain.net/core/logging"
12+
"github.com/0chain/blobber/code/go/0chain.net/core/logging"
13+
"gorm.io/gorm"
1314

1415
"go.uber.org/zap"
1516
)
@@ -21,11 +22,10 @@ type DeleteFileChange struct {
2122
Path string `json:"path"`
2223
Size int64 `json:"size"`
2324
Hash string `json:"hash"`
24-
ContentHash map[string]bool
2525
}
2626

2727
func (nf *DeleteFileChange) ApplyChange(ctx context.Context, change *AllocationChange, allocationRoot string) (*reference.Ref, error) {
28-
rootRef, contentHash, err := reference.DeleteObject(ctx, nf.AllocationID, nf.Path)
28+
rootRef, err := reference.DeleteObject(ctx, nf.AllocationID, nf.Path)
2929
if err != nil {
3030
return nil, err
3131
}
@@ -34,8 +34,6 @@ func (nf *DeleteFileChange) ApplyChange(ctx context.Context, change *AllocationC
3434
return nil, err
3535
}
3636

37-
nf.ContentHash = contentHash
38-
3937
return nil, nil
4038
}
4139

@@ -58,20 +56,67 @@ func (nf *DeleteFileChange) DeleteTempFile() error {
5856

5957
func (nf *DeleteFileChange) CommitToFileStore(ctx context.Context) error {
6058
db := datastore.GetStore().GetTransaction(ctx)
61-
var errFileWasDeleted error
62-
for contenthash := range nf.ContentHash {
63-
var count int64
64-
err := db.Table((&reference.Ref{}).TableName()).Where(db.Where(&reference.Ref{ThumbnailHash: contenthash}).Or(&reference.Ref{ContentHash: contenthash})).Where("deleted_at IS null").Where(&reference.Ref{AllocationID: nf.AllocationID}).Count(&count).Error
65-
if err == nil && count == 0 {
66-
Logger.Info("Deleting content file", zap.String("content_hash", contenthash))
67-
if err := filestore.GetFileStore().DeleteFile(nf.AllocationID, contenthash); err != nil {
68-
if os.IsNotExist(err) {
69-
errFileWasDeleted = common.ErrFileWasDeleted
59+
type Result struct {
60+
ContentHash string
61+
ThumbnailHash string
62+
}
63+
64+
limitCh := make(chan struct{}, 10)
65+
wg := &sync.WaitGroup{}
66+
var results []Result
67+
err := db.Model(&reference.Ref{}).Unscoped().
68+
Select("content_hash", "thumbnail_hash").
69+
Where("allocation_id=? AND path LIKE ? AND type=? AND deleted_at is not NULL",
70+
nf.AllocationID, nf.Path+"%", reference.FILE).
71+
FindInBatches(&results, 100, func(tx *gorm.DB, batch int) error {
72+
73+
for _, res := range results {
74+
var count int64
75+
tx.Model(&reference.Ref{}).
76+
Where("allocation_id=? AND content_hash=?", nf.AllocationID, res.ContentHash).
77+
Count(&count)
78+
79+
if count != 0 {
7080
continue
7181
}
72-
Logger.Error("FileStore_DeleteFile", zap.String("allocation_id", nf.AllocationID), zap.Error(err))
82+
83+
limitCh <- struct{}{}
84+
wg.Add(1)
85+
86+
go func(res Result, count int64) {
87+
defer func() {
88+
<-limitCh
89+
wg.Done()
90+
}()
91+
92+
err := filestore.GetFileStore().DeleteFile(nf.AllocationID, res.ContentHash)
93+
if err != nil {
94+
logging.Logger.Error(fmt.Sprintf("Error while deleting file: %s", err.Error()),
95+
zap.String("content_hash", res.ContentHash))
96+
}
97+
98+
if res.ThumbnailHash != "" {
99+
err := filestore.GetFileStore().DeleteFile(nf.AllocationID, res.ThumbnailHash)
100+
if err != nil {
101+
logging.Logger.Error(fmt.Sprintf("Error while deleting thumbnail: %s", err.Error()),
102+
zap.String("thumbnail", res.ThumbnailHash))
103+
}
104+
}
105+
106+
}(res, count)
107+
73108
}
74-
}
109+
return nil
110+
}).Error
111+
112+
wg.Wait()
113+
114+
if err != nil {
115+
return err
75116
}
76-
return errFileWasDeleted
117+
118+
return db.Model(&reference.Ref{}).Unscoped().
119+
Delete(&reference.Ref{},
120+
"allocation_id = ? AND path LIKE ? AND deleted_at IS NOT NULL",
121+
nf.AllocationID, nf.Path+"%").Error
77122
}

code/go/0chain.net/blobbercore/handler/file_command_update.go

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package handler
33
import (
44
"context"
55
"encoding/json"
6+
"fmt"
67
"net/http"
78

89
"github.com/0chain/blobber/code/go/0chain.net/blobbercore/allocation"
@@ -11,12 +12,15 @@ import (
1112
"github.com/0chain/blobber/code/go/0chain.net/blobbercore/reference"
1213
"github.com/0chain/blobber/code/go/0chain.net/core/common"
1314
"github.com/0chain/blobber/code/go/0chain.net/core/logging"
14-
"github.com/0chain/gosdk/constants"
15-
sdkConstants "github.com/0chain/gosdk/constants"
15+
sdkConst "github.com/0chain/gosdk/constants"
1616
"github.com/0chain/gosdk/zboxcore/fileref"
1717
"go.uber.org/zap"
1818
)
1919

20+
const (
21+
UpdateMeta = "updatedMeta"
22+
)
23+
2024
// UpdateFileCommand command for updating file
2125
type UpdateFileCommand struct {
2226
existingFileRef *reference.Ref
@@ -26,11 +30,11 @@ type UpdateFileCommand struct {
2630

2731
// IsValidated validate request.
2832
func (cmd *UpdateFileCommand) IsValidated(ctx context.Context, req *http.Request, allocationObj *allocation.Allocation, clientID string) error {
29-
uploadMetaString := req.FormValue("uploadMeta")
33+
uploadMetaString := req.FormValue(UploadMeta)
3034

3135
if uploadMetaString == "" {
3236
// backward compatibility for old update request
33-
uploadMetaString = req.FormValue("updatedMeta")
37+
uploadMetaString = req.FormValue(UpdateMeta)
3438
}
3539

3640
err := json.Unmarshal([]byte(uploadMetaString), &cmd.fileChanger)
@@ -54,6 +58,14 @@ func (cmd *UpdateFileCommand) IsValidated(ctx context.Context, req *http.Request
5458
return common.NewError("invalid_operation", "Operation needs to be performed by the owner, collaborator or the payer of the allocation")
5559
}
5660

61+
_, thumbHeader, _ := req.FormFile(UploadThumbnailFile)
62+
if thumbHeader != nil {
63+
if thumbHeader.Size > MaxThumbnailSize {
64+
return common.NewError("max_thumbnail_size",
65+
fmt.Sprintf("thumbnail size %d should not be greater than %d", thumbHeader.Size, MaxThumbnailSize))
66+
}
67+
}
68+
5769
return nil
5870
}
5971

@@ -63,7 +75,7 @@ func (cmd *UpdateFileCommand) ProcessContent(ctx context.Context, req *http.Requ
6375

6476
result.Filename = cmd.fileChanger.Filename
6577

66-
origfile, _, err := req.FormFile("uploadFile")
78+
origfile, _, err := req.FormFile(UploadFile)
6779
if err != nil {
6880
return result, common.NewError("invalid_parameters", "Error Reading multi parts for file."+err.Error())
6981
}
@@ -109,7 +121,7 @@ func (cmd *UpdateFileCommand) ProcessContent(ctx context.Context, req *http.Requ
109121
cmd.allocationChange = &allocation.AllocationChange{}
110122
cmd.allocationChange.ConnectionID = connectionObj.ID
111123
cmd.allocationChange.Size = allocationSize - cmd.existingFileRef.Size
112-
cmd.allocationChange.Operation = sdkConstants.FileOperationUpdate
124+
cmd.allocationChange.Operation = sdkConst.FileOperationUpdate
113125

114126
if cmd.fileChanger.IsFinal {
115127
connectionObj.Size = allocationSize - cmd.existingFileRef.Size
@@ -122,7 +134,7 @@ func (cmd *UpdateFileCommand) ProcessContent(ctx context.Context, req *http.Requ
122134

123135
// ProcessThumbnail flush thumbnail file to FileStorage if it has.
124136
func (cmd *UpdateFileCommand) ProcessThumbnail(ctx context.Context, req *http.Request, allocationObj *allocation.Allocation, connectionObj *allocation.AllocationChangeCollector) error {
125-
thumbfile, thumbHeader, _ := req.FormFile("uploadThumbnailFile")
137+
thumbfile, thumbHeader, _ := req.FormFile(UploadThumbnailFile)
126138

127139
if thumbHeader != nil {
128140
defer thumbfile.Close()
@@ -145,7 +157,7 @@ func (cmd *UpdateFileCommand) ProcessThumbnail(ctx context.Context, req *http.Re
145157

146158
func (cmd *UpdateFileCommand) reloadChange(connectionObj *allocation.AllocationChangeCollector) {
147159
for _, c := range connectionObj.Changes {
148-
if c.Operation != constants.FileOperationUpdate {
160+
if c.Operation != sdkConst.FileOperationUpdate {
149161
continue
150162
}
151163

@@ -168,7 +180,7 @@ func (cmd *UpdateFileCommand) reloadChange(connectionObj *allocation.AllocationC
168180
// UpdateChange add UpdateFileChanger in db
169181
func (cmd *UpdateFileCommand) UpdateChange(ctx context.Context, connectionObj *allocation.AllocationChangeCollector) error {
170182
for _, c := range connectionObj.Changes {
171-
if c.Operation != constants.FileOperationUpdate {
183+
if c.Operation != sdkConst.FileOperationUpdate {
172184
continue
173185
}
174186

code/go/0chain.net/blobbercore/handler/file_command_upload.go

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,13 @@ import (
1919
"go.uber.org/zap"
2020
)
2121

22+
const (
23+
MaxThumbnailSize = MB
24+
UploadMeta = "uploadMeta"
25+
UploadFile = "uploadFile"
26+
UploadThumbnailFile = "uploadThumbnailFile"
27+
)
28+
2229
// UploadFileCommand command for resuming file
2330
type UploadFileCommand struct {
2431
allocationChange *allocation.AllocationChange
@@ -33,7 +40,7 @@ func (cmd *UploadFileCommand) IsValidated(ctx context.Context, req *http.Request
3340

3441
fileChanger := &allocation.UploadFileChanger{}
3542

36-
uploadMetaString := req.FormValue("uploadMeta")
43+
uploadMetaString := req.FormValue(UploadMeta)
3744
err := json.Unmarshal([]byte(uploadMetaString), fileChanger)
3845
if err != nil {
3946
return common.NewError("invalid_parameters",
@@ -64,6 +71,14 @@ func (cmd *UploadFileCommand) IsValidated(ctx context.Context, req *http.Request
6471
return err
6572
}
6673

74+
_, thumbHeader, _ := req.FormFile(UploadThumbnailFile)
75+
if thumbHeader != nil {
76+
if thumbHeader.Size > MaxThumbnailSize {
77+
return common.NewError("max_thumbnail_size",
78+
fmt.Sprintf("thumbnail size %d should not be greater than %d", thumbHeader.Size, MaxThumbnailSize))
79+
}
80+
}
81+
6782
if fileChanger.ChunkSize <= 0 {
6883
fileChanger.ChunkSize = fileref.CHUNK_SIZE
6984
}
@@ -77,7 +92,7 @@ func (cmd *UploadFileCommand) IsValidated(ctx context.Context, req *http.Request
7792
func (cmd *UploadFileCommand) ProcessContent(ctx context.Context, req *http.Request, allocationObj *allocation.Allocation, connectionObj *allocation.AllocationChangeCollector) (blobberhttp.UploadResult, error) {
7893
result := blobberhttp.UploadResult{}
7994

80-
origfile, _, err := req.FormFile("uploadFile")
95+
origfile, _, err := req.FormFile(UploadFile)
8196
if err != nil {
8297
return result, common.NewError("invalid_parameters", "Error Reading multi parts for file."+err.Error())
8398
}
@@ -137,7 +152,7 @@ func (cmd *UploadFileCommand) ProcessContent(ctx context.Context, req *http.Requ
137152

138153
// ProcessThumbnail flush thumbnail file to FileStorage if it has.
139154
func (cmd *UploadFileCommand) ProcessThumbnail(ctx context.Context, req *http.Request, allocationObj *allocation.Allocation, connectionObj *allocation.AllocationChangeCollector) error {
140-
thumbfile, thumbHeader, _ := req.FormFile("uploadThumbnailFile")
155+
thumbfile, thumbHeader, _ := req.FormFile(UploadThumbnailFile)
141156

142157
if thumbHeader != nil {
143158
defer thumbfile.Close()

code/go/0chain.net/blobbercore/reference/object.go

Lines changed: 23 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -3,77 +3,48 @@ package reference
33
import (
44
"context"
55
"path/filepath"
6-
"strings"
76

87
"github.com/0chain/blobber/code/go/0chain.net/blobbercore/datastore"
98
"github.com/0chain/blobber/code/go/0chain.net/core/common"
10-
_ "gorm.io/gorm"
11-
"gorm.io/gorm/clause"
129
)
1310

14-
// DeleteObject delete object from tree, and return tree root and deleted content hash list
15-
func DeleteObject(ctx context.Context, allocationID, path string) (*Ref, map[string]bool, error) {
16-
rootRef, err := GetObjectTree(ctx, allocationID, "/")
17-
if err != nil {
18-
return nil, nil, err
19-
}
20-
21-
rootRef.HashToBeComputed = true
22-
db := datastore.GetStore().
23-
GetTransaction(ctx)
11+
func DeleteObject(ctx context.Context, allocationID, objPath string) (*Ref, error) {
2412

25-
var deletedObjects []*Ref
26-
txDelete := db.Clauses(clause.Returning{Columns: []clause.Column{{Name: "content_hash"}, {Name: "thumbnail_hash"}, {Name: "type"}}})
13+
db := datastore.GetStore().GetTransaction(ctx)
14+
err := db.Delete(&Ref{}, "allocation_id=? AND path LIKE ? AND path != ?",
15+
allocationID, objPath+"%", "/").Error
2716

28-
path = filepath.Join("/", path)
29-
txDelete = txDelete.Where("allocation_id = ? and deleted_at IS NULL and (path LIKE ? or path = ?) and path != ? ", allocationID, path+"%", path, "/")
30-
31-
err = txDelete.Delete(&deletedObjects).Error
3217
if err != nil {
33-
return nil, nil, common.NewError("bad_db_operation", err.Error())
18+
return nil, err
3419
}
3520

36-
deletedFiles := make(map[string]bool)
37-
for _, it := range deletedObjects {
38-
if it.Type == FILE {
39-
deletedFiles[it.ContentHash] = true
40-
if it.ThumbnailHash != "" {
41-
deletedFiles[it.ThumbnailHash] = true
42-
}
43-
}
44-
}
45-
46-
// remove deleted object from tree
47-
if path == "/" {
48-
rootRef.Children = nil
49-
return rootRef, deletedFiles, nil
21+
parentPath := filepath.Dir(objPath)
22+
rootRef, err := GetReferencePath(ctx, allocationID, parentPath)
23+
if err != nil {
24+
return nil, err
5025
}
5126

52-
path = strings.TrimSuffix(path, "/")
53-
tSubDirs := GetSubDirsFromPath(path)
27+
subDirs := GetSubDirsFromPath(parentPath)
5428
dirRef := rootRef
55-
treelevel := 0
56-
for treelevel < len(tSubDirs)-1 {
57-
found := false
58-
for _, child := range dirRef.Children {
59-
if child.Name == tSubDirs[treelevel] && child.Type == DIRECTORY {
60-
dirRef = child
61-
dirRef.HashToBeComputed = true
29+
30+
for _, subDir := range subDirs {
31+
var found bool
32+
for _, ref := range dirRef.Children {
33+
if ref.Name == subDir && ref.Type == DIRECTORY {
34+
ref.HashToBeComputed = true
35+
ref.childrenLoaded = true
6236
found = true
37+
dirRef = ref
6338
break
6439
}
6540
}
41+
6642
if !found {
67-
return nil, nil, common.NewError("invalid_reference_path", "Invalid reference path from the blobber")
43+
return nil, common.NewError("invalid_reference_path", "Reference path has invalid references")
6844
}
69-
treelevel++
7045
}
7146

72-
for i, child := range dirRef.Children {
73-
if child.Path == path {
74-
dirRef.RemoveChild(i)
75-
return rootRef, deletedFiles, nil
76-
}
77-
}
78-
return nil, nil, common.NewError("invalid_reference_path", "Invalid reference path from the blobber")
47+
rootRef.HashToBeComputed = true
48+
rootRef.childrenLoaded = true
49+
return rootRef, nil
7950
}

code/go/0chain.net/blobbercore/stats/filestats.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import (
1212
type FileStats struct {
1313
ID int64 `gorm:"column:id;primaryKey" json:"-"`
1414
RefID int64 `gorm:"column:ref_id;unique" json:"-"`
15-
Ref reference.Ref `gorm:"foreignKey:RefID"`
15+
Ref reference.Ref `gorm:"foreignKey:RefID;constraint:OnDelete:CASCADE"`
1616
NumUpdates int64 `gorm:"column:num_of_updates" json:"num_of_updates"`
1717
NumBlockDownloads int64 `gorm:"column:num_of_block_downloads" json:"num_of_block_downloads"`
1818
SuccessChallenges int64 `gorm:"column:num_of_challenges" json:"num_of_challenges"`
@@ -75,3 +75,8 @@ func GetFileStats(ctx context.Context, refID int64) (*FileStats, error) {
7575
}
7676
return stats, err
7777
}
78+
79+
func DeleteFileStats(ctx context.Context, refID int64) error {
80+
db := datastore.GetStore().GetTransaction(ctx)
81+
return db.Unscoped().Delete(&FileStats{}, "ref_id=?", refID).Error
82+
}

0 commit comments

Comments
 (0)