Repository: syncthing/syncthing
File: lib/syncthing/utils.go (Go)

Code before:
}

// Attempts migration of the old (LevelDB-based) database type to the new (SQLite-based) type
// This will attempt to provide a temporary API server during the migration, if `apiAddr` is not empty.
func TryMigrateDatabase(ctx context.Context, deleteRetention time.Duration, apiAddr string) error {
	oldDBDir := locations.Get(locations.LegacyDatabase)
	if _, err := os.Lstat(oldDBDir); err != nil {
		// No old database
		return nil
	}

	be, err := backend.OpenLevelDBRO(oldDBDir)
	if err != nil {
		// Apparently, not a valid old database
		return nil
	}
	defer be.Close()

	// Start a temporary API server during the migration
	if apiAddr != "" {
		api := migratingAPI{addr: apiAddr}
		apiCtx, cancel := context.WithCancel(ctx)
		defer cancel()
		go func() {
			if err := api.Serve(apiCtx); err != nil {
				slog.Warn("Failed to serve temporary API server", slogutil.Error(err))
			}
		}()
	}

	sdb, err := sqlite.OpenForMigration(locations.Get(locations.Database))
	if err != nil {
		return err
	}
	defer sdb.Close()

	miscDB := db.NewMiscDB(sdb)
	if when, ok, err := miscDB.Time("migrated-from-leveldb-at"); err == nil && ok {
		slog.Error("Old-style database present but already migrated; please manually move or remove.", slog.Any("migratedAt", when), slogutil.FilePath(oldDBDir))
		return nil
	}

	slog.Info("Migrating old-style database to SQLite; this may take a while...")
	t0 := time.Now()

	ll, err := olddb.NewLowlevel(be)
	if err != nil {
		return err
	}

	totFiles, totBlocks := 0, 0

Review comment (bug):
Ah, unfortunately, the better way is like it was despite what the linter thinks. We always get a non-nil error here since we shut down the server when we're done with it, and it's unnecessary effort (imho) to try to distinguish the intentional shutdown from another problem since this is just a temporary convenience anyhow...

Code after:
}

// Attempts migration of the old (LevelDB-based) database type to the new (SQLite-based) type
// This will attempt to provide a temporary API server during the migration, if `apiAddr` is not empty.
func TryMigrateDatabase(ctx context.Context, deleteRetention time.Duration, apiAddr string) error {
	oldDBDir := locations.Get(locations.LegacyDatabase)
	if _, err := os.Lstat(oldDBDir); err != nil {
		// No old database
		return nil
	}

	be, err := backend.OpenLevelDBRO(oldDBDir)
	if err != nil {
		// Apparently, not a valid old database
		return nil
	}
	defer be.Close()

	// Start a temporary API server during the migration
	if apiAddr != "" {
		api := migratingAPI{addr: apiAddr}
		apiCtx, cancel := context.WithCancel(ctx)
		defer cancel()
		go api.Serve(apiCtx)
	}

	sdb, err := sqlite.OpenForMigration(locations.Get(locations.Database))
	if err != nil {
		return err
	}
	defer sdb.Close()

	miscDB := db.NewMiscDB(sdb)
	if when, ok, err := miscDB.Time("migrated-from-leveldb-at"); err == nil && ok {
		slog.Error("Old-style database present but already migrated; please manually move or remove.", slog.Any("migratedAt", when), slogutil.FilePath(oldDBDir))
		return nil
	}

	slog.Info("Migrating old-style database to SQLite; this may take a while...")
	t0 := time.Now()

	ll, err := olddb.NewLowlevel(be)
	if err != nil {
		return err
	}

	totFiles, totBlocks := 0, 0
	for _, folder := range ll.ListFolders() {
		// Start a writer routine
		fis := make(chan protocol.FileInfo, 50)
		var writeErr error