diff --git a/code/go/0chain.net/blobbercore/allocation/allocationchange.go b/code/go/0chain.net/blobbercore/allocation/allocationchange.go index 09c124885..554cf3151 100644 --- a/code/go/0chain.net/blobbercore/allocation/allocationchange.go +++ b/code/go/0chain.net/blobbercore/allocation/allocationchange.go @@ -20,6 +20,7 @@ const ( RENAME_OPERATION = "rename" COPY_OPERATION = "copy" UPDATE_ATTRS_OPERATION = "update_attrs" + CREATEDIR_OPERATION = "createdir" ) const ( diff --git a/code/go/0chain.net/blobbercore/allocation/newfilechange.go b/code/go/0chain.net/blobbercore/allocation/newfilechange.go index 8683f1d65..f3761f158 100644 --- a/code/go/0chain.net/blobbercore/allocation/newfilechange.go +++ b/code/go/0chain.net/blobbercore/allocation/newfilechange.go @@ -43,9 +43,79 @@ type NewFileChange struct { IsFinal bool `json:"is_final,omitempty"` } +func (nf *NewFileChange) CreateDir(ctx context.Context, allocationID, dirName, allocationRoot string) (*reference.Ref, error) { + path := filepath.Clean(dirName) + tSubDirs := reference.GetSubDirsFromPath(path) + + rootRef, err := reference.GetReferencePath(ctx, allocationID, nf.Path) + if err != nil { + return nil, err + } + + dirRef := rootRef + treelevel := 0 + for { + found := false + for _, child := range dirRef.Children { + if child.Type == reference.DIRECTORY && treelevel < len(tSubDirs) { + if child.Name == tSubDirs[treelevel] { + dirRef = child + found = true + break + } + } + } + if found { + treelevel++ + continue + } + if len(tSubDirs) > treelevel { + newRef := reference.NewDirectoryRef() + newRef.AllocationID = dirRef.AllocationID + newRef.Path = "/" + strings.Join(tSubDirs[:treelevel+1], "/") + newRef.ParentPath = "/" + strings.Join(tSubDirs[:treelevel], "/") + newRef.Name = tSubDirs[treelevel] + newRef.LookupHash = reference.GetReferenceLookup(dirRef.AllocationID, newRef.Path) + dirRef.AddChild(newRef) + dirRef = newRef + treelevel++ + continue + } else { + break + } + } + + var newDir = reference.NewDirectoryRef() + newDir.ActualFileSize = 2 + newDir.AllocationID = dirRef.AllocationID + newDir.MerkleRoot = nf.MerkleRoot + newDir.Name = dirName + newDir.Size = 2 + newDir.NumBlocks = 2 + newDir.ParentPath = dirRef.Path + newDir.WriteMarker = allocationRoot + dirRef.AddChild(newDir) + + if _, err := rootRef.CalculateHash(ctx, true); err != nil { + return nil, err + } + + stats.NewDirCreated(ctx, dirRef.ID) + return rootRef, nil +} + func (nf *NewFileChange) ProcessChange(ctx context.Context, change *AllocationChange, allocationRoot string) (*reference.Ref, error) { + if change.Operation == CREATEDIR_OPERATION { + err := nf.Unmarshal(change.Input) + if err != nil { + return nil, err + } + + return nf.CreateDir(ctx, nf.AllocationID, nf.Path, allocationRoot) + } + path, _ := filepath.Split(nf.Path) path = filepath.Clean(path) tSubDirs := reference.GetSubDirsFromPath(path) @@ -117,6 +187,7 @@ func (nf *NewFileChange) ProcessChange(ctx context.Context, if _, err := rootRef.CalculateHash(ctx, true); err != nil { return nil, err } + stats.NewFileCreated(ctx, newFile.ID) return rootRef, nil } diff --git a/code/go/0chain.net/blobbercore/filestore/fs_store.go b/code/go/0chain.net/blobbercore/filestore/fs_store.go index 84b30b7d1..64e43b492 100644 --- a/code/go/0chain.net/blobbercore/filestore/fs_store.go +++ b/code/go/0chain.net/blobbercore/filestore/fs_store.go @@ -475,6 +475,14 @@ func (fs *FileFSStore) GetMerkleTreeForFile(allocationID string, fileData *FileI return mt, nil } +func (fs *FileFSStore) CreateDir(dirName string) error { + return createDirs(dirName) +} + +func (fs *FileFSStore) DeleteDir(allocationID, dirPath, connectionID string) error { + return nil +} + func (fs *FileFSStore) WriteFile(allocationID string, fileData *FileInputData, infile multipart.File, connectionID string) (*FileOutputData, error) { diff --git a/code/go/0chain.net/blobbercore/filestore/store.go b/code/go/0chain.net/blobbercore/filestore/store.go index 4d98a5136..565b27d15 100644 --- a/code/go/0chain.net/blobbercore/filestore/store.go +++ b/code/go/0chain.net/blobbercore/filestore/store.go @@ -43,6 +43,8 @@ type FileObjectHandler func(contentHash string, contentSize int64) type FileStore interface { WriteFile(allocationID string, fileData *FileInputData, infile multipart.File, connectionID string) (*FileOutputData, error) DeleteTempFile(allocationID string, fileData *FileInputData, connectionID string) error + CreateDir(dirName string) error + DeleteDir(allocationID, dirPath, connectionID string) error GetFileBlock(allocationID string, fileData *FileInputData, blockNum int64, numBlocks int64) ([]byte, error) CommitWrite(allocationID string, fileData *FileInputData, connectionID string) (bool, error) //GetMerkleTreeForFile(allocationID string, fileData *FileInputData) (util.MerkleTreeI, error) diff --git a/code/go/0chain.net/blobbercore/handler/handler.go b/code/go/0chain.net/blobbercore/handler/handler.go index 45b7163fb..9e92f37e1 100644 --- a/code/go/0chain.net/blobbercore/handler/handler.go +++ b/code/go/0chain.net/blobbercore/handler/handler.go @@ -34,6 +34,9 @@ func SetupHandlers(r *mux.Router) { r.HandleFunc("/v1/file/rename/{allocation}", common.UserRateLimit(common.ToJSONResponse(WithConnection(RenameHandler)))) r.HandleFunc("/v1/file/copy/{allocation}", common.UserRateLimit(common.ToJSONResponse(WithConnection(CopyHandler)))) r.HandleFunc("/v1/file/attributes/{allocation}", common.UserRateLimit(common.ToJSONResponse(WithConnection(UpdateAttributesHandler)))) + r.HandleFunc("/v1/dir/{allocation}", common.UserRateLimit(common.ToJSONResponse(WithConnection(CreateDirHandler)))).Methods("POST") + r.HandleFunc("/v1/dir/{allocation}", common.UserRateLimit(common.ToJSONResponse(WithConnection(CreateDirHandler)))).Methods("DELETE") + r.HandleFunc("/v1/dir/rename/{allocation}", common.UserRateLimit(common.ToJSONResponse(WithConnection(CreateDirHandler)))).Methods("POST") r.HandleFunc("/v1/connection/commit/{allocation}", common.UserRateLimit(common.ToJSONResponse(WithConnection(CommitHandler)))) r.HandleFunc("/v1/file/commitmetatxn/{allocation}", common.UserRateLimit(common.ToJSONResponse(WithConnection(CommitMetaTxnHandler)))) @@ -259,6 +262,17 @@ func CopyHandler(ctx context.Context, r *http.Request) (interface{}, error) { return response, nil } +/*CreateDirHandler is the handler to respond to create dir for allocation*/ +func CreateDirHandler(ctx context.Context, r *http.Request) (interface{}, error) { + ctx = setupHandlerContext(ctx, r) + response, err := storageHandler.CreateDir(ctx, r) + if err != nil { + return nil, err + } + + return response, nil +} + /*UploadHandler is the handler to respond to upload requests fro clients*/ func UploadHandler(ctx context.Context, r *http.Request) (interface{}, error) { ctx = setupHandlerContext(ctx, r) diff --git a/code/go/0chain.net/blobbercore/handler/object_operation_handler.go b/code/go/0chain.net/blobbercore/handler/object_operation_handler.go index 3f3357d0b..8dd7023e7 100644 --- a/code/go/0chain.net/blobbercore/handler/object_operation_handler.go +++ b/code/go/0chain.net/blobbercore/handler/object_operation_handler.go @@ -910,6 +910,88 @@ func (fsh *StorageHandler) DeleteFile(ctx context.Context, r *http.Request, conn return nil, common.NewError("invalid_file", "File does not exist at path") } +func (fsh *StorageHandler) CreateDir(ctx context.Context, r *http.Request) (*blobberhttp.UploadResult, error) { + allocationTx := ctx.Value(constants.ALLOCATION_CONTEXT_KEY).(string) + clientID := ctx.Value(constants.CLIENT_CONTEXT_KEY).(string) + + allocationObj, err := fsh.verifyAllocation(ctx, allocationTx, false) + if err != nil { + return nil, common.NewError("invalid_parameters", "Invalid allocation id passed."+err.Error()) + } + + valid, err := verifySignatureFromRequest(allocationTx, r.Header.Get(common.ClientSignatureHeader), allocationObj.OwnerPublicKey) + if !valid || err != nil { + return nil, common.NewError("invalid_signature", "Invalid signature") + } + + allocationID := allocationObj.ID + + if len(clientID) == 0 { + return nil, common.NewError("invalid_operation", "Operation needs to be performed by the owner or the payer of the allocation") + } + + dirPath := r.FormValue("dir_path") + if len(dirPath) == 0 { + return nil, common.NewError("invalid_parameters", "Invalid dir path passed") + } + + exisitingRef := fsh.checkIfFileAlreadyExists(ctx, allocationID, dirPath) + if allocationObj.OwnerID != clientID && allocationObj.PayerID != clientID { + return nil, common.NewError("invalid_operation", "Operation needs to be performed by the owner or the payer of the allocation") + } + + if exisitingRef != nil { + return nil, common.NewError("duplicate_file", "File at path already exists") + } + + connectionID := r.FormValue("connection_id") + if len(connectionID) == 0 { + return nil, common.NewError("invalid_parameters", "Invalid connection id passed") + } + + connectionObj, err := allocation.GetAllocationChanges(ctx, connectionID, allocationID, clientID) + if err != nil { + return nil, common.NewError("meta_error", "Error reading metadata for connection") + } + + mutex := lock.GetMutex(connectionObj.TableName(), connectionID) + mutex.Lock() + defer mutex.Unlock() + + allocationChange := &allocation.AllocationChange{} + allocationChange.ConnectionID = connectionObj.ConnectionID + allocationChange.Size = 0 + allocationChange.Operation = allocation.CREATEDIR_OPERATION + connectionObj.Size += allocationChange.Size + var formData allocation.NewFileChange + formData.Filename = dirPath + formData.Path = dirPath + formData.AllocationID = allocationID + formData.ConnectionID = connectionID + formData.ActualHash = "-" + formData.ActualSize = 1 + + connectionObj.AddChange(allocationChange, &formData) + + err = filestore.GetFileStore().CreateDir(dirPath) + if err != nil { + return nil, common.NewError("upload_error", "Failed to upload the file. "+err.Error()) + } + + err = connectionObj.ApplyChanges(ctx, "/") + if err != nil { + return nil, err + } + + result := &blobberhttp.UploadResult{} + result.Filename = dirPath + result.Hash = "" + result.MerkleRoot = "" + result.Size = 0 + + return result, nil +} + //WriteFile stores the file into the blobber files system from the HTTP request func (fsh *StorageHandler) WriteFile(ctx context.Context, r *http.Request) (*blobberhttp.UploadResult, error) { diff --git a/code/go/0chain.net/blobbercore/stats/filestats.go b/code/go/0chain.net/blobbercore/stats/filestats.go index 76ac3d2b0..e2e49e74f 100644 --- a/code/go/0chain.net/blobbercore/stats/filestats.go +++ b/code/go/0chain.net/blobbercore/stats/filestats.go @@ -26,6 +26,14 @@ func (FileStats) TableName() string { return "file_stats" } +func NewDirCreated(ctx context.Context, refID int64) { + db := datastore.GetStore().GetTransaction(ctx) + stats := &FileStats{RefID: refID} + stats.NumBlockDownloads = 0 + stats.NumUpdates = 1 + db.Save(stats) +} + func NewFileCreated(ctx context.Context, refID int64) { db := datastore.GetStore().GetTransaction(ctx) stats := &FileStats{RefID: refID}