Merge pull request #84 from fossyy/staging

Implement Redis caching for S3 list object calls
This commit is contained in:
2024-09-23 10:25:50 +07:00
committed by GitHub
14 changed files with 279 additions and 241 deletions

View File

@ -8,7 +8,6 @@ import (
"github.com/fossyy/filekeeper/utils" "github.com/fossyy/filekeeper/utils"
fileView "github.com/fossyy/filekeeper/view/client/file" fileView "github.com/fossyy/filekeeper/view/client/file"
"net/http" "net/http"
"strconv"
) )
func GET(w http.ResponseWriter, r *http.Request) { func GET(w http.ResponseWriter, r *http.Request) {
@ -22,26 +21,14 @@ func GET(w http.ResponseWriter, r *http.Request) {
var filesData []types.FileData var filesData []types.FileData
for _, file := range files { for _, file := range files {
prefix := fmt.Sprintf("%s/%s/chunk_", file.OwnerID.String(), file.ID.String()) userFile, err := app.Server.Service.GetUserFile(r.Context(), file.Name, file.OwnerID.String())
existingChunks, err := app.Server.Storage.ListObjects(r.Context(), prefix)
if err != nil { if err != nil {
app.Server.Logger.Error(err.Error())
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)
app.Server.Logger.Error(err.Error())
return return
} }
missingChunk := len(existingChunks) != int(file.TotalChunk) filesData = append(filesData, *userFile)
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),
})
} }
allowance, err := app.Server.Database.GetAllowance(userSession.UserID) 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()) app.Server.Logger.Error(err.Error())
return return
} }
usage, err := app.Server.Service.GetUserStorageUsage(userSession.UserID.String()) usage, err := app.Server.Service.GetUserStorageUsage(r.Context(), userSession.UserID.String())
if err != nil { if err != nil {
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)
app.Server.Logger.Error(err.Error()) app.Server.Logger.Error(err.Error())

View File

@ -1,13 +1,10 @@
package queryHandler package queryHandler
import ( import (
"fmt"
"github.com/fossyy/filekeeper/app" "github.com/fossyy/filekeeper/app"
"github.com/fossyy/filekeeper/types" "github.com/fossyy/filekeeper/types"
"github.com/fossyy/filekeeper/utils"
fileView "github.com/fossyy/filekeeper/view/client/file" fileView "github.com/fossyy/filekeeper/view/client/file"
"net/http" "net/http"
"strconv"
) )
func GET(w http.ResponseWriter, r *http.Request) { func GET(w http.ResponseWriter, r *http.Request) {
@ -34,26 +31,14 @@ func GET(w http.ResponseWriter, r *http.Request) {
var filesData []types.FileData var filesData []types.FileData
for _, file := range files { for _, file := range files {
prefix := fmt.Sprintf("%s/%s/chunk_", file.OwnerID.String(), file.ID.String()) userFile, err := app.Server.Service.GetUserFile(r.Context(), file.Name, file.OwnerID.String())
existingChunks, err := app.Server.Storage.ListObjects(r.Context(), prefix)
if err != nil { if err != nil {
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)
app.Server.Logger.Error(err.Error()) app.Server.Logger.Error(err.Error())
return return
} }
missingChunk := len(existingChunks) != int(file.TotalChunk) filesData = append(filesData, *userFile)
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),
})
} }
if r.Header.Get("hx-request") == "true" { if r.Header.Get("hx-request") == "true" {

View File

@ -1,13 +1,10 @@
package renameFileHandler package renameFileHandler
import ( import (
"fmt"
"github.com/fossyy/filekeeper/app" "github.com/fossyy/filekeeper/app"
"github.com/fossyy/filekeeper/types" "github.com/fossyy/filekeeper/types"
"github.com/fossyy/filekeeper/utils"
fileView "github.com/fossyy/filekeeper/view/client/file" fileView "github.com/fossyy/filekeeper/view/client/file"
"net/http" "net/http"
"strconv"
) )
func PATCH(w http.ResponseWriter, r *http.Request) { func PATCH(w http.ResponseWriter, r *http.Request) {
@ -39,28 +36,14 @@ func PATCH(w http.ResponseWriter, r *http.Request) {
return return
} }
prefix := fmt.Sprintf("%s/%s/chunk_", file.OwnerID.String(), file.ID.String()) userFile, err := app.Server.Service.GetUserFile(r.Context(), newFile.Name, newFile.OwnerID.String())
existingChunks, err := app.Server.Storage.ListObjects(r.Context(), prefix)
if err != nil { if err != nil {
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)
app.Server.Logger.Error(err.Error()) app.Server.Logger.Error(err.Error())
return return
} }
missingChunk := len(existingChunks) != int(file.TotalChunk) component := fileView.JustFile(*userFile)
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)
err = component.Render(r.Context(), w) err = component.Render(r.Context(), w)
if err != nil { if err != nil {
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)

View File

@ -16,7 +16,7 @@ func POST(w http.ResponseWriter, r *http.Request) {
return return
} }
file, err := app.Server.Service.GetFile(fileID) file, err := app.Server.Service.GetFile(r.Context(), fileID)
if err != nil { if err != nil {
app.Server.Logger.Error("error getting upload info: " + err.Error()) app.Server.Logger.Error("error getting upload info: " + err.Error())
w.WriteHeader(http.StatusInternalServerError) 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()) app.Server.Logger.Error("error copying byte to file dst: " + err.Error())
return return
} }
app.Server.Service.UpdateFileChunk(r.Context(), file.ID, file.OwnerID, rawIndex, file.TotalChunk)
w.WriteHeader(http.StatusAccepted) w.WriteHeader(http.StatusAccepted)
return return
} }

