# Redo Operations - 0.2.0 (March 03, 2017)

Current Version
0.2.0 - March 03, 2017

Changes
0.2.0 - March 03, 2017

• Added the Universal option. Instead of having a separate list for recent COPY and MOVE operations, they all share the same list. Therefore you can COPY a file/folder to a location where you recently had moved files/folder to. Off by default (turn it on in Options).
0.1.0 - January 27, 2017
Changes since Redo Operation Scripting Help
• Fixed bug where recent operations are added to the beginning of the list and then effectively deleted right after which simply rendered the script useless. This is because in the beginning I had recent operations appended to the programming variable but later changed it to have the recent operations inserted at the beginning of the variable but the code necessary to clean up duplicates and move items to the top did not reflect this change.

Description
This script basically keep tracks of you all your copy and move destinations. You can then apply that same copy/move operations to other selected files. This is helpful a lot if you try to organize folders that have many many files that goes into various folders throughout the system. You could of course keep all those tabs for each of those folders open but it can get pretty messy if there are many many destinations and it could change all the time. The script will then give you a popout list with the most recent destination for an operation at the top. All duplicates are automatically taken care of. The script also comes with two configuration options that allows you to change the length of the history. By default it's set to remember 15 copy and move operations.

In Action

Scenario explainations: I'm organizing the files in the folder and I move each of those items into folder Move 1, Move 2, and Move 3. For simplicity sake, I had all 3 folders in that single folder but imagine those 3 folders are in various places of the system. I then close pane 2. Pretend that I close down Opus and then went to lunch. Then I came back from lunch and then realized that I had a few more items I should clean up and sort. I simply selected the items that will go into folder Move 3, press the hotkey associated with displaying Recent Move Operations, select the destination I want them to go in, and voila! I'm done. no need to browse back to that folder to move those items in.

The Included Button:

Recent Copy - will display the list of locations where a copy to operation was made will the script remember. Default hotkey is Ctrl + Shift + Z.
Recent Move - will display the list of locations where a move to operation was made will the script remember. Default hotkey is Ctrl + Shift + X.
Clear - Clear the history of both copy and move locations.

Options Available
CopyHistoryLength - Default is 15. Allows you to set how many locations where a copy to operation was made to will the script remember.
MoveHistoryLength - Default is 15. Allows you to set how many locations where a move to operation was made to will the script remember.
Universal - Default is Off. Instead of having a separate list for recent COPY and MOVE operations, they all share the same list.
UniversalHistoryLength - Default is 15. Allows you to set how many locations where an operation was made will the script remember.
Debug - For debugging purposes, obviously haha.

Requirements
Preferences > File Operations/Copy Options > Automatically manage file copy queues option must be turned on

Install Instructions

1. Copy button code below and paste into the toolbar in Toolbar Customization mode or download the DCF file and dragged it into the toolbar in Toolbar Customization mode.
2. Download the script and drag it into Settings > Toolbars > Script.
3. Modify settings as desired by clicking on the script.

NOTE!
Due to a bug in Directory Opus as discussed here, the minimum version requirements for the script to work correctly is version 12.3.4 beta.

Button Code

<?xml version="1.0"?>
<button backcol="none" display="both" label_pos="right" textcol="none" type="menu">
<label>Redo Operations</label>
<icon1>#undo2</icon1>
<button backcol="none" display="both" hotkey="ctrl+shift+Z" label_pos="right" textcol="none">
<label>Recent Copy\tCtrl + Shift + Z</label>
<tip>Show recent copy operations</tip>
<icon1>#copy</icon1>
<function type="normal">
<instruction>RedoOperation LISTCOPY 1</instruction>
</function>
</button>
<button backcol="none" display="both" hotkey="ctrl+shift+X" label_pos="right" textcol="none">
<label>Recent Move\tCtrl + Shift + X</label>
<tip>Show recent move operations</tip>
<icon1>#move</icon1>
<function type="normal">
<instruction>RedoOperation LISTMOVE 1</instruction>
</function>
</button>
<button backcol="none" display="both" label_pos="right" textcol="none">
<label>Clear</label>
<tip>Clear history of all recent operations</tip>
<icon1>#recentlistclear</icon1>
<function type="normal">
<instruction>RedoOperation CLEAR 1</instruction>
</function>
</button>
</button>


