﻿var script_name = 'OpusLauncher';
var script_version = '1.0.1';
var g_results_map = null;
var g_enum_map = null;
var g_args_map = null;
var EVI = null;
var close_on_exit = null;

function OnInit(initData) {
	initData.name = script_name;
	initData.version = script_version;
	initData.copyright = '(c) 2024 Christian Arellano';
	initData.desc = 'Run any application with custom arguments, using selected files as parameters—all powered by Opus and Everything';
	initData.default_enable = true;
	initData.min_version = '13.12';
	initData.config = DOpus.Create().OrderedMap();
	initData.config_desc = DOpus.Create().OrderedMap();
	initData.config_groups = DOpus.Create().OrderedMap();
	initData.config_group_order = DOpus.NewVector('General', 'Dialog');
	AddConfig('log level', DOpus.NewVector(2, 'debug', 'standard', 'warning', 'off'), DOpus.strings.Get('config_loglevel'), 'General');
	AddConfig('included paths', DOpus.NewVector('/start', '/commonstartmenu', '/programfiles'), 'Paths that will be included in the Everything search to build the database.', 'General');
	AddConfig('excluded paths', DOpus.NewVector(), 'Paths to be excluded from the database.', 'General');
	AddConfig('excluded words', DOpus.NewVector('unins', 'setup'), 'Specify words in the full path that will be excluded from the database. Supports Everything wildcards.', 'General');
	AddConfig('included exts', DOpus.NewVector('exe', 'lnk'), 'Extensions included in the database. Only use the ones who are supposed to accept files as arguments.', 'General');
	AddConfig('max timeout', 2000, 'Maximum time in milliseconds to wait for Everything results.', 'General');
	AddConfig('database path', '', 'Path where the database will be saved.', 'General');
	AddConfig('use targets for links', true, 'Follow targets for links when building the database.', 'General');
	AddConfig('ignore folders', true, 'Set to True to include selected folders as parameters.', 'General');
	AddConfig('max results listed', 500, 'Set a maximum number of results to improve load speed. Use 0 to always list all.', 'Dialog');
	AddConfig('load icons', true, 'Decide whether icons should be displayed when listing results.', 'Dialog');
	AddConfig('exit on run', true, 'Set to True to close the dialog after a successful run.', 'Dialog');
	return;

	function AddConfig(name, value, desc, group) {
		initData.config[name] = value;
		initData.config_desc(name) = desc;
		initData.config_groups(name) = group;
	}
}

function OnAddCommands(addCmdData) {
	var cmd = addCmdData.AddCommand();
	cmd.name = script_name;
	cmd.method = 'OnLaunch';
	cmd.desc = 'Run any application with custom arguments, using selected files as parameters—all powered by Opus and Everything';
	cmd.label = script_name;
	cmd.template = 'NOSELFILES/S,BUILD/O/K[noload],EDITARGS/S,DROPONLY/S';
	cmd.hide = false;
	cmd.icon = 'script';

	var fayt = cmd.fayt;
	fayt.enable = true;
	fayt.key = '$';
	fayt.backcolor = '#730a76';
	fayt.textcolor = '#ffffff';
	fayt.label = script_name;
	fayt.realtime = true;
	fayt.wantempty = true;
}

function OnLaunch(scriptData) {
	if (scriptData.fayt == script_name) {
		if (!g_results_map) {
			g_results_map = BuildEVDatabase(true);
			if (g_results_map.empty) {
				Log(3, 'No results!');
				return;
			}
			g_enum_map = new Enumerator(g_results_map);
		}
		Log(1, 'Showing suggestions : ' + g_results_map.count);
		var cmdline = scriptData.cmdline;
		var no_use_selected = false;
		if (cmdline.indexOf('$') == cmdline.length - 1) {
			no_use_selected = true;
			cmdline = cmdline.slice(0, -1);
		}
		cmdline = cmdline.trim();
		if (scriptData.suggest) scriptData.tab.UpdateFAYTSuggestions(ShowSuggestions(g_results_map, cmdline));
		if (cmdline && scriptData.key === 'return') {
			var cmd = DOpus.Create().Command();
			cmd.SetFiles(scriptData.tab.selected);

			Exec(true, cmdline, cmd, DOpus.GetQualifiers(), !no_use_selected);
			scriptData.tab.CloseFAYT();
		}
	}
	else {
		if (scriptData.func.args.got_arg.build) Build(scriptData.func.args.build);
		else if (scriptData.func.args.got_arg.editargs) EditArgs();
		else ShowDialog(scriptData.func.sourcetab, scriptData.func.command, scriptData.func.args.got_arg.noselfiles, scriptData.func.args.got_arg.droponly);
	}
	if (EVI) {
		if (close_on_exit && !scriptData.fayt) {
			Log(2, 'Closing Everything...');
			EVI.Stop();
		}
		EVI = null;
	}
	return;

}

function ShowSuggestions(map, query) {
	if (query) var regex = new RegExp(query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '(.*)', 'i');
	var suggestions = DOpus.NewVector();
	var match, value;
	g_enum_map.moveFirst();
	for (; !g_enum_map.atEnd(); g_enum_map.moveNext()) {
		value = g_enum_map.item();
		if (!query) suggestions.push_back(value + '\t' + g_results_map(value));
		else {
			match = value.match(regex);
			if (match) {
				suggestions.push_back(match[0] + '=> ' + value + '\t' + g_results_map(value));
			}
		}
	}
	Log(1, 'suggestions has ' + suggestions.count + ' items');
	return suggestions;
}