View File

@ -1,13 +1,10 @@
package visibilityHandler package visibilityHandler
import ( import (
"fmt"
"github.com/fossyy/filekeeper/app" "github.com/fossyy/filekeeper/app"
"github.com/fossyy/filekeeper/types" "github.com/fossyy/filekeeper/types"
"github.com/fossyy/filekeeper/utils"
fileView "github.com/fossyy/filekeeper/view/client/file" fileView "github.com/fossyy/filekeeper/view/client/file"
"net/http" "net/http"
"strconv"
) )
func PUT(w http.ResponseWriter, r *http.Request) { func PUT(w http.ResponseWriter, r *http.Request) {
@ -32,26 +29,13 @@ func PUT(w http.ResponseWriter, r *http.Request) {
return return
} }
prefix := fmt.Sprintf("%s/%s/chunk_", file.OwnerID.String(), file.ID.String()) userFile, err := app.Server.Service.GetUserFile(r.Context(), file.Name, file.OwnerID.String())
existingChunks, err := app.Server.Storage.ListObjects(r.Context(), prefix)
if err != nil { if err != nil {
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)
app.Server.Logger.Error(err.Error()) app.Server.Logger.Error(err.Error())
return return
} }
component := fileView.JustFile(*userFile)
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)
err = component.Render(r.Context(), w) err = component.Render(r.Context(), w)
if err != nil { if err != nil {
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)

View File

@ -122,7 +122,7 @@ func POST(w http.ResponseWriter, r *http.Request) {
return return
} }
err = app.Server.Service.DeleteUser(userData.User.Email) err = app.Server.Service.DeleteUser(r.Context(), userData.User.Email)
if err != nil { if err != nil {
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)
app.Server.Logger.Error(err.Error()) app.Server.Logger.Error(err.Error())

View File

@ -51,7 +51,7 @@ func POST(w http.ResponseWriter, r *http.Request) {
return return
} }
err = app.Server.Service.DeleteUser(userSession.Email) err = app.Server.Service.DeleteUser(r.Context(), userSession.Email)
if err != nil { if err != nil {
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)
app.Server.Logger.Error(err.Error()) app.Server.Logger.Error(err.Error())

View File

@ -87,7 +87,7 @@ func POST(w http.ResponseWriter, r *http.Request) {
app.Server.Logger.Error(err.Error()) app.Server.Logger.Error(err.Error())
return return
} }
err := app.Server.Service.DeleteUser(userSession.Email) err := app.Server.Service.DeleteUser(r.Context(), userSession.Email)
if err != nil { if err != nil {
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)
app.Server.Logger.Error(err.Error()) app.Server.Logger.Error(err.Error())

View File

@ -76,7 +76,7 @@ func GET(w http.ResponseWriter, r *http.Request) {
return return
} }
usage, err := app.Server.Service.GetUserStorageUsage(userSession.UserID.String()) usage, err := app.Server.Service.GetUserStorageUsage(r.Context(), userSession.UserID.String())
if err != nil { if err != nil {
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)
app.Server.Logger.Error(err.Error()) app.Server.Logger.Error(err.Error())
@ -178,8 +178,9 @@ func handlerWS(conn *websocket.Conn, userSession types.User) {
if err != nil { if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) { if errors.Is(err, gorm.ErrRecordNotFound) {
fileID := uuid.New()
newFile := models.File{ newFile := models.File{
ID: uuid.New(), ID: fileID,
OwnerID: userSession.UserID, OwnerID: userSession.UserID,
Name: uploadNewFile.Name, Name: uploadNewFile.Name,
Size: uploadNewFile.Size, Size: uploadNewFile.Size,
@ -189,42 +190,24 @@ func handlerWS(conn *websocket.Conn, userSession types.User) {
TotalChunk: uploadNewFile.Chunk, TotalChunk: uploadNewFile.Chunk,
Downloaded: 0, Downloaded: 0,
} }
err := app.Server.Database.CreateFile(&newFile) err := app.Server.Database.CreateFile(&newFile)
if err != nil { if err != nil {
sendErrorResponse(conn, action.Action, "Error Creating File") sendErrorResponse(conn, action.Action, "Error Creating File")
continue 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()) userFile, err := app.Server.Service.GetUserFile(context.Background(), uploadNewFile.Name, userSession.UserID.String())
existingChunks, err := app.Server.Storage.ListObjects(context.TODO(), prefix)
if err != nil { if err != nil {
app.Server.Logger.Error(err.Error()) app.Server.Logger.Error(err.Error())
sendErrorResponse(conn, action.Action, "Unknown error") sendErrorResponse(conn, action.Action, "Unknown error")
continue 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 continue
} else { } else {
app.Server.Logger.Error(err.Error())
sendErrorResponse(conn, action.Action, "Unknown error") sendErrorResponse(conn, action.Action, "Unknown error")
continue continue
} }
@ -233,40 +216,14 @@ func handlerWS(conn *websocket.Conn, userSession types.User) {
sendErrorResponse(conn, action.Action, "File Is Different") sendErrorResponse(conn, action.Action, "File Is Different")
continue continue
} }
fileData := &types.FileWithDetail{ userFile, err := app.Server.Service.GetUserFile(context.Background(), file.Name, userSession.UserID.String())
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)
if err != nil { if err != nil {
app.Server.Logger.Error(err.Error()) app.Server.Logger.Error(err.Error())
fileData.Done = false sendErrorResponse(conn, action.Action, "Unknown error")
} else { continue
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
}
}
} }
sendSuccessResponseWithID(conn, action.Action, fileData, uploadNewFile.RequestID) sendSuccessResponseWithID(conn, action.Action, userFile, uploadNewFile.RequestID)
continue continue
case Ping: case Ping:
sendSuccessResponse(conn, action.Action, map[string]string{"message": "received"}) sendSuccessResponse(conn, action.Action, map[string]string{"message": "received"})

View File

@ -3,9 +3,12 @@ package service
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"errors"
"fmt"
"github.com/fossyy/filekeeper/app" "github.com/fossyy/filekeeper/app"
"github.com/fossyy/filekeeper/types" "github.com/fossyy/filekeeper/types"
"github.com/fossyy/filekeeper/types/models" "github.com/fossyy/filekeeper/types/models"
"github.com/google/uuid"
"github.com/redis/go-redis/v9" "github.com/redis/go-redis/v9"
"time" "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) { func (r *Service) GetUser(ctx context.Context, email string) (*models.User, error) {
userJSON, err := app.Server.Cache.GetCache(ctx, "UserCache:"+email) 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 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 return nil, err
} }
@ -59,15 +62,16 @@ func (r *Service) GetUser(ctx context.Context, email string) (*models.User, erro
return &user, nil return &user, nil
} }
func (r *Service) DeleteUser(email string) error { func (r *Service) DeleteUser(ctx context.Context, email string) error {
err := r.cache.DeleteCache(context.Background(), "UserCache:"+email) err := r.cache.DeleteCache(ctx, "UserCache:"+email)
if err != nil { if err != nil {
return err return err
} }
return nil 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) files, err := app.Server.Database.GetFiles(ownerID, "", types.All)
if err != nil { if err != nil {
return 0, err return 0, err
@ -79,22 +83,22 @@ func (r *Service) GetUserStorageUsage(ownerID string) (uint64, error) {
return total, nil return total, nil
} }
func (r *Service) GetFile(id string) (*models.File, error) { func (r *Service) GetFile(ctx context.Context, id string) (*models.File, error) {
fileJSON, err := r.cache.GetCache(context.Background(), "FileCache:"+id) fileJSON, err := r.cache.GetCache(ctx, "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
}
if err != nil { 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 return nil, err
} }
@ -106,18 +110,141 @@ func (r *Service) GetFile(id string) (*models.File, error) {
return &fileCache, nil return &fileCache, nil
} }
func (r *Service) GetUserFile(name, ownerID string) (*types.FileWithDetail, error) { func (r *Service) GetFileChunks(ctx context.Context, fileID uuid.UUID, ownerID uuid.UUID, totalChunk uint64) (*types.FileState, error) {
fileData, err := r.db.GetUserFile(name, ownerID) fileJSON, err := r.cache.GetCache(ctx, "FileChunkCache:"+fileID.String())
if err != nil { 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 return nil, err
} }
dada := &types.FileWithDetail{ var existingCache types.FileState
ID: fileData.ID, err = json.Unmarshal([]byte(fileJSON), &existingCache)
OwnerID: fileData.OwnerID, if err != nil {
Name: fileData.Name, return nil, err
Size: fileData.Size,
Downloaded: fileData.Downloaded,
} }
return dada, nil 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
}
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
}
fileData.Done = chunks.Done
fileData.Chunk = chunks.Chunk
return &fileData, nil
} }