Redo Operations.dcf (1.09 KB)
Redo Operations Script
Redo_Operation.js.txt (Current - Version 0.2; 12.6 KB)
Old Versions
Redo_Operation.js.txt (Version 0.1; 9.12 KB)

// Redo Operation
// (c) 2017 Enternal

// Threads Created by Enternal to Ask for Help to Develop Script:
//https://resource.dopus.com/t/redo-operation-scripting-help/24602
//https://resource.dopus.com/t/how-to-get-the-gui-popuout-list-like-the-undo-list-item-in-the-toolbar/24617
//https://resource.dopus.com/t/quick-easy-way-to-search-an-array-vector-and-return-index/24622
//https://resource.dopus.com/t/enumerate-selected-items-into-a-simple-line-list/24627
//https://resource.dopus.com/t/how-to-insert-data-into-a-vector-not-through-push-back/24629
//https://resource.dopus.com/t/getcopyqueuenamedata-vs-dopus-listers-0-activetab-path/24740

// Misc Notes
//https://resource.dopus.com/t/js-reusing-regular-global-variables-through-multiple-executions/20358

function OnInit(initData)
{
initData.name = "Redo Operation";
initData.version = "0.2.0";
initData.url = "https://resource.dopus.com/t/redo-operations/24675";
initData.desc = "Quickly and easily redo copy/move operations to recent copied/moved to locations.";
initData.default_enable = true;
//initData.min_version = "12.3.4";

//https://resource.dopus.com/t/helper-confighelper-easier-config-item-handling/19129
//  Helper: ConfigHelper (easier config item handling) by tbone
function ConfigHelper(data){ //v1.2
var t=this; t.d=data; t.c=data.config; t.cd=DOpus.Create.Map();
toLowerCase()}; return t.val(val).des(des);}
t.des=function(des){ if (!des) return t; if (t.cd.empty)
t.d.config_desc=t.cd; t.cd(t.l.n)=des; return t;}
t.val=function(val){ var l=t.l; if (l.v!==l.x&&typeof l.v=="object")
l.v.push_back(val);else l.v=t.c[l.n]=val;return t;}
t.trn=function(){return t.des(t("script.config."+t.l.ln));}
}
var cfg = new ConfigHelper(initData);
cfg.add("MoveHistoryLength", "15", "Set the history length for recent MOVE operations.");
cfg.add("CopyHistoryLength", "15", "Set the history length for recent COPY operations.");
cfg.add("Universal", false, "Recent operations are UNIVERSAL instead of recent COPY destinations are only available for copy operations and vice versa.");
cfg.add("UniversalHistoryLength", "15", "Set the history length for UNIVERSAL recent operations.");
cfg.add("Debug", false, "Set debug to on.");

cmd.name = "RedoOperation"
cmd.method = "OnRedoOperation";
cmd.desc = initData.desc;
cmd.label = "Redo Operation"
cmd.template = "CLEAR/N,LISTMOVE/N,LISTCOPY/N"
}

