﻿var script_name = 'EVDirRanges';
var script_version = '2.0';
var script_url = 'https://resource.dopus.com/t/evdirranges-min-max-values-in-folders/57880';
var EVI = {
	inf: undefined,
	CommonQuery: function(path, type, sort, prop, recursive) {
		var arr = ['', '', ''];
		if (!this.inf || !path) {
			Log(3, '=> No Everything Interface detected or path is not valid!');
			return arr;
		}
		var mod = type === 'c' ? 'dc: ' : type === 'm' ? 'dm: ' : 'size: ';
		var folders = type === 's' || Script.config.ignore_folders ? 'file:' : '';
		var addin_query = Script.config.addin_query;
		var rec = !recursive ? ('depth:' + path.components + ' ') : '';
		var query = '"' + path + '\\" ' + rec + folders + mod + addin_query;
		try {
			var r = this.inf.Query(query, '', 'f' + type, sort, 1);
		}
		catch (err) {
			Log(3, '=> Error running query : ' + err.message);
			return arr;
		}
		if (r.empty) {
			// Log(1, '=> ' + query + ': empty!');
			return arr;
		}
		arr[0] = String(r(0).fullpath || '');
		if (Script.config.relative_fullpath) arr[0] = arr[0].slice(path.def_value.length + 1);
		arr[1] = r(0)[prop];
		arr[2] = type !== 's' && arr[1] ? FormatAgo(arr[1]) : '';
		// Log(1, '=> ' + query + '; sort:' + sort + '; prop:' + prop + '; name:' + arr[0] + '; val:' + arr[1] + '; ago:' + arr[2]);
		r = null;
		return arr;
	},
};
// Called by Directory Opus to initialize the script
function OnInit(initData) {
	initData.name = script_name;
	initData.version = script_version;
	initData.copyright = '(c) 2025 Christian Arellano García';
	initData.desc = 'Shows min/max values of common date and size properties for folder contents, using Everything';
	initData.url = script_url;
	initData.default_enable = true;
	initData.min_version = '13.19';
	AddConfig('log_level', DOpus.NewVector(2, 'debug', 'standard', 'warning', 'off'), DOpus.strings.Get('log_level'), 'Misc', 2, 'log level');
	AddConfig('ignore_folders', true, DOpus.strings.Get('ignore_folders'), 'General', 1, 'ignore folders properties');
	AddConfig('addin_query', '', DOpus.strings.Get('addin_query'), 'General', 1, 'additional query');
	AddConfig('relative_fullpath', true, DOpus.strings.Get('relative_fullpath'), 'General', 1, 'relative filenames');
	AddConfig('format_age', DOpus.NewVector(2, 'days', 'duration', 'text'), DOpus.strings.Get('format_age'), 'General', 1, 'age-related columns format');

	function AddConfig(name, value, desc, group, g_order, label, min, max) {
		initData.config[name] = value;
		initData.config[name].desc = desc;
		initData.config[name].group = group;
		initData.config[name].group_order = g_order;
		if (label) initData.config[name].label = label;
		if (min !== undefined && min !== '') initData.config[name].min = min;
		if (max) initData.config[name].max = max;
	}
}

