Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix data corruption when moving folders #4169

Merged
merged 2 commits into from
Oct 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions pkg/file/move.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,3 +212,34 @@ func (m *Mover) rollback() {
}
}
}

// correctSubFolderHierarchy sets the path of all contained folders to be relative to the given folder.
// It does not move the folder hierarchy in the filesystem.
func correctSubFolderHierarchy(ctx context.Context, rw models.FolderReaderWriter, folder *models.Folder) error {
folders, err := rw.FindByParentFolderID(ctx, folder.ID)
if err != nil {
return fmt.Errorf("finding contained folders in folder %s: %w", folder.Path, err)
}

folderPath := folder.Path

for _, f := range folders {
oldPath := f.Path
folderBasename := filepath.Base(f.Path)
correctPath := filepath.Join(folderPath, folderBasename)

logger.Debugf("updating folder %s to %s", oldPath, correctPath)

f.Path = correctPath
if err := rw.Update(ctx, f); err != nil {
return fmt.Errorf("updating folder path %s -> %s: %w", oldPath, f.Path, err)
}

// recurse
if err := correctSubFolderHierarchy(ctx, rw, f); err != nil {
return err
}
}

return nil
}
5 changes: 5 additions & 0 deletions pkg/file/scan.go
Original file line number Diff line number Diff line change
Expand Up @@ -593,6 +593,11 @@ func (s *scanJob) handleFolderRename(ctx context.Context, file scanFile) (*model
return nil, fmt.Errorf("updating folder for rename %q: %w", renamedFrom.Path, err)
}

// #4146 - correct sub-folders to have the correct path
if err := correctSubFolderHierarchy(ctx, s.Repository.FolderStore, renamedFrom); err != nil {
return nil, fmt.Errorf("correcting sub folder hierarchy for %q: %w", renamedFrom.Path, err)
}

return renamedFrom, nil
}

Expand Down
2 changes: 1 addition & 1 deletion pkg/sqlite/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ const (
dbConnTimeout = 30
)

var appSchemaVersion uint = 51
var appSchemaVersion uint = 52

//go:embed migrations/*.sql
var migrationsBox embed.FS
Expand Down
79 changes: 79 additions & 0 deletions pkg/sqlite/migrations/52_postmigrate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package migrations

import (
"context"
"fmt"
"path/filepath"
"strings"

"github.com/jmoiron/sqlx"
"github.com/stashapp/stash/pkg/logger"
"github.com/stashapp/stash/pkg/sqlite"
)

type schema52Migrator struct {
migrator
}

func post52(ctx context.Context, db *sqlx.DB) error {
logger.Info("Running post-migration for schema version 52")

m := schema52Migrator{
migrator: migrator{
db: db,
},
}

return m.migrate(ctx)
}

func (m *schema52Migrator) migrate(ctx context.Context) error {
if err := m.withTxn(ctx, func(tx *sqlx.Tx) error {
query := "SELECT `folders`.`id`, `folders`.`path`, `parent_folder`.`path` FROM `folders` " +
"INNER JOIN `folders` AS `parent_folder` ON `parent_folder`.`id` = `folders`.`parent_folder_id`"

rows, err := m.db.Query(query)
if err != nil {
return err
}
defer rows.Close()

for rows.Next() {
var (
id int
folderPath string
parentFolderPath string
)

err := rows.Scan(&id, &folderPath, &parentFolderPath)
if err != nil {
return err
}

// ensure folder path is correct
if !strings.HasPrefix(folderPath, parentFolderPath) {
logger.Debugf("folder path %s does not have prefix %s. Correcting...", folderPath, parentFolderPath)

// get the basename of the zip folder path and append it to the correct path
folderBasename := filepath.Base(folderPath)
correctPath := filepath.Join(parentFolderPath, folderBasename)

logger.Infof("correcting folder path %s to %s", folderPath, correctPath)

if _, err := m.db.Exec("UPDATE folders SET path = ? WHERE id = ?", correctPath, id); err != nil {
return fmt.Errorf("error updating folder path %s to %s: %w", folderPath, correctPath, err)
}
}
}

return rows.Err()
}); err != nil {
return err
}

return nil
}

func init() {
sqlite.RegisterPostMigration(52, post52)
}
1 change: 1 addition & 0 deletions pkg/sqlite/migrations/52_zip_folder_data_correct.up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
-- no schema changes