diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e6a198d..7ae09e99 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,8 +6,8 @@ - Added helper `filesystem` methods: - `filesystem.NewWriter(key, opts)` to allow direct file create from an `io.Reader` value. - - `filesystem.OnNewWriter()` hook to allow listening for new/to-be-creaded files (app level equivalent `app.OnFilesystemNewWriter()` hook is also available). - - `filesystem.OnDelete()` hook to allow listening for deleted files (app level equivalent `app.OnFilesystemDelete()` hook is also available). + - `filesystem.OnNewWriter()` low-level hook to allow listening for new/to-be-creaded files _(app level equivalent hook is also available but not exposed for now to avoid introducing breaking changes)_. + - `filesystem.OnDelete()` low-level hook to allow listening for deleted files _(app level equivalent hook is also available but not exposed for now to avoid introducing breaking changes)_. - Added new `DELETE /api/logs` endpoint and UI control to delete all logs without changing the `maxDays` retention setting. diff --git a/core/app.go b/core/app.go index 1351f7de..073cf0cd 100644 --- a/core/app.go +++ b/core/app.go @@ -1266,14 +1266,14 @@ type App interface { // Filesystem event hooks // --------------------------------------------------------------- - // OnFilesystemNewWriter is a low level hook for app.NewFilesystem() + // onFilesystemNewWriter is an internal hook for app.NewFilesystem() // instances that is triggered on every storage filesystem writer initialization // (aka. whenever attempting to create a new file). - OnFilesystemNewWriter() *hook.Hook[*FilesystemNewWriterEvent] + onFilesystemNewWriter() *hook.Hook[*FilesystemNewWriterEvent] - // OnFilesystemDelete is a low level hook for app.NewFilesystem() + // onFilesystemDelete is an internal hook for app.NewFilesystem() // instances that is triggered for every storage file delete call. - OnFilesystemDelete() *hook.Hook[*FilesystemDeleteEvent] + onFilesystemDelete() *hook.Hook[*FilesystemDeleteEvent] // --------------------------------------------------------------- // Realtime API event hooks diff --git a/core/backup_create.go b/core/backup_create.go index d5395393..6a62f0fe 100644 --- a/core/backup_create.go +++ b/core/backup_create.go @@ -50,7 +50,7 @@ import ( // // 3. Stop listening for DELETED files. // -// 4. Start listening for NEW storage files and mark all new files as excluded. +// 4. Start listening for NEW storage files and mark all new files from this point as "excluded". // // 5. Copy the logs database with VACUUM INTO, write it in the zip and mark it as "excluded". // @@ -142,8 +142,8 @@ func createZip(be *BackupEvent, tempZipPath string) error { const tempFilesHookId = "__pbTempBackupFilesystemWatcher__" defer func() { // unbind again in cacase of an error - be.App.OnFilesystemDelete().Unbind(tempFilesHookId) - be.App.OnFilesystemNewWriter().Unbind(tempFilesHookId) + be.App.onFilesystemDelete().Unbind(tempFilesHookId) + be.App.onFilesystemNewWriter().Unbind(tempFilesHookId) }() zf, err := os.Create(tempZipPath) @@ -170,8 +170,9 @@ func createZip(be *BackupEvent, tempZipPath string) error { // init deleted files tracker // --------------------------------------------------------------- - be.App.OnFilesystemDelete().Bind(&hook.Handler[*FilesystemDeleteEvent]{ - Id: tempFilesHookId, + be.App.onFilesystemDelete().Bind(&hook.Handler[*FilesystemDeleteEvent]{ + Id: tempFilesHookId, + Priority: -99, Func: func(e *FilesystemDeleteEvent) error { // note: the zip header name allow only forward slashes zipPath := path.Join(LocalStorageDirName, e.FileKey) @@ -214,8 +215,8 @@ func createZip(be *BackupEvent, tempZipPath string) error { return err } - // eageerly stop listenining for deleted files since we already have what we needed - be.App.OnFilesystemDelete().Unbind(tempFilesHookId) + // eagerly stop listening for deleted files since we already have what we needed + be.App.onFilesystemDelete().Unbind(tempFilesHookId) be.App.Logger().Debug( logPrefix+dataDBFilename+" copy completed", @@ -235,12 +236,13 @@ func createZip(be *BackupEvent, tempZipPath string) error { // init to-be-created files tracker // --------------------------------------------------------------- - be.App.OnFilesystemNewWriter().Bind(&hook.Handler[*FilesystemNewWriterEvent]{ - Id: tempFilesHookId, + be.App.onFilesystemNewWriter().Bind(&hook.Handler[*FilesystemNewWriterEvent]{ + Id: tempFilesHookId, + Priority: -99, Func: func(e *FilesystemNewWriterEvent) error { if !be.App.Settings().S3.Enabled { // mark for exclude even if the writer eventually fails - // (all record files have random name so collusions are unlikely) + // (all record files have random name so collisions are unlikely) name := normalizePathExclude(filepath.Join(LocalStorageDirName, e.FileKey)) excluded.Set(name, struct{}{}) } @@ -296,6 +298,10 @@ func copyFileToZip(w *zip.Writer, localPath string, zipPath string) error { return err } + if info.IsDir() { + return nil + } + h, err := zip.FileInfoHeader(info) if err != nil { return err @@ -329,7 +335,7 @@ func copyDirToZip(w *zip.Writer, fsys fs.FS, excludedPrefixes *store.Store[strin // skip excluded prefixes if excludedPrefixes != nil { check := normalizePathExclude(name) - prefixes := excludedPrefixes.Keys() // refetch in case to avoid races + prefixes := excludedPrefixes.Keys() // refetch to avoid races for _, prefix := range prefixes { if strings.HasPrefix(check, prefix) { if d.IsDir() { diff --git a/core/base.go b/core/base.go index 425d871c..21128d5a 100644 --- a/core/base.go +++ b/core/base.go @@ -150,8 +150,8 @@ type BaseApp struct { onMailerRecordAuthAlertSend *hook.Hook[*MailerRecordEvent] // filesystem event hooks - onFilesystemNewWriter *hook.Hook[*FilesystemNewWriterEvent] - onFilesystemDelete *hook.Hook[*FilesystemDeleteEvent] + _onFilesystemNewWriter *hook.Hook[*FilesystemNewWriterEvent] + _onFilesystemDelete *hook.Hook[*FilesystemDeleteEvent] // realtime api event hooks onRealtimeConnectRequest *hook.Hook[*RealtimeConnectRequestEvent] @@ -302,8 +302,12 @@ func (app *BaseApp) initHooks() { app.onMailerRecordAuthAlertSend = &hook.Hook[*MailerRecordEvent]{} // filesystem event hooks - app.onFilesystemNewWriter = &hook.Hook[*FilesystemNewWriterEvent]{} - app.onFilesystemDelete = &hook.Hook[*FilesystemDeleteEvent]{} + // + // intentionally not exposed since the events are too "chatty" and + // can cause unnecessery userland tests breaking changes; + // reevaluate once refactoring the file_field + app._onFilesystemNewWriter = &hook.Hook[*FilesystemNewWriterEvent]{} + app._onFilesystemDelete = &hook.Hook[*FilesystemDeleteEvent]{} // realtime API event hooks app.onRealtimeConnectRequest = &hook.Hook[*RealtimeConnectRequestEvent]{} @@ -743,26 +747,26 @@ func (app *BaseApp) NewFilesystem() (fsys *filesystem.System, err error) { } // attach delete hook - if app.onFilesystemDelete.Length() > 0 { + if app._onFilesystemDelete.Length() > 0 { fsys.OnDelete().BindFunc(func(originalEvent *filesystem.DeleteEvent) error { appEvent := new(FilesystemDeleteEvent) appEvent.DeleteEvent = originalEvent appEvent.App = app - return app.onFilesystemDelete.Trigger(appEvent, func(fde *FilesystemDeleteEvent) error { + return app._onFilesystemDelete.Trigger(appEvent, func(fde *FilesystemDeleteEvent) error { return originalEvent.Next() }) }) } // attach write hook - if app.onFilesystemNewWriter.Length() > 0 { + if app._onFilesystemNewWriter.Length() > 0 { fsys.OnNewWriter().BindFunc(func(originalEvent *filesystem.NewWriterEvent) error { appEvent := new(FilesystemNewWriterEvent) appEvent.NewWriterEvent = originalEvent appEvent.App = app - return app.onFilesystemNewWriter.Trigger(appEvent, func(fwe *FilesystemNewWriterEvent) error { + return app._onFilesystemNewWriter.Trigger(appEvent, func(fwe *FilesystemNewWriterEvent) error { return originalEvent.Next() }) }) @@ -1063,12 +1067,12 @@ func (app *BaseApp) OnMailerRecordAuthAlertSend(tags ...string) *hook.TaggedHook // Filesystem event hooks // ------------------------------------------------------------------- -func (app *BaseApp) OnFilesystemNewWriter() *hook.Hook[*FilesystemNewWriterEvent] { - return app.onFilesystemNewWriter +func (app *BaseApp) onFilesystemNewWriter() *hook.Hook[*FilesystemNewWriterEvent] { + return app._onFilesystemNewWriter } -func (app *BaseApp) OnFilesystemDelete() *hook.Hook[*FilesystemDeleteEvent] { - return app.onFilesystemDelete +func (app *BaseApp) onFilesystemDelete() *hook.Hook[*FilesystemDeleteEvent] { + return app._onFilesystemDelete } // ------------------------------------------------------------------- diff --git a/plugins/jsvm/binds_test.go b/plugins/jsvm/binds_test.go index f138a787..0d8ca9b9 100644 --- a/plugins/jsvm/binds_test.go +++ b/plugins/jsvm/binds_test.go @@ -1613,7 +1613,7 @@ func TestHooksBindsCount(t *testing.T) { vm := goja.New() hooksBinds(app, vm, nil) - testBindsCount(vm, "this", 84, t) + testBindsCount(vm, "this", 82, t) } func TestHooksBinds(t *testing.T) { diff --git a/tests/app.go b/tests/app.go index 5299e806..c22b9d2e 100644 --- a/tests/app.go +++ b/tests/app.go @@ -549,6 +549,21 @@ func NewTestAppWithConfig(config core.BaseAppConfig) (*TestApp, error) { Priority: -99999, }) + // t.OnFilesystemDelete().Bind(&hook.Handler[*core.FilesystemDeleteEvent]{ + // Func: func(e *core.FilesystemDeleteEvent) error { + // t.registerEventCall("OnFilesystemDelete") + // return e.Next() + // }, + // Priority: -99999, + // }) + // t.OnFilesystemNewWriter().Bind(&hook.Handler[*core.FilesystemNewWriterEvent]{ + // Func: func(e *core.FilesystemNewWriterEvent) error { + // t.registerEventCall("OnFilesystemNewWriter") + // return e.Next() + // }, + // Priority: -99999, + // }) + t.OnRealtimeConnectRequest().Bind(&hook.Handler[*core.RealtimeConnectRequestEvent]{ Func: func(e *core.RealtimeConnectRequestEvent) error { t.registerEventCall("OnRealtimeConnectRequest") diff --git a/tools/filesystem/filesystem.go b/tools/filesystem/filesystem.go index a343569e..d8df726f 100644 --- a/tools/filesystem/filesystem.go +++ b/tools/filesystem/filesystem.go @@ -482,7 +482,7 @@ func (s *System) DeletePrefix(prefix string) []error { // A trailing slash will be appended to a non-empty dir string argument // to ensure that the checked prefix is a "directory". // -// Returns "false" in case the has at least one file, otherwise - "true". +// Returns "false" in case it has at least one file, otherwise - "true". func (s *System) IsEmptyDir(dir string) bool { if dir != "" && !strings.HasSuffix(dir, "/") { dir += "/"