// Called when a copy or move operation take place
function OnGetCopyQueueName(GetCopyQueueNameData)
{

var ReOps_dest = GetCopyQueueNameData.dest;
var ReOps_move = GetCopyQueueNameData.move;

// https://resource.dopus.com/t/getcopyqueuenamedata-vs-dopus-listers-0-activetab-path/24740
// Directory Opus below version 12.3.4 beta had a bug where network locations that were mounted
//   onto a drive letter had their true path stripped down to the drive letters only.
// Code below still kept since it may be useful for other debugging purposes.
if (Script.config["Debug"] == true) {
DOpus.ClearOutput
ReOpsInitialize()
Log("Redo Operation (Debug) - Path Checks")
Log("  Redo Operation GetCopyQueueNameData.dest: " +GetCopyQueueNameData.dest)
Log("  Redo Operation DOpus.listers(0).activetab.path: " +DOpus.listers(0).activetab.path)
if (String(DOpus.listers(0).activetab.path) == String(GetCopyQueueNameData.dest)) {
Log("  Redo Operation: GetCopyQueueNameData.dest and DOpus.listers(0).activetab.path Matches")
}
else {
Log("  Redo Operation: GetCopyQueueNameData.dest and DOpus.listers(0).activetab.path Does Not Match")
}
}

if (Script.config["Universal"] == true) {
ReOpsInitialize()
var ReOps_data_universal = DOpus.Vars.Get("ReOpsUniversal");
var index = searchVector(ReOps_data_universal, ReOps_dest);
if (index != null) {for (var i=index.length-1; i>-1; i=i-1) {ReOps_data_universal.erase(index(i));}}
ReOps_data_universal.insert(0, ReOps_dest);
DOpus.Vars("ReOpsUniversal") = ReOps_data_universal;
var UniversalHistoryLength = Script.config["UniversalHistoryLength"];
while (ReOps_data_universal.length>UniversalHistoryLength) {
ReOps_data_universal.erase(ReOps_data_universal.length-1);
DOpus.Vars("ReOpsUniversal") = ReOps_data_universal;
}
}
else if (ReOps_move == true) {
ReOpsInitialize()
var ReOps_data_move = DOpus.Vars.Get("ReOpsMove");
var index = searchVector(ReOps_data_move, ReOps_dest);
if (index != null) {for (var i=index.length-1; i>-1; i=i-1) {ReOps_data_move.erase(index(i));}}
ReOps_data_move.insert(0, ReOps_dest);
DOpus.Vars("ReOpsMove") = ReOps_data_move;
var MoveHistoryLength = Script.config["MoveHistoryLength"];
while (ReOps_data_move.length>MoveHistoryLength) {
ReOps_data_move.erase(ReOps_data_move.length-1);
DOpus.Vars("ReOpsMove") = ReOps_data_move;
}
}
else {
ReOpsInitialize()
var ReOps_data_copy = DOpus.Vars.Get("ReOpsCopy");
var index = searchVector(ReOps_data_copy, ReOps_dest);
if (index != null) {for (var i=0; i<index.length; i++) {ReOps_data_copy.erase(i);}}
ReOps_data_copy.insert(0, ReOps_dest);
DOpus.Vars("ReOpsCopy") = ReOps_data_copy;
var CopyHistoryLength = Script.config["CopyHistoryLength"];
while (ReOps_data_copy.length>CopyHistoryLength) {
ReOps_data_copy.erase(ReOps_data_copy.length-1);
DOpus.Vars("ReOpsCopy") = ReOps_data_copy;
}
}

if (Script.config["Debug"] == true) {
//DOpus.ClearOutput
ReOpsInitialize()
var ReOps_data_move = DOpus.Vars.Get("ReOpsMove");
var ReOps_data_copy = DOpus.Vars.Get("ReOpsCopy");
var ReOps_data_universal = DOpus.Vars.Get("ReOpsUniversal");
Log("Redo Operation (Debug) - General")
Log("  Redo Operation ReOpsMove Exist: " +DOpus.Vars.Exists("ReOpsMove"))
Log("  Redo Operation ReOpsCopy Exist: " +DOpus.Vars.Exists("ReOpsCopy"))
Log("  Redo Operation ReOpsUniversal Exist: " +DOpus.Vars.Exists("ReOpsUniversal"))
Log("  Redo Operation MOVE History: " +Script.config["MoveHistoryLength"]);
Log("  Redo Operation COPY History: " +Script.config["CopyHistoryLength"]);
Log("  Redo Operation UNIVERSAL History: " +Script.config["UniversalHistoryLength"]);
Log("  Redo Operation MOVE Events");
if (ReOps_data_move && ReOps_data_move.length) {
Log("      Vector Length: " +ReOps_data_move.length);
for (var i=0; i<ReOps_data_move.length; i++) {
Log("      Event("+i+"): " +ReOps_data_move(i));}
}
else {Log("      None")}
if (ReOps_data_copy && ReOps_data_copy.length) {
Log("  Redo Operation COPY Events");
Log("      Vector Length: " +ReOps_data_copy.length);
for (var i=0; i<ReOps_data_copy.length; i++) {
Log("      Event("+i+"): " +ReOps_data_copy(i));}
}
else {Log("      None")}
if (ReOps_data_universal && ReOps_data_universal.length) {
Log("  Redo Operation UNIVERSAL Events");
Log("      Vector Length: " +ReOps_data_universal.length);
for (var i=0; i<ReOps_data_universal.length; i++) {
Log("      Event("+i+"): " +ReOps_data_universal(i));}
}
else {Log("      None")}
Log("");
}
}