// Called to add columns to Opus
function OnAddColumns(addColData) {
	var age_type = Script.config.format_age;
	var age_format = ['signed,double', 'duration', ''];
	AddColumn('OnNewestCreated', 'newest_created', 'Newest File Date Created', 1, 'datetime');
	AddColumn('OnNewestCreated', 'newest_created_name', 'Newest File Created', 0);
	AddColumn('OnNewestCreated', 'newest_created_ago', 'Ago Newest Created', 2, age_format[age_type]);

	AddColumn('OnOldestCreated', 'oldest_created', 'Oldest File Date Created', 1, 'datetime');
	AddColumn('OnOldestCreated', 'oldest_created_name', 'Oldest File Created', 0);
	AddColumn('OnOldestCreated', 'oldest_created_ago', 'Ago Oldest Created', 2, age_format[age_type]);

	AddColumn('OnNewestCreatedRec', 'newest_created_recursive', 'Newest File Date Created (Rec)', 1, 'datetime');
	AddColumn('OnNewestCreatedRec', 'newest_created_name_recursive', 'Newest File Created (Rec)', 0);
	AddColumn('OnNewestCreatedRec', 'newest_created_recursive_ago', 'Ago Newest Created (Rec)', 2, age_format[age_type]);

	AddColumn('OnOldestCreatedRec', 'oldest_created_recursive', 'Oldest File Date Created (Rec)', 1, 'datetime');
	AddColumn('OnOldestCreatedRec', 'oldest_created_name_recursive', 'Oldest File Created (Rec)', 0);
	AddColumn('OnOldestCreatedRec', 'oldest_created_recursive_ago', 'Ago Oldest Created (Rec)', 2, age_format[age_type]);

	AddColumn('OnNewestModified', 'newest_modified', 'Newest File Date Modified', 1, 'datetime');
	AddColumn('OnNewestModified', 'newest_modified_name', 'Newest File Modified', 0);
	AddColumn('OnNewestModified', 'newest_modified_ago', 'Ago Newest Modified', 2, age_format[age_type]);

	AddColumn('OnOldestModified', 'oldest_modified', 'Oldest File Date Modified', 1, 'datetime');
	AddColumn('OnOldestModified', 'oldest_modified_name', 'Oldest File Modified', 0);
	AddColumn('OnOldestModified', 'oldest_modified_ago', 'Ago Oldest Modified', 2, age_format[age_type]);

	AddColumn('OnNewestModifiedRec', 'newest_modified_recursive', 'Newest File Date Modified (Rec)', 1, 'datetime');
	AddColumn('OnNewestModifiedRec', 'newest_modified_name_recursive', 'Newest File Modified (Rec)', 0);
	AddColumn('OnNewestModifiedRec', 'newest_modified_recursive_ago', 'Ago Newest Modified (Rec)', 2, age_format[age_type]);

	AddColumn('OnOldestModifiedRec', 'oldest_modified_recursive', 'Oldest File Date Modified (Rec)', 1, 'datetime');
	AddColumn('OnOldestModifiedRec', 'oldest_modified_name_recursive', 'Oldest File Modified (Rec)', 0);
	AddColumn('OnOldestModifiedRec', 'oldest_modified_recursive_ago', 'Ago Oldest Modified (Rec)', 2, age_format[age_type]);

	AddColumn('OnBiggestFileSize', 'biggest_filesize', 'Biggest Filesize', 1, 'size');
	AddColumn('OnBiggestFileSize', 'biggest_filesize_name', 'Biggest File', 0);

	AddColumn('OnBiggestFileSizeRec', 'biggest_filesize_recursive', 'Biggest Filesize (Rec)', 1, 'size');
	AddColumn('OnBiggestFileSizeRec', 'biggest_filesize_name_recursive', 'Biggest File (Rec)', 0);

	AddColumn('OnSmallerFileSize', 'smaller_filesize', 'Smaller Filesize', 1, 'size');
	AddColumn('OnSmallerFileSize', 'smaller_filesize_name', 'Smaller File', 0);

	AddColumn('OnSmallerFileSizeRec', 'smaller_filesize_recursive', 'Smaller Filesize (Rec)', 1, 'size');
	AddColumn('OnSmallerFileSizeRec', 'smaller_filesize_name_recursive', 'Smaller File (Rec)', 0);

	function AddColumn(method, name, label, data, type) {
		var col = addColData.AddColumn();
		col.name = name;
		col.method = method;
		col.label = label;
		if (type) col.type = type;
		else col.ellipsis = 'm';
		col.justify = name.indexOf('name') === -1 ? 'right' : 'left';
		col.autogroup = true;
		col.userdata = data;
		col.multicol = true;
		col.autorefresh = true;
	}

}