function Build(whole) {
	Log(1, 'BUILD => Building extended database...');
	if (Script.vars.Exists('building')) {
		Log(3, 'BUILD =>  Database is currently building. Exit now...');
		return;
	}
	Script.vars.Set('building', true);
	if (!Script.vars.Exists('database')) BuildEVDatabase(false, true, whole == 'noload', false, true);
	try {
		var database = Script.vars.Exists('database') ? JSON.parse(Script.vars.Get('database')) : LoadDatabase();
	}
	catch (err) {
		Log(3, 'BUILD => Unable to load database');
		var database = {
			'EV_database_total': 0
		};
	};
	Log(1, 'BUILD => Total loaded : ' + database['EV_database_total']);
	var FSU = DOpus.FSUtil();
	var tab;
	try {
		var listers = DOpus.listers;
		tab = listers.lastactive.activetab;
	}
	catch (err) {

	};
	listers = null;
	var new_database = {};
	var ini_time = new Date();
	var item, ext, value, fullpath, is_processed, link, indice, ignored;
	var fullpaths = DOpus.Create().StringSetI();
	var shell = new ActiveXObject('WScript.Shell');
	var total = 0;
	if (tab) tab.Notify('status', script_name + ': Building database...', 2500);
	for (var key in database) {
		if (key == 'EV_database_total') continue;
		fullpath = database[key].fullpath || key;
		try {
			ext = database[key].ext;
			icon = database[key].icon;
			if (!database[key].is_processed && (ext == 'exe' || ext == 'lnk')) {
				name = database[key].name;
				if (ext == 'lnk' && Script.config['use targets for links']) {
					link = shell.CreateShortcut(fullpath);
					if (link.Arguments) {
						Log(1, fullpath + ' has arguments : ' + link.Arguments);
					}
					else {
						fullpath = link.TargetPath;
						indice = fullpath.lastIndexOf('.');
						ext = (indice == -1) ? '' : fullpath.slice(indice + 1);
					}
					icon = link.TargetPath + ',0';
				}

				if (ext == 'exe') {
					item = FSU.GetItem(fullpath);
					name = item.name_stem;
					if (item.metadata != 'exe') value = item.name_stem;
					else {
						value = item.metadata.exe.modversion || '';
						if (value) value = ' ' + value;
						value = (item.metadata.exe.moddesc || item.name_stem) + value;
					}
				}
				else value = database[key].value;
				if (!value) value = database[key].value;
			}
			else {
				// fullpath = database[key].fullpath;
				value = database[key].value;
				name = database[key].name;
			}
			is_processed = true;
		}
		catch (err) {
			Log(3, 'BUILD => Error processing ' + key + ' : ' + err);
			value = database[key].value;
			fullpath = database[key].fullpath || key;
			is_processed = false;
		}
		if (value && fullpath) {
			ignored = !fullpaths.insert(fullpath);
			new_database[key] = {
				'value': value,
				'runcount': database[key].runcount,
				'is_processed': is_processed,
				'ext': ext,
				'name': name,
				'modified': database[key].modified,
				'ignored': ignored
			};
			if (fullpath != key) new_database[key]['fullpath'] = fullpath;
			if (icon) new_database[key]['icon'] = icon;
			if (!ignored) total++;
		}
	}
	Log(1, 'BUILD => total data : ' + total);
	new_database['EV_database_total'] = total;
	SaveDatabase(new_database);
	Script.vars.Delete('building');
	Script.vars.Delete('database');
	fullpaths = null;
	FSU = null;
	shell = null;
	link = null;
	item = null;
	tab = null;
	DOpus.SendCustomMsg('EV_database_ready');
	Log(1, 'BUILD => Build finished in ' + (new Date() - ini_time) + 'ms');
	return;
}

