-- migration: stored proc -- CALL migrate_assets_external_to_internal(); CREATE OR REPLACE PROCEDURE migrate_assets_external_to_internal(dry_run BOOLEAN DEFAULT true) LANGUAGE plpgsql AS $$ DECLARE current_time_ms BIGINT; rec RECORD; matched_prefix TEXT; relative_path TEXT; new_originalPath TEXT; new_deviceAssetId TEXT; new_deviceId TEXT; new_libraryId UUID := NULL; new_isExternal BOOLEAN := false; cp_cmd TEXT; mv_cmd TEXT; update_sql TEXT; BEGIN -- migration: history -- TODO: drop+create? CREATE TABLE IF NOT EXISTS migrate_assets_external_to_internal_history ( asset_id UUID PRIMARY KEY, old_originalPath TEXT NOT NULL, new_originalPath TEXT, old_deviceAssetId TEXT NOT NULL, new_deviceAssetId TEXT, old_deviceId TEXT NOT NULL, new_deviceId TEXT, old_libraryId UUID, new_libraryId UUID DEFAULT NULL, old_isExternal BOOLEAN NOT NULL, new_isExternal BOOLEAN, is_skipped BOOLEAN NOT NULL DEFAULT false, skip_reason TEXT, cp_command TEXT, mv_command TEXT, update_sql TEXT, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); TRUNCATE TABLE migrate_assets_external_to_internal_history; -- Get current time in milliseconds once for all updates SELECT (EXTRACT(EPOCH FROM NOW())::double precision * 1000)::bigint INTO current_time_ms; -- Loop through all assets having isExternal = true FOR rec IN SELECT a.id AS asset_id, a."originalPath", a."deviceAssetId", a."deviceId", a."isExternal", a."ownerId", a."libraryId", l."importPaths", u."storageLabel" FROM asset a JOIN library l ON a."libraryId" = l.id JOIN "user" u ON a."ownerId" = u.id WHERE a."isExternal" = true LOOP matched_prefix := NULL; -- Try each importPath in the library's importPaths array to find a match -- Knowing importPath of the asset will allow to deduce its relative path from absolute path (to be used on next step) FOR i IN 1..array_length(rec."importPaths", 1) LOOP IF starts_with(rec."originalPath", rec."importPaths"[i]) THEN matched_prefix := rec."importPaths"[i]; EXIT; END IF; END LOOP; -- If a match was found, perform the update and log history IF matched_prefix IS NOT NULL THEN relative_path := substring(rec."originalPath" FROM length(matched_prefix) + 1); new_originalPath := '/data/library/' || rec."storageLabel" || relative_path; new_deviceAssetId := 'web-' || rec."deviceAssetId" || '-' || current_time_ms::text; new_deviceId := 'Ext2int'; new_libraryId := NULL; new_isExternal := false; -- Perform the update if dry_run = false IF NOT dry_run THEN UPDATE asset SET "originalPath" = new_originalPath, "deviceAssetId" = new_deviceAssetId, "deviceId" = new_deviceId, "libraryId" = NULL, "isExternal" = new_isExternal WHERE id = rec.asset_id; END IF; -- History, including shell commands for moving/copying asset files and the "update" sql command for reviewing/applying manually update_sql := format( 'UPDATE asset SET "originalPath" = %L, "deviceAssetId" = %L, "deviceId" = %L, "libraryId" = NULL, "isExternal" = %L WHERE id = %L;', new_originalPath, new_deviceAssetId, new_deviceId, new_isExternal, rec.asset_id ); cp_cmd := 'cp "' || rec."originalPath" || '" "' || new_originalPath || '"'; mv_cmd := 'mv "' || rec."originalPath" || '" "' || new_originalPath || '"'; INSERT INTO migrate_assets_external_to_internal_history ( asset_id, old_originalPath, new_originalPath, old_deviceAssetId, new_deviceAssetId, old_deviceId, new_deviceId, old_libraryId, new_libraryId, old_isExternal, new_isExternal, is_skipped, skip_reason, cp_command, mv_command, update_sql ) VALUES ( rec.asset_id, rec."originalPath", new_originalPath, rec."deviceAssetId", new_deviceAssetId, rec."deviceId", new_deviceId, rec."libraryId", new_libraryId, rec."isExternal", new_isExternal, false, NULL, cp_cmd, mv_cmd, update_sql ); ELSE -- History: skipped asset INSERT INTO migrate_assets_external_to_internal_history ( asset_id, old_originalPath, new_originalPath, old_deviceAssetId, new_deviceAssetId, old_deviceId, new_deviceId, old_libraryId, new_libraryId, old_isExternal, new_isExternal, is_skipped, skip_reason, cp_command, mv_command, update_sql ) VALUES ( rec.asset_id, rec."originalPath", NULL, rec."deviceAssetId", NULL, rec."deviceId", NULL, rec."libraryId", NULL, rec."isExternal", NULL, true, 'No matching import path prefix found', NULL, NULL, NULL ); END IF; END LOOP; END; $$;