function OnNewestCreated(scriptColData) {
	CommonEVSolver(scriptColData.item, scriptColData.columns, 'c', 12, 'create');
}

function OnOldestCreated(scriptColData) {
	CommonEVSolver(scriptColData.item, scriptColData.columns, 'c', 11, 'create');
}

function OnNewestCreatedRec(scriptColData) {
	CommonEVSolver(scriptColData.item, scriptColData.columns, 'c', 12, 'create', true);
}

function OnOldestCreatedRec(scriptColData) {
	CommonEVSolver(scriptColData.item, scriptColData.columns, 'c', 11, 'create', true);
}

function OnNewestModified(scriptColData) {
	CommonEVSolver(scriptColData.item, scriptColData.columns, 'm', 14, 'modify');
}

function OnOldestModified(scriptColData) {
	CommonEVSolver(scriptColData.item, scriptColData.columns, 'm', 13, 'modify');
}

function OnNewestModifiedRec(scriptColData) {
	CommonEVSolver(scriptColData.item, scriptColData.columns, 'm', 14, 'modify', true);
}

function OnOldestModifiedRec(scriptColData) {
	CommonEVSolver(scriptColData.item, scriptColData.columns, 'm', 13, 'modify', true);
}

function OnBiggestFileSize(scriptColData) {
	CommonEVSolver(scriptColData.item, scriptColData.columns, 's', 6, 'size');
}

function OnBiggestFileSizeRec(scriptColData) {
	CommonEVSolver(scriptColData.item, scriptColData.columns, 's', 6, 'size', true);
}

function OnSmallerFileSize(scriptColData) {
	CommonEVSolver(scriptColData.item, scriptColData.columns, 's', 5, 'size');
}

function OnSmallerFileSizeRec(scriptColData) {
	CommonEVSolver(scriptColData.item, scriptColData.columns, 's', 5, 'size', true);
}
var now, ago_helper;
// Implement the Newest-Oldest column
function CommonEVSolver(item, cols, type, sort, prop, isRecursive) {
	if (!item) return;
	// Log(1, item.name);
	if (!item.is_dir) {
		SetColValue(cols, ['', '', '']);
		return;
	}
	StartEVI();
	var data = EVI.CommonQuery(item.realpath, type, sort, prop, isRecursive);
	SetColValue(cols, data);

	function SetColValue(cols, data) {
		for (var e = new Enumerator(cols); !e.atEnd(); e.moveNext()) {
			var col = cols(e.item());
			var value = data[col.userdata];
			col.value = value;
			// Log(1, '=> ' + e.item() + ':' + value + ' : ' + col.type);
			if (col.type && col.type !== 'datetime' && value === '') {
				col.sort = -1;
			}
		}
	}
}

function FormatAgo(date) {
	var type = Script.config.format_age;
	if (!now)
		now = DOpus.Create().Date();
	var ms = now.GetTime() - date.GetTime();
	if (type < 2) {
		if (type === 1) return ms < 0 ? 0 : ms / 1000;
		return Math.round(ms / 86400000 * 10) / 10; // 86400000 = ms in a day
	}
	if (!ago_helper) {
		var str_tools = DOpus.Create().StringTools();
		ago_helper = [
			[str_tools.LanguageStr(9313), str_tools.LanguageStr(9317)],
			[str_tools.LanguageStr(9312), str_tools.LanguageStr(9316)],
			[str_tools.LanguageStr(9310), str_tools.LanguageStr(9314)],
			[str_tools.LanguageStr(9353), str_tools.LanguageStr(9355)],
			[str_tools.LanguageStr(9352), str_tools.LanguageStr(9354)],
			['s', 's'],
			str_tools.LanguageStr(1446)
		];
		str_tools = null;
	}
	// Log(1, 'date:' + date + '; now:' + now);
	var ini = ms >= 0 ? now : date;
	var end = ms >= 0 ? date : now;
	var years = ini.year - end.year;
	var months = ini.month - end.month;
	var days = ini.day - end.day;
	var hours = ini.hour - end.hour;
	var mins = ini.min - end.min;
	var secs = ini.sec - end.sec;

	// Log(1, years + ',' + months + ', ' + days + ',' + hours + ',' + mins + ',' + secs);
	if (secs < 0) {
		secs += 60;
		mins--;
	}

	if (mins < 0) {
		mins += 60;
		hours--;
	}

	if (hours < 0) {
		hours += 24;
		days--;
	}

	if (days < 0) {
		// get days in previous month
		var prev = now.Clone();
		prev.day = 1;
		prev.Sub(1, 'd');
		days += prev.day;
		months--;
	}

	if (months < 0) {
		months += 12;
		years--;
	}
	var parts = [];

	function add(v, s) {
		if (v > 0) parts.push(v + ' ' + ago_helper[s][v === 1 ? 0 : 1]);
	}

	add(years, 0);
	add(months, 1);
	add(days, 2);
	add(hours, 3);
	add(mins, 4);
	add(secs, 5);

	if (parts.length === 0) return ago_helper[6];
	parts = parts.join(', ');
	if (ms < 0) parts = '+' + parts;
	return parts;
}