function ShowDialog(tab, cmd, nofiles, drop_only) {
	var load_icons = Script.config['load icons'];
	var dlg = {};
	dlg.main = tab.Dlg();
	dlg.main.title = script_name + ' v' + script_version;
	dlg.main.singleton = true;
	dlg.main.template = '<resources><resource name="main" type="dialog"><dialog ' + (drop_only ? 'dragdrop="yes" ' : '') + 'height="224" lang="english" maximize="yes" minimize="yes" resize="yes" title="' + script_name + '" width="258">' +
		'<control halign="left" height="12" name="filter" resize="w" tip="Filter entries" type="edit" width="168" x="4" y="4" />' +
		'<control fullrow="yes" height="174" name="listview" nosortheader="yes" resize="wh" ' + (load_icons ? 'smallicons="yes" ' : '') + 'type="listview" viewmode="details" width="250" x="4" y="18"><columns><item text="Name" /><item text="RunCount" /><item text="Path" /></columns></control>' +
		'<control height="12" image="#clearquickfilter" name="btn_clear" resize="x" title="❌" type="button" width="13" x="172" y="4" />' +
		'<control height="12" image="#refresh" name="btn_refresh" resize="x" title="&amp;Refresh" type="button" width="14" x="185" y="4" />' +
		'<control halign="left" height="8" name="total" resize="x" type="markuptext" width="50" x="202" y="6" />' +
		'<control height="10" name="use_files" resize="y" title="&amp;Open with selected files" type="check" width="246" x="6" y="194" />' +
		'<control enable="no" height="14" name="btn_edit" resize="y" title="Edit Args" type="button" width="62" x="6" y="206" />' +
		(drop_only ? '<control enable="no" height="14" name="btn_clear_files" resize="y" title="Clear Files" type="button" width="62" x="70" y="206" />' : '') +
		'</dialog></resource></resources>';
	if (!dlg.main.Create()) {
		Log(3, 'A dialog is already open!');
		return;
	}
	DOpus.ClearOutput();
	Log(2, '=======' + script_name + ' v' + script_version + '=======');
	var str_tools = DOpus.Create().StringTools();
	var do_extended = !Script.vars.Exists('extended_built');
	var results = BuildEVDatabase(false, do_extended, false, !do_extended);
	var total = 'EV_database_total' in results ? results['EV_database_total'] : 0;
	if (total == 0) {
		if (DOpus.Dlg().Request(DOpus.strings.Get('label_scan_confirm'), str_tools.LanguageStr(5639) + '|' + str_tools.LanguageStr(5640), script_name, tab) == 1) {
			results = BuildEVDatabase(false, true, true, false);
			if ('EV_database_total' in results) total = results['EV_database_total'];
		}
	}
	if (total == 0) {
		alert(tab, DOpus.strings.Get('label_no_results'));
		Log(4, 'There are no results from Everything!');
	}
	else {
		var max_results = Script.config['max results listed'];
		if (max_results <= 0) max_results = total;
		var keys_map = DOpus.Create().Map();

		dlg.list = dlg.main.Control('listview');
		dlg.filter = dlg.main.Control('filter');
		dlg.total = dlg.main.Control('total');
		dlg.use_files = dlg.main.Control('use_files');
		dlg.edit_btn = dlg.main.Control('btn_edit');
		dlg.main.AddHotkey('up_arrow', 'up');
		dlg.main.AddHotkey('down_arrow', 'down');
		dlg.main.AddHotkey('enter', 'enter');
		dlg.main.AddHotkey('go', 'ctrl+enter');
		dlg.main.AddHotkey('f4', 'f4');
		dlg.use_files.label = DOpus.strings.Get('label_use_files');
		dlg.use_files.enabled = !drop_only;
		dlg.use_files.value = !nofiles;
		dlg.edit_btn.label = DOpus.strings.Get('label_edit_args');
		with(dlg.list.columns) {
			GetColumnAt(0).name = str_tools.LanguageStr(7023);
			GetColumnAt(1).name = DOpus.strings.Get('header_runcount');
			GetColumnAt(2).name = str_tools.LanguageStr(7024);
		}
		var total_str = DOpus.strings.Get('label_total') + ' ';
		updateList('');
		dlg.main.FlushMsg();
		dlg.main.AddCustomMsg('EV_database_ready');
		cmd.ClearFiles();
		dlg.main.Show();
		while (true) {
			var msg = dlg.main.GetMsg();
			if (!msg.result) break;
			switch (msg.event) {
				case 'custom':
					Log(1, "Msg received " + msg.event + " in " + msg.name);
					if (msg.name == 'EV_database_ready') {
						results = LoadDatabase();
						total = 'EV_database_total' in results ? results['EV_database_total'] : 0;
						updateList(dlg.filter.value);
					}
					break;
				case 'editchange':
					if (msg.control == 'filter') dlg.main.SetTimer(250, 'update_list_timer');
					break;
				case 'selchange':
					if (msg.control == 'listview') dlg.edit_btn.enabled = dlg.list.value.index >= 0 && dlg.list.value.subitems(1) in results && results[dlg.list.value.subitems(1)].ext != 'lnk';
					break;
				case 'drop':
					// if (DOpus.Dlg().Request(DOpus.strings.Get('label_add_confirm'), str_tools.LanguageStr(5639) + '|' + str_tools.LanguageStr(5640), script_name, dlg.main) == 0) continue;
					cmd.AddFiles(msg.object);
					dlg.main.Control('btn_clear_files').enabled = cmd.filecount > 0;
					break;
				case 'click':
					switch (msg.control) {
						case 'btn_refresh':
							results = BuildEVDatabase(false, msg.qualifiers == 'shift', false, false);
							total = 'EV_database_total' in results ? results['EV_database_total'] : 0;
							updateList(dlg.filter.value);
							break;
						case 'btn_clear':
							dlg.filter.value = '';
							break;
						case 'btn_edit':
							if (dlg.list.value.index >= 0) {
								AddEditArgs(dlg.list.value.subitems(1), results[keys_map(dlg.list.value.subitems(1))], dlg.main);
							}
							break;
						case 'btn_clear_files':
							if (DOpus.Dlg().Request(DOpus.strings.Get('label_clear_confirm'), str_tools.LanguageStr(5639) + '|' + str_tools.LanguageStr(5640), script_name, dlg.main) == 0) continue;
							cmd.ClearFiles();
							dlg.main.Control('btn_clear_files').enabled = false;
							break;
					}
					break;
				case 'dblclk':
					if (dlg.list.value.index >= 0) {
						if (Exec(false, dlg.list.value.subitems(1), cmd, msg.qualifiers, dlg.use_files.value, drop_only) && Script.config['exit on run'])
							dlg.main.EndDlg();
					}
					break;
				case 'timer':
					dlg.main.KillTimer('update_list_timer');
					updateList(dlg.filter.value);
					break;
				case 'hotkey':
					Log(1, "Msg received " + msg.event + " in " + msg.Control);
					switch (msg.control) {
						case 'f4':
							dlg.filter.focus = true;
							break;
						case 'down_arrow':
							if (!dlg.list.focus) dlg.list.focus = true;
							if (dlg.list.value.index === dlg.list.count - 1) dlg.list.value = 0;
							else dlg.list.value = dlg.list.value.index + 1;
							break;
						case 'up_arrow':
							if (!dlg.list.focus) dlg.list.focus = true;
							// Log('list has focus => ' + list.value.index);
							if (dlg.list.value.index <= 0) dlg.list.value = dlg.list.count - 1;
							else dlg.list.value = dlg.list.value.index - 1;
							break;
						case 'enter':
						case 'go':
							if (dlg.list.value.index >= 0) {
								if (Exec(false, dlg.list.value.subitems(1), cmd, msg.control == 'go' ? 'ctrl' : '', dlg.use_files.value, drop_only) && Script.config['exit on run'])
									dlg.main.EndDlg();
							}
							break;
					}
					break;
			}
		}
		dlg = null;
		str_tools = null;
	}
	results = null;
	Log(1, 'RUN END');
	return;

	function updateList(strFilter) {
		Log(1, 'Updating List : "' + strFilter + '"');
		dlg.list.redraw = false;
		dlg.list.RemoveItem(-1);
		keys_map.Clear();
		var strName, row, icon, fullpath;
		if (strFilter != '') var regex = new RegExp(str_tools.RemoveDiacritics(strFilter).replace(/[.*+?^${}()[\]\\]/g, '\\$&'), 'i');
		var c = 0;
		for (var key in results) {
			if (key == 'EV_database_total' || results[key].ignored) continue;
			if (strFilter == '' || regex.test(str_tools.RemoveDiacritics(results[key].value)) || regex.test(str_tools.RemoveDiacritics(results[key].name))) {
				row = dlg.list.GetItemAt(dlg.list.AddItem(results[key].value));
				if (row) {
					fullpath = results[key].fullpath || key;
					row.subitems(0) = results[key].runcount;
					row.subitems(1) = fullpath;
					keys_map.Set(fullpath, key);
					if (load_icons) {
						icon = results[key].icon || (fullpath + ',0');
						row.icon = icon;
						c++;
						if (c >= max_results) break;
					}
				}
				else Log(3, 'unable to load "' + results[key].value + '"');
			}
		}
		dlg.list.columns.AutoSize();
		dlg.list.redraw = true;
		dlg.total.label = total_str + dlg.list.count + '/' + total;
		dlg.total.AutoSize();
	}
}

