diff --git a/cmd/command/main.go b/cmd/command/main.go index 323d500..6ef8673 100644 --- a/cmd/command/main.go +++ b/cmd/command/main.go @@ -1,6 +1,7 @@ package main import ( + "context" "flag" "fmt" "log" @@ -71,7 +72,7 @@ func main() { r.Mount("/lib", libH) - l.ScanAsync() + l.ScanAsync(context.Background()) slog.Info("Starting agent") diff --git a/cmd/desktop/main.go b/cmd/desktop/main.go index 3eb16b3..9ce6661 100644 --- a/cmd/desktop/main.go +++ b/cmd/desktop/main.go @@ -1,6 +1,7 @@ package main import ( + "context" "embed" "flag" "log" @@ -70,7 +71,7 @@ func main() { r.Mount("/lib", libH) - l.ScanAsync() + l.ScanAsync(context.Background()) slog.Info("Starting agent") diff --git a/frontend/public/index.css b/frontend/public/index.css index f57874d..9615ea6 100644 --- a/frontend/public/index.css +++ b/frontend/public/index.css @@ -1,5 +1,113 @@ +*, ::before, ::after { + --tw-border-spacing-x: 0; + --tw-border-spacing-y: 0; + --tw-translate-x: 0; + --tw-translate-y: 0; + --tw-rotate: 0; + --tw-skew-x: 0; + --tw-skew-y: 0; + --tw-scale-x: 1; + --tw-scale-y: 1; + --tw-pan-x: ; + --tw-pan-y: ; + --tw-pinch-zoom: ; + --tw-scroll-snap-strictness: proximity; + --tw-gradient-from-position: ; + --tw-gradient-via-position: ; + --tw-gradient-to-position: ; + --tw-ordinal: ; + --tw-slashed-zero: ; + --tw-numeric-figure: ; + --tw-numeric-spacing: ; + --tw-numeric-fraction: ; + --tw-ring-inset: ; + --tw-ring-offset-width: 0px; + --tw-ring-offset-color: #fff; + --tw-ring-color: rgb(59 130 246 / 0.5); + --tw-ring-offset-shadow: 0 0 #0000; + --tw-ring-shadow: 0 0 #0000; + --tw-shadow: 0 0 #0000; + --tw-shadow-colored: 0 0 #0000; + --tw-blur: ; + --tw-brightness: ; + --tw-contrast: ; + --tw-grayscale: ; + --tw-hue-rotate: ; + --tw-invert: ; + --tw-saturate: ; + --tw-sepia: ; + --tw-drop-shadow: ; + --tw-backdrop-blur: ; + --tw-backdrop-brightness: ; + --tw-backdrop-contrast: ; + --tw-backdrop-grayscale: ; + --tw-backdrop-hue-rotate: ; + --tw-backdrop-invert: ; + --tw-backdrop-opacity: ; + --tw-backdrop-saturate: ; + --tw-backdrop-sepia: ; + --tw-contain-size: ; + --tw-contain-layout: ; + --tw-contain-paint: ; + --tw-contain-style: ; +} + +::backdrop { + --tw-border-spacing-x: 0; + --tw-border-spacing-y: 0; + --tw-translate-x: 0; + --tw-translate-y: 0; + --tw-rotate: 0; + --tw-skew-x: 0; + --tw-skew-y: 0; + --tw-scale-x: 1; + --tw-scale-y: 1; + --tw-pan-x: ; + --tw-pan-y: ; + --tw-pinch-zoom: ; + --tw-scroll-snap-strictness: proximity; + --tw-gradient-from-position: ; + --tw-gradient-via-position: ; + --tw-gradient-to-position: ; + --tw-ordinal: ; + --tw-slashed-zero: ; + --tw-numeric-figure: ; + --tw-numeric-spacing: ; + --tw-numeric-fraction: ; + --tw-ring-inset: ; + --tw-ring-offset-width: 0px; + --tw-ring-offset-color: #fff; + --tw-ring-color: rgb(59 130 246 / 0.5); + --tw-ring-offset-shadow: 0 0 #0000; + --tw-ring-shadow: 0 0 #0000; + --tw-shadow: 0 0 #0000; + --tw-shadow-colored: 0 0 #0000; + --tw-blur: ; + --tw-brightness: ; + --tw-contrast: ; + --tw-grayscale: ; + --tw-hue-rotate: ; + --tw-invert: ; + --tw-saturate: ; + --tw-sepia: ; + --tw-drop-shadow: ; + --tw-backdrop-blur: ; + --tw-backdrop-brightness: ; + --tw-backdrop-contrast: ; + --tw-backdrop-grayscale: ; + --tw-backdrop-hue-rotate: ; + --tw-backdrop-invert: ; + --tw-backdrop-opacity: ; + --tw-backdrop-saturate: ; + --tw-backdrop-sepia: ; + --tw-contain-size: ; + --tw-contain-layout: ; + --tw-contain-paint: ; + --tw-contain-style: ; +} + /* -! tailwindcss v3.4.10 | MIT License | https://tailwindcss.com +! tailwindcss v3.4.12 | MIT License | https://tailwindcss.com */ /* @@ -650,114 +758,6 @@ html { --bc: 74.6477% 0.0216 264.435964; } -*, ::before, ::after { - --tw-border-spacing-x: 0; - --tw-border-spacing-y: 0; - --tw-translate-x: 0; - --tw-translate-y: 0; - --tw-rotate: 0; - --tw-skew-x: 0; - --tw-skew-y: 0; - --tw-scale-x: 1; - --tw-scale-y: 1; - --tw-pan-x: ; - --tw-pan-y: ; - --tw-pinch-zoom: ; - --tw-scroll-snap-strictness: proximity; - --tw-gradient-from-position: ; - --tw-gradient-via-position: ; - --tw-gradient-to-position: ; - --tw-ordinal: ; - --tw-slashed-zero: ; - --tw-numeric-figure: ; - --tw-numeric-spacing: ; - --tw-numeric-fraction: ; - --tw-ring-inset: ; - --tw-ring-offset-width: 0px; - --tw-ring-offset-color: #fff; - --tw-ring-color: rgb(59 130 246 / 0.5); - --tw-ring-offset-shadow: 0 0 #0000; - --tw-ring-shadow: 0 0 #0000; - --tw-shadow: 0 0 #0000; - --tw-shadow-colored: 0 0 #0000; - --tw-blur: ; - --tw-brightness: ; - --tw-contrast: ; - --tw-grayscale: ; - --tw-hue-rotate: ; - --tw-invert: ; - --tw-saturate: ; - --tw-sepia: ; - --tw-drop-shadow: ; - --tw-backdrop-blur: ; - --tw-backdrop-brightness: ; - --tw-backdrop-contrast: ; - --tw-backdrop-grayscale: ; - --tw-backdrop-hue-rotate: ; - --tw-backdrop-invert: ; - --tw-backdrop-opacity: ; - --tw-backdrop-saturate: ; - --tw-backdrop-sepia: ; - --tw-contain-size: ; - --tw-contain-layout: ; - --tw-contain-paint: ; - --tw-contain-style: ; -} - -::backdrop { - --tw-border-spacing-x: 0; - --tw-border-spacing-y: 0; - --tw-translate-x: 0; - --tw-translate-y: 0; - --tw-rotate: 0; - --tw-skew-x: 0; - --tw-skew-y: 0; - --tw-scale-x: 1; - --tw-scale-y: 1; - --tw-pan-x: ; - --tw-pan-y: ; - --tw-pinch-zoom: ; - --tw-scroll-snap-strictness: proximity; - --tw-gradient-from-position: ; - --tw-gradient-via-position: ; - --tw-gradient-to-position: ; - --tw-ordinal: ; - --tw-slashed-zero: ; - --tw-numeric-figure: ; - --tw-numeric-spacing: ; - --tw-numeric-fraction: ; - --tw-ring-inset: ; - --tw-ring-offset-width: 0px; - --tw-ring-offset-color: #fff; - --tw-ring-color: rgb(59 130 246 / 0.5); - --tw-ring-offset-shadow: 0 0 #0000; - --tw-ring-shadow: 0 0 #0000; - --tw-shadow: 0 0 #0000; - --tw-shadow-colored: 0 0 #0000; - --tw-blur: ; - --tw-brightness: ; - --tw-contrast: ; - --tw-grayscale: ; - --tw-hue-rotate: ; - --tw-invert: ; - --tw-saturate: ; - --tw-sepia: ; - --tw-drop-shadow: ; - --tw-backdrop-blur: ; - --tw-backdrop-brightness: ; - --tw-backdrop-contrast: ; - --tw-backdrop-grayscale: ; - --tw-backdrop-hue-rotate: ; - --tw-backdrop-invert: ; - --tw-backdrop-opacity: ; - --tw-backdrop-saturate: ; - --tw-backdrop-sepia: ; - --tw-contain-size: ; - --tw-contain-layout: ; - --tw-contain-paint: ; - --tw-contain-style: ; -} - .alert { display: grid; width: 100%; diff --git a/v2/library/discovery/discovery.go b/v2/library/discovery/discovery.go index 8ae43f6..c5b9dc8 100644 --- a/v2/library/discovery/discovery.go +++ b/v2/library/discovery/discovery.go @@ -1,6 +1,7 @@ package discovery import ( + "context" "io/fs" "log/slog" "path/filepath" @@ -18,6 +19,7 @@ type discProc struct { discoverer *Discoverer l *slog.Logger fs libfs.LibFS + ctx context.Context } type Discoverer struct { @@ -34,11 +36,12 @@ func New(p *process.Processor, r *repo.AssetRepo) *Discoverer { } } -func (d Discoverer) DiscoverFS(fs libfs.LibFS) *discProc { +func (d Discoverer) DiscoverFS(ctx context.Context, fs libfs.LibFS) *discProc { dp := &discProc{ discoverer: &d, l: d.l.With("fs", fs.GetName()), fs: fs, + ctx: ctx, } return dp @@ -55,7 +58,7 @@ func (d *discProc) processPath(currFS libfs.LibFS, path string, parent *entities return nil, err } - asset = entities.NewAsset(currFS, path, pathInfo.IsDir(), parent) + asset = entities.NewAsset(currFS.(entities.LibFS), path, pathInfo.IsDir(), parent) asset.SeenOnScan = utils.Ptr(true) @@ -69,7 +72,7 @@ func (d *discProc) processPath(currFS libfs.LibFS, path string, parent *entities innerFS := currFS var files []fs.DirEntry if libfs.IsBundle(path) { - innerFS, err = libfs.GetBundleFS(currFS, path) + innerFS, err = libfs.GetBundleFS(d.ctx, currFS, *asset) if err != nil { return nil, err } @@ -93,7 +96,7 @@ func (d *discProc) processPath(currFS libfs.LibFS, path string, parent *entities } } if !pathInfo.IsDir() || (!libfs.IsBundle(path) || config.Cfg.Library.RenderBundles) { - d.discoverer.processor.Process(asset) + d.discoverer.processor.Process(d.ctx, asset) } return asset, nil diff --git a/v2/library/downloader/downloader.go b/v2/library/downloader/downloader.go index 17a3764..f081f6d 100644 --- a/v2/library/downloader/downloader.go +++ b/v2/library/downloader/downloader.go @@ -1,6 +1,7 @@ package downloader import ( + "context" "strings" "github.com/eduardooliveira/stLib/v2/library/downloader/thingiverse" @@ -10,6 +11,7 @@ import ( ) type DownloadInput struct { + Ctx context.Context Parent entities.Asset URL string Repo *repo.AssetRepo @@ -23,7 +25,7 @@ func Download(input DownloadInput) error { for _, url := range urls { if strings.Contains(url, "thingiverse.com") || strings.Contains(url, "thing:") { - td, err := thingiverse.New(input.Repo, input.Processor) + td, err := thingiverse.New(input.Ctx, input.Repo, input.Processor) if err != nil { return err } diff --git a/v2/library/downloader/thingiverse/thingiverse.go b/v2/library/downloader/thingiverse/thingiverse.go index 70a7b48..251a494 100644 --- a/v2/library/downloader/thingiverse/thingiverse.go +++ b/v2/library/downloader/thingiverse/thingiverse.go @@ -23,6 +23,7 @@ import ( ) type ThingyDownloader struct { + ctx context.Context l *slog.Logger token string r *repo.AssetRepo @@ -36,8 +37,9 @@ type ThingyDownloader struct { var matcher = regexp.MustCompile(`thing:(\d+)`) -func New(r *repo.AssetRepo, p *process.Processor) (*ThingyDownloader, error) { +func New(ctx context.Context, r *repo.AssetRepo, p *process.Processor) (*ThingyDownloader, error) { rtn := &ThingyDownloader{ + ctx: ctx, l: slog.With("module", "thingiverse"), token: config.Cfg.Integrations.Thingiverse.Token, r: r, @@ -76,7 +78,7 @@ func (t *ThingyDownloader) Fetch(url string, parent entities.Asset) error { return fmt.Errorf("creating folder: %v", err) } - t.asset = entities.NewAsset(t.fSys, path, true, &parent) + t.asset = entities.NewAsset(t.fSys.(entities.LibFS), path, true, &parent) t.asset.Label = utils.Ptr(t.thing.Name) t.asset.Description = utils.Ptr(t.thing.Description) @@ -204,11 +206,11 @@ func (t ThingyDownloader) fetchImages() error { } func (t ThingyDownloader) processFile(name string) error { - i := entities.NewAsset(t.fSys, filepath.Join(*t.asset.Path, name), false, t.asset) + i := entities.NewAsset(t.fSys.(entities.LibFS), filepath.Join(*t.asset.Path, name), false, t.asset) if err := t.r.SaveAsset(*i); err != nil { return fmt.Errorf("saving asset: %v", err) } - return t.p.Process(i).Wait() + return t.p.Process(t.ctx, i).Wait() } type ThingImage struct { diff --git a/v2/library/entities/asset.go b/v2/library/entities/asset.go index 6c2fac7..22aca68 100644 --- a/v2/library/entities/asset.go +++ b/v2/library/entities/asset.go @@ -3,17 +3,32 @@ package entities import ( "crypto/md5" "encoding/hex" + "io" + "io/fs" "log/slog" "path/filepath" "strings" "time" "github.com/eduardooliveira/stLib/v2/config" - "github.com/eduardooliveira/stLib/v2/library/libfs" "github.com/eduardooliveira/stLib/v2/utils" "gorm.io/gorm" ) +type LibFS interface { + GetFS() fs.FS + GetName() string + GetLocation() string + GetRoot() string + Kind() string + Open(name string) (fs.File, error) + Writable() bool + Create(name string) (io.WriteCloser, error) + Mkdir(name string) error + Remove(name string) error + IsBundle(path string) bool +} + type NodeKind string const ( @@ -29,12 +44,12 @@ type Asset struct { Label *string `query:"label" in:"form=label"` Description *string `query:"description" in:"form=description"` Path *string `query:"path" in:"form=path"` - Root *string `query:"root" in:"form=root"` + Root string `query:"root" in:"form=root"` FSKind string `query:"fsKind" in:"form=fsKind"` FSName string `query:"fsName" in:"form=fsName"` Extension *string `query:"extension" in:"form=extension"` Kind *string `query:"kind" in:"form=kind"` - NodeKind *NodeKind `query:"nodeKind" in:"form=nodeKind"` + NodeKind NodeKind `query:"nodeKind" in:"form=nodeKind"` ParentID *string `query:"parentID" in:"form=parentID"` Parent *Asset `in:"form=-"` NestedAssets []*Asset `query:"nestedAssets" in:"form=nestedAssets" gorm:"foreignKey:ParentID;constraint:OnDelete:CASCADE;"` @@ -46,55 +61,17 @@ type Asset struct { UpdatedAt time.Time } -func NewAssetFromRootPath(root, path string, isDir bool, parent *Asset) *Asset { +func NewAsset(fs LibFS, path string, isDir bool, parent *Asset) *Asset { ext := filepath.Ext(path) - data := []byte(filepath.Join(root, path)) + data := []byte(filepath.Join(fs.GetName(), fs.GetRoot(), path)) md5Hash := md5.Sum(data) var asset = &Asset{ ID: hex.EncodeToString(md5Hash[:]), - Root: utils.Ptr(root), Path: utils.Ptr(path), - Label: utils.Ptr(strings.TrimSuffix(filepath.Base(path), ext)), - Extension: utils.Ptr(ext), - } - if parent != nil { - asset.Parent = parent - asset.ParentID = &parent.ID - } - if isDir { - if parent == nil { - asset.NodeKind = utils.Ptr(NodeKindRoot) - } else { - asset.NodeKind = utils.Ptr(NodeKindDir) - } - asset.Kind = utils.Ptr("dir") - return asset - } - - asset.NodeKind = utils.Ptr(NodeKindFile) - kind := config.Cfg.Library.AssetTypes.ByExtension(*asset.Extension) - asset.Kind = utils.Ptr(kind.Name) - - if *asset.Kind == "image" { - asset.Thumbnail = utils.Ptr(asset.ID) - } - - return asset -} - -func NewAsset(fs libfs.LibFS, path string, isDir bool, parent *Asset) *Asset { - ext := filepath.Ext(path) - - data := []byte(filepath.Join(fs.GetName(), fs.GetLocation(), path)) - md5Hash := md5.Sum(data) - - var asset = &Asset{ - ID: hex.EncodeToString(md5Hash[:]), - Path: utils.Ptr(path), - Root: utils.Ptr(fs.GetLocation()), - FSName: fs.GetName(), // if is root + Root: fs.GetRoot(), + FSName: fs.GetName(), FSKind: fs.Kind(), Label: utils.Ptr(strings.TrimSuffix(filepath.Base(path), ext)), Extension: utils.Ptr(ext), @@ -104,19 +81,21 @@ func NewAsset(fs libfs.LibFS, path string, isDir bool, parent *Asset) *Asset { asset.ParentID = &parent.ID } - if fs.Kind() == "bundle" { - asset.NodeKind = utils.Ptr(NodeKindBundled) - } else if libfs.IsBundle(path) { - asset.NodeKind = utils.Ptr(NodeKindBundle) + if fs.IsBundle(path) { + asset.Kind = utils.Ptr("bundle") + asset.NodeKind = NodeKindBundle } else if isDir { if parent == nil { - asset.NodeKind = utils.Ptr(NodeKindRoot) + asset.NodeKind = NodeKindRoot asset.Label = utils.Ptr(fs.GetName()) } else { - asset.NodeKind = utils.Ptr(NodeKindDir) + asset.NodeKind = NodeKindDir } } else { - asset.NodeKind = utils.Ptr(NodeKindFile) + asset.NodeKind = NodeKindFile + } + if fs.Kind() == "bundle" { + asset.NodeKind = NodeKindBundled } if isDir { @@ -124,9 +103,10 @@ func NewAsset(fs libfs.LibFS, path string, isDir bool, parent *Asset) *Asset { return asset } - kind := config.Cfg.Library.AssetTypes.ByExtension(*asset.Extension) - asset.Kind = utils.Ptr(kind.Name) - + if asset.Kind == nil { + kind := config.Cfg.Library.AssetTypes.ByExtension(*asset.Extension) + asset.Kind = utils.Ptr(kind.Name) + } if *asset.Kind == "image" { asset.Thumbnail = utils.Ptr(asset.ID) } @@ -134,30 +114,6 @@ func NewAsset(fs libfs.LibFS, path string, isDir bool, parent *Asset) *Asset { return asset } -func NewBundledAsset(parent *Asset, path string) *Asset { - ext := filepath.Ext(path) - - data := []byte(filepath.Join(*parent.Root, *parent.Path, path)) //TODO: make consistent with the way its calculated on discovery - md5Hash := md5.Sum(data) - - var asset = &Asset{ - ID: hex.EncodeToString(md5Hash[:]), - Root: parent.Root, - Path: utils.Ptr(path), - Label: utils.Ptr(strings.TrimSuffix(filepath.Base(path), ext)), - Extension: utils.Ptr(ext), - } - - asset.Parent = parent - asset.ParentID = &parent.ID - - asset.NodeKind = utils.Ptr(NodeKindBundled) - kind := config.Cfg.Library.AssetTypes.ByExtension(*asset.Extension) - asset.Kind = utils.Ptr(kind.Name) - - return asset -} - func (a *Asset) bubbleThumbnail(tx *gorm.DB) error { if a.Thumbnail == nil || a.ParentID == nil { slog.Debug("no thumbnail or parent", "asset", utils.VoZ(a.Path), "thumbnail", utils.VoZ(a.Thumbnail), "parent", utils.VoZ(a.ParentID)) diff --git a/v2/library/libfs/bundlefs.go b/v2/library/libfs/bundlefs.go index 967f874..3d30c77 100644 --- a/v2/library/libfs/bundlefs.go +++ b/v2/library/libfs/bundlefs.go @@ -8,46 +8,122 @@ import ( "path/filepath" "github.com/eduardooliveira/stLib/v2/config" + "github.com/eduardooliveira/stLib/v2/library/entities" + "github.com/eduardooliveira/stLib/v2/utils" "github.com/mholt/archiver/v4" ) type bundleFS struct { - FS + lFS + parentFS LibFS bundleLocation string } -func newBundleFS(parentFS LibFS, path string) (LibFS, error) { +func resolveBundleFS(ctx context.Context, asset entities.Asset) (LibFS, error) { + if asset.FSKind == "bundle" { + return resolveParentFS(ctx, asset) - base := filepath.Base(path) - rtn := FS{ - FileSystem: config.FileSystem{ - Name: parentFS.GetName(), - Path: path, - Kind: "bundle", - }, - discovarable: true, + } else { + return fileSystems[asset.FSName], nil } +} - var err error +func resolveParentFS(ctx context.Context, asset entities.Asset) (LibFS, error) { + if asset.FSKind == "bundle" { + var parentNode = utils.VoZ(asset.Parent) + for parentNode.ID != asset.Root { + parentNode = utils.VoZ(parentNode.Parent) + } + parent, err := resolveParentFS(ctx, parentNode) + if err != nil { + return nil, err + } + return newBundleFS(ctx, parent, parentNode) + } else { + return fileSystems[asset.FSName], nil + } +} + +func newBundleFS(ctx context.Context, parentFS LibFS, asset entities.Asset) (LibFS, error) { + var path = utils.VoZ(asset.Path) + var base = filepath.Base(path) switch filepath.Ext(base) { case ".zip", ".rar", ".7z", ".tar", ".3mf": - rtn.FS, err = archiver.FileSystem(context.Background(), filepath.Join(parentFS.GetLocation(), path)) + default: + return nil, errors.New("unsupported bundle type") + } + + if parentFS.Kind() == "local" { + bfs, err := archiver.FileSystem(ctx, filepath.Join(parentFS.GetLocation(), path)) if err != nil { return nil, err } + return bundleFS{ + lFS: lFS{ + FileSystem: config.FileSystem{ + Name: base, + Path: asset.ID, + Kind: "bundle", + }, + FS: bfs, + discovarable: true, + }, + parentFS: parentFS, + bundleLocation: filepath.Join(parentFS.GetLocation(), path), + }, nil + } + + tempFS, iErr := GetLibFS("temp") + if iErr != nil { + return nil, iErr + } + if _, err := fs.Stat(tempFS, base); err != nil { + if errors.Is(err, fs.ErrNotExist) { + writer, iErr := tempFS.Create(base) + if iErr != nil { + return nil, iErr + } + f, iErr := parentFS.Open(path) + if iErr != nil { + return nil, iErr + } + defer f.Close() + _, iErr = io.Copy(writer, f) + if iErr != nil { + return nil, iErr + } + } + } + + bfs, err := archiver.FileSystem(ctx, filepath.Join(tempFS.GetLocation(), base)) + + if err != nil { + return nil, err } return bundleFS{ - FS: rtn, - bundleLocation: filepath.Join(parentFS.GetLocation(), path), + lFS: lFS{ + FileSystem: config.FileSystem{ + Name: parentFS.GetName(), + Path: asset.ID, + Kind: "bundle", + }, + FS: bfs, + discovarable: true, + }, + parentFS: parentFS, + bundleLocation: filepath.Join(tempFS.GetLocation(), base), }, nil } func (fs bundleFS) GetFS() fs.FS { - return fs.FS + return fs.lFS } func (fs bundleFS) GetName() string { return fs.Name } +func (fs bundleFS) GetRoot() string { + return fs.lFS.Path +} func (fs bundleFS) GetLocation() string { return fs.Path } @@ -70,3 +146,7 @@ func (fs bundleFS) Mkdir(name string) error { func (fs bundleFS) setDiscovarable(d bool) { fs.discovarable = d } + +func (fs bundleFS) Remove(name string) error { + return errors.New("remove not supported") +} diff --git a/v2/library/libfs/gitfs.go b/v2/library/libfs/gitfs.go index 6763e6b..5038ec7 100644 --- a/v2/library/libfs/gitfs.go +++ b/v2/library/libfs/gitfs.go @@ -13,7 +13,7 @@ import ( ) type gitfs struct { - FS + lFS config gitFSConfig } type gitFSConfig struct { @@ -22,7 +22,7 @@ type gitFSConfig struct { func newGitFS(cfgFS config.FileSystem) (LibFS, error) { rtn := gitfs{ - FS: FS{ + lFS: lFS{ FileSystem: cfgFS, discovarable: true, }, @@ -44,17 +44,20 @@ func newGitFS(cfgFS config.FileSystem) (LibFS, error) { if err != nil { slog.Error(err.Error()) } - rtn.FS.FS = fsys + rtn.lFS.FS = fsys return rtn, nil } func (fs gitfs) GetFS() fs.FS { - return fs.FS + return fs.lFS } func (fs gitfs) GetName() string { return fs.Name } +func (fs gitfs) GetRoot() string { + return fs.Path +} func (fs gitfs) GetLocation() string { return fs.Path } @@ -77,3 +80,7 @@ func (fs gitfs) Mkdir(name string) error { func (fs gitfs) setDiscovarable(d bool) { fs.discovarable = d } + +func (fs gitfs) Remove(name string) error { + return errors.New("gitfs is read-only") +} diff --git a/v2/library/libfs/libfs.go b/v2/library/libfs/libfs.go index d0b4d7b..a5ca161 100644 --- a/v2/library/libfs/libfs.go +++ b/v2/library/libfs/libfs.go @@ -1,6 +1,7 @@ package libfs import ( + "context" "errors" "io" "io/fs" @@ -8,16 +9,17 @@ import ( "slices" "github.com/eduardooliveira/stLib/v2/config" + "github.com/eduardooliveira/stLib/v2/library/entities" "github.com/eduardooliveira/stLib/v2/utils" ) -type FS struct { +type lFS struct { fs.FS config.FileSystem discovarable bool } -func (fs FS) isDiscovarable() bool { +func (fs lFS) isDiscovarable() bool { return fs.discovarable } @@ -25,13 +27,20 @@ type LibFS interface { GetFS() fs.FS GetName() string GetLocation() string + GetRoot() string Kind() string Open(name string) (fs.File, error) Writable() bool Create(name string) (io.WriteCloser, error) Mkdir(name string) error + Remove(name string) error isDiscovarable() bool setDiscovarable(bool) + IsBundle(path string) bool +} + +func (fs lFS) IsBundle(path string) bool { + return slices.Contains(bundleFSs, filepath.Ext(path)) } var ( @@ -79,8 +88,19 @@ func LoadFSs() error { Path: filepath.Join(config.Cfg.Core.DataFolder, "generated"), }) } + if fileSystems["temp"] == nil { + if err := utils.CreateFolder(filepath.Join(config.Cfg.Core.DataFolder, "temp")); err != nil { + return err + } + fileSystems["temp"] = newLocalFS(config.FileSystem{ + Name: "temp", + Kind: "local", + Path: filepath.Join(config.Cfg.Core.DataFolder, "temp"), + }) + } fileSystems["cache"].setDiscovarable(false) fileSystems["generated"].setDiscovarable(false) + fileSystems["temp"].setDiscovarable(false) return nil } @@ -88,14 +108,16 @@ func GetDefaultFS() LibFS { return fileSystems[defaultFSName] } -func GetFS(kind, name, path string) (LibFS, error) { - if kind == "bundle" { - return newBundleFS(fileSystems[name], path) +func GetAssetFS(ctx context.Context, asset entities.Asset) (LibFS, error) { + if asset.FSKind == "bundle" { + return resolveBundleFS(ctx, asset) } - if _, ok := fileSystems[name]; !ok { - return nil, errors.New("file system not found") + + if f, ok := fileSystems[asset.FSName]; ok { + return f, nil } - return fileSystems[name], nil + + return nil, errors.New("file system not found") } func GetLibFS(name string) (LibFS, error) { @@ -120,6 +142,6 @@ func IsBundle(path string) bool { return slices.Contains(bundleFSs, filepath.Ext(path)) } -func GetBundleFS(parentFS LibFS, path string) (LibFS, error) { - return newBundleFS(parentFS, path) +func GetBundleFS(ctx context.Context, parentFS LibFS, asset entities.Asset) (LibFS, error) { + return newBundleFS(ctx, parentFS, asset) } diff --git a/v2/library/libfs/localfs.go b/v2/library/libfs/localfs.go index 8574614..fa53bd1 100644 --- a/v2/library/libfs/localfs.go +++ b/v2/library/libfs/localfs.go @@ -11,12 +11,12 @@ import ( ) type localFS struct { - FS + lFS } func newLocalFS(cfgFS config.FileSystem) LibFS { return &localFS{ - FS: FS{ + lFS: lFS{ FileSystem: cfgFS, FS: os.DirFS(cfgFS.Path), discovarable: true, @@ -25,10 +25,13 @@ func newLocalFS(cfgFS config.FileSystem) LibFS { } func (fs localFS) GetFS() fs.FS { - return fs.FS + return fs.lFS } func (fs localFS) GetName() string { - return fs.FS.Name + return fs.lFS.Name +} +func (fs localFS) GetRoot() string { + return fs.Path } func (fs localFS) GetLocation() string { return fs.Path @@ -58,3 +61,7 @@ func (fs localFS) Mkdir(name string) error { func (fs *localFS) setDiscovarable(d bool) { fs.discovarable = d } + +func (fs localFS) Remove(name string) error { + return os.RemoveAll(filepath.Join(fs.Path, name)) +} diff --git a/v2/library/library.go b/v2/library/library.go index 975f202..a59125d 100644 --- a/v2/library/library.go +++ b/v2/library/library.go @@ -10,6 +10,7 @@ import ( "github.com/eduardooliveira/stLib/v2/library/process" "github.com/eduardooliveira/stLib/v2/library/repo" "github.com/eduardooliveira/stLib/v2/library/web" + "golang.org/x/net/context" "golang.org/x/sync/errgroup" ) @@ -53,21 +54,21 @@ func New() (*Library, http.Handler, http.Handler, error) { return lib, webH, nil, nil } -func (l Library) ScanFS() error { +func (l Library) ScanFS(ctx context.Context) error { eg := errgroup.Group{} if len(config.Cfg.Library.FileSystems) == 0 || (len(config.Cfg.Library.FileSystems) == 1 && config.Cfg.Library.FileSystems[0].Path == "change_me") { slog.Warn("invalid library file systems configured") return nil } for _, ffs := range libfs.GetFSs() { - eg.Go(l.d.DiscoverFS(ffs).Run) + eg.Go(l.d.DiscoverFS(ctx, ffs).Run) } return eg.Wait() } -func (l *Library) ScanAsync() { +func (l *Library) ScanAsync(ctx context.Context) { go func() { - if err := l.ScanFS(); err != nil { + if err := l.ScanFS(ctx); err != nil { slog.Error("Error scanning library", "error", err) } }() diff --git a/v2/library/process/enrichers/enrichers.go b/v2/library/process/enrichers/enrichers.go index 7f36354..e7b07db 100644 --- a/v2/library/process/enrichers/enrichers.go +++ b/v2/library/process/enrichers/enrichers.go @@ -1,11 +1,13 @@ package enrichers import ( + "context" + "github.com/eduardooliveira/stLib/v2/library/entities" ) type Enricher interface { - Enrich(asset *entities.Asset) error + Enrich(ctx context.Context, asset *entities.Asset) error } var enrichers = map[string]Enricher{} diff --git a/v2/library/process/enrichers/gcodeEnricher.go b/v2/library/process/enrichers/gcodeEnricher.go index 4214a03..c80648e 100644 --- a/v2/library/process/enrichers/gcodeEnricher.go +++ b/v2/library/process/enrichers/gcodeEnricher.go @@ -2,6 +2,7 @@ package enrichers import ( "bufio" + "context" "errors" "fmt" "log/slog" @@ -17,13 +18,13 @@ type gCodeEnricher struct { l *slog.Logger } -func (g *gCodeEnricher) Enrich(asset *entities.Asset) error { +func (g *gCodeEnricher) Enrich(ctx context.Context, asset *entities.Asset) error { g.l = slog.With("module", "gcodeEnricher").With("asset", asset.ID) if asset.Properties == nil { asset.Properties = make(entities.Properties) } - fs, err := libfs.GetFS(asset.FSKind, asset.FSName, *asset.Root) + fs, err := libfs.GetAssetFS(ctx, *asset) if err != nil { return fmt.Errorf("error getting fs: %w", err) } diff --git a/v2/library/process/extractors/extractor.go b/v2/library/process/extractors/extractor.go deleted file mode 100644 index ca35491..0000000 --- a/v2/library/process/extractors/extractor.go +++ /dev/null @@ -1,41 +0,0 @@ -package extractors - -import ( - "slices" - - "github.com/eduardooliveira/stLib/v2/library/entities" -) - -type Extractor interface { - Extract(asset *entities.Asset) ([]*entities.Asset, error) - ExtractBundled(asset *entities.Asset) error -} - -var extensions = []string{ - ".3mf", - ".zip", - ".rar", - ".7z", - ".tar", -} - -var extractors = map[string]Extractor{ - ".3mf": &ThreeMFExtractor{}, - ".zip": &StdExtractor{}, - ".rar": &StdExtractor{}, - ".7z": &StdExtractor{}, - ".tar": &StdExtractor{}, -} - -func IsExtractable(asset *entities.Asset) bool { - return slices.Contains(extensions, *asset.Extension) -} - -func GetExtractor(asset *entities.Asset) Extractor { - return extractors[*asset.Extension] -} - -func Get(asset *entities.Asset) (Extractor, bool) { - e, ok := extractors[*asset.Extension] - return e, ok -} diff --git a/v2/library/process/extractors/stdextractor.go b/v2/library/process/extractors/stdextractor.go deleted file mode 100644 index 7ae3c55..0000000 --- a/v2/library/process/extractors/stdextractor.go +++ /dev/null @@ -1,127 +0,0 @@ -package extractors - -import ( - "context" - "errors" - "fmt" - "io/fs" - "path/filepath" - "strings" - - "github.com/eduardooliveira/stLib/v2/config" - "github.com/eduardooliveira/stLib/v2/library/entities" - "github.com/eduardooliveira/stLib/v2/utils" - "github.com/mholt/archiver/v4" -) - -type StdExtractor struct { -} - -func (t *StdExtractor) Extract(asset *entities.Asset) ([]*entities.Asset, error) { - rtn := make([]*entities.Asset, 0) - - fsys, err := archiver.FileSystem(context.Background(), filepath.Join(*asset.Root, *asset.Path)) - if err != nil { - return nil, err - } - imgRoot := filepath.Join(config.Cfg.Core.DataFolder, "img") - parentDir := filepath.Join(imgRoot, *asset.ParentID) - if err := utils.CreateFolder(parentDir); err != nil { - return nil, err - } - - var biggestImage int64 - var biggestImgFile string - var files []string - - fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, err error) error { - if err != nil { - return err - } - if d.IsDir() { - return nil - } - - files = append(files, path) - ext := filepath.Ext(d.Name()) - if config.Cfg.Library.AssetTypes.ByExtension(ext).Name == "image" { - info, err := d.Info() - if err != nil { - return err - } - - if info.Size() > biggestImage { - biggestImage = info.Size() - biggestImgFile = path - } - } - - return nil - }) - - for _, file := range files { - var za *entities.Asset - fName := filepath.Base(file) - ext := filepath.Ext(fName) - name := fmt.Sprintf("%s.e%s", strings.TrimSuffix(fName, ext), ext) - if file == biggestImgFile { - f, err := fsys.Open(file) - if err != nil { - return nil, err - } - defer f.Close() - if err := utils.SaveFile(filepath.Join(parentDir, name), f); err != nil { - return nil, err - } - za = entities.NewAssetFromRootPath(imgRoot, filepath.Join(*asset.ParentID, name), false, asset) - if asset.Thumbnail == nil { - asset.Thumbnail = &za.ID - } - } else { - za = entities.NewBundledAsset(asset, file) - } - - rtn = append(rtn, za) - } - - asset.NodeKind = utils.Ptr(entities.NodeKindBundle) - return rtn, nil -} - -// TODO: propagate context -func (t *StdExtractor) ExtractBundled(asset *entities.Asset) error { - if asset.Parent == nil || asset.Parent.Root == nil || asset.Parent.Path == nil { - return errors.New("invalid parent asset") - } - fsys, err := archiver.FileSystem(context.Background(), filepath.Join(*asset.Root, *asset.Parent.Path)) - if err != nil { - return fmt.Errorf("failed to open bundle: %w", err) - } - - f, err := fsys.Open(utils.VoZ(asset.Path)) - if err != nil { - return fmt.Errorf("failed to open bundled: %w", err) - } - defer f.Close() - - tempPath := filepath.Join(config.Cfg.Core.DataFolder, "temp", *asset.ParentID) - if err := utils.CreateFolder(tempPath); err != nil { - return fmt.Errorf("failed to create temp folder: %w", err) - } - - assetBase := filepath.Base(*asset.Path) - - if err := utils.SaveFile(filepath.Join(tempPath, assetBase), f); err != nil { - return err - } - - if asset.Properties == nil { - asset.Properties = make(map[string]interface{}) - } - asset.Properties["mmp_extracted"] = utils.Ptr(true) - - if utils.VoZ(asset.Kind) == "image" { - asset.Thumbnail = utils.Ptr(asset.ID) - } - return nil -} diff --git a/v2/library/process/extractors/threemfextractor.go b/v2/library/process/extractors/threemfextractor.go deleted file mode 100644 index ecafec2..0000000 --- a/v2/library/process/extractors/threemfextractor.go +++ /dev/null @@ -1,142 +0,0 @@ -package extractors - -import ( - "archive/zip" - "errors" - "fmt" - "path/filepath" - "strings" - - "github.com/eduardooliveira/stLib/v2/config" - "github.com/eduardooliveira/stLib/v2/library/entities" - "github.com/eduardooliveira/stLib/v2/utils" -) - -type ThreeMFExtractor struct { -} - -func (t *ThreeMFExtractor) Extract(asset *entities.Asset) ([]*entities.Asset, error) { - rtn := make([]*entities.Asset, 0) - - archive, err := zip.OpenReader(filepath.Join(*asset.Root, *asset.Path)) - if err != nil { - return nil, err - } - defer archive.Close() - - imgRoot := filepath.Join(config.Cfg.Core.DataFolder, "img") - parentDir := filepath.Join(imgRoot, *asset.ParentID) - if err := utils.CreateFolder(parentDir); err != nil { - return nil, err - } - - thumbnail := t.filterThumb(archive.File) - - for _, f := range archive.File { - - if f.FileInfo().IsDir() { - continue - } - - if strings.Contains(f.Name, ".thumbnails/") { - continue - } - - fName := filepath.Base(f.Name) - ext := filepath.Ext(fName) - name := fmt.Sprintf("%s.e%s", strings.TrimSuffix(fName, ext), ext) - - var za *entities.Asset - - if f.Name == thumbnail { - zipped, err := f.Open() - if err != nil { - return nil, err - } - defer zipped.Close() - if err := utils.SaveFile(filepath.Join(parentDir, name), zipped); err != nil { - return nil, err - } - - za = entities.NewAssetFromRootPath(imgRoot, filepath.Join(*asset.ParentID, name), false, asset) - if asset.Thumbnail == nil { - asset.Thumbnail = &za.ID - } - } else { - za = entities.NewBundledAsset(asset, f.Name) - } - - rtn = append(rtn, za) - - } - - asset.NodeKind = utils.Ptr(entities.NodeKindBundled) - return rtn, nil -} - -func (t *ThreeMFExtractor) filterThumb(files []*zip.File) string { - var biggestSize uint64 - var biggestImage string - - for _, f := range files { - // Ignore thumbnail since we should have the original image already - if strings.Contains(f.Name, ".thumbnails/") { - continue - } - - ext := filepath.Ext(f.Name) - if config.Cfg.Library.AssetTypes.ByExtension(ext).Name != "image" { - continue - } - - // try set makerworld thumbnail - if strings.HasSuffix(f.Name, "Preview.webp") { - return f.Name - } - - if f.UncompressedSize64 > biggestSize { - biggestSize = f.UncompressedSize64 - biggestImage = f.Name - } - } - - return biggestImage -} - -func (t *ThreeMFExtractor) ExtractBundled(asset *entities.Asset) error { - if asset.Parent == nil || asset.Parent.Root == nil || asset.Parent.Path == nil { - return errors.New("invalid parent asset") - } - archive, err := zip.OpenReader(filepath.Join(*asset.Root, *asset.Parent.Path)) - if err != nil { - return err - } - defer archive.Close() - - f, err := archive.Open(utils.VoZ(asset.Path)) - if err != nil { - return err - } - defer f.Close() - - tempPath := filepath.Join(config.Cfg.Core.DataFolder, "temp", *asset.ParentID) - if err := utils.CreateFolder(tempPath); err != nil { - return fmt.Errorf("failed to create temp folder: %w", err) - } - - assetBase := filepath.Base(*asset.Path) - - if err := utils.SaveFile(filepath.Join(tempPath, assetBase), f); err != nil { - return err - } - - if asset.Properties == nil { - asset.Properties = make(map[string]interface{}) - } - asset.Properties["mmp_extracted"] = utils.Ptr(true) - - if utils.VoZ(asset.Kind) == "image" { - asset.Thumbnail = utils.Ptr(asset.ID) - } - return nil -} diff --git a/v2/library/process/process.go b/v2/library/process/process.go index 46df84a..1f69518 100644 --- a/v2/library/process/process.go +++ b/v2/library/process/process.go @@ -1,13 +1,12 @@ package process import ( - "errors" + "context" "log/slog" "github.com/eduardooliveira/stLib/v2/config" "github.com/eduardooliveira/stLib/v2/library/entities" "github.com/eduardooliveira/stLib/v2/library/process/enrichers" - "github.com/eduardooliveira/stLib/v2/library/process/extractors" "github.com/eduardooliveira/stLib/v2/library/process/renderers" "github.com/eduardooliveira/stLib/v2/library/repo" "github.com/eduardooliveira/stLib/v2/utils" @@ -30,28 +29,14 @@ func New(r *repo.AssetRepo) (*Processor, error) { }, nil } -func (p *Processor) ProcessBundled(asset *entities.Asset) (*Process, error) { - e, ok := extractors.Get(asset.Parent) - if !ok { - return nil, errors.New("no extractor found for asset") - } - err := e.ExtractBundled(asset) - if err != nil { - return nil, err - } - if err := p.r.SaveAsset(*asset); err != nil { - return nil, err - } - return p.Process(asset), nil -} - -func (p *Processor) Process(asset *entities.Asset) *Process { +func (p *Processor) Process(ctx context.Context, asset *entities.Asset) *Process { proc := &Process{ + ctx: ctx, p: p, Asset: asset, done: make(chan error), } - if utils.VoZ(asset.NodeKind) == entities.NodeKindBundled && !config.Cfg.Library.RenderBundles { + if asset.FSKind == "bundle" && !config.Cfg.Library.RenderBundles { proc.renderState = "skipped" } else if r, ok := renderers.Get(asset); ok { proc.renderer = r @@ -77,6 +62,7 @@ func (p *Processor) Process(asset *entities.Asset) *Process { } type Process struct { + ctx context.Context p *Processor done chan error Asset *entities.Asset @@ -97,7 +83,7 @@ func (p *Process) Run() error { l := slog.With("module", "process").With("asset", *p.Asset.Label) if p.renderer != nil { - if img, err := p.renderer.Render(p.Asset); err != nil { + if img, err := p.renderer.Render(p.ctx, p.Asset); err != nil { p.renderError = err p.renderState = "failed" l.Error("failed to render asset", "error", err) @@ -111,7 +97,7 @@ func (p *Process) Run() error { } if p.enricher != nil { - if err := p.enricher.Enrich(p.Asset); err != nil { + if err := p.enricher.Enrich(p.ctx, p.Asset); err != nil { p.enrichError = err p.enrichState = "failed" l.Error("failed to enrich asset", "error", err) diff --git a/v2/library/process/renderers/gcodeRenderer.go b/v2/library/process/renderers/gcodeRenderer.go index 7214238..a6b119d 100644 --- a/v2/library/process/renderers/gcodeRenderer.go +++ b/v2/library/process/renderers/gcodeRenderer.go @@ -3,6 +3,7 @@ package renderers import ( "bufio" "bytes" + "context" "crypto/sha1" "encoding/base64" "errors" @@ -28,7 +29,7 @@ type tmpImg struct { data []byte } -func (r *gCodeRenderer) Render(asset *entities.Asset) (*entities.Asset, error) { +func (r *gCodeRenderer) Render(ctx context.Context, asset *entities.Asset) (*entities.Asset, error) { genFS, err := libfs.GetLibFS("generated") if err != nil { return nil, fmt.Errorf("render error getting fs: %w", err) @@ -36,12 +37,12 @@ func (r *gCodeRenderer) Render(asset *entities.Asset) (*entities.Asset, error) { imgName := fmt.Sprintf("%s.r.png", asset.ID) if _, err := fs.Stat(genFS, imgName); err == nil { - return entities.NewAsset(genFS, imgName, false, asset), nil + return entities.NewAsset(genFS.(entities.LibFS), imgName, false, asset), nil } slog.Info("Rendering", "asset", *asset.Path, "img", imgName, "asset", asset) - objFs, err := libfs.GetFS(asset.FSKind, asset.FSName, *asset.Root) + objFs, err := libfs.GetAssetFS(ctx, *asset) if err != nil { return nil, fmt.Errorf("error getting fs: %w", err) } @@ -105,7 +106,7 @@ func (r *gCodeRenderer) Render(asset *entities.Asset) (*entities.Asset, error) { return nil, err } - return entities.NewAsset(genFS, imgName, false, asset), nil + return entities.NewAsset(genFS.(entities.LibFS), imgName, false, asset), nil } func (r *gCodeRenderer) parseThumbnail(scanner *bufio.Scanner, size string, length int) (*tmpImg, error) { diff --git a/v2/library/process/renderers/renderers.go b/v2/library/process/renderers/renderers.go index e60fca5..18bfa9b 100644 --- a/v2/library/process/renderers/renderers.go +++ b/v2/library/process/renderers/renderers.go @@ -1,11 +1,13 @@ package renderers import ( + "context" + "github.com/eduardooliveira/stLib/v2/library/entities" ) type Renderer interface { - Render(asset *entities.Asset) (*entities.Asset, error) + Render(ctx context.Context, asset *entities.Asset) (*entities.Asset, error) } var renderers = map[string]Renderer{} diff --git a/v2/library/process/renderers/stlRenderer.go b/v2/library/process/renderers/stlRenderer.go index 6af5abd..a5f0b06 100644 --- a/v2/library/process/renderers/stlRenderer.go +++ b/v2/library/process/renderers/stlRenderer.go @@ -1,6 +1,7 @@ package renderers import ( + "context" "fmt" "image/png" "io/fs" @@ -47,7 +48,7 @@ func NewSTLRenderer() *stlRenderer { } } -func (s *stlRenderer) Render(asset *entities.Asset) (*entities.Asset, error) { +func (s *stlRenderer) Render(ctx context.Context, asset *entities.Asset) (*entities.Asset, error) { genFS, err := libfs.GetLibFS("generated") if err != nil { return nil, fmt.Errorf("render error getting fs: %w", err) @@ -55,12 +56,12 @@ func (s *stlRenderer) Render(asset *entities.Asset) (*entities.Asset, error) { imgName := fmt.Sprintf("%s.r.png", asset.ID) if _, err := fs.Stat(genFS, imgName); err == nil { - return entities.NewAsset(genFS, imgName, false, asset), nil + return entities.NewAsset(genFS.(entities.LibFS), imgName, false, asset), nil } slog.Info("Rendering", "asset", *asset.Path, "img", imgName, "asset", asset) - objFs, err := libfs.GetFS(asset.FSKind, asset.FSName, *asset.Root) + objFs, err := libfs.GetAssetFS(ctx, *asset) if err != nil { return nil, fmt.Errorf("error getting fs: %w", err) } @@ -121,5 +122,5 @@ func (s *stlRenderer) Render(asset *entities.Asset) (*entities.Asset, error) { return nil, err } - return entities.NewAsset(genFS, imgName, false, asset), nil + return entities.NewAsset(genFS.(entities.LibFS), imgName, false, asset), nil } diff --git a/v2/library/repo/asset.go b/v2/library/repo/asset.go index 016824e..850e3dd 100644 --- a/v2/library/repo/asset.go +++ b/v2/library/repo/asset.go @@ -24,19 +24,26 @@ func (r AssetRepo) GetAsset(id string, deep bool) (rtn entities.Asset, err error } func (r AssetRepo) GetAssetRoots(deep bool) (rtn []*entities.Asset, err error) { - q := database.DB.Debug().Where(&entities.Asset{NodeKind: utils.Ptr(entities.NodeKindRoot)}) + q := database.DB.Debug().Where(&entities.Asset{NodeKind: entities.NodeKindRoot}) if deep { q = q.Preload("NestedAssets.NestedAssets") } return rtn, q.Find(&rtn).Error } -func (r AssetRepo) GetAssetByRootAndPath(root, path string, deep bool) (rtn entities.Asset, err error) { - q := database.DB.Where(&entities.Asset{Root: &root, Path: &path}) - if deep { - q = q.Preload("NestedAssets.NestedAssets") +func (r AssetRepo) LoadTree(a *entities.Asset, stop func(a *entities.Asset) bool) error { + if a.ParentID == nil { + return nil } - return rtn, q.First(&rtn).Error + var parent entities.Asset + if err := database.DB.Where("ID = ? ", *a.ParentID).Find(&parent).Error; err != nil { + return err + } + a.Parent = &parent + if stop(a) { + return nil + } + return r.LoadTree(&parent, stop) } func (r AssetRepo) LoadParents(a *entities.Asset, dept int, fields ...string) error { @@ -91,11 +98,6 @@ func (r AssetRepo) DeleteUnSeenInFS(fsName string) error { Delete(entities.Asset{}, entities.Asset{SeenOnScan: utils.Ptr(false), FSName: fsName}).Error } -func (r AssetRepo) DeleteUnSeenInRoot(root string) error { - return database.DB.Model(entities.Asset{}). - Delete(entities.Asset{}, entities.Asset{SeenOnScan: utils.Ptr(false), Root: &root}).Error -} - func (r AssetRepo) UpdateAsset(a *entities.Asset) error { return database.DB.Model(&entities.Asset{ID: a.ID}).Updates(a).Error } diff --git a/v2/library/web/comp/assetcard.templ b/v2/library/web/comp/assetcard.templ index e02bb22..2d74e93 100644 --- a/v2/library/web/comp/assetcard.templ +++ b/v2/library/web/comp/assetcard.templ @@ -16,7 +16,7 @@ templ AssetCard(m *AssetCardModel) { >
folder-outline case entities.NodeKindBundle: diff --git a/v2/library/web/files.go b/v2/library/web/files.go index 11a8507..82a4246 100644 --- a/v2/library/web/files.go +++ b/v2/library/web/files.go @@ -38,7 +38,7 @@ func (h webHandler) getFileHandler(w http.ResponseWriter, r *http.Request) { } if shouldCacheFile(asset) { - if err := h.r.LoadParents(&asset, 1); err != nil { + if err := h.r.LoadTree(&asset, func(a *entities.Asset) bool { return false }); err != nil { h.l.Error("get file", "error", err) http.Error(w, err.Error(), http.StatusInternalServerError) return @@ -67,7 +67,7 @@ func (h webHandler) getFileHandler(w http.ResponseWriter, r *http.Request) { } defer target.Close() - fs, err := libfs.GetFS(asset.FSKind, asset.FSName, *asset.Root) + fs, err := libfs.GetAssetFS(r.Context(), asset) if err != nil { h.l.Error("get fs", "error", err) http.Error(w, err.Error(), http.StatusInternalServerError) @@ -96,7 +96,7 @@ func (h webHandler) getFileHandler(w http.ResponseWriter, r *http.Request) { return } - fs, err := libfs.GetFS(asset.FSKind, asset.FSName, *asset.Root) + fs, err := libfs.GetAssetFS(r.Context(), asset) if err != nil { h.l.Error("get fs", "error", err) http.Error(w, err.Error(), http.StatusInternalServerError) diff --git a/v2/library/web/new.go b/v2/library/web/new.go index 941b2a7..f58df0a 100644 --- a/v2/library/web/new.go +++ b/v2/library/web/new.go @@ -112,6 +112,7 @@ func (h webHandler) handleDownload(r *http.Request, parent entities.Asset, req n h.l.Info("new", "urls", req.Urls) err := downloader.Download(downloader.DownloadInput{ + Ctx: r.Context(), Parent: parent, URL: req.Urls, Repo: h.r, @@ -131,7 +132,7 @@ func (h webHandler) handleUpload(r *http.Request, parent entities.Asset, req new return errors.New("no files or folder"), http.StatusBadRequest } - fSys, err := libfs.GetFS(parent.FSKind, parent.FSName, *parent.Root) + fSys, err := libfs.GetAssetFS(r.Context(), parent) if err != nil { return err, http.StatusInternalServerError } @@ -140,7 +141,7 @@ func (h webHandler) handleUpload(r *http.Request, parent entities.Asset, req new if err != nil { return fmt.Errorf("creating folder: %v", err), http.StatusInternalServerError } - f := entities.NewAsset(fSys, filepath.Join(*parent.Path, req.Folder), true, &parent) + f := entities.NewAsset(fSys.(entities.LibFS), filepath.Join(*parent.Path, req.Folder), true, &parent) if err := h.r.SaveAsset(*f); err != nil { h.l.Error("new", "err", err) @@ -169,12 +170,12 @@ func (h webHandler) handleUpload(r *http.Request, parent entities.Asset, req new return err, http.StatusInternalServerError } - a := entities.NewAsset(fSys, filepath.Join(*parent.Path, file.Filename()), false, &parent) + a := entities.NewAsset(fSys.(entities.LibFS), filepath.Join(*parent.Path, file.Filename()), false, &parent) if err := h.r.SaveAsset(*a); err != nil { h.l.Error("new", "err", err) return err, http.StatusInternalServerError } - if err := h.p.Process(a).Wait(); err != nil { + if err := h.p.Process(r.Context(), a).Wait(); err != nil { h.l.Error("new", "err", err) return err, http.StatusInternalServerError }