function StartEVI() {
	if (EVI.inf !== undefined) return;
	var ev = DOpus.Create().EverythingInterface();
	var sw = false;
	Log(2, 'Starting Everything Interface...');
	Log(1, '=> is running:' + ev.isrunning + '; autorun:' + ev.autorun);
	if (!ev.isrunning && ev.autorun) {
		Log(2, '=> Starting Everything...');
		sw = ev.start(2000);
		DOpus.Delay(50);
	}
	else sw = true;
	if (!sw) {
		EVI.inf = null;
		return;
	}

	var attempt = 0;
	var loaded;
	Log(2, '=> Waiting for EV database...');
	do {
		loaded = ev.SendCmd(401); //wait for database
		if (loaded == 1) {
			Log(2, '==> Succesfully loaded...');
			break;
		}
		DOpus.Delay(10);
		attempt++;
		if (attempt === 150) {
			Log(3, ' ==> Max attempts waiting for the database has reached!');
			sw = false;
			break;
		}
	} while (true);
	EVI.inf = sw ? ev : null;
}
// Called whenever the user modifies the script's configuration
function OnScriptConfigChange(scriptConfigChangeData) {
	var changed = DOpus.Create().StringSet(scriptConfigChangeData.changed);
	if (changed.Exists('log_level') && changed.size === 1) return;
	if (changed.Exists('format_age')) Script.InitColumns();
	var cols = [
'newest_created', 'newest_created_name', 'newest_created_ago',
'oldest_created', 'oldest_created_name', 'oldest_created_ago',
'newest_created_recursive', 'newest_created_name_recursive',
'newest_created_recursive_ago', 'oldest_created_recursive',
'oldest_created_name_recursive', 'oldest_created_recursive_ago',
'newest_modified', 'newest_modified_name', 'newest_modified_ago',
'oldest_modified', 'oldest_modified_name', 'oldest_modified_ago',
'newest_modified_recursive', 'newest_modified_name_recursive',
'newest_modified_recursive_ago', 'oldest_modified_recursive',
'oldest_modified_name_recursive', 'oldest_modified_recursive_ago',
'biggest_filesize', 'biggest_filesize_name', 'biggest_filesize_recursive',
'biggest_filesize_name_recursive', 'smaller_filesize', 'smaller_filesize_name',
'smaller_filesize_recursive', 'smaller_filesize_name_recursive'
	];
	for (var i = cols.length - 1; i >= 0; i--) {
		Script.RefreshColumn(cols[i]);
	}
	cols = null;
	changed = null;
}