// Implement the OnRedoOperation command to clear Redo Operation's history
function OnRedoOperation(scriptCmd)
{
ReOps_clear = 0; // Default value = 0, command not passed
if (scriptCmd.func.args.got_arg.clear)
ReOps_clear = scriptCmd.func.args.got_arg.clear;
ReOps_listmove = 0;
if (scriptCmd.func.args.got_arg.listmove)
ReOps_listmove = scriptCmd.func.args.got_arg.listmove;
ReOps_listcopy = 0;
if (scriptCmd.func.args.got_arg.listcopy)
ReOps_listcopy = scriptCmd.func.args.got_arg.listcopy;

if (ReOps_listmove == 1) {
ReOpsInitialize() // Ensure all required global varaibles containing history of recent operations exists. If not, initialize them.
if (Script.config["Universal"] == true) {var ReOps_data_move = DOpus.Vars.Get("ReOpsUniversal");} // If config Universal is on, load the global history for shared operations into a local variable to work on
else {var ReOps_data_move = DOpus.Vars.Get("ReOpsMove");} // Else, load the global history for MOVE operations into a local variable to work on
if (ReOps_data_move.empty == true) {Log("Redo Operation: MOVE History Empty");return;} // If history empty, quit.

// Create a Dialog object.
dlg = DOpus.Dlg;
// Initialise the object to display a popup menu the user can select from
dlg.window = DOpus.Listers(0);
dlg.choices = ReOps_data_move; // The list of recent operations stored are the choices to be shown in the menu
ret = dlg.Show;
if (ret == 0) {return;} // Nothing was selected so quit the script
if (Script.config["Debug"] == true) {
DOpus.Output("Redo Operation (Debug): MOVE Dialog.Show: " +ret);
DOpus.Output("Redo Operation (Debug): MOVE Dialog.Selected: " +ReOps_data_move(ret-1));
}

//https://resource.dopus.com/t/enumerate-selected-items-into-a-simple-line-list/24627
if (scriptCmd.func.sourcetab.selected.count == 0) {Log("Redo Operation: No Items Are Selected. Aborting..."); return;} // Quick check to make sure there are something selected
var cmd = DOpus.Create.Command(); // Create new command to apply to the selected files and folders
cmd.SetFiles(scriptCmd.func.sourcetab.selected); // Set the selected files and folders as the main items to be acted upon
cmd.RunCommand('COPY MOVE TO ' + '"' + ReOps_data_move(ret-1) + '"'); // (ret-1) because vectors start from 0 while the dlg.Show starts at 1
}
if (ReOps_listcopy == 1) {
ReOpsInitialize()
if (Script.config["Universal"] == true) {var ReOps_data_copy = DOpus.Vars.Get("ReOpsUniversal");}
else {var ReOps_data_copy = DOpus.Vars.Get("ReOpsCopy");}
if (ReOps_data_copy.empty == true) {Log("Redo Operation: Copy History Empty");return;}

dlg = DOpus.Dlg;
dlg.window = DOpus.Listers(0);
dlg.choices = ReOps_data_copy;
ret = dlg.Show;
if (ret == 0) {return;}
if (Script.config["Debug"] == true) {
DOpus.Output("Redo Operation (Debug): COPY Dialog.Show: " +ret);
DOpus.Output("Redo Operation (Debug): COPY Dialog.Selected: " +ReOps_data_copy(ret-1));
}

if (scriptCmd.func.sourcetab.selected.count == 0) {Log("Redo Operation: No Items Are Selected. Aborting..."); return;}
var cmd = DOpus.Create.Command();
cmd.SetFiles(scriptCmd.func.sourcetab.selected);
cmd.RunCommand('COPY TO ' + '"' + ReOps_data_copy(ret-1) + '"');
}

if (ReOps_clear == 1) {
DOpus.Vars.Delete("ReOpsMove"); // Delete global variable that contains the history of recent operations
DOpus.Vars.Delete("ReOpsCopy");
DOpus.Vars.Delete("ReOpsUniversal");
Log("Redo Operation: History Cleared");
}
if (Script.config["Debug"] == true) {
Log("Redo Operation (Debug) - Permanent Variables Check");
Log("  ReOpsMove Exist: " +DOpus.Vars.Exists("ReOpsMove"));
Log("  ReOpsCopy Exist: " +DOpus.Vars.Exists("ReOpsCopy"));
Log("  ReOpsUniversal Exist: " +DOpus.Vars.Exists("ReOpsUniversal"));
Log("");
}
}

