BackupMeta (Backup and restore Opus metadata)

BackupMeta creates recovery files that let you restore Opus-specific metadata. Currently supported metadata are labels and user comments. This metadata is stored in the NTFS filesystem and might get deleted by other software or when files are moved to drives that don't support this type of metadata. Examples are non-NTFS drives, cloud storage, and archives except WinRAR.

The recovery file contains the Opus commands Properties and SetAttr to restore the metadata. It should be self-explanatory and can easily be mass-edited to reflect e.g. changes in the drive/folder structure.

User comments may contain line breaks. Opus can read these multi-line comments, but cannot write them. For these files, the recovery file contains ExifTool commands.

BackupMeta supports one switch: ONLYEXPLICIT. If used, only explicitly assigned labels will be returned, wildcard and label filters will not be considered.

(Last update: 2025-02-06)

How to set up and use

:one: Save CommandBackupMeta.js.txt to   

%appdata%\GPSoftware\Directory Opus\Script AddIns

Add the new command to a button, hotkey, context menu, etc. like any built-in command.

Optional: Get ExifTool and install it on your system. It's not needed to create the recovery file.

:two: Select the files and folders for which you want to create a recovery file, and run BackupMeta on them. Folders will be read recursively. The dated recovery file will show up in /profile\OpusBackup. Make the necessary changes with a text editor. Depending on your system, you might need to adjust the path to exiftool.exe. Starting a line with // will prevent it from being executed.

:three: Select a single recovery file and run BackupMeta to restore metadata. The script will read and execute the commands.

You can also manually copy the commands from the recovery file to an Opus button and run them from there. See below for a short demo.

Things you might enjoy reading

FAQ: How to use buttons and scripts from this forum
Docs: What are aliases?

The script's inner workings

JScript
function OnInit(initData) {
    initData.name = 'BackupMeta';
    initData.version = '2025-02-06';
    initData.url = 'https://resource.dopus.com/t/backupmeta-backup-and-restore-opus-metadata/45497';
    initData.desc = 'Write recovery commands for some metadata to a text file.';
    initData.default_enable = true;
    initData.min_version = '12.0';
}

function OnAddCommands(addCmdData) {
    var cmd = addCmdData.AddCommand();
    cmd.name = 'BackupMeta';
    cmd.method = 'OnBackupMeta';
    cmd.desc = 'Write recovery commands for some metadata to a text file.';
    cmd.label = 'BackupMeta';
    cmd.template = 'onlyexplicit/s';
    cmd.hide = false;
    cmd.icon = 'script';
}