View File

@ -21,6 +21,10 @@ type Message struct {
Message string Message string
} }
type Number interface {
int | int8 | int16 | int32 | int64 | uint | uint8 | uint16 | uint32 | uint64
}
type User struct { type User struct {
UserID uuid.UUID UserID uuid.UUID
Email string Email string
@ -36,23 +40,23 @@ type Allowance struct {
} }
type FileData struct { type FileData struct {
ID string
Name string
Size string
IsPrivate bool
Type string
Done bool
Downloaded string
}
type FileWithDetail struct {
ID uuid.UUID ID uuid.UUID
OwnerID uuid.UUID OwnerID uuid.UUID
Name string Name string
Size uint64 Size uint64
TotalChunk uint64
StartHash string
EndHash string
Downloaded uint64 Downloaded uint64
Chunk map[string]bool IsPrivate bool
Type string
Done bool Done bool
Chunk map[string]bool
}
type FileState struct {
Done bool
Chunk map[string]bool
} }
type Database interface { type Database interface {
@ -88,10 +92,12 @@ type CachingServer interface {
type Services interface { type Services interface {
GetUser(ctx context.Context, email string) (*models.User, error) GetUser(ctx context.Context, email string) (*models.User, error)
DeleteUser(email string) error DeleteUser(ctx context.Context, email string) error
GetFile(id string) (*models.File, error) GetFile(ctx context.Context, id string) (*models.File, error)
GetUserFile(name, ownerID string) (*FileWithDetail, error) GetUserFile(ctx context.Context, name, ownerID string) (*FileData, error)
GetUserStorageUsage(ownerID string) (uint64, 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 { type Storage interface {

View File

@ -6,6 +6,7 @@ import (
"encoding/base64" "encoding/base64"
"fmt" "fmt"
"github.com/fossyy/filekeeper/app" "github.com/fossyy/filekeeper/app"
"github.com/fossyy/filekeeper/types"
mathRand "math/rand" mathRand "math/rand"
"net/http" "net/http"
"os" "os"
@ -87,15 +88,17 @@ func ValidatePassword(password string) bool {
return hasNumber && hasUppercase return hasNumber && hasUppercase
} }
func ConvertFileSize(byte uint64) string { func ConvertFileSize[T types.Number](size T) string {
if byte < 1024 { sizeInBytes := int64(size)
return fmt.Sprintf("%d B", byte)
} else if byte < 1024*1024 { if sizeInBytes < 1024 {
return fmt.Sprintf("%d KB", byte/1024) return fmt.Sprintf("%d B", sizeInBytes)
} else if byte < 1024*1024*1024 { } else if sizeInBytes < 1024*1024 {
return fmt.Sprintf("%d MB", byte/(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 { } 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 return browserInfo, osInfo
} }
func IntToString[T types.Number](number T) string {
return fmt.Sprintf("%d", number)
}

View File

@ -3,6 +3,7 @@ package fileView
import ( import (
"github.com/fossyy/filekeeper/types" "github.com/fossyy/filekeeper/types"
"github.com/fossyy/filekeeper/view/client/layout" "github.com/fossyy/filekeeper/view/client/layout"
"github.com/fossyy/filekeeper/utils"
"strconv" "strconv"
) )
@ -289,7 +290,7 @@ templ FileTable(files []types.FileData) {
} }
templ JustFile(file types.FileData) { templ JustFile(file types.FileData) {
<tr id={ "file-" + file.ID } class="bg-white border-b"> <tr id={ "file-" + file.ID.String() } class="bg-white border-b">
if !file.Done { if !file.Done {
<td class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap flex items-center"> <td class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap flex items-center">
@FileIcon(file.Type) @FileIcon(file.Type)
@ -315,7 +316,7 @@ templ JustFile(file types.FileData) {
</div> </div>
</td> </td>
} }
<td class="px-6 py-4">{ file.Size }</td> <td class="px-6 py-4">{ utils.ConvertFileSize(file.Size) }</td>
<td class="px-6 py-4"> <td class="px-6 py-4">
<div class="flex items-center"> <div class="flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="h-5 w-5 mr-2 text-gray-400"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="h-5 w-5 mr-2 text-gray-400">
@ -323,7 +324,7 @@ templ JustFile(file types.FileData) {
<polyline points="7 10 12 15 17 10"></polyline> <polyline points="7 10 12 15 17 10"></polyline>
<line x1="12" x2="12" y1="15" y2="3"></line> <line x1="12" x2="12" y1="15" y2="3"></line>
</svg> </svg>
{ file.Downloaded } { utils.IntToString(file.Downloaded) }
</div> </div>
</td> </td>
<td class="px-6 py-4"> <td class="px-6 py-4">
@ -373,12 +374,12 @@ templ JustFile(file types.FileData) {
<div class="dropdown-menu hidden absolute right-0 mt-2 w-56 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 z-10"> <div class="dropdown-menu hidden absolute right-0 mt-2 w-56 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 z-10">
if file.Done { if file.Done {
<div class="py-1" role="menu" aria-orientation="vertical" aria-labelledby="options-menu"> <div class="py-1" role="menu" aria-orientation="vertical" aria-labelledby="options-menu">
<a href={ templ.SafeURL("/file/" + file.ID) } class="flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900 w-full" role="menuitem"> <a href={ templ.SafeURL("/file/" + file.ID.String()) } class="flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900 w-full" role="menuitem">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-download"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="7 10 12 15 17 10"></polyline><line x1="12" x2="12" y1="15" y2="3"></line></svg> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-download"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="7 10 12 15 17 10"></polyline><line x1="12" x2="12" y1="15" y2="3"></line></svg>
<i class="ri-file-copy-line mr-3 text-gray-400"></i> Download <i class="ri-file-copy-line mr-3 text-gray-400"></i> Download
</a> </a>
if file.IsPrivate { if file.IsPrivate {
<button class="flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900 w-full" role="menuitem" hx-put={ "/file/" + file.ID } hx-target={ "#file-" + file.ID } hx-swap="outerHTML"> <button class="flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900 w-full" role="menuitem" hx-put={ "/file/" + file.ID.String() } hx-target={ "#file-" + file.ID.String() } hx-swap="outerHTML">
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
class="h-4 w-4 text-gray-600" class="h-4 w-4 text-gray-600"
@ -395,7 +396,7 @@ templ JustFile(file types.FileData) {
<i class="ri-delete-bin-line mr-3 text-gray-400"></i> Make Public <i class="ri-delete-bin-line mr-3 text-gray-400"></i> Make Public
</button> </button>
} else { } else {
<button class="flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900 w-full" role="menuitem" hx-put={ "/file/" + file.ID } hx-target={ "#file-" + file.ID } hx-swap="outerHTML"> <button class="flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900 w-full" role="menuitem" hx-put={ "/file/" + file.ID.String() } hx-target={ "#file-" + file.ID.String() } hx-swap="outerHTML">
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
class="h-4 w-4 text-gray-600" class="h-4 w-4 text-gray-600"
@ -412,18 +413,18 @@ templ JustFile(file types.FileData) {
<i class="ri-delete-bin-line mr-3 text-gray-400"></i> Make Private <i class="ri-delete-bin-line mr-3 text-gray-400"></i> Make Private
</button> </button>
} }
<button onClick={ showShareModal(file.IsPrivate, file.ID) } class="flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900 w-full" role="menuitem"> <button onClick={ showShareModal(file.IsPrivate, file.ID.String()) } class="flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900 w-full" role="menuitem">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-share-2"><circle cx="18" cy="5" r="3"></circle><circle cx="6" cy="12" r="3"></circle><circle cx="18" cy="19" r="3"></circle><line x1="8.59" x2="15.42" y1="13.51" y2="17.49"></line><line x1="15.41" x2="8.59" y1="6.51" y2="10.49"></line></svg> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-share-2"><circle cx="18" cy="5" r="3"></circle><circle cx="6" cy="12" r="3"></circle><circle cx="18" cy="19" r="3"></circle><line x1="8.59" x2="15.42" y1="13.51" y2="17.49"></line><line x1="15.41" x2="8.59" y1="6.51" y2="10.49"></line></svg>
<i class="ri-delete-bin-line mr-3 text-gray-400"></i> Share <i class="ri-delete-bin-line mr-3 text-gray-400"></i> Share
</button> </button>
</div> </div>
} }
<div class="py-1 border-t" role="menu" aria-orientation="vertical" aria-labelledby="options-menu"> <div class="py-1 border-t" role="menu" aria-orientation="vertical" aria-labelledby="options-menu">
<button onClick={ showRenameModal(file.Name, file.ID) } class="flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900 w-full" role="menuitem"> <button onClick={ showRenameModal(file.Name, file.ID.String()) } class="flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900 w-full" role="menuitem">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-folder-pen"><path d="M2 11.5V5a2 2 0 0 1 2-2h3.9c.7 0 1.3.3 1.7.9l.8 1.2c.4.6 1 .9 1.7.9H20a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2h-9.5"></path><path d="M11.378 13.626a1 1 0 1 0-3.004-3.004l-5.01 5.012a2 2 0 0 0-.506.854l-.837 2.87a.5.5 0 0 0 .62.62l2.87-.837a2 2 0 0 0 .854-.506z"></path></svg> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-folder-pen"><path d="M2 11.5V5a2 2 0 0 1 2-2h3.9c.7 0 1.3.3 1.7.9l.8 1.2c.4.6 1 .9 1.7.9H20a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2h-9.5"></path><path d="M11.378 13.626a1 1 0 1 0-3.004-3.004l-5.01 5.012a2 2 0 0 0-.506.854l-.837 2.87a.5.5 0 0 0 .62.62l2.87-.837a2 2 0 0 0 .854-.506z"></path></svg>
<i class="ri-share-line mr-3 text-gray-400"></i> Rename <i class="ri-share-line mr-3 text-gray-400"></i> Rename
</button> </button>
<button onClick={ showDeletionModal(file.Name, file.ID) } class="flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900 w-full" role="menuitem"> <button onClick={ showDeletionModal(file.Name, file.ID.String()) } class="flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900 w-full" role="menuitem">
<svg width="16px" height="16px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><g stroke-width="0"></g><g stroke-linecap="round" stroke-linejoin="round"></g><g><path d="M20.5001 6H3.5" stroke="#000000" stroke-width="1.5" stroke-linecap="round"></path> <path d="M9.5 11L10 16" stroke="#000000" stroke-width="1.5" stroke-linecap="round"></path> <path d="M14.5 11L14 16" stroke="#000000" stroke-width="1.5" stroke-linecap="round"></path> <path d="M6.5 6C6.55588 6 6.58382 6 6.60915 5.99936C7.43259 5.97849 8.15902 5.45491 8.43922 4.68032C8.44784 4.65649 8.45667 4.62999 8.47434 4.57697L8.57143 4.28571C8.65431 4.03708 8.69575 3.91276 8.75071 3.8072C8.97001 3.38607 9.37574 3.09364 9.84461 3.01877C9.96213 3 10.0932 3 10.3553 3H13.6447C13.9068 3 14.0379 3 14.1554 3.01877C14.6243 3.09364 15.03 3.38607 15.2493 3.8072C15.3043 3.91276 15.3457 4.03708 15.4286 4.28571L15.5257 4.57697C15.5433 4.62992 15.5522 4.65651 15.5608 4.68032C15.841 5.45491 16.5674 5.97849 17.3909 5.99936C17.4162 6 17.4441 6 17.5 6" stroke="#000000" stroke-width="1.5"></path> <path d="M18.3735 15.3991C18.1965 18.054 18.108 19.3815 17.243 20.1907C16.378 21 15.0476 21 12.3868 21H11.6134C8.9526 21 7.6222 21 6.75719 20.1907C5.89218 19.3815 5.80368 18.054 5.62669 15.3991L5.16675 8.5M18.8334 8.5L18.6334 11.5" stroke="#000000" stroke-width="1.5" stroke-linecap="round"></path> </g></svg> <svg width="16px" height="16px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><g stroke-width="0"></g><g stroke-linecap="round" stroke-linejoin="round"></g><g><path d="M20.5001 6H3.5" stroke="#000000" stroke-width="1.5" stroke-linecap="round"></path> <path d="M9.5 11L10 16" stroke="#000000" stroke-width="1.5" stroke-linecap="round"></path> <path d="M14.5 11L14 16" stroke="#000000" stroke-width="1.5" stroke-linecap="round"></path> <path d="M6.5 6C6.55588 6 6.58382 6 6.60915 5.99936C7.43259 5.97849 8.15902 5.45491 8.43922 4.68032C8.44784 4.65649 8.45667 4.62999 8.47434 4.57697L8.57143 4.28571C8.65431 4.03708 8.69575 3.91276 8.75071 3.8072C8.97001 3.38607 9.37574 3.09364 9.84461 3.01877C9.96213 3 10.0932 3 10.3553 3H13.6447C13.9068 3 14.0379 3 14.1554 3.01877C14.6243 3.09364 15.03 3.38607 15.2493 3.8072C15.3043 3.91276 15.3457 4.03708 15.4286 4.28571L15.5257 4.57697C15.5433 4.62992 15.5522 4.65651 15.5608 4.68032C15.841 5.45491 16.5674 5.97849 17.3909 5.99936C17.4162 6 17.4441 6 17.5 6" stroke="#000000" stroke-width="1.5"></path> <path d="M18.3735 15.3991C18.1965 18.054 18.108 19.3815 17.243 20.1907C16.378 21 15.0476 21 12.3868 21H11.6134C8.9526 21 7.6222 21 6.75719 20.1907C5.89218 19.3815 5.80368 18.054 5.62669 15.3991L5.16675 8.5M18.8334 8.5L18.6334 11.5" stroke="#000000" stroke-width="1.5" stroke-linecap="round"></path> </g></svg>
<i class="ri-clipboard-line mr-3 text-gray-400"></i> Remove <i class="ri-clipboard-line mr-3 text-gray-400"></i> Remove
</button> </button>

View File

@ -10,6 +10,7 @@ import templruntime "github.com/a-h/templ/runtime"
import ( import (
"github.com/fossyy/filekeeper/types" "github.com/fossyy/filekeeper/types"
"github.com/fossyy/filekeeper/utils"
"github.com/fossyy/filekeeper/view/client/layout" "github.com/fossyy/filekeeper/view/client/layout"
"strconv" "strconv"
) )
@ -89,7 +90,7 @@ func MainContent(title string, files []types.FileData, user types.User, allowanc
var templ_7745c5c3_Var4 string var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(title) templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(title)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/client/file/file.templ`, Line: 16, Col: 15} return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/client/file/file.templ`, Line: 17, Col: 15}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@ -205,7 +206,7 @@ func MainContent(title string, files []types.FileData, user types.User, allowanc
var templ_7745c5c3_Var10 string var templ_7745c5c3_Var10 string
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(len(files))) templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(len(files)))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/client/file/file.templ`, Line: 204, Col: 88} return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/client/file/file.templ`, Line: 205, Col: 88}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@ -218,7 +219,7 @@ func MainContent(title string, files []types.FileData, user types.User, allowanc
var templ_7745c5c3_Var11 string var templ_7745c5c3_Var11 string
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(allowance.AllowanceUsedByte) templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(allowance.AllowanceUsedByte)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/client/file/file.templ`, Line: 205, Col: 91} return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/client/file/file.templ`, Line: 206, Col: 91}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@ -231,7 +232,7 @@ func MainContent(title string, files []types.FileData, user types.User, allowanc
var templ_7745c5c3_Var12 string var templ_7745c5c3_Var12 string
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(allowance.AllowanceByte) templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(allowance.AllowanceByte)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/client/file/file.templ`, Line: 206, Col: 91} return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/client/file/file.templ`, Line: 207, Col: 91}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@ -336,9 +337,9 @@ func JustFile(file types.FileData) templ.Component {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var15 string var templ_7745c5c3_Var15 string
templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs("file-" + file.ID) templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs("file-" + file.ID.String())
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/client/file/file.templ`, Line: 292, Col: 27} return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/client/file/file.templ`, Line: 293, Col: 36}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@ -364,7 +365,7 @@ func JustFile(file types.FileData) templ.Component {
var templ_7745c5c3_Var16 string var templ_7745c5c3_Var16 string
templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(file.Name) templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(file.Name)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/client/file/file.templ`, Line: 297, Col: 80} return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/client/file/file.templ`, Line: 298, Col: 80}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@ -377,7 +378,7 @@ func JustFile(file types.FileData) templ.Component {
var templ_7745c5c3_Var17 string var templ_7745c5c3_Var17 string
templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(file.Name) templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(file.Name)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/client/file/file.templ`, Line: 298, Col: 17} return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/client/file/file.templ`, Line: 299, Col: 17}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@ -403,7 +404,7 @@ func JustFile(file types.FileData) templ.Component {
var templ_7745c5c3_Var18 string var templ_7745c5c3_Var18 string
templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(file.Name) templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(file.Name)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/client/file/file.templ`, Line: 312, Col: 80} return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/client/file/file.templ`, Line: 313, Col: 80}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@ -416,7 +417,7 @@ func JustFile(file types.FileData) templ.Component {
var templ_7745c5c3_Var19 string var templ_7745c5c3_Var19 string
templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(file.Name) templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(file.Name)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/client/file/file.templ`, Line: 313, Col: 17} return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/client/file/file.templ`, Line: 314, Col: 17}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@ -432,9 +433,9 @@ func JustFile(file types.FileData) templ.Component {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var20 string var templ_7745c5c3_Var20 string
templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinStringErrs(file.Size) templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinStringErrs(utils.ConvertFileSize(file.Size))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/client/file/file.templ`, Line: 318, Col: 35} return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/client/file/file.templ`, Line: 319, Col: 58}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var20)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var20))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@ -445,9 +446,9 @@ func JustFile(file types.FileData) templ.Component {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var21 string var templ_7745c5c3_Var21 string
templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(file.Downloaded) templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(utils.IntToString(file.Downloaded))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/client/file/file.templ`, Line: 326, Col: 21} return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/client/file/file.templ`, Line: 327, Col: 40}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@ -494,7 +495,7 @@ func JustFile(file types.FileData) templ.Component {
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var23 templ.SafeURL = templ.SafeURL("/file/" + file.ID) var templ_7745c5c3_Var23 templ.SafeURL = templ.SafeURL("/file/" + file.ID.String())
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var23))) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var23)))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
@ -509,9 +510,9 @@ func JustFile(file types.FileData) templ.Component {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var24 string var templ_7745c5c3_Var24 string
templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinStringErrs("/file/" + file.ID) templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinStringErrs("/file/" + file.ID.String())
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/client/file/file.templ`, Line: 381, Col: 162} return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/client/file/file.templ`, Line: 382, Col: 171}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@ -522,9 +523,9 @@ func JustFile(file types.FileData) templ.Component {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var25 string var templ_7745c5c3_Var25 string
templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinStringErrs("#file-" + file.ID) templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinStringErrs("#file-" + file.ID.String())
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/client/file/file.templ`, Line: 381, Col: 195} return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/client/file/file.templ`, Line: 382, Col: 213}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var25)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var25))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@ -540,9 +541,9 @@ func JustFile(file types.FileData) templ.Component {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var26 string var templ_7745c5c3_Var26 string
templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.JoinStringErrs("/file/" + file.ID) templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.JoinStringErrs("/file/" + file.ID.String())
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/client/file/file.templ`, Line: 398, Col: 162} return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/client/file/file.templ`, Line: 399, Col: 171}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var26)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var26))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@ -553,9 +554,9 @@ func JustFile(file types.FileData) templ.Component {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var27 string var templ_7745c5c3_Var27 string
templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.JoinStringErrs("#file-" + file.ID) templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.JoinStringErrs("#file-" + file.ID.String())
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/client/file/file.templ`, Line: 398, Col: 195} return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/client/file/file.templ`, Line: 399, Col: 213}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var27)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var27))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@ -566,7 +567,7 @@ func JustFile(file types.FileData) templ.Component {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
} }
templ_7745c5c3_Err = templ.RenderScriptItems(ctx, templ_7745c5c3_Buffer, showShareModal(file.IsPrivate, file.ID)) templ_7745c5c3_Err = templ.RenderScriptItems(ctx, templ_7745c5c3_Buffer, showShareModal(file.IsPrivate, file.ID.String()))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
@ -574,7 +575,7 @@ func JustFile(file types.FileData) templ.Component {
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var28 templ.ComponentScript = showShareModal(file.IsPrivate, file.ID) var templ_7745c5c3_Var28 templ.ComponentScript = showShareModal(file.IsPrivate, file.ID.String())
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var28.Call) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var28.Call)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
@ -588,7 +589,7 @@ func JustFile(file types.FileData) templ.Component {
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templ.RenderScriptItems(ctx, templ_7745c5c3_Buffer, showRenameModal(file.Name, file.ID)) templ_7745c5c3_Err = templ.RenderScriptItems(ctx, templ_7745c5c3_Buffer, showRenameModal(file.Name, file.ID.String()))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
@ -596,7 +597,7 @@ func JustFile(file types.FileData) templ.Component {
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var29 templ.ComponentScript = showRenameModal(file.Name, file.ID) var templ_7745c5c3_Var29 templ.ComponentScript = showRenameModal(file.Name, file.ID.String())
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var29.Call) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var29.Call)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
@ -605,7 +606,7 @@ func JustFile(file types.FileData) templ.Component {
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templ.RenderScriptItems(ctx, templ_7745c5c3_Buffer, showDeletionModal(file.Name, file.ID)) templ_7745c5c3_Err = templ.RenderScriptItems(ctx, templ_7745c5c3_Buffer, showDeletionModal(file.Name, file.ID.String()))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
@ -613,7 +614,7 @@ func JustFile(file types.FileData) templ.Component {
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var30 templ.ComponentScript = showDeletionModal(file.Name, file.ID) var templ_7745c5c3_Var30 templ.ComponentScript = showDeletionModal(file.Name, file.ID.String())
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var30.Call) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var30.Call)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err