github.com/cozy/cozy-stack@v0.0.0-20240603063001-31110fa4cae1/pkg/assets/asset.go (about) 1 package assets 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "errors" 7 "fmt" 8 "io" 9 "os" 10 "time" 11 12 "github.com/cozy/cozy-stack/pkg/assets/dynamic" 13 "github.com/cozy/cozy-stack/pkg/assets/model" 14 "github.com/cozy/cozy-stack/pkg/assets/statik" 15 "github.com/cozy/cozy-stack/pkg/config/config" 16 "github.com/cozy/cozy-stack/pkg/i18n" 17 "github.com/cozy/cozy-stack/pkg/logger" 18 ) 19 20 // Get looks for an asset. It tries in this order: 21 // 1. A dynamic asset for the given context 22 // 2. A dynamic asset for the default context 23 // 3. A static asset. 24 func Get(name, context string) (*model.Asset, bool) { 25 if context == "" { 26 context = config.DefaultInstanceContext 27 } 28 29 // Check if a dynamic asset is existing 30 dynAsset, err := dynamic.GetAsset(context, name) 31 if err == nil { 32 return dynAsset, true 33 } 34 if !errors.Is(err, dynamic.ErrDynAssetNotFound) { 35 logger.WithNamespace("asset").Errorf("Error while retreiving dynamic asset: %s", err) 36 } 37 38 if context != config.DefaultInstanceContext { 39 dynAsset, err = dynamic.GetAsset(config.DefaultInstanceContext, name) 40 if err == nil { 41 return dynAsset, true 42 } 43 } 44 45 // If asset was not found, try to retrieve it from static assets 46 asset := statik.GetAsset(name) 47 if asset == nil { 48 return nil, false 49 } 50 return asset, true 51 } 52 53 // Head does the same job as Get, but the returned model.Asset can have no body 54 // data. It allows to use a cache for it. 55 func Head(name, context string) (*model.Asset, bool) { 56 if context == "" { 57 context = config.DefaultInstanceContext 58 } 59 key := fmt.Sprintf("dyn-assets:%s/%s", context, name) 60 cache := config.GetConfig().CacheStorage 61 if data, ok := cache.Get(key); ok { 62 asset := &model.Asset{} 63 if err := json.Unmarshal(data, asset); err == nil { 64 return asset, true 65 } 66 } 67 asset, ok := Get(name, context) 68 if !ok { 69 return nil, false 70 } 71 if data, err := json.Marshal(asset); err == nil { 72 cache.Set(key, data, 24*time.Hour) 73 } 74 return asset, true 75 } 76 77 // Add adds dynamic assets 78 func Add(options []model.AssetOption) error { 79 err := dynamic.RegisterCustomExternals(options, 0) 80 if err == nil { 81 cache := config.GetConfig().CacheStorage 82 for _, opt := range options { 83 key := fmt.Sprintf("dyn-assets:%s/%s", opt.Context, opt.Name) 84 cache.Clear(key) 85 } 86 } 87 return err 88 } 89 90 // Remove removes an asset 91 // Note: Only dynamic assets can be removed 92 func Remove(name, context string) error { 93 err := dynamic.RemoveAsset(context, name) 94 if err == nil { 95 key := fmt.Sprintf("dyn-assets:%s/%s", context, name) 96 cache := config.GetConfig().CacheStorage 97 cache.Clear(key) 98 } 99 return err 100 } 101 102 // List returns a map containing all the existing assets (statik & dynamic) 103 // Each map key represents the context 104 func List() (map[string][]*model.Asset, error) { 105 assetsMap := make(map[string][]*model.Asset) 106 107 defctx := config.DefaultInstanceContext 108 109 // Get dynamic assets 110 dynAssets, err := dynamic.ListAssets() 111 if err != nil { 112 return nil, err 113 } 114 for ctx, assets := range dynAssets { 115 assetsMap[ctx] = append(assetsMap[ctx], assets...) 116 } 117 118 // Get statik assets 119 statik.Foreach(func(name string, f *model.Asset) { 120 for _, asset := range assetsMap[defctx] { 121 if asset.Name == f.Name { 122 return 123 } 124 } 125 assetsMap[defctx] = append(assetsMap[defctx], f) 126 }) 127 128 return assetsMap, nil 129 } 130 131 // Open returns a bytes.Reader for an asset in the given context, or the 132 // default context if no context is given. 133 func Open(name string, context string) (*bytes.Reader, error) { 134 f, ok := Get(name, context) 135 if ok { 136 return f.Reader(), nil 137 } 138 return nil, os.ErrNotExist 139 } 140 141 // LoadContextualizedLocale loads the translations dictionary from dynamic 142 // assets for the given locale and context. 143 func LoadContextualizedLocale(context, locale string) { 144 name := "/locales/" + locale + ".po" 145 asset, ok := Head(name, context) 146 if !ok || asset.Context != context { 147 return 148 } 149 f, err := Open(name, context) 150 if err != nil { 151 return 152 } 153 buf, err := io.ReadAll(f) 154 if err == nil { 155 i18n.LoadLocale(locale, context, buf) 156 } 157 }