function OnBackupMeta(scriptCmdData) {
    var cmd = scriptCmdData.func.command;
    var tab = scriptCmdData.func.sourcetab;
    var dlg = scriptCmdData.func.Dlg();
    var fsu = DOpus.FSUtil();
    var args = scriptCmdData.func.args;
    var stt = DOpus.Create().StringTools();

    cmd.deselect = false;

    if (tab.selected.count == 0) return;

    var bakFolder = '/profile\\OpusBackup';

    // var datetimeShort = 'D#yyyyMMdd-T#HHmmss';  // Opus 12
    var datetimeShort = 'A#yyyyMMdd-HHmmss';

    // var exeExifTool = fsu.Resolve('/programfiles\\ExifTool\\exiftool.exe');
    var exeExifTool = 'exiftool.exe';

    var CRLF = '\r\n';

    var re = /^Recovery.+\.txt$/;

    if (tab.selected_files.count == 1 && String(tab.selected_files[0].name).match(re)) {
        var item = tab.selected_files[0];
        var tmpFile = fsu.OpenFile(item);
        var arr = stt.Decode(tmpFile.Read(), 'utf8').split(CRLF);
        tmpFile.Close();

        cmd.Clear();
        for (var i = 0; i < arr.length; i++) {
            var cmdLine = arr[i];
            if (!cmdLine) continue;
            if (cmdLine.substring(0, 2) == '//') continue;
            cmd.AddLine(cmdLine);
        }

        if (cmd.linecount) {
            var message = 'Execute ' + cmd.linecount + ' commands found in\n\n' + item.name + '?';
            if (dlg.Request(message)) cmd.Run();
        } else {
            var message = 'No commands found in \n\n' + item.name + '!';
            dlg.Request(message, 'OK');
        }

        return;
    }

    var prg = DOpus.Create().Command().progress;

    prg.abort = true;
    prg.delay = false;
    prg.Init(tab.lister);
    prg.SetTitle('BackupMeta');
    prg.SetStatus('Enumerating selection...');
    prg.SetFiles(tab.selected.count);
    prg.Show();

    var vec = DOpus.Create().Vector(tab.selected_files);

    for (var e = new Enumerator(tab.selected_dirs); !e.atEnd(); e.moveNext()) {
        var item = e.item();
        vec.push_back(item);
        prg.StepFiles(1);
        prg.SetName(item);
        var folderEnum = fsu.ReadDir(item, 'r');
        while (!folderEnum.complete) {
            if (prg.GetAbortState() == 'a') return;
            var folderItem = folderEnum.Next();
            vec.push_back(folderItem);
            if (!folderItem.is_dir) continue;
            prg.SetName(folderItem);
        }
        folderEnum.Close();
    }

    if (vec.empty) return;

    prg.Restart();
    prg.SetStatus('Analyzing ' + vec.count + ' files...');
    prg.SetFiles(vec.count);

    var bakFileItem = fsu.GetItem(fsu.Resolve(bakFolder + '\\Recovery-' + DOpus.Create().Date().Format(datetimeShort) + '.txt'));
    cmd.RunCommand('CreateFolder NAME="' + bakFileItem.path + '"');
    var bakFile = bakFileItem.Open('wa'); // create a new file, always. If the file already exists it will be overwritten. 

    var fileCounter = 0;

    for (var e = new Enumerator(vec); !e.atEnd(); e.moveNext()) {
        if (prg.GetAbortState() == 'a') break;

        var item = e.item();
        prg.StepFiles(1);
        prg.SetName(item);

        if (item.metadata == 'none') continue;

        var hasMeta = false;

        var itemLabels = args.onlyexplicit ? item.Labels('*', 'explicit') : item.Labels(); // only explicitly assigned labels will be returned, wildcard and label filters will not be considered

        if (itemLabels.count) {
            var strLabels = '';

            for (var ee = new Enumerator(itemLabels); !ee.atEnd(); ee.moveNext()) {
                if (strLabels) strLabels += ',';
                strLabels += ee.item();
            }

            var cmdLine = 'Properties' +
                ' FILE="' + item + '"' +
                ' SETLABEL="' + strLabels + '"';

            bakFile.Write(cmdLine + CRLF);

            hasMeta = true;
        }

        var userComment = item.metadata.other.usercomment;

        if (userComment) {
            if (userComment.indexOf('\r') < 0 && userComment.indexOf('\n') < 0) {
                userComment = userComment.replace(/"/g, '""');
                var cmdLine = 'SetAttr' +
                    ' FILE="' + item + '"' +
                    ' META' +
                    ' "usercomment:' + userComment + '"';
            } else {
                userComment = userComment.replace(/\r/g, '\\r');
                userComment = userComment.replace(/\n/g, '\\n');
                userComment = userComment.replace(/"/g, '\\"');
                var cmdLine = '"' + exeExifTool + '"' +
                    ' -escapeC' +
                    ' -UserComment="' + userComment + '"' +
                    ' "' + item + '"';
            }

            bakFile.Write(cmdLine + CRLF);

            hasMeta = true;
        }

        if (hasMeta) {
            prg.SetStatus('Generating commands for ' + ++fileCounter + ' of ' + vec.count + ' files...');
        }
    }

    bakFile.Close();

    cmd.RunCommand('Go' +
        ' PATH="' + bakFileItem + '"' +
        ' NEWTAB=findexisting' +
        ' OPENINDUAL');

    cmd.SetFiles(tab.selected);
    cmd.RunCommand('Select FROMSCRIPT SETFOCUS');
    cmd.RunCommand('Select SHOWFOCUS');
}
4 Likes

Hello lxp,

following your humble pointer to this thread, did not recognize yet, sorry! o)

It's an interesting approach, but I think it does not fit my backup needs so well. I wrote a powershell script some time ago, which would just copy all the ADS from source to destination files. It was kind of slow and had some drawbacks I currently cannot remember anymore, but this is more of what I am looking for.

Your approach also does not take into account ADS created by other applications and restoring would take quite a while using DO commands and exiftool.exe if you have to go over a larger file base (I guess). o)

