From deb3abe3949913143c7181f1147813340bbffa0c Mon Sep 17 00:00:00 2001 From: d-robotics Date: Thu, 15 May 2025 18:09:59 +0800 Subject: [PATCH] feat: add mkdir --- api_directory.go | 84 +++++++++++++++++ api_file.go | 84 +++++++++++++++++ api_other.go | 2 +- config.yml | 0 config/robotfs.go | 33 +++++++ config/security.go | 29 ------ engine/engine.go | 27 +++--- engine/filesystem_manager.go | 85 ++++++++++++++++++ engine/metadata_manager.go | 28 ------ engine/workspace_manager.go | 42 --------- main.go | 14 +-- router.go | 110 ++++++++++++++++++++++- service.go | 11 +-- store/meta_store.go | 13 ++- store/redis_lua/redis_store.go | 4 +- store/redis_lua/universal_redis_store.go | 33 +++---- utils/config.go | 50 +++++------ utils/entry.go | 74 +++++++++++++++ utils/file.go | 2 +- 19 files changed, 541 insertions(+), 184 deletions(-) create mode 100644 api_directory.go create mode 100644 api_file.go create mode 100644 config.yml create mode 100644 config/robotfs.go delete mode 100644 config/security.go create mode 100644 engine/filesystem_manager.go delete mode 100644 engine/metadata_manager.go delete mode 100644 engine/workspace_manager.go create mode 100644 utils/entry.go diff --git a/api_directory.go b/api_directory.go new file mode 100644 index 0000000..6d80c9a --- /dev/null +++ b/api_directory.go @@ -0,0 +1,84 @@ +package main + +import ( + "gosvc/httpserver" + "gosvc/validator" + "robotfs/utils" +) + +type Entry struct { + Name string `json:"name"` + IsDir bool `json:"is_dir"` +} + +type ListResult struct { + Entries []Entry `json:"entries"` + LastFileName string `json:"last_file_name"` +} + +type MkdirParams struct { + Path string +} + +func (m *MkdirParams) Validate() error { + return validator.WithIf( + m.Path == "", "Path is empty", + ).Validate() +} + +func (s *Service) HandleMkdir( + req *httpserver.Request, + resp *httpserver.Response, +) *httpserver.Response { + params := req.Binded.(*MkdirParams) + fullPath := utils.FullPath(params.Path) + + err := s.FileSystemManager.MakeDirectory(req.Context(), fullPath) + if err != nil { + return resp.InternalServerError("mkdir failed, " + err.Error()) + } + + return resp.NoContent() +} + +func (s *Service) HandleListDirectory( + req *httpserver.Request, + resp *httpserver.Response, +) *httpserver.Response { + // path := req.QueryString("path") + // startFileName := req.QueryString("startFileName") + // limit := req.QueryInt("limit") + entries := []Entry{ + {Name: "file1.txt", IsDir: false}, + {Name: "folder1", IsDir: true}, + } + lastFileName := "file1.txt" + + result := ListResult{ + Entries: entries, + LastFileName: lastFileName, + } + + return resp.OK(result).JSON() +} + +func (s *Service) HandleDeleteDirectory( + req *httpserver.Request, + resp *httpserver.Response, +) *httpserver.Response { + return resp.NoContent() +} + +func (s *Service) HandleRenameDirectory( + req *httpserver.Request, + resp *httpserver.Response, +) *httpserver.Response { + return resp.NoContent() +} + +func (s *Service) HandleCopyDirectory( + req *httpserver.Request, + resp *httpserver.Response, +) *httpserver.Response { + return resp.NoContent() +} diff --git a/api_file.go b/api_file.go new file mode 100644 index 0000000..0e76dbb --- /dev/null +++ b/api_file.go @@ -0,0 +1,84 @@ +package main + +import ( + "gosvc/httpserver" + "gosvc/validator" +) + +type DeleteParams struct { + Path string + IsDir bool +} + +func (d *DeleteParams) Validate() error { + return validator.WithIf( + d.Path == "", "Path is empty", + ).Validate() +} + +type GeneralParams struct { + SrcPath string + DstPath string + IsDir bool +} + +func (g *GeneralParams) Validate() error { + return validator.ChainValidate( + validator.WithIf( + g.SrcPath == "", "SrcPath is empty", + ), + validator.WithIf( + g.DstPath == "", "DstPath is empty", + ), + ) +} + +type CopyParams struct { + GeneralParams +} + +type RenameParams struct { + GeneralParams +} + +func (s *Service) HandleUploadFile( + req *httpserver.Request, + resp *httpserver.Response, +) *httpserver.Response { + return resp.NoContent() +} + +func (s *Service) HandleInfoFile( + req *httpserver.Request, + resp *httpserver.Response, +) *httpserver.Response { + return resp.NoContent() +} + +func (s *Service) HandleDownloadFile( + req *httpserver.Request, + resp *httpserver.Response, +) *httpserver.Response { + return resp.NoContent() +} + +func (s *Service) HandleDeleteFile( + req *httpserver.Request, + resp *httpserver.Response, +) *httpserver.Response { + return resp.NoContent() +} + +func (s *Service) HandleRenameFile( + req *httpserver.Request, + resp *httpserver.Response, +) *httpserver.Response { + return resp.NoContent() +} + +func (s *Service) HandleCopyFile( + req *httpserver.Request, + resp *httpserver.Response, +) *httpserver.Response { + return resp.NoContent() +} diff --git a/api_other.go b/api_other.go index d338b0b..3b62019 100644 --- a/api_other.go +++ b/api_other.go @@ -4,7 +4,7 @@ import ( "gosvc/httpserver" ) -func (s *Service) API_Ping( +func (s *Service) HandlePing( req *httpserver.Request, resp *httpserver.Response, ) *httpserver.Response { diff --git a/config.yml b/config.yml new file mode 100644 index 0000000..e69de29 diff --git a/config/robotfs.go b/config/robotfs.go new file mode 100644 index 0000000..ac8468b --- /dev/null +++ b/config/robotfs.go @@ -0,0 +1,33 @@ +package config + +type RobotFSConfig struct { + Address string `mapstructure:"address"` + LogPath string `mapstructure:"log_path"` +} + +type RedisConfig struct { + Address string `mapstructure:"address"` + Password string `mapstructure:"password"` + DB int `mapstructure:"db"` +} + +type S3Config struct { + Region string `mapstructure:"region"` + Endpoint string `mapstructure:"endpoint"` + AccessKeyID string `mapstructure:"access_key_id"` + SecretAccessKey string `mapstructure:"secret_access_key"` + BucketName string `mapstructure:"bucket_name"` + UsePathStyle bool `mapstructure:"use_path_style"` +} + +type Config struct { + RobotFS RobotFSConfig `mapstructure:"robotfs"` + Redis RedisConfig `mapstructure:"redis"` + S3 S3Config `mapstructure:"s3"` +} + +func GetConfig() *Config { + config := &Config{} + + return config +} diff --git a/config/security.go b/config/security.go deleted file mode 100644 index 078cf78..0000000 --- a/config/security.go +++ /dev/null @@ -1,29 +0,0 @@ -package config - -type Config struct { - Robotfs struct { - Address string `mapstructure:"ADDRESS"` - LogPath string `mapstructure:"LOGPATH"` - } `mapstructure:"robotfs"` - - Redis struct { - Address string `mapstructure:"address"` - Password string `mapstructure:"password"` - DB int `mapstructure:"db"` - } `mapstructure:"redis"` - - S3 struct { - Region string `mapstructure:"region"` - Endpoint string `mapstructure:"endpoint"` - AccessKeyID string `mapstructure:"access_key_id"` - SecretAccessKey string `mapstructure:"secret_access_key"` - BucketName string `mapstructure:"bucket_name"` - UsePathStyle bool `mapstructure:"use_path_style"` - } `mapstructure:"s3"` -} - -func GetConfig() *Config { - config := &Config{} - - return config -} diff --git a/engine/engine.go b/engine/engine.go index c6239f5..de036b0 100644 --- a/engine/engine.go +++ b/engine/engine.go @@ -8,8 +8,7 @@ import ( ) type Engine struct { - metadataManager *MetaDataManager - storageManager *StorageManager + FileSystemManager *FileSystemManager } func NewEngine() *Engine { @@ -17,31 +16,25 @@ func NewEngine() *Engine { } func (e *Engine) Start() error { - store := redis_lua.NewRedisLuaStore() - if err := store.Initialize(utils.GetViper(), "redis."); err != nil { - return fmt.Errorf("failed to initialize meta store: %v", err) - } - metadata, err := NewMetaDataManager(store) - if err != nil { - return fmt.Errorf("failed to create metadata manager: %v", err) - } - e.metadataManager = metadata - s3Config, err := Initialize(utils.GetViper(), "s3.") if err != nil { return fmt.Errorf("failed to initialize s3 config: %v", err) } - storageManager, err := NewStorageManager(s3Config) + object, err := NewStorageManager(s3Config) if err != nil { return fmt.Errorf("failed to create storage manager: %v", err) } - e.storageManager = storageManager + + meta := redis_lua.NewRedisLuaStore() + if err := meta.Initialize(utils.GetViper(), "redis."); err != nil { + return fmt.Errorf("failed to initialize meta store: %v", err) + } + filesystem := NewFileSystemManager(meta, object) + e.FileSystemManager = filesystem return nil } func (e *Engine) Stop() { - if e.metadataManager != nil { - e.metadataManager.Shutdown() - } + e.FileSystemManager.Shutdown() } diff --git a/engine/filesystem_manager.go b/engine/filesystem_manager.go new file mode 100644 index 0000000..cbe2c34 --- /dev/null +++ b/engine/filesystem_manager.go @@ -0,0 +1,85 @@ +package engine + +import ( + "context" + "fmt" + "time" + + "robotfs/pb" + "robotfs/store" + "robotfs/utils" +) + +type FileSystemManager struct { + meta store.MetaStore + storage *StorageManager +} + +func NewFileSystemManager(meta store.MetaStore, storage *StorageManager) *FileSystemManager { + return &FileSystemManager{ + meta: meta, + storage: storage, + } +} + +func (f *FileSystemManager) BeginTransaction(ctx context.Context) (context.Context, error) { + return f.meta.BeginTransaction(ctx) +} + +func (f *FileSystemManager) CommitTransaction(ctx context.Context) error { + return f.meta.CommitTransaction(ctx) +} + +func (f *FileSystemManager) RollbackTransaction(ctx context.Context) error { + return f.meta.RollbackTransaction(ctx) +} + +var ( + Root = &utils.Entry{ + FullPath: utils.FullPath("/"), + IsDir: true, + CreateTime: time.Now().Unix(), + LastModificationTime: time.Now().Unix(), + } +) + +func (f *FileSystemManager) FindEntry(ctx context.Context, p utils.FullPath) (entry *pb.FileEntry, err error) { + if p == "/" { + return nil, nil + } + + entry, err = f.meta.FindEntry(ctx, p) + if err != nil { + return nil, err + } + + return +} + +func (f *FileSystemManager) MakeDirectory(ctx context.Context, path utils.FullPath) error { + if string(path) == "/" { + return nil + } + + if entry, _ := f.FindEntry(ctx, path); entry != nil { + return fmt.Errorf("directory %s already exists", path) + } + + parentDir, _ := path.DirAndName() + if parentDir != "/" { + if parentEntry, _ := f.FindEntry(ctx, utils.FullPath(parentDir)); parentEntry == nil { + return fmt.Errorf("parent directory %s does not exist", parentDir) + } + } + + entry := utils.MakeEntry(path, true) + return f.meta.InsertEntry(ctx, entry) +} + +func (f *FileSystemManager) Shutdown() error { + if f.meta != nil { + f.meta.Shutdown() + } + + return nil +} diff --git a/engine/metadata_manager.go b/engine/metadata_manager.go deleted file mode 100644 index e4eda18..0000000 --- a/engine/metadata_manager.go +++ /dev/null @@ -1,28 +0,0 @@ -package engine - -import ( - "fmt" - - "robotfs/store" -) - -type MetaDataManager struct { - store store.MetaStore -} - -func NewMetaDataManager(store store.MetaStore) (*MetaDataManager, error) { - if store == nil { - return nil, fmt.Errorf("meta store is required") - } - return &MetaDataManager{ - store: store, - }, nil -} - -func (m *MetaDataManager) Shutdown() error { - if m.store != nil { - m.store.Shutdown() - } - - return nil -} diff --git a/engine/workspace_manager.go b/engine/workspace_manager.go deleted file mode 100644 index 08b37b4..0000000 --- a/engine/workspace_manager.go +++ /dev/null @@ -1,42 +0,0 @@ -package engine - -import ( - "context" - "time" -) - -type WorkSpaceManager struct{} - -func NewWorkSpaceManager() *WorkSpaceManager { - return &WorkSpaceManager{} -} - -type StorageSpace struct { - ID string - Name string - OwnerID string - Type string - CreateTime time.Time - UpdateTime time.Time - InviteCode string - RootPath string -} - -func (wsm *WorkSpaceManager) CreateWorkSpace(ctx context.Context, ownerID, name, visibility string) (string, error) { - return "", nil -} - -func (wsm *WorkSpaceManager) GetWorkSpace(ctx context.Context, spaceID string) { -} - -func (wsm *WorkSpaceManager) UpdateWorkSpace(ctx context.Context, spaceID string) (*StorageSpace, error) { - return nil, nil -} - -func (wsm *WorkSpaceManager) GenerateInviteCode(ctx context.Context, spaceID string) (string, error) { - return "", nil -} - -func (wsm *WorkSpaceManager) ImportWorkSpace(ctx context.Context, code string, newOwnerID string) (string, error) { - return "", nil -} diff --git a/main.go b/main.go index 875651b..08287a9 100644 --- a/main.go +++ b/main.go @@ -20,14 +20,17 @@ Options: Example: robotfs --config=/path/to/config.yaml` +var ( + configFile = flag.String("config", "", "path to config file") + version = flag.Bool("version", false, "show version information") + help = flag.Bool("help", false, "show help message") +) + func printUsage() { fmt.Println(usage) } func main() { - configFile := flag.String("config", "", "path to config file") - version := flag.Bool("version", false, "show version information") - help := flag.Bool("help", false, "show help message") flag.Parse() if *help { @@ -41,8 +44,9 @@ func main() { } if *configFile != "" { - utils.ConfigurationFileDirectory.Set(*configFile) - if !utils.LoadConfiguration("config", true) { + err := utils.LoadConfiguration(*configFile) + if err != nil { + fmt.Println("load config file failed, " + err.Error()) os.Exit(1) } } else { diff --git a/router.go b/router.go index 4a689a6..9c74dbc 100644 --- a/router.go +++ b/router.go @@ -12,7 +12,115 @@ func (s *Service) RegisterRouteRules() { { Path: "/ping", Method: http.MethodGet, - Handler: s.API_Ping, + Handler: s.HandlePing, + }, + { + Path: "/robotfs", + SubRules: []*httpserver.RouteRule{ + { + Path: "/mkdir", + Method: http.MethodPost, + Handler: s.HandleMkdir, + Bind: &MkdirParams{}, + }, + { + Path: "/dir/list", + Method: http.MethodGet, + Handler: s.HandleListDirectory, + QueryRules: []*httpserver.QueryRule{ + { + Key: "path", + Type: httpserver.QueryTypeString, + Required: true, + }, + { + Key: "startFileName", + Type: httpserver.QueryTypeString, + Required: true, + }, + { + Key: "limit", + Type: httpserver.QueryTypeInt, + Required: true, + Min: 1, + Max: 2048, + }, + }, + }, + { + Path: "/dir/delete", + Method: http.MethodPost, + Handler: s.HandleDeleteDirectory, + Bind: &DeleteParams{}, + }, + { + Path: "/dir/rename", + Method: http.MethodPost, + Handler: s.HandleRenameDirectory, + Bind: &RenameParams{}, + }, + // Todo: need juicefs clone + { + Path: "/dir/copy", + Method: http.MethodPost, + Handler: s.HandleCopyDirectory, + Bind: &CopyParams{}, + }, + { + Path: "/file/upload", + Method: http.MethodPost, + Handler: s.HandleUploadFile, + QueryRules: []*httpserver.QueryRule{ + { + Key: "path", + Type: httpserver.QueryTypeString, + Required: true, + }, + }, + }, + { + Path: "/info", + Method: http.MethodGet, + Handler: s.HandleInfoFile, + QueryRules: []*httpserver.QueryRule{ + { + Key: "path", + Type: httpserver.QueryTypeString, + Required: true, + }, + }, + }, + { + Path: "/file/download", + Method: http.MethodGet, + Handler: s.HandleDownloadFile, + QueryRules: []*httpserver.QueryRule{ + { + Key: "path", + Type: httpserver.QueryTypeString, + Required: true, + }, + }, + }, + { + Path: "/file/delete", + Method: http.MethodPost, + Handler: s.HandleDeleteFile, + Bind: &DeleteParams{}, + }, + { + Path: "/file/rename", + Method: http.MethodPost, + Handler: s.HandleRenameFile, + Bind: &RenameParams{}, + }, + { + Path: "/file/copy", + Method: http.MethodPost, + Handler: s.HandleCopyFile, + Bind: &CopyParams{}, + }, + }, }, }, ) diff --git a/service.go b/service.go index 3bfba40..e51e02b 100644 --- a/service.go +++ b/service.go @@ -10,17 +10,17 @@ import ( type Service struct { gosvc.ServiceBase - engine *engine.Engine + *engine.Engine } func CreateService() (*Service, error) { return &Service{ - engine: engine.NewEngine(), + Engine: engine.NewEngine(), }, nil } func (s *Service) Start() error { - if err := s.engine.Start(); err != nil { + if err := s.Engine.Start(); err != nil { return fmt.Errorf("failed to start engine: %v", err) } @@ -28,8 +28,5 @@ func (s *Service) Start() error { } func (s *Service) Stop() { - s.engine.Stop() -} - -func (s *Service) Tick() { + s.Engine.Stop() } diff --git a/store/meta_store.go b/store/meta_store.go index b57b774..7a6c2c2 100644 --- a/store/meta_store.go +++ b/store/meta_store.go @@ -2,20 +2,27 @@ package store import ( "context" + "errors" "robotfs/pb" "robotfs/utils" ) -type ListEachEntryFunc func(entry *pb.FileEntry) bool +var ( + ErrUnsupportedListDirectoryPrefixed = errors.New("unsupported directory prefix listing") + ErrKvNotImplemented = errors.New("kv not implemented yet") + ErrKvNotFound = errors.New("kv: not found") +) + +type ListEachEntryFunc func(entry *utils.Entry) bool type MetaStore interface { GetName() string Initialize(configuration utils.Configuration, prefix string) error - InsertEntry(context.Context, *pb.FileEntry) error - UpdateEntry(context.Context, *pb.FileEntry) (err error) + InsertEntry(context.Context, *utils.Entry) error + UpdateEntry(context.Context, *utils.Entry) (err error) FindEntry(context.Context, utils.FullPath) (entry *pb.FileEntry, err error) DeleteEntry(context.Context, utils.FullPath) (err error) DeleteFolderChildren(context.Context, utils.FullPath) (err error) diff --git a/store/redis_lua/redis_store.go b/store/redis_lua/redis_store.go index 2636a5c..4befd0e 100644 --- a/store/redis_lua/redis_store.go +++ b/store/redis_lua/redis_store.go @@ -23,14 +23,16 @@ func (store *RedisLuaStore) Initialize(configuration utils.Configuration, prefix configuration.GetString(prefix+"address"), configuration.GetString(prefix+"password"), configuration.GetInt(prefix+"database"), + configuration.GetStringSlice(prefix+"superLargeDirectories"), ) } -func (store *RedisLuaStore) initialize(hostPort string, password string, database int) (err error) { +func (store *RedisLuaStore) initialize(hostPort string, password string, database int, superLargeDirectories []string) (err error) { store.Client = redis.NewClient(&redis.Options{ Addr: hostPort, Password: password, DB: database, }) + store.loadSuperLargeDirectories(superLargeDirectories) return } diff --git a/store/redis_lua/universal_redis_store.go b/store/redis_lua/universal_redis_store.go index 327c862..2ed2dd7 100644 --- a/store/redis_lua/universal_redis_store.go +++ b/store/redis_lua/universal_redis_store.go @@ -43,32 +43,27 @@ func (store *UniversalRedisLuaStore) RollbackTransaction(ctx context.Context) er return nil } -func (store *UniversalRedisLuaStore) InsertEntry(ctx context.Context, entry *pb.FileEntry) (err error) { +func (store *UniversalRedisLuaStore) InsertEntry(ctx context.Context, entry *utils.Entry) (err error) { + value, err := entry.Encode() + if err != nil { + return fmt.Errorf("encoding %s: %v", entry.FullPath, err) + } - // value, err := entry.EncodeAttributesAndChunks() - // if err != nil { - // return fmt.Errorf("encoding %s %+v: %v", entry.FullPath, entry.Attr, err) - // } + dir, name := entry.FullPath.DirAndName() - // if len(entry.GetChunks()) > CountEntryChunksForGzip { - // value = util.MaybeGzipData(value) - // } + err = stored_procedure.InsertEntryScript.Run(ctx, store.Client, + []string{string(entry.FullPath), genDirectoryListKey(dir)}, + value, 0, + store.isSuperLargeDirectory(dir), 0, name).Err() - // dir, name := entry.FullPath.DirAndName() - - // err = stored_procedure.InsertEntryScript.Run(ctx, store.Client, - // []string{string(entry.FullPath), genDirectoryListKey(dir)}, - // value, entry.TtlSec, - // store.isSuperLargeDirectory(dir), 0, name).Err() - - // if err != nil { - // return fmt.Errorf("persisting %s : %v", entry.FullPath, err) - // } + if err != nil { + return fmt.Errorf("persisting %s : %v", entry.FullPath, err) + } return nil } -func (store *UniversalRedisLuaStore) UpdateEntry(ctx context.Context, entry *pb.FileEntry) (err error) { +func (store *UniversalRedisLuaStore) UpdateEntry(ctx context.Context, entry *utils.Entry) (err error) { return store.InsertEntry(ctx, entry) } diff --git a/utils/config.go b/utils/config.go index 768c9a5..bac534e 100644 --- a/utils/config.go +++ b/utils/config.go @@ -1,19 +1,13 @@ package utils import ( + "path/filepath" "strings" "sync" - "gosvc/logger" - "github.com/spf13/viper" ) -var ( - ConfigurationFileDirectory DirectoryValueType - loadSecurityConfigOnce sync.Once -) - type DirectoryValueType string func (s *DirectoryValueType) Set(value string) error { @@ -32,33 +26,29 @@ type Configuration interface { SetDefault(key string, value interface{}) } -func LoadSecurityConfiguration() { - loadSecurityConfigOnce.Do(func() { - LoadConfiguration("security", false) - }) -} +func LoadConfiguration(configFilePath string) error { + dir := filepath.Dir(configFilePath) + base := filepath.Base(configFilePath) + ext := filepath.Ext(base) -func LoadConfiguration(configFileName string, required bool) (loaded bool) { - viper.SetConfigName(configFileName) // name of config file (without extension) - viper.AddConfigPath(".") // look for config in the working directory + configName := strings.TrimSuffix(base, ext) + viper.SetConfigName(configName) + viper.AddConfigPath(dir) - if err := viper.MergeInConfig(); err != nil { // Handle errors reading the config file - if strings.Contains(err.Error(), "Not Found") { - logger.Info("Reading %s: %v", viper.ConfigFileUsed(), err) - } else { - logger.Error("Reading %s: %v", viper.ConfigFileUsed(), err) - } - if required { - logger.Error("Failed to load %s.yaml file from current directory\n"+ - "\nPlease use this command to run the program:\n"+ - " robotfs --config=%s.yaml\n\n\n", - configFileName, configFileName) - } - return false + switch ext { + case ".yaml", ".yml": + viper.SetConfigType("yaml") + case ".json": + viper.SetConfigType("json") + case ".toml": + viper.SetConfigType("toml") } - logger.Info("Reading %s.yaml from %s", configFileName, viper.ConfigFileUsed()) - return true + if err := viper.MergeInConfig(); err != nil { + return err + } + + return nil } type ViperProxy struct { diff --git a/utils/entry.go b/utils/entry.go new file mode 100644 index 0000000..f95776e --- /dev/null +++ b/utils/entry.go @@ -0,0 +1,74 @@ +package utils + +import ( + "fmt" + "robotfs/pb" + "time" + + "google.golang.org/protobuf/proto" +) + +type Entry struct { + FullPath FullPath + IsDir bool + Size uint64 + CreateTime int64 + S3Key string + ContentType string + Etag string + VersionID string + LastModificationTime int64 + Extended map[string][]byte +} + +func MakeEntry(fullPath FullPath, isDirectory bool) *Entry { + return &Entry{ + FullPath: fullPath, + IsDir: isDirectory, + CreateTime: time.Now().Unix(), + LastModificationTime: time.Now().Unix(), + } +} + +func (entry *Entry) Encode() ([]byte, error) { + message := entry.ToProto() + return proto.Marshal(message) +} + +func (entry *Entry) Decode(blob []byte) error { + message := &pb.FileEntry{} + if err := proto.Unmarshal(blob, message); err != nil { + return fmt.Errorf("decoding value blob for %s: %v", entry.FullPath, err) + } + + entry.FromProto(message) + return nil +} + +func (entry *Entry) ToProto() *pb.FileEntry { + return &pb.FileEntry{ + FullPath: string(entry.FullPath), + IsDir: entry.IsDir, + Size: entry.Size, + CreateTime: entry.CreateTime, + S3Key: entry.S3Key, + ContentType: entry.ContentType, + Etag: entry.Etag, + VersionId: entry.VersionID, + LastModificationTime: entry.LastModificationTime, + Extended: entry.Extended, + } +} + +func (entry *Entry) FromProto(pb *pb.FileEntry) { + entry.FullPath = FullPath(pb.FullPath) + entry.IsDir = pb.IsDir + entry.Size = pb.Size + entry.CreateTime = pb.CreateTime + entry.S3Key = pb.S3Key + entry.ContentType = pb.ContentType + entry.Etag = pb.Etag + entry.VersionID = pb.VersionId + entry.LastModificationTime = pb.LastModificationTime + entry.Extended = pb.Extended +} diff --git a/utils/file.go b/utils/file.go index 2bc7e9a..3c02300 100644 --- a/utils/file.go +++ b/utils/file.go @@ -1,8 +1,8 @@ package utils import ( - "strings" "path/filepath" + "strings" ) func NormalizePath(path string) string {