// Function to check whether the required global variables for this script to work exist or not. If not, make them and initialize them.
function ReOpsInitialize()
{
if (DOpus.Vars.Exists("ReOpsMove") == false) {
DOpus.Vars("ReOpsMove") = DOpus.Create.Vector(); // Initialize global variable as a vector
DOpus.Vars("ReOpsMove").persist = true; // Set global variable property "persist" to true to last across reboots
var ReOps_data_move = DOpus.Vars.Get("ReOpsMove");
}
if (DOpus.Vars.Exists("ReOpsCopy") == false) {
DOpus.Vars("ReOpsCopy") = DOpus.Create.Vector();
DOpus.Vars("ReOpsCopy").persist = true;
var ReOps_data_copy = DOpus.Vars.Get("ReOpsCopy");
}
if (DOpus.Vars.Exists("ReOpsUniversal") == false) {
DOpus.Vars("ReOpsUniversal") = DOpus.Create.Vector();
DOpus.Vars("ReOpsUniversal").persist = true;
var ReOps_data_copy = DOpus.Vars.Get("ReOpsUniversal");
}
}

function Log(scriptCmd){DOpus.Output(scriptCmd);} // Saves time from writing the whole "DOpus.Output("Text") to simply "Log("Text")
function searchVector(vector, searchterm) { // Search vector for matched terms and return indexes
var index = DOpus.Create.Vector(); // Initialize the index vector
for (var n=0; n<vector.length; n++) { // Go through each element and compare value with search term
if (String(vector(n)) === String(searchterm)) {index.push_back = n;} // If match search term, store the index into the index vector
// Had to use String function. Otherwise it won't work... will need to figure out why...
}
return index; // Return the index vector
}


1. Settings > File Types...

RedoOperation LISTCOPY 1

RedoOperation LISTMOVE 1


Very neat script! I love the example gif.

This was a great idea! Thank you for sharing

And I'm glad that people liked it!

I initially had at least seven screenshots to explain what this script really does but then realized that a single "video" will take care of all that haha.

Hi, I love your script ! Thank you !