But nonetheless, it's good to see people dealing with the same problem! o) There will be a proper solution one day. I will look into this myself at some point again, using C# or something which is not as quirky as powershell. In theory, it's not that hard to copy/sync ADS, these are just files attached to files, not much different than a regular file copy.

The difficulty is detecting whether you need to update the ADS in the backup location and making sure you hit the same files and folders, which the regular backup synced beforehand. You always have filetypes / folders excluded and maybe junctions in between when doing the regular backup. If the ADS sync does not hit the very same items as the regular backup before, it's kind of pointless again. That's why I'm also looking out for a backup tool, which does ADS sync while doing the regular file sync - in one step, making sure each file will be treated.

Thanks! o)

I think you could do it easily with AutoHotkey, maybe using the multi-threading version AutoHotkey_H.

Wha! AutoHotkey? o) I think AH is definitely not the right tool for the job. You need something which gets as close to the official filesystem APIs and better use a language / tool that was done by the filesystem issuer (Microsoft).

To me AutoHotkey seems a capable swiss knife or system hack, but unfortunately it also has the worst language and syntax I have seen so far. I often tried to get some simple things done with it and it's just a try and error pita! o) In AH v3 they address some issues, but it still is very confusing if you are used to "regular" languages of some kind.

We better stop spamming lxps thread now?! o) Sorry lxp! o)

Good morning, I finally tried to backup the metadata, I followed the instructions here BackupMeta (Backup and restore Opus metadata), I then created the "Recovery...-txt" file, but now I'm not clear on how to restore it, can someone kindly tell me the procedure? Thanks

Here are some files with labels:

Running BackupMeta on them will create a Recovery-...txt file:

Properties FILE="D:\45497\file1.txt" SETLABEL="Red"
Properties FILE="D:\45497\file2.txt" SETLABEL="Green"
Properties FILE="D:\45497\file3.txt" SETLABEL="Checked"

In case the labels get lost, they can be recovered by running the commands from a button:

The commands include the complete path to the files, so there's no need to select anything before running the button.

Use the editor if the paths have changed:

1 Like

Update 2025-02-05

  • Added switch ONLYEXPLICIT
  • Progress dialogs show a bit more info
  • A bit of code cleaning

I'm sorry but I really don't understand: before formatting the pc I created a backup of the metadata of a folder containing many files to which I had modified the labels (colours), after a while I found a .txt file called ‘Recovery. date and time’, what I expected was, after formatting the pc, to restore these labels with a reverse procedure, i.e. with a button that would copy those metadata to the files contained in the folder in question. From the instructions you sent me today I don't understand how to import the ‘Recovery.txt’ file; I've tried opening the Recovery file, selected all the files, copied them and inserted them into the editor (as in the screenshot) but nothing changes. Where do I go wrong? Thanks

You're almost there! :slight_smile:

You have two options:

  1. Run the commands directly by clicking the "Run" button on the lower left.
  2. Close the button by selecting "Ok" on the lower right and then click it in the toolbar.

(I'm planning to add a few lines of code to automate this process when you run BackupMeta on one of the recovery files.)

Many thanks, it works! At least partially, in the sense that I seem to be missing some labels, or rather some files to which I had applied labels, strange. If it were possible to add the ability to automate the creation and restoration of the backup, as if it were a real Directory Opus feature, that would be really great. In any case, thank you

Update 2025-02-06

  • Added restore functionality. Select a recovery file and run BackupMeta on it.

Thank you so much. I'll try it.