function Exec(from_fayt, value, cmd, qualifiers, use_sel, drop_only) {
	Log(1, 'Exec called : from_fayt=' + from_fayt + '; qualifiers=' + qualifiers + '; use_sel=' + use_sel);
	var success, is_run;
	try {

		if (from_fayt) {
			value = value.substr(value.indexOf('=>') + 3).trim();
			value = g_results_map.Exists(value) ? g_results_map(value) : null;
		}

		if (!value) return;
		var args = '';
		if (!g_args_map) g_args_map = LoadArgsMap();
		if (qualifiers.indexOf('ctrl') == -1) {
			is_run = true;
			if (use_sel && !drop_only) {
				try {
					var lister = DOpus.listers.lastactive;
					var tab = lister.activetab;
					cmd.SetSourceTab(tab);
					if (lister.desttab) cmd.SetDestTab(lister.desttab);
					cmd.SetFiles(Script.config['ignore folders'] ? tab.selected_files : tab.selected);

				}
				catch (err) {
					Log(3, 'Unable to set selected items for the command : ' + err.description);
					use_sel = false;
				};
				listers = null;
				tab = null;
			}
			if (cmd.filecount) args = g_args_map.Exists(value) ? g_args_map(value) : (use_sel ? '{allfilepath}' : '');
			else use_sel = false;
			var cmdline = '"' + value + '" ' + args;
			Log(1, 'Running ' + cmdline + (use_sel ? ' using selected items' : ' with no files'));
		}
		else {
			Log(1, 'Opening ' + value + ' path');
			var cmdline = 'Go NEWTAB=tofront,findexisting,deflister "' + value + '"';
		}
		success = cmd.RunCommand(cmdline);

		if (success && is_run) {
			if (!EVI) success = initEVI(from_fayt);
			if (success) {
				Log(1, 'Increasing runcount for ' + value);
				try {
					EVI.RunCountInc(value);
				}
				catch (err) {
					Log(3, 'Unable to increase runcount : ' + err.description);
				};
			}
		}
	}
	catch (err) {
		Log(4, 'Error while trying to run ' + value + ' : ' + err.description);
	}
	cmd.ClearFiles();

	return success;
}