1. One request please ! your script work perfectly on Hardisc etc... but when i try to use it on a network drive (Smb drive (for my Synology server), it's only remember the root of the drive, and not the full path to the folder used.
so for example i move a file to S:/Pictures, and the path remembered is S:/, not S:/Pictures.
is it possible to add this feature ? Thanks .

2. got an error from Directory opus :

- - - - - - - ->
<- - - - - - - -
28/01/2017 15:00 Redo Operation:  Erreur à la ligne 73, position 59 (Error line 73, position 59)
28/01/2017 15:00 Redo Operation:  Indice en dehors de la plage (0x800a0009) (Out of range)


#1 Ok! Just to confirm though, it's UNC path isn't it? So it should be \\ServerName\Path\to\Folder? Or you meant that you mounted the network drive to a drive letter like S:\? Because in both cases, it should work.

#2 Ooh that's weird. I don't know why there's a google search term in there and also not sure why there's an out of range error since that should have been taken care of in the script. Please do the following:

1. Turn on the Debug setting in Settings > Toolbars > Script > Click on Redo Operation > Change Debug from False to True.
2. Display the Script Logs by Help > Logs > Script Log.
3. Do a copy and a move operation
4. Save the log and send it to me.

After that, clear your Redo Operations History and try to reproduce the error you had with #2 and send the log to me again. And finally, clear your Redo Operation History again and do a copy and move operations to your network drive and then send me that log for that too. So there should be three log files total.

By the way, it may take me a few days since I'm busy so try to use it on Harddisk for now until I fix the error and check why #1 did not work.

Hi ! yes, it's a network drive mounted, but the address path is as well \\**\**
i can give you some example, but i confirm this.

ok i will do the #2 and will come back here as soon as it's done (French time, 10p.m, Rzzzzzzzzz )

no problem i'll try my best ! .

i sent the link to Logs / screen captures by PM !

.

So I looked through the code and attempted to mount a network location to a drive letter. I then used this code:

function OnGetCopyQueueName(GetCopyQueueNameData)
{
var ReOps_dest = GetCopyQueueNameData.dest;
var ReOps_move = GetCopyQueueNameData.move;

if (Script.config["Debug"] == true) {
DOpus.ClearOutput
ReOpsInitialize()
Log("Redo Operation (Debug)")
Log("  Redo Operation GetCopyQueueNameData.dest: " +GetCopyQueueNameData.dest)
Log("  Redo Operation DOpus.listers(0).activetab.path: " +DOpus.listers(0).activetab.path) // DOpus.Lister.activetab.path does not work?
if (String(DOpus.listers(0).activetab.path) == String(GetCopyQueueNameData.dest)) {
Log("  Redo Operation: GetCopyQueueNameData.dest and DOpus.listers(0).activetab.path Matches")
}
else {
Log("  Redo Operation: GetCopyQueueNameData.dest and DOpus.listers(0).activetab.path Does Not Match")
}
}
.
.
.
}


and the results were:

 1/30/2017 7:25 PM Redo Operation:  Redo Operation (Debug)
1/30/2017 7:25 PM Redo Operation:    Redo Operation GetCopyQueueNameData.dest: Z:\
1/30/2017 7:25 PM Redo Operation:    Redo Operation DOpus.listers(0).activetab.path: Z:\Documents
1/30/2017 7:25 PM Redo Operation:    Redo Operation: GetCopyQueueNameData.dest and DOpus.listers(0).activetab.path Does Not Match


The results totally matched up if it's on local drives and network paths (UNC format) but once I mounted the server path to a drive letter, Z in this case, it does not work. OnGetCopyQueueName's destination only stored the root of the path contrary to the entire path when the destination is local or a UNC path.

In the mean time, please test out this work around and see how it works for you.
Updated script below is unnecessary. The problem has been fixed now with the new DOpus beta 12.3.4.
Redo_Operation.js.txt (10.0 KB)

So according to Jon here:

It's actually a bug. So it should be fixed in the next update. So martyprod, it's better to wait till the new version of DOpus comes out and use the script again. The workaround I had earlier worked only if you manually got to a tab and then paste a copied/move file/folder into it. So it's definitely not idea. Once the fix for DOpus is out, my initial version of the script should work perfectly.

Thanks Jon!

New beta version DOpus 12.3.4 has fixed the problem.

I confirm the bug has been fixed (wow, a bit lost with the new forum, i received only a notification of the topic today, missed your previous messages ).

Thanks for the fix it's working great !

one suggestion, or maybe it can be done in DO : is there a possibility to have the list of the
Copy / Move link as a right mouse click option ?
For example, i right click on a picture, and there is a list of the copy / move history links instead of using a keyboard workshop or clicking on the button ?

Yes it can! I just learned more about this feature (well I known about it for a while but haven't used it since it's a feature that I use more heavily in XYplorer where I already have it all setup). So to set it up:

1. Settings > File Types...

RedoOperation LISTCOPY 1

RedoOperation LISTMOVE 1


Finally!

2 Likes

Ahh you're amazing !! Thanks, and I even learned something in DO thank you !

The code doesn't work here. All i get, is a path i am actually in, but not, where the recent copy was made to, the destination path. What am i doing wrong?

I don't know what you did wrong sorry, but it's working here. I followed the instruction to install it, not easy at first, my directory opus is set up in French,but after i found the right windows and translation, it's pretty easy to do.

Hello! Just double checking but you're using version 0.1.0 and not the one that I made as a workaround, correct? Oh, as I typed this, I realized what may be the problem. Preferences > File Operations/Copy Options > Automatically manage file copy queues option must be turned on for it to work.

New version 0.2 is out

Added the Universal option. Instead of having a separate individual list for recent COPY and MOVE operations, they all share the same list now. Therefore you can COPY a file/folder to a location where you recently had moved files/folder to and vice versa. Off by default (turn it On in the script's Options). I may actually have it On by default in the future as I find this may be the best way of handling it because what if you moved several files to destination X:\ but then you decided to only copy the remaining files to the same location?

1 Like

Would it be possible as add a dynamic list option so that the context option lists the X number of folders rather than have to press redo move or redo copy first?

This script will save me so much time!

1 Like

I agree ! i stopped using it because of that !