diff --git a/application/constants/constants.go b/application/constants/constants.go index 81b96b5..a50d415 100644 --- a/application/constants/constants.go +++ b/application/constants/constants.go @@ -3,7 +3,7 @@ package constants // These values will be injected at build time, DO NOT EDIT. // BackendVersion 当前后端版本号 -var BackendVersion = "4.0.0-alpha.1" +var BackendVersion = "4.1.0" // IsPro 是否为Pro版本 var IsPro = "false" diff --git a/go.mod b/go.mod index fb4f280..1fb7538 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.23.0 require ( entgo.io/ent v0.13.0 + github.com/Masterminds/semver/v3 v3.3.1 github.com/abslant/gzip v0.0.9 github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible github.com/aws/aws-sdk-go v1.31.5 diff --git a/go.sum b/go.sum index e2c97f5..8fe7330 100644 --- a/go.sum +++ b/go.sum @@ -76,6 +76,8 @@ github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF0 github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Masterminds/semver/v3 v3.0.3/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/Masterminds/semver/v3 v3.1.0/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +github.com/Masterminds/semver/v3 v3.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7rj+4nv4= +github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/Masterminds/sprig v2.15.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= diff --git a/inventory/client.go b/inventory/client.go index d93cf52..6e3d293 100644 --- a/inventory/client.go +++ b/inventory/client.go @@ -5,20 +5,12 @@ import ( rawsql "database/sql" "database/sql/driver" "fmt" - "os" "time" "entgo.io/ent/dialect/sql" - "github.com/cloudreve/Cloudreve/v4/application/constants" "github.com/cloudreve/Cloudreve/v4/ent" - "github.com/cloudreve/Cloudreve/v4/ent/group" - "github.com/cloudreve/Cloudreve/v4/ent/node" _ "github.com/cloudreve/Cloudreve/v4/ent/runtime" - "github.com/cloudreve/Cloudreve/v4/ent/setting" - "github.com/cloudreve/Cloudreve/v4/ent/storagepolicy" "github.com/cloudreve/Cloudreve/v4/inventory/debug" - "github.com/cloudreve/Cloudreve/v4/inventory/types" - "github.com/cloudreve/Cloudreve/v4/pkg/boolset" "github.com/cloudreve/Cloudreve/v4/pkg/cache" "github.com/cloudreve/Cloudreve/v4/pkg/conf" "github.com/cloudreve/Cloudreve/v4/pkg/logging" @@ -153,302 +145,3 @@ func (d sqlite3Driver) Open(name string) (conn driver.Conn, err error) { func init() { rawsql.Register("sqlite3", sqlite3Driver{Driver: &sqlite.Driver{}}) } - -// needMigration exams if required schema version is satisfied. -func needMigration(client *ent.Client, ctx context.Context, requiredDbVersion string) bool { - c, _ := client.Setting.Query().Where(setting.NameEQ(DBVersionPrefix + requiredDbVersion)).Count(ctx) - return c == 0 -} - -func migrate(l logging.Logger, client *ent.Client, ctx context.Context, kv cache.Driver, requiredDbVersion string) error { - l.Info("Start initializing database schema...") - l.Info("Creating basic table schema...") - if err := client.Schema.Create(ctx); err != nil { - return fmt.Errorf("Failed creating schema resources: %w", err) - } - - migrateDefaultSettings(l, client, ctx, kv) - - if err := migrateDefaultStoragePolicy(l, client, ctx); err != nil { - return fmt.Errorf("failed migrating default storage policy: %w", err) - } - - if err := migrateSysGroups(l, client, ctx); err != nil { - return fmt.Errorf("failed migrating default storage policy: %w", err) - } - - client.Setting.Create().SetName(DBVersionPrefix + requiredDbVersion).SetValue("installed").Save(ctx) - return nil -} - -func migrateDefaultSettings(l logging.Logger, client *ent.Client, ctx context.Context, kv cache.Driver) { - // clean kv cache - if err := kv.DeleteAll(); err != nil { - l.Warning("Failed to remove all KV entries while schema migration: %s", err) - } - - // List existing settings into a map - existingSettings := make(map[string]struct{}) - settings, err := client.Setting.Query().All(ctx) - if err != nil { - l.Warning("Failed to query existing settings: %s", err) - } - - for _, s := range settings { - existingSettings[s.Name] = struct{}{} - } - - l.Info("Insert default settings...") - for k, v := range DefaultSettings { - if _, ok := existingSettings[k]; ok { - l.Debug("Skip inserting setting %s, already exists.", k) - continue - } - - if override, ok := os.LookupEnv(EnvDefaultOverwritePrefix + k); ok { - l.Info("Override default setting %q with env value %q", k, override) - v = override - } - - client.Setting.Create().SetName(k).SetValue(v).SaveX(ctx) - } -} - -func migrateDefaultStoragePolicy(l logging.Logger, client *ent.Client, ctx context.Context) error { - if _, err := client.StoragePolicy.Query().Where(storagepolicy.ID(1)).First(ctx); err == nil { - l.Info("Default storage policy (ID=1) already exists, skip migrating.") - return nil - } - - l.Info("Insert default storage policy...") - if _, err := client.StoragePolicy.Create(). - SetName("Default storage policy"). - SetType(types.PolicyTypeLocal). - SetDirNameRule(util.DataPath("uploads/{uid}/{path}")). - SetFileNameRule("{uid}_{randomkey8}_{originname}"). - SetSettings(&types.PolicySetting{ - ChunkSize: 25 << 20, // 25MB - PreAllocate: true, - }). - Save(ctx); err != nil { - return fmt.Errorf("failed to create default storage policy: %w", err) - } - - return nil -} - -func migrateSysGroups(l logging.Logger, client *ent.Client, ctx context.Context) error { - if err := migrateAdminGroup(l, client, ctx); err != nil { - return err - } - - if err := migrateUserGroup(l, client, ctx); err != nil { - return err - } - - if err := migrateAnonymousGroup(l, client, ctx); err != nil { - return err - } - - if err := migrateMasterNode(l, client, ctx); err != nil { - return err - } - - return nil -} - -func migrateAdminGroup(l logging.Logger, client *ent.Client, ctx context.Context) error { - if _, err := client.Group.Query().Where(group.ID(1)).First(ctx); err == nil { - l.Info("Default admin group (ID=1) already exists, skip migrating.") - return nil - } - - l.Info("Insert default admin group...") - permissions := &boolset.BooleanSet{} - boolset.Sets(map[types.GroupPermission]bool{ - types.GroupPermissionIsAdmin: true, - types.GroupPermissionShare: true, - types.GroupPermissionWebDAV: true, - types.GroupPermissionWebDAVProxy: true, - types.GroupPermissionArchiveDownload: true, - types.GroupPermissionArchiveTask: true, - types.GroupPermissionShareDownload: true, - types.GroupPermissionRemoteDownload: true, - types.GroupPermissionRedirectedSource: true, - types.GroupPermissionAdvanceDelete: true, - types.GroupPermissionIgnoreFileOwnership: true, - // TODO: review default permission - }, permissions) - if _, err := client.Group.Create(). - SetName("Admin"). - SetStoragePoliciesID(1). - SetMaxStorage(1 * constants.TB). // 1 TB default storage - SetPermissions(permissions). - SetSettings(&types.GroupSetting{ - SourceBatchSize: 1000, - Aria2BatchSize: 50, - MaxWalkedFiles: 100000, - TrashRetention: 7 * 24 * 3600, - RedirectedSource: true, - }). - Save(ctx); err != nil { - return fmt.Errorf("failed to create default admin group: %w", err) - } - - return nil -} - -func migrateUserGroup(l logging.Logger, client *ent.Client, ctx context.Context) error { - if _, err := client.Group.Query().Where(group.ID(2)).First(ctx); err == nil { - l.Info("Default user group (ID=2) already exists, skip migrating.") - return nil - } - - l.Info("Insert default user group...") - permissions := &boolset.BooleanSet{} - boolset.Sets(map[types.GroupPermission]bool{ - types.GroupPermissionShare: true, - types.GroupPermissionShareDownload: true, - types.GroupPermissionRedirectedSource: true, - }, permissions) - if _, err := client.Group.Create(). - SetName("User"). - SetStoragePoliciesID(1). - SetMaxStorage(1 * constants.GB). // 1 GB default storage - SetPermissions(permissions). - SetSettings(&types.GroupSetting{ - SourceBatchSize: 10, - Aria2BatchSize: 1, - MaxWalkedFiles: 100000, - TrashRetention: 7 * 24 * 3600, - RedirectedSource: true, - }). - Save(ctx); err != nil { - return fmt.Errorf("failed to create default user group: %w", err) - } - - return nil -} - -func migrateAnonymousGroup(l logging.Logger, client *ent.Client, ctx context.Context) error { - if _, err := client.Group.Query().Where(group.ID(AnonymousGroupID)).First(ctx); err == nil { - l.Info("Default anonymous group (ID=3) already exists, skip migrating.") - return nil - } - - l.Info("Insert default anonymous group...") - permissions := &boolset.BooleanSet{} - boolset.Sets(map[types.GroupPermission]bool{ - types.GroupPermissionIsAnonymous: true, - types.GroupPermissionShareDownload: true, - }, permissions) - if _, err := client.Group.Create(). - SetName("Anonymous"). - SetPermissions(permissions). - SetSettings(&types.GroupSetting{ - MaxWalkedFiles: 100000, - RedirectedSource: true, - }). - Save(ctx); err != nil { - return fmt.Errorf("failed to create default anonymous group: %w", err) - } - - return nil -} - -func migrateMasterNode(l logging.Logger, client *ent.Client, ctx context.Context) error { - if _, err := client.Node.Query().Where(node.TypeEQ(node.TypeMaster)).First(ctx); err == nil { - l.Info("Default master node already exists, skip migrating.") - return nil - } - - capabilities := &boolset.BooleanSet{} - boolset.Sets(map[types.NodeCapability]bool{ - types.NodeCapabilityCreateArchive: true, - types.NodeCapabilityExtractArchive: true, - types.NodeCapabilityRemoteDownload: true, - }, capabilities) - - stm := client.Node.Create(). - SetType(node.TypeMaster). - SetCapabilities(capabilities). - SetName("Master"). - SetSettings(&types.NodeSetting{ - Provider: types.DownloaderProviderAria2, - }). - SetStatus(node.StatusActive) - - _, enableAria2 := os.LookupEnv(EnvEnableAria2) - if enableAria2 { - l.Info("Aria2 is override as enabled.") - stm.SetSettings(&types.NodeSetting{ - Provider: types.DownloaderProviderAria2, - Aria2Setting: &types.Aria2Setting{ - Server: "http://127.0.0.1:6800/jsonrpc", - }, - }) - } - - l.Info("Insert default master node...") - if _, err := stm.Save(ctx); err != nil { - return fmt.Errorf("failed to create default master node: %w", err) - } - - return nil -} - -func createMockData(client *ent.Client, ctx context.Context) { - //userCount := 100 - //folderCount := 10000 - //fileCount := 25000 - // - //// create users - //pwdDigest, _ := digestPassword("52121225") - //userCreates := make([]*ent.UserCreate, userCount) - //for i := 0; i < userCount; i++ { - // nick := uuid.Must(uuid.NewV4()).String() - // userCreates[i] = client.User.Create(). - // SetEmail(nick + "@cloudreve.org"). - // SetNick(nick). - // SetPassword(pwdDigest). - // SetStatus(user.StatusActive). - // SetGroupID(1) - //} - //users, err := client.User.CreateBulk(userCreates...).Save(ctx) - //if err != nil { - // panic(err) - //} - // - //// Create root folder - //rootFolderCreates := make([]*ent.FileCreate, userCount) - //folderIds := make([][]int, 0, folderCount*userCount+userCount) - //for i, user := range users { - // rootFolderCreates[i] = client.File.Create(). - // SetName(RootFolderName). - // SetOwnerID(user.ID). - // SetType(int(FileTypeFolder)) - //} - //rootFolders, err := client.File.CreateBulk(rootFolderCreates...).Save(ctx) - //for _, rootFolders := range rootFolders { - // folderIds = append(folderIds, []int{rootFolders.ID, rootFolders.OwnerID}) - //} - //if err != nil { - // panic(err) - //} - // - //// create random folder - //for i := 0; i < folderCount*userCount; i++ { - // parent := lo.Sample(folderIds) - // res := client.File.Create(). - // SetName(uuid.Must(uuid.NewV4()).String()). - // SetType(int(FileTypeFolder)). - // SetOwnerID(parent[1]). - // SetFileChildren(parent[0]). - // SaveX(ctx) - // folderIds = append(folderIds, []int{res.ID, res.OwnerID}) - //} - - for i := 0; i < 255; i++ { - fmt.Printf("%d/", i) - } -} diff --git a/inventory/migration.go b/inventory/migration.go new file mode 100644 index 0000000..9f1e28b --- /dev/null +++ b/inventory/migration.go @@ -0,0 +1,416 @@ +package inventory + +import ( + "context" + "encoding/json" + "fmt" + "os" + "strings" + + "github.com/Masterminds/semver/v3" + "github.com/cloudreve/Cloudreve/v4/application/constants" + "github.com/cloudreve/Cloudreve/v4/ent" + "github.com/cloudreve/Cloudreve/v4/ent/group" + "github.com/cloudreve/Cloudreve/v4/ent/node" + "github.com/cloudreve/Cloudreve/v4/ent/setting" + "github.com/cloudreve/Cloudreve/v4/ent/storagepolicy" + "github.com/cloudreve/Cloudreve/v4/inventory/types" + "github.com/cloudreve/Cloudreve/v4/pkg/boolset" + "github.com/cloudreve/Cloudreve/v4/pkg/cache" + "github.com/cloudreve/Cloudreve/v4/pkg/logging" + "github.com/cloudreve/Cloudreve/v4/pkg/util" + "github.com/samber/lo" +) + +// needMigration exams if required schema version is satisfied. +func needMigration(client *ent.Client, ctx context.Context, requiredDbVersion string) bool { + c, _ := client.Setting.Query().Where(setting.NameEQ(DBVersionPrefix + requiredDbVersion)).Count(ctx) + return c == 0 +} + +func migrate(l logging.Logger, client *ent.Client, ctx context.Context, kv cache.Driver, requiredDbVersion string) error { + l.Info("Start initializing database schema...") + l.Info("Creating basic table schema...") + if err := client.Schema.Create(ctx); err != nil { + return fmt.Errorf("Failed creating schema resources: %w", err) + } + + migrateDefaultSettings(l, client, ctx, kv) + + if err := migrateDefaultStoragePolicy(l, client, ctx); err != nil { + return fmt.Errorf("failed migrating default storage policy: %w", err) + } + + if err := migrateSysGroups(l, client, ctx); err != nil { + return fmt.Errorf("failed migrating default storage policy: %w", err) + } + + if err := applyPatches(l, client, ctx, requiredDbVersion); err != nil { + return fmt.Errorf("failed applying schema patches: %w", err) + } + + client.Setting.Create().SetName(DBVersionPrefix + requiredDbVersion).SetValue("installed").Save(ctx) + return nil +} + +func migrateDefaultSettings(l logging.Logger, client *ent.Client, ctx context.Context, kv cache.Driver) { + // clean kv cache + if err := kv.DeleteAll(); err != nil { + l.Warning("Failed to remove all KV entries while schema migration: %s", err) + } + + // List existing settings into a map + existingSettings := make(map[string]struct{}) + settings, err := client.Setting.Query().All(ctx) + if err != nil { + l.Warning("Failed to query existing settings: %s", err) + } + + for _, s := range settings { + existingSettings[s.Name] = struct{}{} + } + + l.Info("Insert default settings...") + for k, v := range DefaultSettings { + if _, ok := existingSettings[k]; ok { + l.Debug("Skip inserting setting %s, already exists.", k) + continue + } + + if override, ok := os.LookupEnv(EnvDefaultOverwritePrefix + k); ok { + l.Info("Override default setting %q with env value %q", k, override) + v = override + } + + client.Setting.Create().SetName(k).SetValue(v).SaveX(ctx) + } +} + +func migrateDefaultStoragePolicy(l logging.Logger, client *ent.Client, ctx context.Context) error { + if _, err := client.StoragePolicy.Query().Where(storagepolicy.ID(1)).First(ctx); err == nil { + l.Info("Default storage policy (ID=1) already exists, skip migrating.") + return nil + } + + l.Info("Insert default storage policy...") + if _, err := client.StoragePolicy.Create(). + SetName("Default storage policy"). + SetType(types.PolicyTypeLocal). + SetDirNameRule(util.DataPath("uploads/{uid}/{path}")). + SetFileNameRule("{uid}_{randomkey8}_{originname}"). + SetSettings(&types.PolicySetting{ + ChunkSize: 25 << 20, // 25MB + PreAllocate: true, + }). + Save(ctx); err != nil { + return fmt.Errorf("failed to create default storage policy: %w", err) + } + + return nil +} + +func migrateSysGroups(l logging.Logger, client *ent.Client, ctx context.Context) error { + if err := migrateAdminGroup(l, client, ctx); err != nil { + return err + } + + if err := migrateUserGroup(l, client, ctx); err != nil { + return err + } + + if err := migrateAnonymousGroup(l, client, ctx); err != nil { + return err + } + + if err := migrateMasterNode(l, client, ctx); err != nil { + return err + } + + return nil +} + +func migrateAdminGroup(l logging.Logger, client *ent.Client, ctx context.Context) error { + if _, err := client.Group.Query().Where(group.ID(1)).First(ctx); err == nil { + l.Info("Default admin group (ID=1) already exists, skip migrating.") + return nil + } + + l.Info("Insert default admin group...") + permissions := &boolset.BooleanSet{} + boolset.Sets(map[types.GroupPermission]bool{ + types.GroupPermissionIsAdmin: true, + types.GroupPermissionShare: true, + types.GroupPermissionWebDAV: true, + types.GroupPermissionWebDAVProxy: true, + types.GroupPermissionArchiveDownload: true, + types.GroupPermissionArchiveTask: true, + types.GroupPermissionShareDownload: true, + types.GroupPermissionRemoteDownload: true, + types.GroupPermissionRedirectedSource: true, + types.GroupPermissionAdvanceDelete: true, + types.GroupPermissionIgnoreFileOwnership: true, + // TODO: review default permission + }, permissions) + if _, err := client.Group.Create(). + SetName("Admin"). + SetStoragePoliciesID(1). + SetMaxStorage(1 * constants.TB). // 1 TB default storage + SetPermissions(permissions). + SetSettings(&types.GroupSetting{ + SourceBatchSize: 1000, + Aria2BatchSize: 50, + MaxWalkedFiles: 100000, + TrashRetention: 7 * 24 * 3600, + RedirectedSource: true, + }). + Save(ctx); err != nil { + return fmt.Errorf("failed to create default admin group: %w", err) + } + + return nil +} + +func migrateUserGroup(l logging.Logger, client *ent.Client, ctx context.Context) error { + if _, err := client.Group.Query().Where(group.ID(2)).First(ctx); err == nil { + l.Info("Default user group (ID=2) already exists, skip migrating.") + return nil + } + + l.Info("Insert default user group...") + permissions := &boolset.BooleanSet{} + boolset.Sets(map[types.GroupPermission]bool{ + types.GroupPermissionShare: true, + types.GroupPermissionShareDownload: true, + types.GroupPermissionRedirectedSource: true, + }, permissions) + if _, err := client.Group.Create(). + SetName("User"). + SetStoragePoliciesID(1). + SetMaxStorage(1 * constants.GB). // 1 GB default storage + SetPermissions(permissions). + SetSettings(&types.GroupSetting{ + SourceBatchSize: 10, + Aria2BatchSize: 1, + MaxWalkedFiles: 100000, + TrashRetention: 7 * 24 * 3600, + RedirectedSource: true, + }). + Save(ctx); err != nil { + return fmt.Errorf("failed to create default user group: %w", err) + } + + return nil +} + +func migrateAnonymousGroup(l logging.Logger, client *ent.Client, ctx context.Context) error { + if _, err := client.Group.Query().Where(group.ID(AnonymousGroupID)).First(ctx); err == nil { + l.Info("Default anonymous group (ID=3) already exists, skip migrating.") + return nil + } + + l.Info("Insert default anonymous group...") + permissions := &boolset.BooleanSet{} + boolset.Sets(map[types.GroupPermission]bool{ + types.GroupPermissionIsAnonymous: true, + types.GroupPermissionShareDownload: true, + }, permissions) + if _, err := client.Group.Create(). + SetName("Anonymous"). + SetPermissions(permissions). + SetSettings(&types.GroupSetting{ + MaxWalkedFiles: 100000, + RedirectedSource: true, + }). + Save(ctx); err != nil { + return fmt.Errorf("failed to create default anonymous group: %w", err) + } + + return nil +} + +func migrateMasterNode(l logging.Logger, client *ent.Client, ctx context.Context) error { + if _, err := client.Node.Query().Where(node.TypeEQ(node.TypeMaster)).First(ctx); err == nil { + l.Info("Default master node already exists, skip migrating.") + return nil + } + + capabilities := &boolset.BooleanSet{} + boolset.Sets(map[types.NodeCapability]bool{ + types.NodeCapabilityCreateArchive: true, + types.NodeCapabilityExtractArchive: true, + types.NodeCapabilityRemoteDownload: true, + }, capabilities) + + stm := client.Node.Create(). + SetType(node.TypeMaster). + SetCapabilities(capabilities). + SetName("Master"). + SetSettings(&types.NodeSetting{ + Provider: types.DownloaderProviderAria2, + }). + SetStatus(node.StatusActive) + + _, enableAria2 := os.LookupEnv(EnvEnableAria2) + if enableAria2 { + l.Info("Aria2 is override as enabled.") + stm.SetSettings(&types.NodeSetting{ + Provider: types.DownloaderProviderAria2, + Aria2Setting: &types.Aria2Setting{ + Server: "http://127.0.0.1:6800/jsonrpc", + }, + }) + } + + l.Info("Insert default master node...") + if _, err := stm.Save(ctx); err != nil { + return fmt.Errorf("failed to create default master node: %w", err) + } + + return nil +} + +type ( + PatchFunc func(l logging.Logger, client *ent.Client, ctx context.Context) error + Patch struct { + Name string + EndVersion string + Func PatchFunc + } +) + +var patches = []Patch{ + { + Name: "apply_default_excalidraw_viewer", + EndVersion: "4.1.0", + Func: func(l logging.Logger, client *ent.Client, ctx context.Context) error { + // 1. Apply excalidraw file icons + // 1.1 Check if it's already applied + iconSetting, err := client.Setting.Query().Where(setting.Name("explorer_icons")).First(ctx) + if err != nil { + return fmt.Errorf("failed to query explorer_icons setting: %w", err) + } + + var icons []types.FileTypeIconSetting + if err := json.Unmarshal([]byte(iconSetting.Value), &icons); err != nil { + return fmt.Errorf("failed to unmarshal explorer_icons setting: %w", err) + } + + iconExisted := false + for _, icon := range icons { + if lo.Contains(icon.Exts, "excalidraw") { + iconExisted = true + break + } + } + + // 1.2 If not existed, add it + if !iconExisted { + // Found existing excalidraw icon default setting + var defaultExcalidrawIcon types.FileTypeIconSetting + for _, icon := range defaultIcons { + if lo.Contains(icon.Exts, "excalidraw") { + defaultExcalidrawIcon = icon + break + } + } + + icons = append(icons, defaultExcalidrawIcon) + newIconSetting, err := json.Marshal(icons) + if err != nil { + return fmt.Errorf("failed to marshal explorer_icons setting: %w", err) + } + + if _, err := client.Setting.UpdateOne(iconSetting).SetValue(string(newIconSetting)).Save(ctx); err != nil { + return fmt.Errorf("failed to update explorer_icons setting: %w", err) + } + } + + // 2. Apply default file viewers + // 2.1 Check if it's already applied + fileViewersSetting, err := client.Setting.Query().Where(setting.Name("file_viewers")).First(ctx) + if err != nil { + return fmt.Errorf("failed to query file_viewers setting: %w", err) + } + + var fileViewers []types.ViewerGroup + if err := json.Unmarshal([]byte(fileViewersSetting.Value), &fileViewers); err != nil { + return fmt.Errorf("failed to unmarshal file_viewers setting: %w", err) + } + + fileViewerExisted := false + for _, viewer := range fileViewers[0].Viewers { + if viewer.ID == "excalidraw" { + fileViewerExisted = true + break + } + } + + // 2.2 If not existed, add it + if !fileViewerExisted { + // Found existing excalidraw viewer default setting + var defaultExcalidrawViewer types.Viewer + for _, viewer := range defaultFileViewers[0].Viewers { + if viewer.ID == "excalidraw" { + defaultExcalidrawViewer = viewer + break + } + } + + fileViewers[0].Viewers = append(fileViewers[0].Viewers, defaultExcalidrawViewer) + newFileViewersSetting, err := json.Marshal(fileViewers) + if err != nil { + return fmt.Errorf("failed to marshal file_viewers setting: %w", err) + } + + if _, err := client.Setting.UpdateOne(fileViewersSetting).SetValue(string(newFileViewersSetting)).Save(ctx); err != nil { + return fmt.Errorf("failed to update file_viewers setting: %w", err) + } + } + + return nil + }, + }, +} + +func applyPatches(l logging.Logger, client *ent.Client, ctx context.Context, requiredDbVersion string) error { + allVersionMarks, err := client.Setting.Query().Where(setting.NameHasPrefix(DBVersionPrefix)).All(ctx) + if err != nil { + return err + } + + requiredDbVersion = strings.TrimSuffix(requiredDbVersion, "-pro") + + // Find the latest applied version + var latestAppliedVersion *semver.Version + for _, v := range allVersionMarks { + v.Name = strings.TrimSuffix(v.Name, "-pro") + version, err := semver.NewVersion(strings.TrimPrefix(v.Name, DBVersionPrefix)) + if err != nil { + l.Warning("Failed to parse past version %s: %s", v.Name, err) + continue + } + if latestAppliedVersion == nil || version.Compare(latestAppliedVersion) > 0 { + latestAppliedVersion = version + } + } + + requiredVersion, err := semver.NewVersion(requiredDbVersion) + if err != nil { + return fmt.Errorf("failed to parse required version %s: %w", requiredDbVersion, err) + } + + if latestAppliedVersion == nil || requiredVersion.Compare(requiredVersion) > 0 { + latestAppliedVersion = requiredVersion + } + + for _, patch := range patches { + if latestAppliedVersion.Compare(semver.MustParse(patch.EndVersion)) < 0 { + l.Info("Applying schema patch %s...", patch.Name) + if err := patch.Func(l, client, ctx); err != nil { + return err + } + } + } + + return nil +} diff --git a/inventory/setting.go b/inventory/setting.go index cae6715..45b5e8c 100644 --- a/inventory/setting.go +++ b/inventory/setting.go @@ -2,10 +2,12 @@ package inventory import ( "context" + "encoding/json" "fmt" "github.com/cloudreve/Cloudreve/v4/ent" "github.com/cloudreve/Cloudreve/v4/ent/setting" + "github.com/cloudreve/Cloudreve/v4/inventory/types" "github.com/cloudreve/Cloudreve/v4/pkg/cache" "github.com/cloudreve/Cloudreve/v4/pkg/util" "github.com/gofrs/uuid" @@ -77,6 +79,253 @@ func (c *settingClient) Set(ctx context.Context, settings map[string]string) err return nil } +var ( + defaultIcons = []types.FileTypeIconSetting{ + { + Exts: []string{"mp3", "flac", "ape", "wav", "acc", "ogg", "m4a"}, + Icon: "audio", + Color: "#651fff", + }, + { + Exts: []string{"m3u8", "mp4", "flv", "avi", "wmv", "mkv", "rm", "rmvb", "mov", "ogv"}, + Icon: "video", + Color: "#d50000", + }, + { + Exts: []string{"bmp", "iff", "png", "gif", "jpg", "jpeg", "psd", "svg", "webp", "heif", "heic", "tiff", "avif"}, + Icon: "image", + Color: "#d32f2f", + }, + { + Exts: []string{"3fr", "ari", "arw", "bay", "braw", "crw", "cr2", "cr3", "cap", "dcs", "dcr", "dng", "drf", "eip", "erf", "fff", "gpr", "iiq", "k25", "kdc", "mdc", "mef", "mos", "mrw", "nef", "nrw", "obm", "orf", "pef", "ptx", "pxn", "r3d", "raf", "raw", "rwl", "rw2", "rwz", "sr2", "srf", "srw", "tif", "x3f"}, + Icon: "raw", + Color: "#d32f2f", + }, + { + Exts: []string{"pdf"}, + Color: "#f44336", + Icon: "pdf", + }, + { + Exts: []string{"doc", "docx"}, + Color: "#538ce5", + Icon: "word", + }, + { + Exts: []string{"ppt", "pptx"}, + Color: "#EF633F", + Icon: "ppt", + }, + { + Exts: []string{"xls", "xlsx", "csv"}, + Color: "#4caf50", + Icon: "excel", + }, + { + Exts: []string{"txt", "html"}, + Color: "#607d8b", + Icon: "text", + }, + { + Exts: []string{"torrent"}, + Color: "#5c6bc0", + Icon: "torrent", + }, + { + Exts: []string{"zip", "gz", "xz", "tar", "rar", "7z", "bz2", "z"}, + Color: "#f9a825", + Icon: "zip", + }, + { + Exts: []string{"exe", "msi"}, + Color: "#1a237e", + Icon: "exe", + }, + { + Exts: []string{"apk"}, + Color: "#8bc34a", + Icon: "android", + }, + { + Exts: []string{"go"}, + Color: "#16b3da", + Icon: "go", + }, + { + Exts: []string{"py"}, + Color: "#3776ab", + Icon: "python", + }, + { + Exts: []string{"c"}, + Color: "#a4c639", + Icon: "c", + }, + { + Exts: []string{"cpp"}, + Color: "#f34b7d", + Icon: "cpp", + }, + { + Exts: []string{"js", "jsx"}, + Color: "#f4d003", + Icon: "js", + }, + { + Exts: []string{"epub"}, + Color: "#81b315", + Icon: "book", + }, + { + Exts: []string{"rs"}, + Color: "#000", + ColorDark: "#fff", + Icon: "rust", + }, + { + Exts: []string{"drawio"}, + Color: "#F08705", + Icon: "flowchart", + }, + { + Exts: []string{"dwb"}, + Color: "#F08705", + Icon: "whiteboard", + }, + { + Exts: []string{"md"}, + Color: "#383838", + ColorDark: "#cbcbcb", + Icon: "markdown", + }, + { + Img: "/static/img/viewers/excalidraw.svg", + Exts: []string{"excalidraw"}, + }, + } + + defaultFileViewers = []types.ViewerGroup{ + { + Viewers: []types.Viewer{ + { + ID: "music", + Type: types.ViewerTypeBuiltin, + DisplayName: "fileManager.musicPlayer", + Exts: []string{"mp3", "ogg", "wav", "flac", "m4a"}, + }, + { + ID: "epub", + Type: types.ViewerTypeBuiltin, + DisplayName: "fileManager.epubViewer", + Exts: []string{"epub"}, + }, + { + ID: "googledocs", + Type: types.ViewerTypeCustom, + DisplayName: "fileManager.googledocs", + Icon: "/static/img/viewers/gdrive.png", + Url: "https://docs.google.com/gview?url={$src}&embedded=true", + Exts: []string{"jpeg", "png", "gif", "tiff", "bmp", "webm", "mpeg4", "3gpp", "mov", "avi", "mpegps", "wmv", "flv", "txt", "css", "html", "php", "c", "cpp", "h", "hpp", "js", "doc", "docx", "xls", "xlsx", "ppt", "pptx", "pdf", "pages", "ai", "psd", "tiff", "dxf", "svg", "eps", "ps", "ttf", "xps"}, + MaxSize: 26214400, + }, + { + ID: "m365online", + Type: types.ViewerTypeCustom, + DisplayName: "fileManager.m365viewer", + Icon: "/static/img/viewers/m365.svg", + Url: "https://view.officeapps.live.com/op/view.aspx?src={$src}", + Exts: []string{"doc", "docx", "docm", "dotm", "dotx", "xlsx", "xlsb", "xls", "xlsm", "pptx", "ppsx", "ppt", "pps", "pptm", "potm", "ppam", "potx", "ppsm"}, + MaxSize: 10485760, + }, + { + ID: "pdf", + Type: types.ViewerTypeBuiltin, + DisplayName: "fileManager.pdfViewer", + Exts: []string{"pdf"}, + }, + { + ID: "video", + Type: types.ViewerTypeBuiltin, + Icon: "/static/img/viewers/artplayer.png", + DisplayName: "Artplayer", + Exts: []string{"mp4", "mkv", "webm", "avi", "mov", "m3u8", "flv"}, + }, + { + ID: "markdown", + Type: types.ViewerTypeBuiltin, + DisplayName: "fileManager.markdownEditor", + Exts: []string{"md"}, + Templates: []types.NewFileTemplate{ + { + Ext: "md", + DisplayName: "Markdown", + }, + }, + }, + { + ID: "drawio", + Type: types.ViewerTypeBuiltin, + Icon: "/static/img/viewers/drawio.svg", + DisplayName: "draw.io", + Exts: []string{"drawio", "dwb"}, + Props: map[string]string{ + "host": "https://embed.diagrams.net", + }, + Templates: []types.NewFileTemplate{ + { + Ext: "drawio", + DisplayName: "fileManager.diagram", + }, + { + Ext: "dwb", + DisplayName: "fileManager.whiteboard", + }, + }, + }, + { + ID: "image", + Type: types.ViewerTypeBuiltin, + DisplayName: "fileManager.imageViewer", + Exts: []string{"bmp", "png", "gif", "jpg", "jpeg", "svg", "webp", "heic", "heif"}, + }, + { + ID: "monaco", + Type: types.ViewerTypeBuiltin, + Icon: "/static/img/viewers/monaco.svg", + DisplayName: "fileManager.monacoEditor", + Exts: []string{"md", "txt", "json", "php", "py", "bat", "c", "h", "cpp", "hpp", "cs", "css", "dockerfile", "go", "html", "htm", "ini", "java", "js", "jsx", "less", "lua", "sh", "sql", "xml", "yaml"}, + Templates: []types.NewFileTemplate{ + { + Ext: "txt", + DisplayName: "fileManager.text", + }, + }, + }, + { + ID: "photopea", + Type: types.ViewerTypeBuiltin, + Icon: "/static/img/viewers/photopea.png", + DisplayName: "Photopea", + Exts: []string{"psd", "ai", "indd", "xcf", "xd", "fig", "kri", "clip", "pxd", "pxz", "cdr", "ufo", "afphoyo", "svg", "esp", "pdf", "pdn", "wmf", "emf", "png", "jpg", "jpeg", "gif", "webp", "ico", "icns", "bmp", "avif", "heic", "jxl", "ppm", "pgm", "pbm", "tiff", "dds", "iff", "anim", "tga", "dng", "nef", "cr2", "cr3", "arw", "rw2", "raf", "orf", "gpr", "3fr", "fff"}, + }, + { + ID: "excalidraw", + Type: types.ViewerTypeBuiltin, + Icon: "/static/img/viewers/excalidraw.svg", + DisplayName: "Excalidraw", + Exts: []string{"excalidraw"}, + Templates: []types.NewFileTemplate{ + { + Ext: "excalidraw", + DisplayName: "Excalidraw", + }, + }, + }, + }, + }, + } +) + var DefaultSettings = map[string]string{ "siteURL": `http://localhost:5212`, "siteName": `Cloudreve`, @@ -233,7 +482,6 @@ var DefaultSettings = map[string]string{ "site_logo_light": "/static/img/logo_light.svg", "tos_url": "https://cloudreve.org/privacy-policy", "privacy_policy_url": "https://cloudreve.org/privacy-policy", - "explorer_icons": `[{"exts":["mp3","flac","ape","wav","acc","ogg","m4a"],"icon":"audio","color":"#651fff"},{"exts":["m3u8","mp4","flv","avi","wmv","mkv","rm","rmvb","mov","ogv"],"icon":"video","color":"#d50000"},{"exts":["bmp","iff","png","gif","jpg","jpeg","psd","svg","webp","heif","heic","tiff","avif"],"icon":"image","color":"#d32f2f"},{"exts":["3fr","ari","arw","bay","braw","crw","cr2","cr3","cap","dcs","dcr","dng","drf","eip","erf","fff","gpr","iiq","k25","kdc","mdc","mef","mos","mrw","nef","nrw","obm","orf","pef","ptx","pxn","r3d","raf","raw","rwl","rw2","rwz","sr2","srf","srw","tif","x3f"],"icon":"raw","color":"#d32f2f"},{"exts":["pdf"],"color":"#f44336","icon":"pdf"},{"exts":["doc","docx"],"color":"#538ce5","icon":"word"},{"exts":["ppt","pptx"],"color":"#EF633F","icon":"ppt"},{"exts":["xls","xlsx","csv"],"color":"#4caf50","icon":"excel"},{"exts":["txt","html"],"color":"#607d8b","icon":"text"},{"exts":["torrent"],"color":"#5c6bc0","icon":"torrent"},{"exts":["zip","gz","xz","tar","rar","7z","bz2","z"],"color":"#f9a825","icon":"zip"},{"exts":["exe","msi"],"color":"#1a237e","icon":"exe"},{"exts":["apk"],"color":"#8bc34a","icon":"android"},{"exts":["go"],"color":"#16b3da","icon":"go"},{"exts":["py"],"color":"#3776ab","icon":"python"},{"exts":["c"],"color":"#a4c639","icon":"c"},{"exts":["cpp"],"color":"#f34b7d","icon":"cpp"},{"exts":["js","jsx"],"color":"#f4d003","icon":"js"},{"exts":["epub"],"color":"#81b315","icon":"book"},{"exts":["rs"],"color":"#000","color_dark":"#fff","icon":"rust"},{"exts":["drawio"],"color":"#F08705","icon":"flowchart"},{"exts":["dwb"],"color":"#F08705","icon":"whiteboard"},{"exts":["md"],"color":"#383838","color_dark":"#cbcbcb","icon":"markdown"},{"img":"/static/img/viewers/excalidraw.svg","exts":["excalidraw"]}]`, "explorer_category_image_query": "type=file&case_folding&use_or&name=*.bmp&name=*.iff&name=*.png&name=*.gif&name=*.jpg&name=*.jpeg&name=*.psd&name=*.svg&name=*.webp&name=*.heif&name=*.heic&name=*.tiff&name=*.avif&name=*.3fr&name=*.ari&name=*.arw&name=*.bay&name=*.braw&name=*.crw&name=*.cr2&name=*.cr3&name=*.cap&name=*.dcs&name=*.dcr&name=*.dng&name=*.drf&name=*.eip&name=*.erf&name=*.fff&name=*.gpr&name=*.iiq&name=*.k25&name=*.kdc&name=*.mdc&name=*.mef&name=*.mos&name=*.mrw&name=*.nef&name=*.nrw&name=*.obm&name=*.orf&name=*.pef&name=*.ptx&name=*.pxn&name=*.r3d&name=*.raf&name=*.raw&name=*.rwl&name=*.rw2&name=*.rwz&name=*.sr2&name=*.srf&name=*.srw&name=*.tif&name=*.x3f", "explorer_category_video_query": "type=file&case_folding&use_or&name=*.mp4&name=*.m3u8&name=*.flv&name=*.avi&name=*.wmv&name=*.mkv&name=*.rm&name=*.rmvb&name=*.mov&name=*.ogv", "explorer_category_audio_query": "type=file&case_folding&use_or&name=*.mp3&name=*.flac&name=*.ape&name=*.wav&name=*.acc&name=*.ogg&name=*.m4a", @@ -243,10 +491,24 @@ var DefaultSettings = map[string]string{ "map_provider": "openstreetmap", "map_google_tile_type": "regular", "mime_mapping": `{".xlsx":"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",".xltx":"application/vnd.openxmlformats-officedocument.spreadsheetml.template",".potx":"application/vnd.openxmlformats-officedocument.presentationml.template",".ppsx":"application/vnd.openxmlformats-officedocument.presentationml.slideshow",".pptx":"application/vnd.openxmlformats-officedocument.presentationml.presentation",".sldx":"application/vnd.openxmlformats-officedocument.presentationml.slide",".docx":"application/vnd.openxmlformats-officedocument.wordprocessingml.document",".dotx":"application/vnd.openxmlformats-officedocument.wordprocessingml.template",".xlam":"application/vnd.ms-excel.addin.macroEnabled.12",".xlsb":"application/vnd.ms-excel.sheet.binary.macroEnabled.12",".apk":"application/vnd.android.package-archive",".hqx":"application/mac-binhex40",".cpt":"application/mac-compactpro",".doc":"application/msword",".ogg":"application/ogg",".pdf":"application/pdf",".rtf":"text/rtf",".mif":"application/vnd.mif",".xls":"application/vnd.ms-excel",".ppt":"application/vnd.ms-powerpoint",".odc":"application/vnd.oasis.opendocument.chart",".odb":"application/vnd.oasis.opendocument.database",".odf":"application/vnd.oasis.opendocument.formula",".odg":"application/vnd.oasis.opendocument.graphics",".otg":"application/vnd.oasis.opendocument.graphics-template",".odi":"application/vnd.oasis.opendocument.image",".odp":"application/vnd.oasis.opendocument.presentation",".otp":"application/vnd.oasis.opendocument.presentation-template",".ods":"application/vnd.oasis.opendocument.spreadsheet",".ots":"application/vnd.oasis.opendocument.spreadsheet-template",".odt":"application/vnd.oasis.opendocument.text",".odm":"application/vnd.oasis.opendocument.text-master",".ott":"application/vnd.oasis.opendocument.text-template",".oth":"application/vnd.oasis.opendocument.text-web",".sxw":"application/vnd.sun.xml.writer",".stw":"application/vnd.sun.xml.writer.template",".sxc":"application/vnd.sun.xml.calc",".stc":"application/vnd.sun.xml.calc.template",".sxd":"application/vnd.sun.xml.draw",".std":"application/vnd.sun.xml.draw.template",".sxi":"application/vnd.sun.xml.impress",".sti":"application/vnd.sun.xml.impress.template",".sxg":"application/vnd.sun.xml.writer.global",".sxm":"application/vnd.sun.xml.math",".sis":"application/vnd.symbian.install",".wbxml":"application/vnd.wap.wbxml",".wmlc":"application/vnd.wap.wmlc",".wmlsc":"application/vnd.wap.wmlscriptc",".bcpio":"application/x-bcpio",".torrent":"application/x-bittorrent",".bz2":"application/x-bzip2",".vcd":"application/x-cdlink",".pgn":"application/x-chess-pgn",".cpio":"application/x-cpio",".csh":"application/x-csh",".dvi":"application/x-dvi",".spl":"application/x-futuresplash",".gtar":"application/x-gtar",".hdf":"application/x-hdf",".jar":"application/x-java-archive",".jnlp":"application/x-java-jnlp-file",".js":"application/x-javascript",".ksp":"application/x-kspread",".chrt":"application/x-kchart",".kil":"application/x-killustrator",".latex":"application/x-latex",".rpm":"application/x-rpm",".sh":"application/x-sh",".shar":"application/x-shar",".swf":"application/x-shockwave-flash",".sit":"application/x-stuffit",".sv4cpio":"application/x-sv4cpio",".sv4crc":"application/x-sv4crc",".tar":"application/x-tar",".tcl":"application/x-tcl",".tex":"application/x-tex",".man":"application/x-troff-man",".me":"application/x-troff-me",".ms":"application/x-troff-ms",".ustar":"application/x-ustar",".src":"application/x-wais-source",".zip":"application/zip",".m3u":"audio/x-mpegurl",".ra":"audio/x-pn-realaudio",".wav":"audio/x-wav",".wma":"audio/x-ms-wma",".wax":"audio/x-ms-wax",".pdb":"chemical/x-pdb",".xyz":"chemical/x-xyz",".bmp":"image/bmp",".gif":"image/gif",".ief":"image/ief",".png":"image/png",".wbmp":"image/vnd.wap.wbmp",".ras":"image/x-cmu-raster",".pnm":"image/x-portable-anymap",".pbm":"image/x-portable-bitmap",".pgm":"image/x-portable-graymap",".ppm":"image/x-portable-pixmap",".rgb":"image/x-rgb",".xbm":"image/x-xbitmap",".xpm":"image/x-xpixmap",".xwd":"image/x-xwindowdump",".css":"text/css",".rtx":"text/richtext",".tsv":"text/tab-separated-values",".jad":"text/vnd.sun.j2me.app-descriptor",".wml":"text/vnd.wap.wml",".wmls":"text/vnd.wap.wmlscript",".etx":"text/x-setext",".mxu":"video/vnd.mpegurl",".flv":"video/x-flv",".wm":"video/x-ms-wm",".wmv":"video/x-ms-wmv",".wmx":"video/x-ms-wmx",".wvx":"video/x-ms-wvx",".avi":"video/x-msvideo",".movie":"video/x-sgi-movie",".ice":"x-conference/x-cooltalk",".3gp":"video/3gpp",".ai":"application/postscript",".aif":"audio/x-aiff",".aifc":"audio/x-aiff",".aiff":"audio/x-aiff",".asc":"text/plain",".atom":"application/atom+xml",".au":"audio/basic",".bin":"application/octet-stream",".cdf":"application/x-netcdf",".cgm":"image/cgm",".class":"application/octet-stream",".dcr":"application/x-director",".dif":"video/x-dv",".dir":"application/x-director",".djv":"image/vnd.djvu",".djvu":"image/vnd.djvu",".dll":"application/octet-stream",".dmg":"application/octet-stream",".dms":"application/octet-stream",".dtd":"application/xml-dtd",".dv":"video/x-dv",".dxr":"application/x-director",".eps":"application/postscript",".exe":"application/octet-stream",".ez":"application/andrew-inset",".gram":"application/srgs",".grxml":"application/srgs+xml",".gz":"application/x-gzip",".htm":"text/html",".html":"text/html",".ico":"image/x-icon",".ics":"text/calendar",".ifb":"text/calendar",".iges":"model/iges",".igs":"model/iges",".jp2":"image/jp2",".jpe":"image/jpeg",".jpeg":"image/jpeg",".jpg":"image/jpeg",".kar":"audio/midi",".lha":"application/octet-stream",".lzh":"application/octet-stream",".m4a":"audio/mp4a-latm",".m4p":"audio/mp4a-latm",".m4u":"video/vnd.mpegurl",".m4v":"video/x-m4v",".mac":"image/x-macpaint",".mathml":"application/mathml+xml",".mesh":"model/mesh",".mid":"audio/midi",".midi":"audio/midi",".mov":"video/quicktime",".mp2":"audio/mpeg",".mp3":"audio/mpeg",".mp4":"video/mp4",".mpe":"video/mpeg",".mpeg":"video/mpeg",".mpg":"video/mpeg",".mpga":"audio/mpeg",".msh":"model/mesh",".nc":"application/x-netcdf",".oda":"application/oda",".ogv":"video/ogv",".pct":"image/pict",".pic":"image/pict",".pict":"image/pict",".pnt":"image/x-macpaint",".pntg":"image/x-macpaint",".ps":"application/postscript",".qt":"video/quicktime",".qti":"image/x-quicktime",".qtif":"image/x-quicktime",".ram":"audio/x-pn-realaudio",".rdf":"application/rdf+xml",".rm":"application/vnd.rn-realmedia",".roff":"application/x-troff",".sgm":"text/sgml",".sgml":"text/sgml",".silo":"model/mesh",".skd":"application/x-koan",".skm":"application/x-koan",".skp":"application/x-koan",".skt":"application/x-koan",".smi":"application/smil",".smil":"application/smil",".snd":"audio/basic",".so":"application/octet-stream",".svg":"image/svg+xml",".t":"application/x-troff",".texi":"application/x-texinfo",".texinfo":"application/x-texinfo",".tif":"image/tiff",".tiff":"image/tiff",".tr":"application/x-troff",".txt":"text/plain; charset=utf-8",".vrml":"model/vrml",".vxml":"application/voicexml+xml",".webm":"video/webm",".wrl":"model/vrml",".xht":"application/xhtml+xml",".xhtml":"application/xhtml+xml",".xml":"application/xml",".xsl":"application/xml",".xslt":"application/xslt+xml",".xul":"application/vnd.mozilla.xul+xml",".webp":"image/webp",".323":"text/h323",".aab":"application/x-authoware-bin",".aam":"application/x-authoware-map",".aas":"application/x-authoware-seg",".acx":"application/internet-property-stream",".als":"audio/X-Alpha5",".amc":"application/x-mpeg",".ani":"application/octet-stream",".asd":"application/astound",".asf":"video/x-ms-asf",".asn":"application/astound",".asp":"application/x-asap",".asr":"video/x-ms-asf",".asx":"video/x-ms-asf",".avb":"application/octet-stream",".awb":"audio/amr-wb",".axs":"application/olescript",".bas":"text/plain",".bin ":"application/octet-stream",".bld":"application/bld",".bld2":"application/bld2",".bpk":"application/octet-stream",".c":"text/plain",".cal":"image/x-cals",".cat":"application/vnd.ms-pkiseccat",".ccn":"application/x-cnc",".cco":"application/x-cocoa",".cer":"application/x-x509-ca-cert",".cgi":"magnus-internal/cgi",".chat":"application/x-chat",".clp":"application/x-msclip",".cmx":"image/x-cmx",".co":"application/x-cult3d-object",".cod":"image/cis-cod",".conf":"text/plain",".cpp":"text/plain",".crd":"application/x-mscardfile",".crl":"application/pkix-crl",".crt":"application/x-x509-ca-cert",".csm":"chemical/x-csml",".csml":"chemical/x-csml",".cur":"application/octet-stream",".dcm":"x-lml/x-evm",".dcx":"image/x-dcx",".der":"application/x-x509-ca-cert",".dhtml":"text/html",".dot":"application/msword",".dwf":"drawing/x-dwf",".dwg":"application/x-autocad",".dxf":"application/x-autocad",".ebk":"application/x-expandedbook",".emb":"chemical/x-embl-dl-nucleotide",".embl":"chemical/x-embl-dl-nucleotide",".epub":"application/epub+zip",".eri":"image/x-eri",".es":"audio/echospeech",".esl":"audio/echospeech",".etc":"application/x-earthtime",".evm":"x-lml/x-evm",".evy":"application/envoy",".fh4":"image/x-freehand",".fh5":"image/x-freehand",".fhc":"image/x-freehand",".fif":"application/fractals",".flr":"x-world/x-vrml",".fm":"application/x-maker",".fpx":"image/x-fpx",".fvi":"video/isivideo",".gau":"chemical/x-gaussian-input",".gca":"application/x-gca-compressed",".gdb":"x-lml/x-gdb",".gps":"application/x-gps",".h":"text/plain",".hdm":"text/x-hdml",".hdml":"text/x-hdml",".hlp":"application/winhlp",".hta":"application/hta",".htc":"text/x-component",".hts":"text/html",".htt":"text/webviewhtml",".ifm":"image/gif",".ifs":"image/ifs",".iii":"application/x-iphone",".imy":"audio/melody",".ins":"application/x-internet-signup",".ips":"application/x-ipscript",".ipx":"application/x-ipix",".isp":"application/x-internet-signup",".it":"audio/x-mod",".itz":"audio/x-mod",".ivr":"i-world/i-vrml",".j2k":"image/j2k",".jam":"application/x-jam",".java":"text/plain",".jfif":"image/pipeg",".jpz":"image/jpeg",".jwc":"application/jwc",".kjx":"application/x-kjx",".lak":"x-lml/x-lak",".lcc":"application/fastman",".lcl":"application/x-digitalloca",".lcr":"application/x-digitalloca",".lgh":"application/lgh",".lml":"x-lml/x-lml",".lmlpack":"x-lml/x-lmlpack",".log":"text/plain",".lsf":"video/x-la-asf",".lsx":"video/x-la-asf",".m13":"application/x-msmediaview",".m14":"application/x-msmediaview",".m15":"audio/x-mod",".m3url":"audio/x-mpegurl",".m4b":"audio/mp4a-latm",".ma1":"audio/ma1",".ma2":"audio/ma2",".ma3":"audio/ma3",".ma5":"audio/ma5",".map":"magnus-internal/imagemap",".mbd":"application/mbedlet",".mct":"application/x-mascot",".mdb":"application/x-msaccess",".mdz":"audio/x-mod",".mel":"text/x-vmel",".mht":"message/rfc822",".mhtml":"message/rfc822",".mi":"application/x-mif",".mil":"image/x-cals",".mio":"audio/x-mio",".mmf":"application/x-skt-lbs",".mng":"video/x-mng",".mny":"application/x-msmoney",".moc":"application/x-mocha",".mocha":"application/x-mocha",".mod":"audio/x-mod",".mof":"application/x-yumekara",".mol":"chemical/x-mdl-molfile",".mop":"chemical/x-mopac-input",".mpa":"video/mpeg",".mpc":"application/vnd.mpohun.certificate",".mpg4":"video/mp4",".mpn":"application/vnd.mophun.application",".mpp":"application/vnd.ms-project",".mps":"application/x-mapserver",".mpv2":"video/mpeg",".mrl":"text/x-mrml",".mrm":"application/x-mrm",".msg":"application/vnd.ms-outlook",".mts":"application/metastream",".mtx":"application/metastream",".mtz":"application/metastream",".mvb":"application/x-msmediaview",".mzv":"application/metastream",".nar":"application/zip",".nbmp":"image/nbmp",".ndb":"x-lml/x-ndb",".ndwn":"application/ndwn",".nif":"application/x-nif",".nmz":"application/x-scream",".nokia-op-logo":"image/vnd.nok-oplogo-color",".npx":"application/x-netfpx",".nsnd":"audio/nsnd",".nva":"application/x-neva1",".nws":"message/rfc822",".oom":"application/x-AtlasMate-Plugin",".p10":"application/pkcs10",".p12":"application/x-pkcs12",".p7b":"application/x-pkcs7-certificates",".p7c":"application/x-pkcs7-mime",".p7m":"application/x-pkcs7-mime",".p7r":"application/x-pkcs7-certreqresp",".p7s":"application/x-pkcs7-signature",".pac":"audio/x-pac",".pae":"audio/x-epac",".pan":"application/x-pan",".pcx":"image/x-pcx",".pda":"image/x-pda",".pfr":"application/font-tdpfr",".pfx":"application/x-pkcs12",".pko":"application/ynd.ms-pkipko",".pm":"application/x-perl",".pma":"application/x-perfmon",".pmc":"application/x-perfmon",".pmd":"application/x-pmd",".pml":"application/x-perfmon",".pmr":"application/x-perfmon",".pmw":"application/x-perfmon",".pnz":"image/png",".pot,":"application/vnd.ms-powerpoint",".pps":"application/vnd.ms-powerpoint",".pqf":"application/x-cprplayer",".pqi":"application/cprplayer",".prc":"application/x-prc",".prf":"application/pics-rules",".prop":"text/plain",".proxy":"application/x-ns-proxy-autoconfig",".ptlk":"application/listenup",".pub":"application/x-mspublisher",".pvx":"video/x-pv-pvx",".qcp":"audio/vnd.qcelp",".r3t":"text/vnd.rn-realtext3d",".rar":"application/octet-stream",".rc":"text/plain",".rf":"image/vnd.rn-realflash",".rlf":"application/x-richlink",".rmf":"audio/x-rmf",".rmi":"audio/mid",".rmm":"audio/x-pn-realaudio",".rmvb":"audio/x-pn-realaudio",".rnx":"application/vnd.rn-realplayer",".rp":"image/vnd.rn-realpix",".rt":"text/vnd.rn-realtext",".rte":"x-lml/x-gps",".rtg":"application/metastream",".rv":"video/vnd.rn-realvideo",".rwc":"application/x-rogerwilco",".s3m":"audio/x-mod",".s3z":"audio/x-mod",".sca":"application/x-supercard",".scd":"application/x-msschedule",".sct":"text/scriptlet",".sdf":"application/e-score",".sea":"application/x-stuffit",".setpay":"application/set-payment_old-initiation",".setreg":"application/set-registration-initiation",".shtml":"text/html",".shtm":"text/html",".shw":"application/presentations",".si6":"image/si6",".si7":"image/vnd.stiwap.sis",".si9":"image/vnd.lgtwap.sis",".slc":"application/x-salsa",".smd":"audio/x-smd",".smp":"application/studiom",".smz":"audio/x-smd",".spc":"application/x-pkcs7-certificates",".spr":"application/x-sprite",".sprite":"application/x-sprite",".sdp":"application/sdp",".spt":"application/x-spt",".sst":"application/vnd.ms-pkicertstore",".stk":"application/hyperstudio",".stl":"application/vnd.ms-pkistl",".stm":"text/html",".svf":"image/vnd",".svh":"image/svh",".svr":"x-world/x-svr",".swfl":"application/x-shockwave-flash",".tad":"application/octet-stream",".talk":"text/x-speech",".taz":"application/x-tar",".tbp":"application/x-timbuktu",".tbt":"application/x-timbuktu",".tgz":"application/x-compressed",".thm":"application/vnd.eri.thm",".tki":"application/x-tkined",".tkined":"application/x-tkined",".toc":"application/toc",".toy":"image/toy",".trk":"x-lml/x-gps",".trm":"application/x-msterminal",".tsi":"audio/tsplayer",".tsp":"application/dsptype",".ttf":"application/octet-stream",".ttz":"application/t-time",".uls":"text/iuls",".ult":"audio/x-mod",".uu":"application/x-uuencode",".uue":"application/x-uuencode",".vcf":"text/x-vcard",".vdo":"video/vdo",".vib":"audio/vib",".viv":"video/vivo",".vivo":"video/vivo",".vmd":"application/vocaltec-media-desc",".vmf":"application/vocaltec-media-file",".vmi":"application/x-dreamcast-vms-info",".vms":"application/x-dreamcast-vms",".vox":"audio/voxware",".vqe":"audio/x-twinvq-plugin",".vqf":"audio/x-twinvq",".vql":"audio/x-twinvq",".vre":"x-world/x-vream",".vrt":"x-world/x-vrt",".vrw":"x-world/x-vream",".vts":"workbook/formulaone",".wcm":"application/vnd.ms-works",".wdb":"application/vnd.ms-works",".web":"application/vnd.xara",".wi":"image/wavelet",".wis":"application/x-InstallShield",".wks":"application/vnd.ms-works",".wmd":"application/x-ms-wmd",".wmf":"application/x-msmetafile",".wmlscript":"text/vnd.wap.wmlscript",".wmz":"application/x-ms-wmz",".wpng":"image/x-up-wpng",".wps":"application/vnd.ms-works",".wpt":"x-lml/x-gps",".wri":"application/x-mswrite",".wrz":"x-world/x-vrml",".ws":"text/vnd.wap.wmlscript",".wsc":"application/vnd.wap.wmlscriptc",".wv":"video/wavelet",".wxl":"application/x-wxl",".x-gzip":"application/x-gzip",".xaf":"x-world/x-vrml",".xar":"application/vnd.xara",".xdm":"application/x-xdma",".xdma":"application/x-xdma",".xdw":"application/vnd.fujixerox.docuworks",".xhtm":"application/xhtml+xml",".xla":"application/vnd.ms-excel",".xlc":"application/vnd.ms-excel",".xll":"application/x-excel",".xlm":"application/vnd.ms-excel",".xlt":"application/vnd.ms-excel",".xlw":"application/vnd.ms-excel",".xm":"audio/x-mod",".xmz":"audio/x-mod",".xof":"x-world/x-vrml",".xpi":"application/x-xpinstall",".xsit":"text/xml",".yz1":"application/x-yz1",".z":"application/x-compress",".zac":"application/x-zaurus-zac",".json":"application/json"}`, - "file_viewers": `[{"viewers":[{"id":"music","type":"builtin","action":"view","display_name":"fileManager.musicPlayer","exts":["mp3","ogg","wav","flac","m4a"]},{"id":"epub","type":"builtin","action":"view","display_name":"fileManager.epubViewer","exts":["epub"]},{"id":"googledocs","type":"custom","action":"view","display_name":"fileManager.googledocs","icon":"/static/img/viewers/gdrive.png","url":"https://docs.google.com/gview?url={$src}&embedded=true","exts":["jpeg","png","gif","tiff","bmp","webm","mpeg4","3gpp","mov","avi","mpegps","wmv","flv","txt","css","html","php","c","cpp","h","hpp","js","doc","docx","xls","xlsx","ppt","pptx","pdf","pages","ai","psd","tiff","dxf","svg","eps","ps","ttf","xps"],"max_size":26214400},{"id":"m365online","type":"custom","action":"view","display_name":"fileManager.m365viewer","icon":"/static/img/viewers/m365.svg","url":"https://view.officeapps.live.com/op/view.aspx?src={$src}","exts":["doc","docx","docm","dotm","dotx","xlsx","xlsb","xls","xlsm","pptx","ppsx","ppt","pps","pptm","potm","ppam","potx","ppsm"],"max_size":10485760},{"id":"pdf","type":"builtin","action":"view","display_name":"fileManager.pdfViewer","exts":["pdf"]},{"id":"video","type":"builtin","action":"view","icon":"/static/img/viewers/artplayer.png","display_name":"Artplayer","exts":["mp4","mkv","webm","avi","m3u8","mov","flv"]},{"id":"markdown","type":"builtin","action":"edit","display_name":"fileManager.markdownEditor","exts":["md"],"templates":[{"ext":"md","display_name":"Markdown"}]},{"id":"drawio","type":"builtin","action":"edit","icon":"/static/img/viewers/drawio.svg","display_name":"draw.io","exts":["drawio","dwb"],"props":{"host":"https://embed.diagrams.net"},"templates":[{"ext":"drawio","display_name":"fileManager.diagram"},{"ext":"dwb","display_name":"fileManager.whiteboard"}]},{"id":"image","type":"builtin","action":"edit","display_name":"fileManager.imageViewer","exts":["bmp","png","gif","jpg","jpeg","svg","webp","heic","heif"]},{"id":"monaco","type":"builtin","action":"edit","icon":"/static/img/viewers/monaco.svg","display_name":"fileManager.monacoEditor","exts":["md","txt","json","php","py","bat","c","h","cpp","hpp","cs","css","dockerfile","go","html","htm","ini","java","js","jsx","less","lua","sh","sql","xml","yaml"],"templates":[{"ext":"txt","display_name":"fileManager.text"}]},{"id":"photopea","type":"builtin","icon":"/static/img/viewers/photopea.png","action":"edit","display_name":"Photopea","exts":["psd","ai","indd","xcf","xd","fig","kri","clip","pxd","pxz","cdr","ufo","afphoyo","svg","esp","pdf","pdn","wmf","emf","png","jpg","jpeg","gif","webp","ico","icns","bmp","avif","heic","jxl","ppm","pgm","pbm","tiff","dds","iff","anim","tga","dng","nef","cr2","cr3","arw","rw2","raf","orf","gpr","3fr","fff"]},{"id":"excalidraw","type":"builtin","action":"view","icon":"/static/img/viewers/excalidraw.svg","display_name":"Excalidraw","exts":["excalidraw"],"templates":[{"ext":"excalidraw","display_name":"Excalidraw"}]}]}]`, "logto_enabled": "0", "logto_config": `{"direct_sign_in":true,"display_name":"vas.sso"}`, "qq_login": `0`, "qq_login_config": `{"direct_sign_in":false}`, "license": "", } + +func init() { + explorerIcons, err := json.Marshal(defaultIcons) + if err != nil { + panic(err) + } + DefaultSettings["explorer_icons"] = string(explorerIcons) + + viewers, err := json.Marshal(defaultFileViewers) + if err != nil { + panic(err) + } + + DefaultSettings["file_viewers"] = string(viewers) +} diff --git a/inventory/types/types.go b/inventory/types/types.go index 3cadc33..2e6b9df 100644 --- a/inventory/types/types.go +++ b/inventory/types/types.go @@ -178,6 +178,14 @@ type ( // Whether to share view setting from owner ShareView bool `json:"share_view,omitempty"` } + + FileTypeIconSetting struct { + Exts []string `json:"exts"` + Icon string `json:"icon,omitempty"` + Color string `json:"color,omitempty"` + ColorDark string `json:"color_dark,omitempty"` + Img string `json:"img,omitempty"` + } ) const ( @@ -250,3 +258,40 @@ const ( DownloaderProviderAria2 = DownloaderProvider("aria2") DownloaderProviderQBittorrent = DownloaderProvider("qbittorrent") ) + +type ( + ViewerAction string + ViewerType string +) + +const ( + ViewerActionView = "view" + ViewerActionEdit = "edit" + + ViewerTypeBuiltin = "builtin" + ViewerTypeWopi = "wopi" + ViewerTypeCustom = "custom" +) + +type Viewer struct { + ID string `json:"id"` + Type ViewerType `json:"type"` + DisplayName string `json:"display_name"` + Exts []string `json:"exts"` + Url string `json:"url,omitempty"` + Icon string `json:"icon,omitempty"` + WopiActions map[string]map[ViewerAction]string `json:"wopi_actions,omitempty"` + Props map[string]string `json:"props,omitempty"` + MaxSize int64 `json:"max_size,omitempty"` + Disabled bool `json:"disabled,omitempty"` + Templates []NewFileTemplate `json:"templates,omitempty"` +} + +type ViewerGroup struct { + Viewers []Viewer `json:"viewers"` +} + +type NewFileTemplate struct { + Ext string `json:"ext"` + DisplayName string `json:"display_name"` +} diff --git a/middleware/wopi.go b/middleware/wopi.go index 0d46866..8e498e1 100644 --- a/middleware/wopi.go +++ b/middleware/wopi.go @@ -2,9 +2,9 @@ package middleware import ( "github.com/cloudreve/Cloudreve/v4/application/dependency" + "github.com/cloudreve/Cloudreve/v4/inventory/types" "github.com/cloudreve/Cloudreve/v4/pkg/filemanager/manager" "github.com/cloudreve/Cloudreve/v4/pkg/hashid" - "github.com/cloudreve/Cloudreve/v4/pkg/setting" "github.com/cloudreve/Cloudreve/v4/pkg/util" "github.com/cloudreve/Cloudreve/v4/pkg/wopi" "github.com/gin-gonic/gin" @@ -67,7 +67,7 @@ func ViewerSessionValidation() gin.HandlerFunc { // Check if the viewer is still available viewers := settings.FileViewers(c) - var v *setting.Viewer + var v *types.Viewer for _, group := range viewers { for _, viewer := range group.Viewers { if viewer.ID == session.ViewerID && !viewer.Disabled { diff --git a/pkg/filemanager/manager/manager.go b/pkg/filemanager/manager/manager.go index 5dcda16..6cce600 100644 --- a/pkg/filemanager/manager/manager.go +++ b/pkg/filemanager/manager/manager.go @@ -54,7 +54,7 @@ type ( // UpsertMedata update or insert metadata of given file PatchMedata(ctx context.Context, path []*fs.URI, data ...fs.MetadataPatch) error // CreateViewerSession creates a viewer session for given file - CreateViewerSession(ctx context.Context, uri *fs.URI, version string, viewer *setting.Viewer) (*ViewerSession, error) + CreateViewerSession(ctx context.Context, uri *fs.URI, version string, viewer *types.Viewer) (*ViewerSession, error) // TraverseFile traverses a file to its root file, return the file with linked root. TraverseFile(ctx context.Context, fileID int) (fs.File, error) } diff --git a/pkg/filemanager/manager/viewer.go b/pkg/filemanager/manager/viewer.go index 819ef31..30fd8a6 100644 --- a/pkg/filemanager/manager/viewer.go +++ b/pkg/filemanager/manager/viewer.go @@ -9,7 +9,6 @@ import ( "github.com/cloudreve/Cloudreve/v4/inventory/types" "github.com/cloudreve/Cloudreve/v4/pkg/filemanager/fs" "github.com/cloudreve/Cloudreve/v4/pkg/filemanager/fs/dbfs" - "github.com/cloudreve/Cloudreve/v4/pkg/setting" "github.com/cloudreve/Cloudreve/v4/pkg/util" "github.com/gofrs/uuid" ) @@ -44,7 +43,7 @@ func init() { gob.Register(ViewerSessionCache{}) } -func (m *manager) CreateViewerSession(ctx context.Context, uri *fs.URI, version string, viewer *setting.Viewer) (*ViewerSession, error) { +func (m *manager) CreateViewerSession(ctx context.Context, uri *fs.URI, version string, viewer *types.Viewer) (*ViewerSession, error) { file, err := m.fs.Get(ctx, uri, dbfs.WithFileEntities(), dbfs.WithNotRoot()) if err != nil { return nil, err @@ -88,6 +87,6 @@ func ViewerSessionFromContext(ctx context.Context) *ViewerSessionCache { return ctx.Value(ViewerSessionCacheCtx{}).(*ViewerSessionCache) } -func ViewerFromContext(ctx context.Context) *setting.Viewer { - return ctx.Value(ViewerCtx{}).(*setting.Viewer) +func ViewerFromContext(ctx context.Context) *types.Viewer { + return ctx.Value(ViewerCtx{}).(*types.Viewer) } diff --git a/pkg/setting/provider.go b/pkg/setting/provider.go index 2b37922..b3005e3 100644 --- a/pkg/setting/provider.go +++ b/pkg/setting/provider.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "fmt" + "github.com/cloudreve/Cloudreve/v4/inventory/types" "net/url" "strconv" "strings" @@ -169,7 +170,7 @@ type ( // FolderPropsCacheTTL returns the cache TTL of folder summary. FolderPropsCacheTTL(ctx context.Context) int // FileViewers returns the file viewers settings. - FileViewers(ctx context.Context) []ViewerGroup + FileViewers(ctx context.Context) []types.ViewerGroup // ViewerSessionTTL returns the TTL of viewer session. ViewerSessionTTL(ctx context.Context) int // MimeMapping returns the extension to MIME mapping settings. @@ -232,11 +233,11 @@ func (s *settingProvider) Avatar(ctx context.Context) *Avatar { } } -func (s *settingProvider) FileViewers(ctx context.Context) []ViewerGroup { +func (s *settingProvider) FileViewers(ctx context.Context) []types.ViewerGroup { raw := s.getString(ctx, "file_viewers", "[]") - var viewers []ViewerGroup + var viewers []types.ViewerGroup if err := json.Unmarshal([]byte(raw), &viewers); err != nil { - return []ViewerGroup{} + return []types.ViewerGroup{} } return viewers diff --git a/pkg/setting/types.go b/pkg/setting/types.go index d3e4d5e..a31337a 100644 --- a/pkg/setting/types.go +++ b/pkg/setting/types.go @@ -176,42 +176,6 @@ type MapSetting struct { // Viewer related -type ( - ViewerAction string - ViewerType string -) - -const ( - ViewerActionView = "view" - ViewerActionEdit = "edit" - - ViewerTypeBuiltin = "builtin" - ViewerTypeWopi = "wopi" -) - -type Viewer struct { - ID string `json:"id"` - Type ViewerType `json:"type"` - DisplayName string `json:"display_name"` - Exts []string `json:"exts"` - Url string `json:"url,omitempty"` - Icon string `json:"icon,omitempty"` - WopiActions map[string]map[ViewerAction]string `json:"wopi_actions,omitempty"` - Props map[string]string `json:"props,omitempty"` - MaxSize int64 `json:"max_size,omitempty"` - Disabled bool `json:"disabled,omitempty"` - Templates []NewFileTemplate `json:"templates,omitempty"` -} - -type ViewerGroup struct { - Viewers []Viewer `json:"viewers"` -} - -type NewFileTemplate struct { - Ext string `json:"ext"` - DisplayName string `json:"display_name"` -} - type ( SearchCategory string ) diff --git a/pkg/wopi/discovery.go b/pkg/wopi/discovery.go index 6411730..42918c8 100644 --- a/pkg/wopi/discovery.go +++ b/pkg/wopi/discovery.go @@ -3,7 +3,7 @@ package wopi import ( "encoding/xml" "fmt" - "github.com/cloudreve/Cloudreve/v4/pkg/setting" + "github.com/cloudreve/Cloudreve/v4/inventory/types" "github.com/gofrs/uuid" "github.com/samber/lo" ) @@ -16,23 +16,23 @@ var ( ActionEdit = ActonType("edit") ) -func DiscoveryXmlToViewerGroup(xmlStr string) (*setting.ViewerGroup, error) { +func DiscoveryXmlToViewerGroup(xmlStr string) (*types.ViewerGroup, error) { var discovery WopiDiscovery if err := xml.Unmarshal([]byte(xmlStr), &discovery); err != nil { return nil, fmt.Errorf("failed to parse WOPI discovery XML: %w", err) } - group := &setting.ViewerGroup{ - Viewers: make([]setting.Viewer, 0, len(discovery.NetZone.App)), + group := &types.ViewerGroup{ + Viewers: make([]types.Viewer, 0, len(discovery.NetZone.App)), } for _, app := range discovery.NetZone.App { - viewer := setting.Viewer{ + viewer := types.Viewer{ ID: uuid.Must(uuid.NewV4()).String(), DisplayName: app.Name, - Type: setting.ViewerTypeWopi, + Type: types.ViewerTypeWopi, Icon: app.FavIconUrl, - WopiActions: make(map[string]map[setting.ViewerAction]string), + WopiActions: make(map[string]map[types.ViewerAction]string), } for _, action := range app.Action { @@ -41,21 +41,21 @@ func DiscoveryXmlToViewerGroup(xmlStr string) (*setting.ViewerGroup, error) { } if _, ok := viewer.WopiActions[action.Ext]; !ok { - viewer.WopiActions[action.Ext] = make(map[setting.ViewerAction]string) + viewer.WopiActions[action.Ext] = make(map[types.ViewerAction]string) } if action.Name == string(ActionPreview) { - viewer.WopiActions[action.Ext][setting.ViewerActionView] = action.Urlsrc + viewer.WopiActions[action.Ext][types.ViewerActionView] = action.Urlsrc } else if action.Name == string(ActionPreviewFallback) { - viewer.WopiActions[action.Ext][setting.ViewerActionView] = action.Urlsrc + viewer.WopiActions[action.Ext][types.ViewerActionView] = action.Urlsrc } else if action.Name == string(ActionEdit) { - viewer.WopiActions[action.Ext][setting.ViewerActionEdit] = action.Urlsrc + viewer.WopiActions[action.Ext][types.ViewerActionEdit] = action.Urlsrc } else if len(viewer.WopiActions[action.Ext]) == 0 { delete(viewer.WopiActions, action.Ext) } } - viewer.Exts = lo.MapToSlice(viewer.WopiActions, func(key string, value map[setting.ViewerAction]string) string { + viewer.Exts = lo.MapToSlice(viewer.WopiActions, func(key string, value map[types.ViewerAction]string) string { return key }) diff --git a/pkg/wopi/wopi.go b/pkg/wopi/wopi.go index c158987..9664afb 100644 --- a/pkg/wopi/wopi.go +++ b/pkg/wopi/wopi.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "github.com/cloudreve/Cloudreve/v4/inventory/types" "net/url" "strings" "time" @@ -56,7 +57,7 @@ const ( LockDuration = time.Duration(30) * time.Minute ) -func GenerateWopiSrc(ctx context.Context, action setting.ViewerAction, viewer *setting.Viewer, viewerSession *manager.ViewerSession) (*url.URL, error) { +func GenerateWopiSrc(ctx context.Context, action types.ViewerAction, viewer *types.Viewer, viewerSession *manager.ViewerSession) (*url.URL, error) { dep := dependency.FromContext(ctx) base := dep.SettingProvider().SiteURL(setting.UseFirstSiteUrl(ctx)) hasher := dep.HashIDEncoder() @@ -69,7 +70,7 @@ func GenerateWopiSrc(ctx context.Context, action setting.ViewerAction, viewer *s var ( src string ) - fallbackOrder := []setting.ViewerAction{action, setting.ViewerActionView, setting.ViewerActionEdit} + fallbackOrder := []types.ViewerAction{action, types.ViewerActionView, types.ViewerActionEdit} for _, a := range fallbackOrder { if src, ok = availableActions[a]; ok { break diff --git a/service/admin/tools.go b/service/admin/tools.go index 20dc90f..9409b94 100644 --- a/service/admin/tools.go +++ b/service/admin/tools.go @@ -12,6 +12,7 @@ import ( "github.com/cloudreve/Cloudreve/v4/pkg/serializer" "github.com/cloudreve/Cloudreve/v4/pkg/setting" "github.com/cloudreve/Cloudreve/v4/pkg/wopi" + "github.com/cloudreve/Cloudreve/v4/inventory/types" "github.com/gin-gonic/gin" "github.com/go-mail/mail" ) @@ -107,7 +108,7 @@ type ( FetchWOPIDiscoveryParamCtx struct{} ) -func (s *FetchWOPIDiscoveryService) Fetch(c *gin.Context) (*setting.ViewerGroup, error) { +func (s *FetchWOPIDiscoveryService) Fetch(c *gin.Context) (*types.ViewerGroup, error) { dep := dependency.FromContext(c) requestClient := dep.RequestClient(request2.WithContext(c), request2.WithLogger(dep.Logger())) content, err := requestClient.Request("GET", s.Endpoint, nil).CheckHTTPResponse(http.StatusOK).GetResponse() diff --git a/service/basic/site.go b/service/basic/site.go index d291a25..7a54d15 100644 --- a/service/basic/site.go +++ b/service/basic/site.go @@ -3,6 +3,7 @@ package basic import ( "github.com/cloudreve/Cloudreve/v4/application/dependency" "github.com/cloudreve/Cloudreve/v4/inventory" + "github.com/cloudreve/Cloudreve/v4/inventory/types" "github.com/cloudreve/Cloudreve/v4/pkg/setting" "github.com/cloudreve/Cloudreve/v4/service/user" "github.com/gin-gonic/gin" @@ -39,7 +40,7 @@ type SiteConfig struct { EmojiPreset string `json:"emoji_preset,omitempty"` MapProvider setting.MapProvider `json:"map_provider,omitempty"` GoogleMapTileType setting.MapGoogleTileType `json:"google_map_tile_type,omitempty"` - FileViewers []setting.ViewerGroup `json:"file_viewers,omitempty"` + FileViewers []types.ViewerGroup `json:"file_viewers,omitempty"` MaxBatchSize int `json:"max_batch_size,omitempty"` ThumbnailWidth int `json:"thumbnail_width,omitempty"` ThumbnailHeight int `json:"thumbnail_height,omitempty"` diff --git a/service/explorer/viewer.go b/service/explorer/viewer.go index d8ebc88..f8fd01b 100644 --- a/service/explorer/viewer.go +++ b/service/explorer/viewer.go @@ -21,7 +21,6 @@ import ( "github.com/cloudreve/Cloudreve/v4/pkg/filemanager/manager/entitysource" "github.com/cloudreve/Cloudreve/v4/pkg/hashid" "github.com/cloudreve/Cloudreve/v4/pkg/serializer" - "github.com/cloudreve/Cloudreve/v4/pkg/setting" "github.com/cloudreve/Cloudreve/v4/pkg/wopi" "github.com/gin-gonic/gin" ) @@ -371,7 +370,7 @@ type ( Uri string `json:"uri" form:"uri" binding:"required"` Version string `json:"version" form:"version"` ViewerID string `json:"viewer_id" form:"viewer_id" binding:"required"` - PreferredAction setting.ViewerAction `json:"preferred_action" form:"preferred_action" binding:"required"` + PreferredAction types.ViewerAction `json:"preferred_action" form:"preferred_action" binding:"required"` } CreateViewerSessionParamCtx struct{} ) @@ -389,7 +388,7 @@ func (s *CreateViewerSessionService) Create(c *gin.Context) (*ViewerSessionRespo // Find the given viewer viewers := dep.SettingProvider().FileViewers(c) - var targetViewer *setting.Viewer + var targetViewer *types.Viewer for _, group := range viewers { for _, viewer := range group.Viewers { if viewer.ID == s.ViewerID && !viewer.Disabled { @@ -413,7 +412,7 @@ func (s *CreateViewerSessionService) Create(c *gin.Context) (*ViewerSessionRespo } res := &ViewerSessionResponse{Session: viewerSession} - if targetViewer.Type == setting.ViewerTypeWopi { + if targetViewer.Type == types.ViewerTypeWopi { // For WOPI viewer, generate WOPI src wopiSrc, err := wopi.GenerateWopiSrc(c, s.PreferredAction, targetViewer, viewerSession) if err != nil {