function BuildEVDatabase(is_fayt, do_extended, no_load, save, no_return) {
	Log(1, 'Building quick database => is_fayt=' + is_fayt + '; do_extended=' + do_extended + '; noload=' + no_load + '; save=' + save + '; no_return=' + no_return);
	var results = is_fayt ? DOpus.Create().Map() : null;
	var total;
	try {
		var database = no_load ? {
			'EV_database_total': 0
		} : LoadDatabase();
	}
	catch (err) {
		Log(3, 'Unable to load database');
		var database = {
			'EV_database_total': 0
		};
	};

	total = database['EV_database_total'];
	var new_database = {};
	var ini_time = new Date();
	var sw = initEVI(is_fayt);
	if (!sw) {
		Log(2, 'Everything is not running, using saved database...');
		if (is_fayt) results = GetMapFromDatabase(database);
		else new_database = database;
	}
	else {
		var FSU = DOpus.FSUtil();

		Log(1, 'indexed props : ' + EVI.indexed);
		var check_if_indexed = EVI.Indexed('c:\\');
		Log(1, 'indexed props : ' + EVI.indexed);
		if (!(EVI.indexed & 16)) Log(3, 'Indexing modified date is greatly recommended for speed up content update.');
		var value;
		var v = DOpus.Create().StringSetI(Script.config['included paths']);
		var inc_paths_cmd = '';
		for (var i = 0; i < v.length; i++) {
			value = FSU.Resolve(v(i).trim());
			if (!value) continue;
			if (check_if_indexed && !EVI.Indexed(value)) {
				Log(3, value + ' seems is not indexed by Everything!');
				continue;
			}
			inc_paths_cmd += '"' + value + '";';
		}
		if (inc_paths_cmd) inc_paths_cmd = 'path:' + inc_paths_cmd.slice(0, -1);
		v = DOpus.Create().StringSetI(Script.config['excluded paths']);
		v.insert('$Recycle.Bin');
		var exc_paths_cmd = '';
		for (var i = 0; i < v.length; i++) {
			value = FSU.Resolve(v(i).trim());
			if (!value) continue;
			exc_paths_cmd += '"' + value + '";';
		}
		if (exc_paths_cmd) exc_paths_cmd = '!path:' + exc_paths_cmd.slice(0, -1);
		v = DOpus.Create().StringSetI(Script.config['excluded words']);
		var words_forbidden_cmd = '';
		for (var i = 0; i < v.length; i++) {
			value = v(i).trim();
			if (!value) continue;
			words_forbidden_cmd += '"' + value + '";';
		}
		if (words_forbidden_cmd) words_forbidden_cmd = '!fullpath:ww:' + words_forbidden_cmd.slice(0, -1);
		v = DOpus.Create().StringSetI(Script.config['included exts']);
		var exts_cmd = '';
		for (var i = 0; i < v.length; i++) {
			value = v(i).trim();
			if (!value) continue;
			exts_cmd += value + ';';
		}
		if (exts_cmd) exts_cmd = 'exact:ext:' + exts_cmd.slice(0, -1);
		if (inc_paths_cmd) {
			var query = inc_paths_cmd + ' ' + exc_paths_cmd + ' ' + words_forbidden_cmd + ' ' + exts_cmd;
			Log(1, 'query    : ' + query);

			var EV_res = EVI.Query(query, 0, 'frnxm', 20, '', '', Script.config['max timeout']); //20 means sort by runcount
			Log(1, 'Query finished, results: ' + EV_res.count + '\ttime : ' + (new Date() - ini_time));
			var fullpath, is_processed, f, ignored, icon, ext, modified;
			var fullpaths = DOpus.Create().StringSetI();
			total = EV_res.count;
			var ignored_count = 0;
			for (var i = 0; i < total; i++) {
				f = EV_res(i).fullpath.def_value;
				modified = EV_res(i).modify.Format('D#yyyy-MM-dd T#HH:mm:ss');
				if (f in database && modified == database[f]['modified']) {
					value = database[f]['value'];
					fullpath = database[f]['fullpath'] || f;
					ignored = database[f]['ignored'];
					if (!is_fayt) {
						is_processed = database[f]['is_processed'];
						name = database[f]['name'];
						icon = database[f]['icon'];
						ext = database[f]['ext'];
					}
				}
				else {
					ext = EV_res(i).ext || '';
					value = EV_res(i).name.split('.').slice(0, -1).join('.');
					fullpath = f;
					ignored = !fullpaths.insert(fullpath);
					if (!is_fayt) {
						is_processed = false;
						name = value;
						icon = ext == 'exe' ? '' : ('.' + ext);
					}
				}
				if (value && fullpath) {
					if (!is_fayt) {
						// Log(1, 'Saving ' + f);
						new_database[f] = {
							'value': value,
							'runcount': EV_res(i).runcount,
							'is_processed': is_processed,
							'ext': ext,
							'name': name,
							'icon': icon,
							'modified': modified,
							'ignored': ignored
						};
						if (f != fullpath) new_database[f]['fullpath'] = fullpath;
						if (ignored) ignored_count++;
					}
					else if (!ignored && is_fayt) results.Set(value, fullpath);
				}
				else ignored_count++;
			}
		}
		total -= ignored_count;
		if (!is_fayt) new_database['EV_database_total'] = total;
		Log(1, 'Quick database done in ' + (new Date() - ini_time) + 'ms. Results : ' + (is_fayt ? results.count : total));
		if (!is_fayt && save) SaveDatabase(new_database);
	}
	if (do_extended) {
		Script.vars.Set('extended_built', true);
		Script.vars.Set('database', JSON.stringify(new_database));
		if (!Script.vars.Exists('building')) {
			var cmd = DOpus.Create().Command();
			cmd.RunCommandAsync('OpusLauncher BUILD=noload');
			cmd = null;
		}
	}
	// ignored = null;
	FSU = null;
	v = null;
	if (no_return) {
		new_database = null;
		return;
	}
	return is_fayt ? results : new_database;
}

