From 705bdc2350ae86a30d3d607be8b4dc529cbc5417 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Sun, 29 Sep 2024 03:25:45 -0700 Subject: [PATCH] fix: update files in index when edited. - also watch package dir for to edit file index when files are changed added or removed Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- go.mod | 2 +- internal/gui/gui.go | 33 ++++-- internal/gui/pack.go | 112 ++++++++++++++++---- internal/gui/package.go | 156 +++++++++++++++++++--------- internal/gui/translation/en-US.json | 4 + internal/gui/unpack.go | 14 ++- internal/utils/utils.go | 20 +--- pkg/ddimage/ddimage.go | 10 +- pkg/ddpackage/ddpackage.go | 7 +- pkg/ddpackage/pack.go | 101 ++++++++++-------- pkg/ddpackage/pack_data.go | 47 +++------ pkg/ddpackage/unpack.go | 2 - pkg/structures/files.go | 5 - 13 files changed, 324 insertions(+), 189 deletions(-) diff --git a/go.mod b/go.mod index 7c43747..01cfaa6 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/chai2010/webp v1.1.1 github.com/davecgh/go-spew v1.1.1 github.com/dustin/go-humanize v1.0.1 + github.com/fsnotify/fsnotify v1.7.0 github.com/schollz/progressbar/v3 v3.16.0 github.com/sirupsen/logrus v1.9.3 github.com/snowzach/rotatefilehook v0.0.0-20220211133110-53752135082d @@ -22,7 +23,6 @@ require ( fyne.io/systray v1.11.0 // indirect github.com/BurntSushi/toml v1.4.0 // indirect github.com/fredbi/uri v1.1.0 // indirect - github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fyne-io/gl-js v0.0.0-20220119005834-d2da28d9ccfe // indirect github.com/fyne-io/glfw-js v0.0.0-20240101223322-6e1efdc71b7a // indirect github.com/fyne-io/image v0.0.0-20220602074514-4956b0afb3d2 // indirect diff --git a/internal/gui/gui.go b/internal/gui/gui.go index df13ac5..7a362d0 100644 --- a/internal/gui/gui.go +++ b/internal/gui/gui.go @@ -20,6 +20,7 @@ import ( "fyne.io/fyne/v2/storage" "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" + "github.com/fsnotify/fsnotify" "github.com/ryex/dungeondraft-gopackager/internal/gui/bindings" "github.com/ryex/dungeondraft-gopackager/internal/gui/layouts" "github.com/ryex/dungeondraft-gopackager/internal/utils" @@ -45,17 +46,24 @@ type App struct { tagSaveTimer *time.Timer resSaveTimers map[string]*time.Timer + + packageWatcher *fsnotify.Watcher + + pkgUpdateCounter int + packageUpdated binding.Int } //go:embed translation var translations embed.FS func NewApp() *App { - return &App{ + app := &App{ operatingPath: binding.NewString(), disableButtons: binding.NewBool(), resSaveTimers: make(map[string]*time.Timer), } + app.packageUpdated = binding.BindInt(&app.pkgUpdateCounter) + return app } func (a *App) Main() { @@ -77,10 +85,16 @@ func (a *App) Main() { a.clean() } -func (a *App) clean() { +func (a *App) resetPkg() { + a.teardownPackageWatcher() if a.pkg != nil { a.pkg.Close() } + a.pkg = nil +} + +func (a *App) clean() { + a.resetPkg() fmt.Println("Exited") } @@ -266,8 +280,8 @@ func (a *App) setErrContent(msg string, errs ...error) { errContainer := container.NewVBox() for i, err := range errs { errText := multilineCanvasText( - fmt.Sprintf("%d) ", i)+err.Error(), - 12, + fmt.Sprintf("%d) ", i+1)+err.Error(), + 14, fyne.TextStyle{Italic: true}, fyne.TextAlignLeading, theme.Color(theme.ColorNameError), @@ -275,9 +289,15 @@ func (a *App) setErrContent(msg string, errs ...error) { errContainer.Add(errText) } - msgContent := container.NewCenter( + msgContent := container.NewVBox( + layout.NewSpacer(), msgText, - container.NewScroll(errContainer), + container.NewHBox( + layout.NewSpacer(), + container.NewVScroll(errContainer), + layout.NewSpacer(), + ), + layout.NewSpacer(), ) a.setMainContent(msgContent) @@ -327,6 +347,7 @@ func (a *App) setWaitContent(msg string) (binding.Float, binding.String) { activity, msgText, activityText, + progressBar, layout.NewSpacer(), ) a.setMainContent(activityContent) diff --git a/internal/gui/pack.go b/internal/gui/pack.go index 7e3114b..b8a3618 100644 --- a/internal/gui/pack.go +++ b/internal/gui/pack.go @@ -4,6 +4,8 @@ import ( "errors" "fmt" "path/filepath" + "sync" + "time" "fyne.io/fyne/v2" "fyne.io/fyne/v2/container" @@ -15,25 +17,20 @@ import ( "fyne.io/fyne/v2/widget" "github.com/davecgh/go-spew/spew" - "github.com/ryex/dungeondraft-gopackager/internal/gui/bindings" + "github.com/fsnotify/fsnotify" "github.com/ryex/dungeondraft-gopackager/internal/gui/layouts" "github.com/ryex/dungeondraft-gopackager/internal/utils" "github.com/ryex/dungeondraft-gopackager/pkg/ddpackage" + "github.com/ryex/dungeondraft-gopackager/pkg/structures" log "github.com/sirupsen/logrus" ) func (a *App) loadUnpackedPath(path string) { - if a.pkg != nil { - a.pkg.Close() - } - a.pkg = nil - packjsonPath := filepath.Join(path, "pack.json") if !utils.FileExists(packjsonPath) { a.setNotAPackageContent(path) return } - activityProgress, activityStr := a.setWaitContent(lang.X( "pack.wait", "Loading unpacked resources from {{.Path}} (building index) ...", @@ -43,6 +40,8 @@ func (a *App) loadUnpackedPath(path string) { )) a.disableButtons.Set(true) + a.resetPkg() + go func() { l := log.WithFields(log.Fields{ "path": path, @@ -103,7 +102,81 @@ func (a *App) loadUnpackedPath(path string) { err = nil } a.setUnpackedContent(pkg) + a.setupPackageWatcher() + }() +} + +func (a *App) setupPackageWatcher() { + watcher, err := fsnotify.NewWatcher() + if err != nil { + log.WithError(err).Error("failed to setup filesystem watcher") + return + } + a.packageWatcher = watcher + paths := structures.NewSet[string]() + var eventTimer *time.Timer + lock := &sync.RWMutex{} + + updatePackage := func() { + lock.Lock() + defer lock.Unlock() + eventTimer = nil + toUpdate := paths.AsSlice() + paths = structures.NewSet[string]() + if a.pkg != nil { + a.pkg.UpdateFromPaths(toUpdate) + } + a.packageUpdated.Set(a.pkgUpdateCounter + 1) + } + + go func() { + for { + select { + case event, ok := <-watcher.Events: + if !ok { + return + } + if !event.Has(fsnotify.Chmod) { + path := event.Name + func() { + lock.Lock() + defer lock.Unlock() + if eventTimer != nil { + eventTimer.Stop() + } + paths.Add(path) + eventTimer = time.AfterFunc( + 2*time.Second, + updatePackage, + ) + }() + } + case err, ok := <-watcher.Errors: + if !ok { + return + } + log.WithError(err).Warn("filesystem watcher error") + } + } }() + + toWatchPath := a.pkg.UnpackedPath() + if toWatchPath != "" { + _, dirs, _ := utils.ListDir(toWatchPath) + for _, dir := range dirs { + log.WithField("package", toWatchPath).Infof("watching %s", dir) + err := watcher.Add(dir) + if err != nil { + log.WithError(err).WithField("package", toWatchPath).Warnf("failed to watch %s", dir) + } + } + } +} + +func (a *App) teardownPackageWatcher() { + if a.packageWatcher != nil { + a.packageWatcher.Close() + } } func (a *App) setNotAPackageContent(path string) { @@ -240,6 +313,7 @@ func (a *App) setUnpackedContent(pkg *ddpackage.Package) { errDlg.Show() return } + a.pkg.UpdateFromPaths([]string{filepath.Join(options.Path, "pack.json")}) err = a.pkg.LoadUnpackedPackJSON(options.Path) if err != nil { errDlg := dialog.NewError( @@ -291,31 +365,24 @@ func (a *App) setUnpackedContent(pkg *ddpackage.Package) { func (a *App) packPackage(path string, options ddpackage.PackOptions, genThumbnails bool) { a.disableButtons.Set(true) - thumbProgresVal := binding.NewFloat() - packProgresVal := binding.NewFloat() - progressVal := bindings.FloatMath( - func(d ...float64) float64 { - thumbP := d[0] - packP := d[1] - return thumbP*0.2 + packP*0.8 - }, - thumbProgresVal, - packProgresVal, - ) + progressVal := binding.NewFloat() + taskStr := binding.NewString() progressBar := widget.NewProgressBarWithData(progressVal) + taskLbl := widget.NewLabelWithData(taskStr) targetPath := filepath.Join(path, a.pkg.Name()+".dungeondraft_pack") progressDlg := dialog.NewCustomWithoutButtons( lang.X("pack.packageProgressDlg.title", "Packing to {{.Path}}", map[string]any{"Path": targetPath}), - progressBar, + container.NewVBox(taskLbl, progressBar), a.window, ) progressDlg.Show() go func() { if genThumbnails { + taskStr.Set(lang.X("task.genthumbnails.text", "Generating thumbnails ...")) err := a.pkg.GenerateThumbnails(func(p float64) { - thumbProgresVal.Set(p) + progressVal.Set(p) }) if err != nil { progressDlg.Hide() @@ -332,11 +399,12 @@ func (a *App) packPackage(path string, options ddpackage.PackOptions, genThumbna errDlg.Show() return } + progressVal.Set(1.0) } - thumbProgresVal.Set(1.0) + taskStr.Set(lang.X("task.package.text", "Packaging resources ...")) err := a.pkg.PackPackage(path, options, func(p float64) { - packProgresVal.Set(p) + progressVal.Set(p) }) progressDlg.Hide() if err != nil { diff --git a/internal/gui/package.go b/internal/gui/package.go index 25cc19b..b11f10b 100644 --- a/internal/gui/package.go +++ b/internal/gui/package.go @@ -5,6 +5,7 @@ import ( "image/color" "math" "path/filepath" + "slices" "strings" "time" @@ -22,7 +23,6 @@ import ( "github.com/ryex/dungeondraft-gopackager/internal/gui/bindings" "github.com/ryex/dungeondraft-gopackager/internal/gui/layouts" "github.com/ryex/dungeondraft-gopackager/internal/gui/widgets" - "github.com/ryex/dungeondraft-gopackager/internal/utils" "github.com/ryex/dungeondraft-gopackager/pkg/ddimage" "github.com/ryex/dungeondraft-gopackager/pkg/structures" ddcolor "github.com/ryex/dungeondraft-gopackager/pkg/structures/color" @@ -47,16 +47,17 @@ func (a *App) buildPackageTreeAndInfoPane(editable bool) fyne.CanvasObject { tree, treeSelected := a.buildPackageTree(filter) - leftSplit := container.New( - layouts.NewBottomExpandVBoxLayout(), - container.New( - layouts.NewRightExpandHBoxLayout(), - widget.NewLabel(lang.X("tree.label", "Resources")), - filterEntry, - ), - container.NewStack( - canvas.NewRectangle(theme.Color(theme.ColorNameInputBackground)), - tree, + leftSplit := container.NewPadded( + layouts.NewBottomExpandVBox( + container.New( + layouts.NewRightExpandHBoxLayout(), + widget.NewLabel(lang.X("tree.label", "Resources")), + filterEntry, + ), + container.NewStack( + canvas.NewRectangle(theme.Color(theme.ColorNameInputBackground)), + tree, + ), ), ) @@ -83,7 +84,7 @@ func (a *App) buildPackageTreeAndInfoPane(editable bool) fyne.CanvasObject { }) split := container.NewPadded(container.NewHSplit( - leftSplit, + container.NewPadded(leftSplit), container.NewPadded(rightSplit), )) @@ -94,16 +95,6 @@ func (a *App) buildPackageTree(filter binding.String) (*widget.Tree, binding.Str filterFunc := func(fi *structures.FileInfo) bool { return !fi.IsThumbnail() && !strings.HasSuffix(fi.ResPath, ".json") } - mappedList := bindings.NewMapping( - filter, - func(filter string) ([]*structures.FileInfo, error) { - log.Tracef("filtering tree list with '%s'", filter) - if filter == "" { - return a.pkg.FileList().Filter(filterFunc), nil - } - return a.pkg.FileList().Glob(filterFunc, filter) - }, - ) nodeTree := make(map[string][]string) selected := binding.NewString() @@ -157,13 +148,30 @@ func (a *App) buildPackageTree(filter binding.String) (*widget.Tree, binding.Str }, ) - bindings.ListenErr(mappedList, func(fil []*structures.FileInfo) { + filteredList := func() ([]*structures.FileInfo, error) { + val, err := filter.Get() + if err != nil { + return a.pkg.FileList().Filter(filterFunc), err + } + log.Tracef("filtering tree list with '%s'", val) + if val == "" { + return a.pkg.FileList().Filter(filterFunc), nil + } + return a.pkg.FileList().Glob(filterFunc, val) + } + + rebuildTree := func() { + fil, err := filteredList() + if err != nil { + log.WithError(err).Error("failed to retrieve filtered file list") + } log.Trace("rebuilding tree") nodeTree = buildInfoMaps(fil) tree.Refresh() - }, func(err error) { - log.WithError(err).Debug("file list fetch failure") - }) + } + + filter.AddListener(binding.NewDataListener(rebuildTree)) + a.packageUpdated.AddListener(binding.NewDataListener(rebuildTree)) tree.OnSelected = func(uid widget.TreeNodeID) { selected.Set(uid) @@ -207,29 +215,42 @@ func (a *App) buildFilePreview(info *structures.FileInfo) fyne.CanvasObject { return widget.NewLabel(fmt.Sprintf("Failed to read image data for %s", info.ResPath)) } - pathLabel := widget.NewLabel( - lang.X( - "preview.path.label", - "Path", + path := container.NewStack( + &canvas.Rectangle{ + FillColor: theme.Color(theme.ColorNameHeaderBackground), + CornerRadius: 4, + }, + layouts.NewRightExpandHBox( + container.NewCenter( + widget.NewLabel(lang.X( + "preview.path.label", + "Path", + )), + ), + container.NewPadded( + container.NewStack( + &canvas.Rectangle{ + FillColor: theme.Color(theme.ColorNameInputBackground), + CornerRadius: 4, + }, + container.NewPadded( + container.NewHScroll( + canvas.NewText(info.ResPath, theme.Color(theme.ColorNameForeground)), + ), + ), + ), + ), ), ) - pathEntry := widget.NewEntry() - pathEntry.SetText(info.CalcRelPath()) - pathEntry.OnChanged = func(_ string) { - pathEntry.SetText(info.CalcRelPath()) - } - pathEntry.Refresh() - path := container.New( - layouts.NewRightExpandHBoxLayout(), - pathLabel, - pathEntry, - ) tooLarge := container.NewCenter( widget.NewLabel(lang.X("preview.tooLarge", "This file is too large!\nOpen it in a text editor.")), ) - bg := canvas.NewRectangle(theme.Color(theme.ColorNameInputBackground)) + bg := &canvas.Rectangle{ + FillColor: theme.Color(theme.ColorNameInputBackground), + CornerRadius: 4, + } if !ddimage.PathIsSupportedImage(info.RelPath) { textContent := string(fileData) if len(strings.Split(textContent, "\n")) > 200 { @@ -248,7 +269,7 @@ func (a *App) buildFilePreview(info *structures.FileInfo) fyne.CanvasObject { path, container.NewStack( bg, - textEntry, + container.NewPadded(textEntry), container.NewPadded( container.NewVBox( container.NewHBox(layout.NewSpacer(), copyBtn), @@ -288,8 +309,11 @@ func (a *App) buildFilePreview(info *structures.FileInfo) fyne.CanvasObject { path, container.NewPadded(container.NewStack( bg, - container.NewScroll( - imgW, + container.New( + layout.NewCustomPaddedLayout(8, 8, 8, 8), + container.NewScroll( + imgW, + ), ), )), )) @@ -336,7 +360,39 @@ func (a *App) buildTagInfo(info *structures.FileInfo, editable bool) fyne.Canvas } }, ) - content := layouts.NewTopExpandVBox(tagsList) + + content := layouts.NewTopExpandVBox( + layouts.NewBottomExpandVBox( + container.NewStack( + &canvas.Rectangle{ + FillColor: theme.Color(theme.ColorNameHeaderBackground), + CornerRadius: 4, + }, + layouts.NewRightExpandHBox( + container.NewCenter( + widget.NewLabel(lang.X( + "preview.tab.tags.label", + "Tags for", + )), + ), + container.NewPadded( + container.NewStack( + &canvas.Rectangle{ + FillColor: theme.Color(theme.ColorNameInputBackground), + CornerRadius: 4, + }, + container.NewPadded( + container.NewHScroll( + canvas.NewText(info.ResPath, theme.Color(theme.ColorNameForeground)), + ), + ), + ), + ), + ), + ), + tagsList, + ), + ) if editable { tagSelecter := widget.NewSelectEntry(a.pkg.Tags().AllTags()) @@ -354,7 +410,7 @@ func (a *App) buildTagInfo(info *structures.FileInfo, editable bool) fyne.Canvas content.Add(layouts.NewLeftExpandHBox(tagSelecter, addBtn)) } - return content + return container.NewPadded(content) } func (a *App) saveUnpackedTags() { @@ -364,7 +420,7 @@ func (a *App) saveUnpackedTags() { } a.tagSaveTimer = time.AfterFunc(500*time.Millisecond, func() { a.tagSaveTimer = nil - err := a.pkg.SaveUnpackedTags() + err := a.pkg.WriteUnpackedTags() if err != nil { a.showErrorDialog(err) } @@ -602,11 +658,11 @@ func buildInfoMaps(infoList []*structures.FileInfo) map[string][]string { path = next dir, _ = filepath.Split(next) next = dir[:max(len(dir)-1, 0)] - if !utils.InSlice(path, nodeTree[next]) { + if !slices.Contains(nodeTree[next], path) { nodeTree[next] = append(nodeTree[next], path) } } - if !utils.InSlice(path, nodeTree[next]) { + if !slices.Contains(nodeTree[next], path) { nodeTree[next] = append(nodeTree[next], path) } } diff --git a/internal/gui/translation/en-US.json b/internal/gui/translation/en-US.json index 999965d..9c1f6c4 100644 --- a/internal/gui/translation/en-US.json +++ b/internal/gui/translation/en-US.json @@ -18,10 +18,12 @@ "preview.path.label": "Path", "preview.tab.resource": "Resource", "preview.tab.tags": "Tags", + "preview.tab.tags.label": "Tags for", "preview.tab.metadata": "Settings", "unpack.wait": "Loading {{.Path}} ...", "unpack.outPath.placeholder": "Where to extract resources", "unpack.extractBtn.text": "Extract", + "unpack.infoBtn.text": "Package Information", "unpack.option.overwrite.text": "Overwrite existing files", "unpack.option.repTex.text": "Rip Textures to Png", "unpack.option.thumbnails.text": "Extract thumbnails", @@ -71,6 +73,8 @@ "metadata.name.label": "Name", "metadata.tilesetType.label": "Tileset Type", "metadata.colorPickDialog.title": "Pick a default color", + "task.genthumbnails.text": "Generating thumbnails ...", + "task.package.text": "Packaging resources ...", "Open": "Open", "Cancel": "Cancel", "File": "File", diff --git a/internal/gui/unpack.go b/internal/gui/unpack.go index 586256d..c23870d 100644 --- a/internal/gui/unpack.go +++ b/internal/gui/unpack.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "path/filepath" + "strings" "fyne.io/fyne/v2" "fyne.io/fyne/v2/container" @@ -14,6 +15,7 @@ import ( "fyne.io/fyne/v2/widget" "github.com/ryex/dungeondraft-gopackager/internal/gui/layouts" + "github.com/ryex/dungeondraft-gopackager/internal/utils" "github.com/ryex/dungeondraft-gopackager/pkg/ddpackage" log "github.com/sirupsen/logrus" @@ -31,10 +33,7 @@ func (a *App) loadPack(path string) { ) a.disableButtons.Set(true) - if a.pkg != nil { - a.pkg.Close() - } - a.pkg = nil + a.resetPkg() go func() { l := log.WithFields(log.Fields{ @@ -45,7 +44,12 @@ func (a *App) loadPack(path string) { err := pkg.LoadFromPackedPath(path, func(p float64, curRes string) { activityProgress.Set(p) - activityStr.Set(curRes) + activityStr.Set( + "res://" + utils.TruncatePathHumanFriendly( + strings.TrimPrefix(curRes, "res://"), + 80, + ), + ) }) if err != nil { l.WithError(err).Error("could not load path") diff --git a/internal/utils/utils.go b/internal/utils/utils.go index 200aced..02745e3 100644 --- a/internal/utils/utils.go +++ b/internal/utils/utils.go @@ -12,13 +12,6 @@ import ( "github.com/sirupsen/logrus" ) -func AssertTrue(condition bool, msg string) { - if condition { - return - } - logrus.Fatalf("assertion failure: %s", msg) -} - func MapKeys[K comparable, V any](m map[K]V) []K { keys := make([]K, len(m)) i := 0 @@ -32,7 +25,7 @@ func MapKeys[K comparable, V any](m map[K]V) []K { // FileExists tests if a file exists and is not a Directory func FileExists(filename string) bool { info, err := os.Stat(filename) - if errors.Is(err, os.ErrNotExist) { + if errors.Is(err, os.ErrNotExist) { return false } return !info.IsDir() @@ -92,16 +85,6 @@ func RipTexture(data []byte) (fileExt string, fileData []byte, err error) { return } -// InSlice tests inclusion of a string in a slice -func InSlice[T comparable](a T, list []T) bool { - for i := 0; i < len(list); i++ { - if list[i] == a { - return true - } - } - return false -} - func SplitOne(s string, sep string) (string, string) { x := strings.SplitN(s, sep, 1) return x[0], x[1] @@ -169,7 +152,6 @@ func Pad(out io.Writer, bytes int64) error { } func TruncatePathHumanFriendly(path string, maxLen int) string { - path = filepath.Clean(path) repeat := false depth := 1 for len(path) > maxLen && !repeat { diff --git a/pkg/ddimage/ddimage.go b/pkg/ddimage/ddimage.go index 2f085e2..33e72ce 100644 --- a/pkg/ddimage/ddimage.go +++ b/pkg/ddimage/ddimage.go @@ -12,6 +12,7 @@ import ( "io" "os" "path/filepath" + "slices" "strings" _ "golang.org/x/image/bmp" @@ -27,7 +28,6 @@ import ( // for webp 1.4.0 _ "github.com/chai2010/webp" - "github.com/ryex/dungeondraft-gopackager/internal/utils" "github.com/sunshineplan/imgconv" "github.com/srwiley/oksvg" @@ -107,14 +107,14 @@ func ResizeVirticalAndCropWidth(img image.Image, height int, width int) image.Im func PathIsSupportedImage(path string) bool { ext := strings.ToLower(filepath.Ext(path)) - return utils.InSlice(ext, []string{ + return slices.Contains([]string{ ".jpg", ".jpeg", ".png", ".webp", ".gif", ".tif", ".tiff", ".bmp", ".svg", - }) + }, ext) } func PathIsSupportedDDImage(path string) bool { ext := strings.ToLower(filepath.Ext(path)) - return utils.InSlice(ext, []string{ + return slices.Contains([]string{ ".jpg", ".jpeg", ".png", ".webp", ".bmp", ".svg", - }) + }, ext) } diff --git a/pkg/ddpackage/ddpackage.go b/pkg/ddpackage/ddpackage.go index fcccd79..8e947fa 100644 --- a/pkg/ddpackage/ddpackage.go +++ b/pkg/ddpackage/ddpackage.go @@ -235,8 +235,11 @@ func (p *Package) AddResource(fInfo *structures.FileInfo) { // DO NOT USE WITHOUT flLock func (p *Package) addResource(fInfo *structures.FileInfo) { - p.fileList = append(p.fileList, fInfo) - p.resourceMap[fInfo.ResPath] = fInfo + if _, ok := p.resourceMap[fInfo.ResPath]; !ok { + p.fileList = append(p.fileList, fInfo) + p.resourceMap[fInfo.ResPath] = fInfo + p.log.Debugf("added %s", fInfo.ResPath) + } } func (p *Package) resetData() { diff --git a/pkg/ddpackage/pack.go b/pkg/ddpackage/pack.go index 7855bee..a566d24 100644 --- a/pkg/ddpackage/pack.go +++ b/pkg/ddpackage/pack.go @@ -8,7 +8,7 @@ import ( "os" "path/filepath" "runtime" - "sort" + "slices" "strings" "github.com/ryex/dungeondraft-gopackager/internal/utils" @@ -149,12 +149,12 @@ func (p *Package) BuildFileList() (errs []error) { return p.buildFileList(nil) } -func (p *Package) UpdateFromPathProgress(path string, progressCallback func(p float64, curPath string)) (errs []error) { - return p.updateFromPath(path, progressCallback) +func (p *Package) UpdateFromPathsProgress(paths []string, progressCallback func(p float64, curPath string)) (errs []error) { + return p.updateFromPaths(paths, progressCallback) } -func (p *Package) UpdateFromPath(path string) (errs []error) { - return p.updateFromPath(path, nil) +func (p *Package) UpdateFromPaths(paths []string) (errs []error) { + return p.updateFromPaths(paths, nil) } // Rebuilds the list of files at the target directory for inclusion in a .dungeondraft_pack file @@ -166,12 +166,12 @@ func (p *Package) buildFileList(progressCallback func(p float64, curPath string) return []error{ErrPackageNotUnpacked} } p.resetData() - return p.updateFromPath(p.unpackedPath, progressCallback) + return p.updateFromPaths([]string{p.unpackedPath}, progressCallback) } // updates the current list of files at the target directory for inclusion in a .dungeondraft_pack file // on duplicate entries updates the current info -func (p *Package) updateFromPath(path string, progressCallback func(p float64, curPath string)) (errs []error) { +func (p *Package) updateFromPaths(paths []string, progressCallback func(p float64, curPath string)) (errs []error) { if p.unpackedPath == "" { return []error{ErrUnsetUnpackedPath} } @@ -179,44 +179,47 @@ func (p *Package) updateFromPath(path string, progressCallback func(p float64, c return []error{ErrPackageNotUnpacked} } - p.log.WithField("listPath", path).Debug("beginning directory traversal to collect file list") + dirs := structures.NewSet[string]() + files := structures.NewSet[string]() + toRemove := structures.NewSet[string]() - path, err := filepath.Abs(path) - if err != nil { - return []error{err} - } - - var toRemove []string - var files []string - pathIsDir := false + for _, path := range paths { + absPath, err := filepath.Abs(path) + if err != nil { + errs = append(errs, err) + continue + } + statInfo, err := os.Stat(absPath) + if err != nil { + errs = append(errs, err) + continue + } + if statInfo.IsDir() { + dirs.Add(absPath) + p.log.WithField("listPath", absPath).Debug("beginning directory traversal to collect file list") + toAdd, _, _ := utils.ListDir(absPath) + files.AddM(toAdd...) + } else { + files.Add(absPath) + } - statInfo, err := os.Stat(path) - if err != nil { - return []error{err} } - if statInfo.IsDir() { - pathIsDir = true - files, _, _ = utils.ListDir(path) - } else { - files = []string{path} - } - filesSet := structures.SetFrom(files) p.flLock.Lock() defer p.flLock.Unlock() - if pathIsDir { + for _, dir := range dirs.AsSlice() { for _, fi := range p.fileList { - if isSub, _ := utils.PathIsSub(path, fi.Path); isSub && !filesSet.Has(fi.Path) { - toRemove = append(toRemove, fi.ResPath) + if isSub, _ := utils.PathIsSub(dir, fi.Path); isSub && !files.Has(fi.Path) { + toRemove.Add(fi.ResPath) } } } - for i, file := range files { + for i, file := range files.AsSlice() { // filter extensions ext := strings.ToLower(filepath.Ext(file)) - if !utils.InSlice(ext, p.packOptions.ValidExts) { + if !slices.Contains(p.packOptions.ValidExts, ext) { continue } // construct resource path @@ -226,6 +229,10 @@ func (p *Package) updateFromPath(path string, progressCallback func(p float64, c errs = append(errs, err) continue } + + if runtime.GOOS == "windows" { // windows path separators..... + relPath = strings.ReplaceAll(relPath, "\\", "/") + } resPath := fmt.Sprintf("res://packs/%s/%s", p.id, relPath) // update or add @@ -237,9 +244,13 @@ func (p *Package) updateFromPath(path string, progressCallback func(p float64, c errs = append(errs, err) continue } - existing.Size = statInfo.Size() + size := statInfo.Size() + if size != existing.Size { + existing.Size = size + p.log.WithField("res", existing.ResPath).Infof("Updating resource size to %d", size) + } } else { - fInfo, err := p.NewFileInfo(NewFileInfoOptions{Path: file}) + fInfo, err := p.NewFileInfo(NewFileInfoOptions{Path: file, ResPath: &resPath, RelPath: &relPath}) if err != nil { errs = append(errs, err) continue @@ -248,11 +259,11 @@ func (p *Package) updateFromPath(path string, progressCallback func(p float64, c p.addResource(fInfo) } if progressCallback != nil { - progressCallback(float64(i)/float64(len(files)), file) + progressCallback(float64(i)/float64(files.Size()), file) } } - for _, res := range toRemove { + for _, res := range toRemove.AsSlice() { p.log.Warnf("removing %s (file missing)", res) p.removeResource(res) } @@ -281,12 +292,20 @@ func (p *Package) updateFromPath(path string, progressCallback func(p float64, c } p.resourceMap[packJSONResPath] = packJSONInfo - // sort list and assure pack json it first - sort.Sort(p.fileList) - p.fileList = append(p.fileList, nil) - copy(p.fileList[1:], p.fileList) - p.fileList[0] = packJSONInfo - + // sort list and assure pack json is first + slices.SortFunc(p.fileList, func(a, b *structures.FileInfo) int { + if a.ResPath < b.ResPath { + return -1 + } + if a.ResPath > b.ResPath { + return 1 + } + return 0 + }) + p.fileList = slices.CompactFunc(p.fileList, func(a, b *structures.FileInfo) bool { + return a.ResPath == b.ResPath + }) + p.fileList = slices.Insert(p.fileList, 0, packJSONInfo) return } diff --git a/pkg/ddpackage/pack_data.go b/pkg/ddpackage/pack_data.go index 594fa49..882aedd 100644 --- a/pkg/ddpackage/pack_data.go +++ b/pkg/ddpackage/pack_data.go @@ -118,37 +118,6 @@ func (p *Package) loadUnpackedTags() error { return nil } -func (p *Package) SaveUnpackedTags() error { - if p.mode != PackageModeUnpacked { - return ErrPackageNotUnpacked - } - tagsPath := filepath.Join(p.unpackedPath, "data", "default.dungeondraft_tags") - - l := p.log. - WithField("path", p.unpackedPath). - WithField("tagsPath", tagsPath) - tagsBytes, err := json.MarshalIndent(&p.tags, "", " ") - if err != nil { - l.WithError(err). - Error("can't save tags file") - return errors.Join(err, ErrTagsWrite) - } - - err = os.MkdirAll(filepath.Dir(tagsPath), 0o777) - if err != nil { - l.WithError(err).Error("can't save wall data") - return errors.Join(err, ErrTagsWrite) - } - - err = os.WriteFile(tagsPath, tagsBytes, 0o644) - if err != nil { - l.WithError(err). - Error("can't save tags file") - return errors.Join(err, ErrTagsWrite) - } - return nil -} - func (p *Package) SaveUnpackedWall(resPath string) error { if p.mode != PackageModeUnpacked { return ErrPackageNotUnpacked @@ -183,6 +152,10 @@ func (p *Package) SaveUnpackedWall(resPath string) error { l.WithError(err).Error("can't save wall data") return errors.Join(err, ErrWallSave) } + errs := p.updateFromPaths([]string{wallDataPath}, nil) + if len(errs) != 0 { + return errors.Join(errs...) + } return nil } @@ -219,6 +192,10 @@ func (p *Package) SaveUnpackedTileset(resPath string) error { l.WithError(err).Error("can't save wall data") return errors.Join(err, ErrTilesetSave) } + errs := p.updateFromPaths([]string{tilesetDataPath}, nil) + if len(errs) != 0 { + return errors.Join(errs...) + } return nil } @@ -290,6 +267,10 @@ func (p *Package) WriteUnpackedTags() error { Error("failed to write tags file") return errors.Join(err, errors.New("failed to write tags file")) } + errs := p.updateFromPaths([]string{tagsPath}, nil) + if len(errs) != 0 { + return errors.Join(errs...) + } return nil } @@ -343,6 +324,10 @@ func (p *Package) WriteResourceMetadata() error { Error("failed to write metadata file") return errors.Join(err, fmt.Errorf("failed to write metadata file for %s", fi.ResPath)) } + errs := p.updateFromPaths([]string{fi.Path}, nil) + if len(errs) != 0 { + return errors.Join(errs...) + } } } diff --git a/pkg/ddpackage/unpack.go b/pkg/ddpackage/unpack.go index 1ac2bdd..585714d 100644 --- a/pkg/ddpackage/unpack.go +++ b/pkg/ddpackage/unpack.go @@ -448,8 +448,6 @@ func (p *Package) newFileInfoPacked(resPath []byte, infoBytes structures.FileInf func (p *Package) updatePackedFileInfoAfter() { for _, fi := range p.fileList { - p.log.Infof("unpdating info for %s", fi.ResPath) - fi.RelPath = strings.TrimPrefix(strings.TrimPrefix(fi.ResPath, "res://packs/"), p.id+"/") if fi.IsTexture() { hash := md5.Sum([]byte(fi.ResPath)) diff --git a/pkg/structures/files.go b/pkg/structures/files.go index f8c392b..8aa58c5 100644 --- a/pkg/structures/files.go +++ b/pkg/structures/files.go @@ -213,11 +213,6 @@ func(fil FileInfoList) RemoveRes(res string) *FileInfo { return nil } -// sort.Interface - -func (fil FileInfoList) Len() int { return len(fil) } -func (fil FileInfoList) Swap(i, j int) { fil[i], fil[j] = fil[j], fil[i] } -func (fil FileInfoList) Less(i, j int) bool { return fil[i].ResPath < fil[j].ResPath } var replaces = regexp.MustCompile(`(\.)|(\*\*/)|(\*\*$)|(\*)|(\[)|(\])|(\})|(\{)|(\+)|(\()|(\))|([^/\*])`)