function OnAddConfigPages(AddConfigPagesData) {
	Log(1, 'Calling OnAddConfigPages Dialog!');
	var dlg = {};
	dlg.main = DOpus.Dlg();
	dlg.main.template = 'config';
	dlg.main.title = script_name + ' v' + script_version;
	dlg.main.icon = DOpus.LoadImage(DOpus.Aliases('home').path + '\\dopusrt.exe,5');
	dlg.main.singleton = script_name + ' v' + script_version + '_config';
	if (!dlg.main.Create()) {
		Log(3, 'The Config dialog is already opened!')
		return;
	}
	dlg.tabs = dlg.main.Control('tabs');
	dlg.main.AddConfigPages(AddConfigPagesData);
	dlg.tabs.RemoveItem(0);
	dlg.main.SetTimer(500, 'check_update_timer', true);
	var msg, id = '57880';
	while (true) {
		msg = dlg.main.GetMsg();
		if (!msg.result || msg.event === 'invalid') break;
		// Log(1, msg.result + ': event=' + msg.event + '; value=' + msg.value);
		if (msg.event === 'timer' && msg.name === 'check_update_timer')
			dlg.http = CheckForUpdates(id);
		else if (msg.event === 'click' && msg.name === 'notifyicon') {
			var cmd = DOpus.Create().Command();
			cmd.RunCommand(script_url + '#release');
		}
		else if (dlg.http && msg.event === 'http') {
			if (msg.value === 'error') {
				Log(3, 'Unable to connect to the endpoint to check for updates!');
				dlg.http = null;
				continue;
			}
			if (msg.value !== 'data') continue;
			try {
				var res = dlg.http.ReadResponse().match(/<a name="release">([^<]+)<\/a>/i);
				if (res) {
					var new_version = ParseVersion(res[1]);
					Log(1, 'latest version=' + new_version + '; current version=' + script_version);
					if (new_version > ParseVersion(script_version)) {
						Log(1, 'latest version detected:' + res[1]);
						dlg.main.NotifyIcon('add', dlg.main.icon, script_name);
						dlg.main.NotifyIcon('notify', script_name, 'New version available:' + res[1]);
					}
				}
				else Log(3, 'Unable to find the pattern required to detect the latest release!');
				dlg.http.shutdown();
			}
			catch (err) {};
			dlg.http = null;
		}
	}
	dlg = null;

	return;

	function CheckForUpdates(id) {
		try {
			var http = dlg.main.NewHTTPReq();
			http.SendRequest('https://resource.dopus.com/raw/' + id + '/1');
			Log(1, 'Checking for updates from "https://resource.dopus.com/raw/' + id + '/1"...');
		}
		catch (err) {
			Log(3, 'Error sending http request : ' + err.description);
			return null;
		}
		return http;
	}
}

function ParseVersion(version) {
	version = version.replace(/[^0-9.]+/g, '');
	var parts = version.split('.');
	if (parts.length <= 1) return parseFloat(version);
	var first = parts.shift();
	return parseFloat(first + '.' + parts.join(''));
}

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);
	}
}
==SCRIPT RESOURCES
<resources>
	<resource type="strings">
		<strings lang="english">
			<string id="addin_query">Additional Everything query to use as a refinement tool for results, e.g. to exclude some file types, etc.</string>
			<string id="format_age">Format for age-related columns.
days : Show as days elapsed. Numeric value (e.g. 30)
duration : Show as a duration type column (e.g. 2 days, 12:01:23)
text : Show as a full textual duration (e.g. 2 days, 12 hours, 1 minute, 23 s)</string>
			<string id="ignore_folders">Set to True to ignore properties from folders itself</string>
			<string id="log_level">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="relative_fullpath">When True, filename-related columns will have their content set relative to the calculated folder</string>
		</strings>
	</resource>
	<resource name="config" type="dialog">
		<dialog height="154" lang="english" width="232">
			<control height="146" name="tabs" type="tab" width="224" x="4" y="4">
				<tabs>
					<tab dialog="config_update" />
				</tabs>
			</control>
		</dialog>
	</resource>
	<resource name="config_update" type="dialog">
		<dialog height="100" lang="english" width="180" />
	</resource>
</resources>