function LoadDatabase() {
	var ini_time = new Date();
	var database = {
		'EV_database_total': 0
	};
	var FSU = DOpus.FSUtil();
	var dbfile = Script.config['database path'];
	if (!dbfile) dbfile = DOpus.Aliases('dopusdata').path + '\\User Data\\EV_database.db';
	else dbfile = FSU.Resolve(dbfile) + '\\EV_database.db';
	Log(1, 'Loading "' + dbfile + '" as database');
	if (FSU.Exists(dbfile)) {
		try {
			var file = FSU.OpenFile(dbfile);
			if (file.error == 0) {
				var content = DOpus.Create().StringTools().Decode(file.Read(), 'utf-8');
				if (content) database = JSON.parse(content);
				file.Close();
			}
		}
		catch (err) {
			Log(3, '=>Error reading the file : ' + err.description);
		}
	}
	else Log(3, '=> Database doesn\'t exists!');
	FSU = null;
	Log(1, 'Loading finished in ' + (new Date() - ini_time) + 'ms');
	return database;
}

function SaveDatabase(data) {
	if (!data || data['EV_database_total'] == 0) return false;
	var ini_time = new Date();
	Log(1, 'Saving database...');
	var success = false;
	var FSU = DOpus.FSUtil();
	var dbfile = FSU.GetItem(Script.config['database path'] ? (FSU.Resolve(Script.config['database path']) + '\\EV_database.db') :
		(DOpus.Aliases('dopusdata').path + '\\User Data\\EV_database.db'));
	Log(1, '=> ' + dbfile);
	try {
		var c;
		if (!(c = FSU.Exists(dbfile.path))) {
			c = DOpus.Create().Command().RunCommand('CreateFolder "' + dbfile.path + '" READAUTO=no');
		}
		if (c) {
			var content = JSON.stringify(data);
			var file = dbfile.Open('wf');
			if (file.error == 0) {
				file.Write(content);
				file.Close();
				success = true;
				Log(1, 'Save finished in ' + (new Date() - ini_time) + 'ms; Success : ' + success);
			}
			else Log(3, '=> Error saving the file : ' + file.error);
		}
		else Log(3, '=> Error creating the folder : ' + dbfile.path);
	}
	catch (err) {
		Log(3, '=> Error saving the file : ' + err.description);
	}
	file = null;
	dbfile = null;
	return success;
}

function GetMapFromDatabase(database) {
	var map = DOpus.Create().Map();
	var value, fullpath, ignored;
	for (var key in database) {
		value = database[key]['value'];
		fullpath = database[key]['fullpath'] || key;
		ignored = database[key]['ignored'];
		if (!ignored) map.Set(value, fullpath);
	}
	return map;
}

function EditArgs(parent, from_dlg) {
	var str_tools = DOpus.Create().StringTools();
	var dlg = {};
	dlg.main = DOpus.Dlg();
	dlg.main.template = 'custom_args';
	dlg.main.window = parent;
	if (from_dlg) dlg.disable_window = parent;
	dlg.main.Create();
	dlg.listview = dlg.main.Control('listview');
	dlg.edit_btn = dlg.main.Control('edit_btn');
	dlg.del_btn = dlg.main.Control('del_btn');
	dlg.del_btn.label = str_tools.LanguageStr(23107);
	dlg.edit_btn.label = str_tools.LanguageStr(23110);
	var args_map = LoadArgsMap();
	ListArgs();
	with(dlg.listview.columns) {
		GetColumnAt(0).name = str_tools.LanguageStr(7023);
		Autosize();
	}
	dlg.main.title = script_name + ' v' + script_version + ' - Custom Args';
	if (!args_map.empty) {
		dlg.listview.value = 0;
	}
	var msg, selected, change;
	dlg.main.Show();
	while (true) {
		msg = dlg.main.GetMsg();
		if (!msg.result) break;
		if (msg.event === 'click') {
			if (msg.control === 'add_btn') {
				if (AddEditArgs('', dlg.main)) {
					args_map = LoadArgsMap();
					ListArgs();
				}
			}
			else if (msg.control === 'edit_btn' && selected) {
				Log(1, 'edit :' + selected);
				if (AddEditArgs(selected.name, null, dlg.main)) {
					args_map = LoadArgsMap();
					ListArgs();
				}
			}
			else if (msg.control === 'del_btn' && selected) {
				Log(1, 'del :' + selected);
				if (DOpus.Dlg.Request('Delete' + selected + '?', 'OK|Cancel', 'Delete Template ' + selected, dlg.main) === 1) {
					args_map.erase(selected);
					ListArgs();
					change = true;
				}
			}
		}
		else if (msg.event === 'selchange' && msg.control === 'listview') {
			Log(1, 'value:' + msg.value + '\tevent:' + msg.event + '\tdata:' + msg.data + '\tindex:' + msg.index);
			selected = msg.value;
			Log(1, 'selchange :' + selected);
			dlg.edit_btn.enabled = dlg.del_btn.enabled = (!args_map.empty && selected ? true : false);
		}
	}
	if (change) {
		Script.Vars.Set('args_map', args_map);
		Script.Vars('args_map').persist = true;
	}
	dlg = null;
	args_map = null;
	str_tools = null;
	return;

	function ListArgs() {
		Log(1, 'Listing custom args : ' + args_map.count);
		dlg.edit_btn.enabled = dlg.del_btn.enabled = (!args_map.empty && selected ? true : false);
		dlg.listview.redraw = false;
		dlg.listview.RemoveItem(-1);
		for (var e = new Enumerator(args_map); !e.atEnd(); e.moveNext()) {
			Log(1, '=>' + e.item());
			dlg.listview.AddItem(e.item());
		}
		dlg.listview.redraw = true;
		return;
	}

}

function LoadArgsMap() {
	return Script.Vars.Exists('args_map') ? Script.Vars.Get('args_map') : DOpus.Create().Map();
}

