|
| 1 | +-- @description Repair missing JSFX files in the current project |
| 2 | +-- @author cfillion |
| 3 | +-- @version 1.0 |
| 4 | +-- @link https://cfillion.ca |
| 5 | +-- @donation https://www.paypal.me/cfillion/10 |
| 6 | +-- @about |
| 7 | +-- # Repair missing JSFX files in the current project |
| 8 | +-- |
| 9 | +-- This script scans the current project file for broken JSFX references |
| 10 | +-- and prompts for the new location of each unique missing JSFX found. |
| 11 | +-- |
| 12 | +-- A fixed copy of the project file is created (OriginalName-jsfix.RPP). |
| 13 | +-- |
| 14 | +-- This script supports JSFX located either in the resource folder or |
| 15 | +-- in the project directory. |
| 16 | + |
| 17 | +local global_dir = reaper.GetResourcePath() .. '/Effects/' |
| 18 | +local title = ({reaper.get_action_context()})[2]:match('([^/\\_]+).lua$') |
| 19 | +local bucket = {} |
| 20 | + |
| 21 | +-- no undo point for this script |
| 22 | +reaper.defer(function() end) |
| 23 | + |
| 24 | +function file_exists(fn) |
| 25 | + local file = io.open(fn, 'rb') |
| 26 | + |
| 27 | + if file then |
| 28 | + io.close(file) |
| 29 | + return true |
| 30 | + else |
| 31 | + return false |
| 32 | + end |
| 33 | +end |
| 34 | + |
| 35 | +function saw(jsfx) |
| 36 | + for i,entry in ipairs(bucket) do |
| 37 | + if jsfx == entry.name then |
| 38 | + return true |
| 39 | + end |
| 40 | + end |
| 41 | + |
| 42 | + return false |
| 43 | +end |
| 44 | + |
| 45 | +-- http://stackoverflow.com/a/34953646 |
| 46 | +function escape(pattern) |
| 47 | + return pattern:gsub("([^%w])", "%%%1") |
| 48 | +end |
| 49 | + |
| 50 | +if reaper.IsProjectDirty(0) ~= 0 then |
| 51 | + local btn = reaper.ShowMessageBox([[Project is modified. Do you want to save changes before repairing missing JSFX files? |
| 52 | +Unsaved changes will not be included in the fixed copy.]], title, 3) |
| 53 | + |
| 54 | + if btn == 6 then |
| 55 | + reaper.Main_SaveProject() |
| 56 | + elseif btn == 2 then |
| 57 | + return |
| 58 | + end |
| 59 | +end |
| 60 | + |
| 61 | +local _, project_fn = reaper.EnumProjects(-1, '', 0) |
| 62 | +local project_dir = project_fn:match('^(.-)[^/\\]+$') .. 'Effects/' |
| 63 | + |
| 64 | +local project_io, error = io.open(project_fn, 'rb') |
| 65 | +if not project_io then |
| 66 | + reaper.ShowMessageBox( |
| 67 | + string.format('Could not open project file "%s" (%s).', |
| 68 | + project_fn, error), title, 0) |
| 69 | + return |
| 70 | +end |
| 71 | + |
| 72 | +local chunk, modified = project_io:read('*all'), false |
| 73 | +project_io:close() |
| 74 | + |
| 75 | +function scan(pattern, start, stop) |
| 76 | + local from, to |
| 77 | + |
| 78 | + while true do |
| 79 | + from, to = chunk:find('<JS ' .. pattern, (from or 0) + 1) |
| 80 | + if not from then break end |
| 81 | + |
| 82 | + local token = chunk:sub(from, to):sub(5) |
| 83 | + local jsfx = token:sub(start, stop) |
| 84 | + local file, dir |
| 85 | + |
| 86 | + if jsfx:sub(1, 10) == "<Project>/" then |
| 87 | + dir = project_dir |
| 88 | + file = jsfx:sub(11) |
| 89 | + else |
| 90 | + dir = global_dir |
| 91 | + file = jsfx |
| 92 | + end |
| 93 | + |
| 94 | + if not saw(jsfx) and not file_exists(dir .. file) then |
| 95 | + table.insert(bucket, {name=jsfx, path=dir, token=token}) |
| 96 | + end |
| 97 | + end |
| 98 | +end |
| 99 | + |
| 100 | +scan('"[^\n]-"', 2, -2) |
| 101 | +scan('[^"\x20]+', 1, -1) |
| 102 | + |
| 103 | +if #bucket == 0 then |
| 104 | + reaper.ShowMessageBox('No missing JSFX were found. Nothing to do!', title, 0) |
| 105 | + return |
| 106 | +end |
| 107 | + |
| 108 | +for i,old in ipairs(bucket) do |
| 109 | + local title = string.format('Looking for "%s"', old.name) |
| 110 | + local ok, new = reaper.GetUserFileNameForRead(old.path, title, '*.jsfx') |
| 111 | + |
| 112 | + if ok then |
| 113 | + new = new:sub(old.path:len() + 1) |
| 114 | + if old.path == project_dir then |
| 115 | + new = '<Project>/' .. new |
| 116 | + end |
| 117 | + |
| 118 | + old_pt = string.format('<JS %s', old.token) |
| 119 | + new_pt = string.format('<JS "%s"', new) |
| 120 | + |
| 121 | + chunk = chunk:gsub(escape(old_pt), new_pt) |
| 122 | + modified = true |
| 123 | + end |
| 124 | +end |
| 125 | + |
| 126 | +if not modified then |
| 127 | + reaper.ShowMessageBox('Repair process aborted by user request.', title, 0) |
| 128 | + return |
| 129 | +end |
| 130 | + |
| 131 | +new_fn = project_fn:gsub('%.(%w+)$', '-jsfix.%1') |
| 132 | +local new_io, error = io.open(new_fn, 'wb') |
| 133 | +if not new_io then |
| 134 | + reaper.ShowMessageBox( |
| 135 | + string.format('Could not write project file "%s" (%s).', |
| 136 | + new_fn, error), title, 0) |
| 137 | + return |
| 138 | +end |
| 139 | +new_io:write(chunk) |
| 140 | +new_io:close() |
| 141 | + |
| 142 | +reaper.Main_openProject(new_fn) |
0 commit comments