From 9452d48bbd5aa66126515a97d33058affa5c5f22 Mon Sep 17 00:00:00 2001 From: bagas Date: Mon, 23 Sep 2024 10:25:10 +0700 Subject: [PATCH] Implement Redis caching for S3 list object calls --- handler/file/file.go | 21 +- handler/file/query/query.go | 19 +- handler/file/rename/rename.go | 21 +- handler/file/upload/upload.go | 4 +- handler/file/visibility/visibility.go | 20 +- handler/forgotPassword/verify/verify.go | 2 +- handler/user/ResetPassword/ResetPassword.go | 2 +- handler/user/totp/setup.go | 2 +- handler/user/user.go | 67 ++---- service/service.go | 225 +++++++++++++++----- types/types.go | 36 ++-- utils/utils.go | 23 +- view/client/file/file.templ | 19 +- view/client/file/file_templ.go | 59 ++--- 14 files changed, 279 insertions(+), 241 deletions(-) diff --git a/handler/file/file.go b/handler/file/file.go index 29e1b60..31d60bb 100644 --- a/handler/file/file.go +++ b/handler/file/file.go @@ -8,7 +8,6 @@ import ( "github.com/fossyy/filekeeper/utils" fileView "github.com/fossyy/filekeeper/view/client/file" "net/http" - "strconv" ) func GET(w http.ResponseWriter, r *http.Request) { @@ -22,26 +21,14 @@ func GET(w http.ResponseWriter, r *http.Request) { var filesData []types.FileData for _, file := range files { - prefix := fmt.Sprintf("%s/%s/chunk_", file.OwnerID.String(), file.ID.String()) - - existingChunks, err := app.Server.Storage.ListObjects(r.Context(), prefix) + userFile, err := app.Server.Service.GetUserFile(r.Context(), file.Name, file.OwnerID.String()) if err != nil { - app.Server.Logger.Error(err.Error()) w.WriteHeader(http.StatusInternalServerError) + app.Server.Logger.Error(err.Error()) return } - missingChunk := len(existingChunks) != int(file.TotalChunk) - - filesData = append(filesData, types.FileData{ - ID: file.ID.String(), - Name: file.Name, - Size: utils.ConvertFileSize(file.Size), - IsPrivate: file.IsPrivate, - Type: file.Type, - Done: !missingChunk, - Downloaded: strconv.FormatUint(file.Downloaded, 10), - }) + filesData = append(filesData, *userFile) } allowance, err := app.Server.Database.GetAllowance(userSession.UserID) @@ -50,7 +37,7 @@ func GET(w http.ResponseWriter, r *http.Request) { app.Server.Logger.Error(err.Error()) return } - usage, err := app.Server.Service.GetUserStorageUsage(userSession.UserID.String()) + usage, err := app.Server.Service.GetUserStorageUsage(r.Context(), userSession.UserID.String()) if err != nil { w.WriteHeader(http.StatusInternalServerError) app.Server.Logger.Error(err.Error()) diff --git a/handler/file/query/query.go b/handler/file/query/query.go index e45e291..4f9fac7 100644 --- a/handler/file/query/query.go +++ b/handler/file/query/query.go @@ -1,13 +1,10 @@ package queryHandler import ( - "fmt" "github.com/fossyy/filekeeper/app" "github.com/fossyy/filekeeper/types" - "github.com/fossyy/filekeeper/utils" fileView "github.com/fossyy/filekeeper/view/client/file" "net/http" - "strconv" ) func GET(w http.ResponseWriter, r *http.Request) { @@ -34,26 +31,14 @@ func GET(w http.ResponseWriter, r *http.Request) { var filesData []types.FileData for _, file := range files { - prefix := fmt.Sprintf("%s/%s/chunk_", file.OwnerID.String(), file.ID.String()) - - existingChunks, err := app.Server.Storage.ListObjects(r.Context(), prefix) + userFile, err := app.Server.Service.GetUserFile(r.Context(), file.Name, file.OwnerID.String()) if err != nil { w.WriteHeader(http.StatusInternalServerError) app.Server.Logger.Error(err.Error()) return } - missingChunk := len(existingChunks) != int(file.TotalChunk) - - filesData = append(filesData, types.FileData{ - ID: file.ID.String(), - Name: file.Name, - Size: utils.ConvertFileSize(file.Size), - IsPrivate: file.IsPrivate, - Type: file.Type, - Done: !missingChunk, - Downloaded: strconv.FormatUint(file.Downloaded, 10), - }) + filesData = append(filesData, *userFile) } if r.Header.Get("hx-request") == "true" { diff --git a/handler/file/rename/rename.go b/handler/file/rename/rename.go index 63469db..4ff9d5c 100644 --- a/handler/file/rename/rename.go +++ b/handler/file/rename/rename.go @@ -1,13 +1,10 @@ package renameFileHandler import ( - "fmt" "github.com/fossyy/filekeeper/app" "github.com/fossyy/filekeeper/types" - "github.com/fossyy/filekeeper/utils" fileView "github.com/fossyy/filekeeper/view/client/file" "net/http" - "strconv" ) func PATCH(w http.ResponseWriter, r *http.Request) { @@ -39,28 +36,14 @@ func PATCH(w http.ResponseWriter, r *http.Request) { return } - prefix := fmt.Sprintf("%s/%s/chunk_", file.OwnerID.String(), file.ID.String()) - - existingChunks, err := app.Server.Storage.ListObjects(r.Context(), prefix) + userFile, err := app.Server.Service.GetUserFile(r.Context(), newFile.Name, newFile.OwnerID.String()) if err != nil { w.WriteHeader(http.StatusInternalServerError) app.Server.Logger.Error(err.Error()) return } - missingChunk := len(existingChunks) != int(file.TotalChunk) - - fileData := types.FileData{ - ID: newFile.ID.String(), - Name: newFile.Name, - Size: utils.ConvertFileSize(newFile.Size), - IsPrivate: newFile.IsPrivate, - Type: newFile.Type, - Done: !missingChunk, - Downloaded: strconv.FormatUint(newFile.Downloaded, 10), - } - - component := fileView.JustFile(fileData) + component := fileView.JustFile(*userFile) err = component.Render(r.Context(), w) if err != nil { w.WriteHeader(http.StatusInternalServerError) diff --git a/handler/file/upload/upload.go b/handler/file/upload/upload.go index d947175..4c0a3ea 100644 --- a/handler/file/upload/upload.go +++ b/handler/file/upload/upload.go @@ -16,7 +16,7 @@ func POST(w http.ResponseWriter, r *http.Request) { return } - file, err := app.Server.Service.GetFile(fileID) + file, err := app.Server.Service.GetFile(r.Context(), fileID) if err != nil { app.Server.Logger.Error("error getting upload info: " + err.Error()) w.WriteHeader(http.StatusInternalServerError) @@ -52,7 +52,7 @@ func POST(w http.ResponseWriter, r *http.Request) { app.Server.Logger.Error("error copying byte to file dst: " + err.Error()) return } - + app.Server.Service.UpdateFileChunk(r.Context(), file.ID, file.OwnerID, rawIndex, file.TotalChunk) w.WriteHeader(http.StatusAccepted) return } diff --git a/handler/file/visibility/visibility.go b/handler/file/visibility/visibility.go index 648e2fb..9540e7c 100644 --- a/handler/file/visibility/visibility.go +++ b/handler/file/visibility/visibility.go @@ -1,13 +1,10 @@ package visibilityHandler import ( - "fmt" "github.com/fossyy/filekeeper/app" "github.com/fossyy/filekeeper/types" - "github.com/fossyy/filekeeper/utils" fileView "github.com/fossyy/filekeeper/view/client/file" "net/http" - "strconv" ) func PUT(w http.ResponseWriter, r *http.Request) { @@ -32,26 +29,13 @@ func PUT(w http.ResponseWriter, r *http.Request) { return } - prefix := fmt.Sprintf("%s/%s/chunk_", file.OwnerID.String(), file.ID.String()) - - existingChunks, err := app.Server.Storage.ListObjects(r.Context(), prefix) + userFile, err := app.Server.Service.GetUserFile(r.Context(), file.Name, file.OwnerID.String()) if err != nil { w.WriteHeader(http.StatusInternalServerError) app.Server.Logger.Error(err.Error()) return } - - missingChunk := len(existingChunks) != int(file.TotalChunk) - fileData := types.FileData{ - ID: file.ID.String(), - Name: file.Name, - Size: utils.ConvertFileSize(file.Size), - IsPrivate: !file.IsPrivate, - Type: file.Type, - Done: !missingChunk, - Downloaded: strconv.FormatUint(file.Downloaded, 10), - } - component := fileView.JustFile(fileData) + component := fileView.JustFile(*userFile) err = component.Render(r.Context(), w) if err != nil { w.WriteHeader(http.StatusInternalServerError) diff --git a/handler/forgotPassword/verify/verify.go b/handler/forgotPassword/verify/verify.go index 8a0e937..fb68c35 100644 --- a/handler/forgotPassword/verify/verify.go +++ b/handler/forgotPassword/verify/verify.go @@ -122,7 +122,7 @@ func POST(w http.ResponseWriter, r *http.Request) { return } - err = app.Server.Service.DeleteUser(userData.User.Email) + err = app.Server.Service.DeleteUser(r.Context(), userData.User.Email) if err != nil { w.WriteHeader(http.StatusInternalServerError) app.Server.Logger.Error(err.Error()) diff --git a/handler/user/ResetPassword/ResetPassword.go b/handler/user/ResetPassword/ResetPassword.go index 7fbec6a..46f54ba 100644 --- a/handler/user/ResetPassword/ResetPassword.go +++ b/handler/user/ResetPassword/ResetPassword.go @@ -51,7 +51,7 @@ func POST(w http.ResponseWriter, r *http.Request) { return } - err = app.Server.Service.DeleteUser(userSession.Email) + err = app.Server.Service.DeleteUser(r.Context(), userSession.Email) if err != nil { w.WriteHeader(http.StatusInternalServerError) app.Server.Logger.Error(err.Error()) diff --git a/handler/user/totp/setup.go b/handler/user/totp/setup.go index 5278bd6..ab34af0 100644 --- a/handler/user/totp/setup.go +++ b/handler/user/totp/setup.go @@ -87,7 +87,7 @@ func POST(w http.ResponseWriter, r *http.Request) { app.Server.Logger.Error(err.Error()) return } - err := app.Server.Service.DeleteUser(userSession.Email) + err := app.Server.Service.DeleteUser(r.Context(), userSession.Email) if err != nil { w.WriteHeader(http.StatusInternalServerError) app.Server.Logger.Error(err.Error()) diff --git a/handler/user/user.go b/handler/user/user.go index 6fb050d..4b7abf6 100644 --- a/handler/user/user.go +++ b/handler/user/user.go @@ -76,7 +76,7 @@ func GET(w http.ResponseWriter, r *http.Request) { return } - usage, err := app.Server.Service.GetUserStorageUsage(userSession.UserID.String()) + usage, err := app.Server.Service.GetUserStorageUsage(r.Context(), userSession.UserID.String()) if err != nil { w.WriteHeader(http.StatusInternalServerError) app.Server.Logger.Error(err.Error()) @@ -178,8 +178,9 @@ func handlerWS(conn *websocket.Conn, userSession types.User) { if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { + fileID := uuid.New() newFile := models.File{ - ID: uuid.New(), + ID: fileID, OwnerID: userSession.UserID, Name: uploadNewFile.Name, Size: uploadNewFile.Size, @@ -189,42 +190,24 @@ func handlerWS(conn *websocket.Conn, userSession types.User) { TotalChunk: uploadNewFile.Chunk, Downloaded: 0, } + err := app.Server.Database.CreateFile(&newFile) if err != nil { sendErrorResponse(conn, action.Action, "Error Creating File") continue } - fileData := &types.FileWithDetail{ - ID: newFile.ID, - OwnerID: newFile.OwnerID, - Name: newFile.Name, - Size: newFile.Size, - Downloaded: newFile.Downloaded, - Done: false, - } - fileData.Chunk = make(map[string]bool) - prefix := fmt.Sprintf("%s/%s/chunk_", userSession.UserID.String(), newFile.ID.String()) - - existingChunks, err := app.Server.Storage.ListObjects(context.TODO(), prefix) + userFile, err := app.Server.Service.GetUserFile(context.Background(), uploadNewFile.Name, userSession.UserID.String()) if err != nil { app.Server.Logger.Error(err.Error()) sendErrorResponse(conn, action.Action, "Unknown error") continue - } else { - for i := 0; i < int(newFile.TotalChunk); i++ { - fileData.Chunk[fmt.Sprintf("chunk_%d", i)] = false - } - - for _, chunkFile := range existingChunks { - var chunkIndex int - fmt.Sscanf(chunkFile, "chunk_%d", &chunkIndex) - fileData.Chunk[fmt.Sprintf("chunk_%d", chunkIndex)] = true - } } - sendSuccessResponseWithID(conn, action.Action, fileData, uploadNewFile.RequestID) + + sendSuccessResponseWithID(conn, action.Action, userFile, uploadNewFile.RequestID) continue } else { + app.Server.Logger.Error(err.Error()) sendErrorResponse(conn, action.Action, "Unknown error") continue } @@ -233,40 +216,14 @@ func handlerWS(conn *websocket.Conn, userSession types.User) { sendErrorResponse(conn, action.Action, "File Is Different") continue } - fileData := &types.FileWithDetail{ - ID: file.ID, - OwnerID: file.OwnerID, - Name: file.Name, - Size: file.Size, - Downloaded: file.Downloaded, - Chunk: make(map[string]bool), - Done: true, - } - - prefix := fmt.Sprintf("%s/%s/chunk_", userSession.UserID.String(), file.ID.String()) - existingChunks, err := app.Server.Storage.ListObjects(context.TODO(), prefix) + userFile, err := app.Server.Service.GetUserFile(context.Background(), file.Name, userSession.UserID.String()) if err != nil { app.Server.Logger.Error(err.Error()) - fileData.Done = false - } else { - for i := 0; i < int(file.TotalChunk); i++ { - fileData.Chunk[fmt.Sprintf("chunk_%d", i)] = false - } - for _, chunkFile := range existingChunks { - var chunkIndex int - fmt.Sscanf(chunkFile, "chunk_%d", &chunkIndex) - fileData.Chunk[fmt.Sprintf("chunk_%d", chunkIndex)] = true - } - - for i := 0; i < int(file.TotalChunk); i++ { - if !fileData.Chunk[fmt.Sprintf("chunk_%d", i)] { - fileData.Done = false - break - } - } + sendErrorResponse(conn, action.Action, "Unknown error") + continue } - sendSuccessResponseWithID(conn, action.Action, fileData, uploadNewFile.RequestID) + sendSuccessResponseWithID(conn, action.Action, userFile, uploadNewFile.RequestID) continue case Ping: sendSuccessResponse(conn, action.Action, map[string]string{"message": "received"}) diff --git a/service/service.go b/service/service.go index 282fbf3..059c29e 100644 --- a/service/service.go +++ b/service/service.go @@ -3,9 +3,12 @@ package service import ( "context" "encoding/json" + "errors" + "fmt" "github.com/fossyy/filekeeper/app" "github.com/fossyy/filekeeper/types" "github.com/fossyy/filekeeper/types/models" + "github.com/google/uuid" "github.com/redis/go-redis/v9" "time" ) @@ -24,29 +27,29 @@ func NewService(db types.Database, cache types.CachingServer) *Service { func (r *Service) GetUser(ctx context.Context, email string) (*models.User, error) { userJSON, err := app.Server.Cache.GetCache(ctx, "UserCache:"+email) - if err == redis.Nil { - userData, err := r.db.GetUser(email) - if err != nil { - return nil, err - } - - user := &models.User{ - UserID: userData.UserID, - Username: userData.Username, - Email: userData.Email, - Password: userData.Password, - Totp: userData.Totp, - } - - newUserJSON, _ := json.Marshal(user) - err = r.cache.SetCache(ctx, "UserCache:"+email, newUserJSON, time.Hour*12) - if err != nil { - return nil, err - } - - return user, nil - } if err != nil { + if errors.Is(err, redis.Nil) { + userData, err := r.db.GetUser(email) + if err != nil { + return nil, err + } + + user := &models.User{ + UserID: userData.UserID, + Username: userData.Username, + Email: userData.Email, + Password: userData.Password, + Totp: userData.Totp, + } + + newUserJSON, _ := json.Marshal(user) + err = r.cache.SetCache(ctx, "UserCache:"+email, newUserJSON, time.Hour*12) + if err != nil { + return nil, err + } + + return user, nil + } return nil, err } @@ -59,15 +62,16 @@ func (r *Service) GetUser(ctx context.Context, email string) (*models.User, erro return &user, nil } -func (r *Service) DeleteUser(email string) error { - err := r.cache.DeleteCache(context.Background(), "UserCache:"+email) +func (r *Service) DeleteUser(ctx context.Context, email string) error { + err := r.cache.DeleteCache(ctx, "UserCache:"+email) if err != nil { return err } return nil } -func (r *Service) GetUserStorageUsage(ownerID string) (uint64, error) { +func (r *Service) GetUserStorageUsage(ctx context.Context, ownerID string) (uint64, error) { + // TODO: Implement GetUserStorageUsage Cache files, err := app.Server.Database.GetFiles(ownerID, "", types.All) if err != nil { return 0, err @@ -79,22 +83,22 @@ func (r *Service) GetUserStorageUsage(ownerID string) (uint64, error) { return total, nil } -func (r *Service) GetFile(id string) (*models.File, error) { - fileJSON, err := r.cache.GetCache(context.Background(), "FileCache:"+id) - if err == redis.Nil { - uploadData, err := r.db.GetFile(id) - if err != nil { - return nil, err - } - - newFileJSON, _ := json.Marshal(uploadData) - err = r.cache.SetCache(context.Background(), "FileCache:"+id, newFileJSON, time.Hour*24) - if err != nil { - return nil, err - } - return uploadData, nil - } +func (r *Service) GetFile(ctx context.Context, id string) (*models.File, error) { + fileJSON, err := r.cache.GetCache(ctx, "FileCache:"+id) if err != nil { + if errors.Is(err, redis.Nil) { + uploadData, err := r.db.GetFile(id) + if err != nil { + return nil, err + } + + newFileJSON, _ := json.Marshal(uploadData) + err = r.cache.SetCache(ctx, "FileCache:"+id, newFileJSON, time.Hour*24) + if err != nil { + return nil, err + } + return uploadData, nil + } return nil, err } @@ -106,18 +110,141 @@ func (r *Service) GetFile(id string) (*models.File, error) { return &fileCache, nil } -func (r *Service) GetUserFile(name, ownerID string) (*types.FileWithDetail, error) { - fileData, err := r.db.GetUserFile(name, ownerID) +func (r *Service) GetFileChunks(ctx context.Context, fileID uuid.UUID, ownerID uuid.UUID, totalChunk uint64) (*types.FileState, error) { + fileJSON, err := r.cache.GetCache(ctx, "FileChunkCache:"+fileID.String()) + if err != nil { + if errors.Is(err, redis.Nil) { + prefix := fmt.Sprintf("%s/%s/chunk_", ownerID.String(), fileID.String()) + + existingChunks, err := app.Server.Storage.ListObjects(ctx, prefix) + if err != nil { + return nil, err + } + missingChunk := len(existingChunks) != int(totalChunk) + + newChunkCache := types.FileState{ + Done: !missingChunk, + Chunk: make(map[string]bool), + } + for i := 0; i < int(totalChunk); i++ { + newChunkCache.Chunk[fmt.Sprintf("chunk_%d", i)] = false + } + + for _, chunkFile := range existingChunks { + var chunkIndex int + fmt.Sscanf(chunkFile, "chunk_%d", &chunkIndex) + newChunkCache.Chunk[fmt.Sprintf("chunk_%d", chunkIndex)] = true + } + newChunkCacheJSON, err := json.Marshal(newChunkCache) + if err != nil { + return nil, err + } + err = r.cache.SetCache(ctx, "FileChunkCache:"+fileID.String(), newChunkCacheJSON, time.Minute*30) + if err != nil { + return nil, err + } + return &newChunkCache, nil + } + return nil, err + } + + var existingCache types.FileState + err = json.Unmarshal([]byte(fileJSON), &existingCache) if err != nil { return nil, err } + return &existingCache, nil +} + +func (r *Service) UpdateFileChunk(ctx context.Context, fileID uuid.UUID, ownerID uuid.UUID, chunk string, totalChunk uint64) error { + chunks, err := r.GetFileChunks(ctx, fileID, ownerID, totalChunk) + if err != nil { + return err + } - dada := &types.FileWithDetail{ - ID: fileData.ID, - OwnerID: fileData.OwnerID, - Name: fileData.Name, - Size: fileData.Size, - Downloaded: fileData.Downloaded, + chunks.Chunk[fmt.Sprintf("chunk_%s", chunk)] = true + chunks.Done = true + + for i := 0; i < int(totalChunk); i++ { + if !chunks.Chunk[fmt.Sprintf("chunk_%d", i)] { + fmt.Println("chunk", i, " ", chunks.Chunk[fmt.Sprintf("chunk_%d", i)]) + chunks.Done = false + break + } + } + + updatedChunkCacheJSON, err := json.Marshal(chunks) + if err != nil { + return err + } + err = r.cache.SetCache(ctx, "FileChunkCache:"+fileID.String(), updatedChunkCacheJSON, time.Minute*30) + if err != nil { + return err + } + + return nil +} + +func (r *Service) GetUserFile(ctx context.Context, name, ownerID string) (*types.FileData, error) { + cacheKey := "UserFileCache:" + ownerID + ":" + name + cachedFileData, err := r.cache.GetCache(ctx, cacheKey) + if err != nil { + if errors.Is(err, redis.Nil) { + fileData, err := r.db.GetUserFile(name, ownerID) + if err != nil { + return nil, err + } + + chunks, err := r.GetFileChunks(ctx, fileData.ID, fileData.OwnerID, fileData.TotalChunk) + if err != nil { + return nil, err + } + + data := &types.FileData{ + ID: fileData.ID, + OwnerID: fileData.OwnerID, + Name: fileData.Name, + Size: fileData.Size, + TotalChunk: fileData.TotalChunk, + StartHash: fileData.StartHash, + EndHash: fileData.EndHash, + Downloaded: fileData.Downloaded, + IsPrivate: fileData.IsPrivate, + Type: fileData.Type, + Done: chunks.Done, + Chunk: chunks.Chunk, + } + + fileDataToCache := &types.FileData{ + ID: fileData.ID, + OwnerID: fileData.OwnerID, + Name: fileData.Name, + Size: fileData.Size, + TotalChunk: fileData.TotalChunk, + StartHash: fileData.StartHash, + EndHash: fileData.EndHash, + Downloaded: fileData.Downloaded, + IsPrivate: fileData.IsPrivate, + Type: fileData.Type, + } + cachedFileDataJSON, err := json.Marshal(fileDataToCache) + if err == nil { + _ = r.cache.SetCache(ctx, cacheKey, cachedFileDataJSON, time.Minute*30) + } + return data, nil + } + return nil, err + } + var fileData types.FileData + err = json.Unmarshal([]byte(cachedFileData), &fileData) + if err != nil { + return nil, err + } + chunks, err := r.GetFileChunks(ctx, fileData.ID, fileData.OwnerID, fileData.TotalChunk) + if err != nil { + return nil, err } - return dada, nil + fileData.Done = chunks.Done + fileData.Chunk = chunks.Chunk + return &fileData, nil } diff --git a/types/types.go b/types/types.go index 0199028..40bd7c9 100644 --- a/types/types.go +++ b/types/types.go @@ -21,6 +21,10 @@ type Message struct { Message string } +type Number interface { + int | int8 | int16 | int32 | int64 | uint | uint8 | uint16 | uint32 | uint64 +} + type User struct { UserID uuid.UUID Email string @@ -36,23 +40,23 @@ type Allowance struct { } type FileData struct { - ID string - Name string - Size string - IsPrivate bool - Type string - Done bool - Downloaded string -} - -type FileWithDetail struct { ID uuid.UUID OwnerID uuid.UUID Name string Size uint64 + TotalChunk uint64 + StartHash string + EndHash string Downloaded uint64 - Chunk map[string]bool + IsPrivate bool + Type string Done bool + Chunk map[string]bool +} + +type FileState struct { + Done bool + Chunk map[string]bool } type Database interface { @@ -88,10 +92,12 @@ type CachingServer interface { type Services interface { GetUser(ctx context.Context, email string) (*models.User, error) - DeleteUser(email string) error - GetFile(id string) (*models.File, error) - GetUserFile(name, ownerID string) (*FileWithDetail, error) - GetUserStorageUsage(ownerID string) (uint64, error) + DeleteUser(ctx context.Context, email string) error + GetFile(ctx context.Context, id string) (*models.File, error) + GetUserFile(ctx context.Context, name, ownerID string) (*FileData, error) + GetUserStorageUsage(ctx context.Context, ownerID string) (uint64, error) + GetFileChunks(ctx context.Context, fileID uuid.UUID, ownerID uuid.UUID, totalChunk uint64) (*FileState, error) + UpdateFileChunk(ctx context.Context, fileID uuid.UUID, ownerID uuid.UUID, chunk string, totalChunk uint64) error } type Storage interface { diff --git a/utils/utils.go b/utils/utils.go index 95a1b47..42b1256 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -6,6 +6,7 @@ import ( "encoding/base64" "fmt" "github.com/fossyy/filekeeper/app" + "github.com/fossyy/filekeeper/types" mathRand "math/rand" "net/http" "os" @@ -87,15 +88,17 @@ func ValidatePassword(password string) bool { return hasNumber && hasUppercase } -func ConvertFileSize(byte uint64) string { - if byte < 1024 { - return fmt.Sprintf("%d B", byte) - } else if byte < 1024*1024 { - return fmt.Sprintf("%d KB", byte/1024) - } else if byte < 1024*1024*1024 { - return fmt.Sprintf("%d MB", byte/(1024*1024)) +func ConvertFileSize[T types.Number](size T) string { + sizeInBytes := int64(size) + + if sizeInBytes < 1024 { + return fmt.Sprintf("%d B", sizeInBytes) + } else if sizeInBytes < 1024*1024 { + return fmt.Sprintf("%.2f KB", float64(sizeInBytes)/1024) + } else if sizeInBytes < 1024*1024*1024 { + return fmt.Sprintf("%.2f MB", float64(sizeInBytes)/(1024*1024)) } else { - return fmt.Sprintf("%d GB", byte/(1024*1024*1024)) + return fmt.Sprintf("%.2f GB", float64(sizeInBytes)/(1024*1024*1024)) } } @@ -205,3 +208,7 @@ func ParseUserAgent(userAgent string) (map[string]string, map[string]string) { return browserInfo, osInfo } + +func IntToString[T types.Number](number T) string { + return fmt.Sprintf("%d", number) +} diff --git a/view/client/file/file.templ b/view/client/file/file.templ index 56ee555..f1f015a 100644 --- a/view/client/file/file.templ +++ b/view/client/file/file.templ @@ -3,6 +3,7 @@ package fileView import ( "github.com/fossyy/filekeeper/types" "github.com/fossyy/filekeeper/view/client/layout" + "github.com/fossyy/filekeeper/utils" "strconv" ) @@ -289,7 +290,7 @@ templ FileTable(files []types.FileData) { } templ JustFile(file types.FileData) { - + if !file.Done { @FileIcon(file.Type) @@ -315,7 +316,7 @@ templ JustFile(file types.FileData) { } - { file.Size } + { utils.ConvertFileSize(file.Size) }
@@ -323,7 +324,7 @@ templ JustFile(file types.FileData) { - { file.Downloaded } + { utils.IntToString(file.Downloaded) }
@@ -373,12 +374,12 @@ templ JustFile(file types.FileData) {