function AddEditArgs(fullpath, db_item, parent_window) {
	if (!fullpath) return false;
	Log(1, 'Adding/Editing args for "' + fullpath + '"');
	var dlg_sub = {};
	dlg_sub.main = DOpus.Dlg();
	dlg_sub.main.template = 'edit_args';
	dlg_sub.main.window = parent_window;
	dlg_sub.main.disable_window = parent_window;
	dlg_sub.main.Create();
	dlg_sub.fullpath = dlg_sub.main.Control('fullpath');
	dlg_sub.name = dlg_sub.main.Control('name');
	dlg_sub.thumb = dlg_sub.main.Control('thumb');
	dlg_sub.prompt = dlg_sub.main.Control('prompt');
	var str_tools = DOpus.Create().StringTools();
	var args_map = Script.Vars.Exists('args_map') ? Script.Vars.Get('args_map') : DOpus.Create().Map();

	dlg_sub.main.Control('title_name').label = '<b><#%vs_listview_header_text>' + str_tools.LanguageStr(7023) + ' :</#></b>';
	dlg_sub.main.Control('title_path').label = '<b><#%vs_listview_header_text>' + str_tools.LanguageStr(7024) + ' :</#></b>';
	dlg_sub.main.Control('title_args').label = '<b><#%vs_listview_header_text>' + str_tools.LanguageStr(29537) + ' :</#></b>';
	dlg_sub.main.Control('info').label = DOpus.strings.Get('label_args_info');

	dlg_sub.main.title = script_name + ' v' + script_version + (fullpath ? ' - New Custom Args' : (' - Edit "' + fullpath + '"'));
	var success, value;
	dlg_sub.fullpath.label = fullpath;
	if (db_item) {
		dlg_sub.name.label = db_item.value;
		LoadThumbnail(fullpath);
	}
	dlg_sub.prompt.value = args_map.Exists(fullpath) ? args_map(fullpath) : '';
	dlg_sub.main.RunDlg();
	if (dlg_sub.main.result) {
		value = dlg_sub.prompt.value.replace(/(\r\n|\n)+/g, '').trim();
		Log(2, 'Saving custom args for ' + fullpath + ':' + value);
		if (!value) args_map.Erase(fullpath);
		else args_map.Set(fullpath, value);
		success = true;
		Script.Vars.Set('args_map', args_map);
		Script.Vars('args_map').persist = true;
	}
	else Log(1, 'User close the dialog. Nothing to change!');
	dlg_sub = null;
	args_map = null;
	return success;

	function LoadThumbnail(icon) {
		Log(1, 'Loading icon ' + icon);
		try {
			var thumb = DOpus.LoadThumbnail(icon, 2000, '', '', 'i');
			dlg_sub.thumb.label = thumb ? thumb : '';
			thumb = null;
		}
		catch (err) {
			Log(3, 'Error loading icon ' + icon);
		}
	}
}

function initEVI(from_fayt) {
	if (!EVI) EVI = DOpus.Create().EverythingInterface();
	var sw = false;
	Log(1, 'EV is running:' + EVI.isrunning + '; autorun:' + EVI.autorun);
	if (!EVI.isrunning) {
		if (from_fayt) return false;
		if (EVI.autorun) {
			Log(2, 'Starting Everything...');
			sw = EVI.start();
			close_on_exit = sw;
			DOpus.Delay(150);
		}
	}
	else sw = true;
	var attempt = 0;
	var loaded;
	do {
		loaded = EVI.SendCmd(401);
		Log(1, 'Waiting for EV database to be loaded: ' + loaded);
		if (loaded == 1) break;
		DOpus.Delay(50);
		attempt++;
		if (attempt == 100) {
			Log(3, 'Max attempts waiting for the database has reached!');
			sw = false;
			break;
		}
	} while (true);
	return sw;
}

// Called whenever the user modifies the script's configuration
function OnScriptConfigChange(configChangeData) {
	var v = configChangeData.changed;
	outer: for (var i = 0; i < v.length; i++) {
		switch (v(i)) {
			case 'included paths':
			case 'excluded paths':
			case 'included exts':
			case 'database path':
			case 'use targets for links':
				DOpus.Create().Command.RunCommandAsync('OpusLauncher BUILD=noload');
				break outer;
			case 'excluded words':
				DOpus.Create().Command.RunCommandAsync('OpusLauncher BUILD');
				break outer;
		}
	}
	v = null;
	return;
}

function Log(level, text) {
	if (level === 4 || Script.config['log level'] < level) {
		if (level == 1) DOpus.Output('<#%vs_dragdrop_normal_action>DEBUG   => ' + text + '</#>');
		else if (level == 2) DOpus.Output('INFO    => ' + text);
		else if (level === 3) DOpus.Output('<#%vs_dragdrop_warning_action>WARNING => ' + text + '</#>');
		else DOpus.Output('ERROR   => ' + text, true);
	}
}

function alert(parent, message, level) {
	var dlg = DOpus.Dlg();
	if (parent) {
		dlg.window = parent;
		dlg.disable_window = parent;
	}
	dlg.message = message;
	dlg.buttons = '&OK';
	dlg.title = script_name + ' v' + script_version;
	if (level === 0) dlg.icon = 'info';
	else if (level === 1) dlg.icon = 'question';
	else if (level === 2) dlg.icon = 'warning';
	else dlg.icon = 'error';
	dlg.Show();
	dlg = null;
}

String.prototype.trim = function() {
	return this.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '');
};
==SCRIPT RESOURCES
<resources>
	<resource name="main" type="dialog">
		<dialog dragdrop="yes" height="224" lang="english" maximize="yes" minimize="yes" resize="yes" title="OpusLauncher" width="258">
			<control halign="left" height="12" name="filter" resize="w" tip="Filter entries" type="edit" width="168" x="4" y="4" />
			<control fullrow="yes" height="174" name="listview" nosortheader="yes" resize="wh" smallicons="yes" type="listview" viewmode="details" width="250" x="4" y="18">
				<columns>
					<item text="Name" />
					<item text="RunCount" />
					<item text="Path" />
				</columns>
			</control>
			<control height="12" image="#clearquickfilter" name="btn_clear" resize="x" title="❌" type="button" width="13" x="172" y="4" />
			<control height="12" image="#refresh" name="btn_refresh" resize="x" title="&amp;Refresh" type="button" width="14" x="185" y="4" />
			<control halign="left" height="8" name="total" resize="x" type="markuptext" width="50" x="202" y="6" />
			<control enable="no" height="14" name="btn_edit" resize="y" title="Edit Args" type="button" width="62" x="6" y="206" />
			<control checked="yes" height="10" name="use_files" resize="y" title="&amp;Open with selected files" type="check" width="246" x="6" y="194" />
		</dialog>
	</resource>
	<resource type="strings">
		<strings lang="esm">
			<string id="config_loglevel">El nivel de registro a mostrar. OFF para mostrar solo errores. 
DEBUG para mostrar todos los mensajes. 
STANDARD para mostrar solo la información más relevante. 
WARNING para mostrar mensajes que necesitan tu atención.</string>
			<string id="header_runcount">RunCount</string>
			<string id="label_add_confirm">¿Desea añadir estos archivos?</string>
			<string id="label_args_info">Puede usar las referencias de controles externos de DOpus para pasar nombres, rutas y otros. ({filepath}, etc).
Esto afectará a como se procesan los archivos seleccionados al abrirse con el programa relacionado.</string>
			<string id="label_clear_confirm">¿Quiere limpiar el comando de los archivos cargados?</string>
			<string id="label_edit_args">Editar argumentos</string>
			<string id="label_no_results">No existen resultados de la búsqueda</string>
			<string id="label_scan_confirm">No se encontró la database. ¿Desea crear una ahora?</string>
			<string id="label_total">Total :</string>
			<string id="label_use_files">&amp;Usar los archivos cargados al abrir el programa</string>
		</strings>
		<strings lang="english">
			<string id="config_loglevel">Logging level to be displayed. OFF to show only errors. 
DEBUG to show all messages. 
STANDARD to show only the most relevant information.
WARNING to show messages that needs your attention.</string>
			<string id="header_runcount">RunCount</string>
			<string id="label_add_confirm">Add the dropped items?</string>
			<string id="label_args_info">You can use DOpus external controls codes to pass info about names, paths and so on. ({filepath}, etc).
This will affect how the selected files are processed when running the related app.</string>
			<string id="label_clear_confirm">Do you want to clear files loaded in command?</string>
			<string id="label_edit_args">Edit args</string>
			<string id="label_no_results">There&apos;s no search results available</string>
			<string id="label_scan_confirm">No database was found. Do you want to create one now?</string>
			<string id="label_total">Total :</string>
			<string id="label_use_files">&amp;Use files in command when open</string>
		</strings>
	</resource>
	<resource name="custom_args" type="dialog">
		<dialog height="136" lang="english" resize="yes" width="302">
			<control fullrow="yes" height="128" name="listview" resize="wh" type="listview" viewmode="details" width="242" x="4" y="4">
				<columns>
					<item text="Name" />
				</columns>
			</control>
			<control enable="no" height="14" name="edit_btn" resize="x" title="Edit" type="button" width="50" x="248" y="4" />
			<control enable="no" height="14" name="del_btn" resize="x" title="Delete" type="button" width="50" x="248" y="20" />
		</dialog>
	</resource>
	<resource name="edit_args" type="dialog">
		<dialog height="110" lang="english" resize="yes" standard_buttons="ok,cancel" width="308">
			<control halign="left" height="28" multiline="yes" name="prompt" resize="wh" type="edit" width="300" x="4" y="36" />
			<control halign="left" height="8" name="title_args" title="&lt;b&gt;&lt;#%vs_listview_header_text&gt;Args :&lt;/#&gt;&lt;/b&gt;" type="markuptext" width="166" x="4" y="26" />
			<control halign="left" height="8" name="title_name" title="&lt;b&gt;&lt;#%vs_listview_header_text&gt;Name:&lt;/#&gt;&lt;/b&gt;" type="markuptext" width="40" x="4" y="4" />
			<control halign="left" height="8" name="name" resize="w" type="static" valign="center" width="220" x="48" y="4" />
			<control halign="left" height="24" icon="1" name="info" resize="yw" title="info about\nargs" type="markuptext" width="300" x="4" y="66" />
			<control halign="left" height="8" name="title_path" title="&lt;b&gt;&lt;#%vs_listview_header_text&gt;Path:&lt;/#&gt;&lt;/b&gt;" type="markuptext" width="40" x="4" y="14" />
			<control ellipsis="path" halign="left" height="8" name="fullpath" resize="w" type="static" valign="center" width="220" x="48" y="14" />
			<control halign="left" height="30" image="yes" name="thumb" resize="x" type="static" valign="top" width="32" x="272" y="4" />
		</dialog>
	</resource>
</resources>
