1 //=============================================================================
2 //
3 //   File : libkviaddon.cpp
4 //   Creation date : Tue 31 Mar 01:02:12 2005 GMT by Szymon Stefanek
5 //
6 //   This file is part of the KVIrc IRC client distribution
7 //   Copyright (C) 2005-2010 Szymon Stefanek (pragma at kvirc dot net)
8 //
9 //   This program is FREE software. You can redistribute it and/or
10 //   modify it under the terms of the GNU General Public License
11 //   as published by the Free Software Foundation; either version 2
12 //   of the License, or (at your option) any later version.
13 //
14 //   This program is distributed in the HOPE that it will be USEFUL,
15 //   but WITHOUT ANY WARRANTY; without even the implied warranty of
16 //   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
17 //   See the GNU General Public License for more details.
18 //
19 //   You should have received a copy of the GNU General Public License
20 //   along with this program. If not, write to the Free Software Foundation,
21 //   Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22 //
23 //=============================================================================
24 
25 #include "AddonManagementDialog.h"
26 #include "AddonFunctions.h"
27 
28 #include "KviModule.h"
29 #include "KviKvsScriptAddonManager.h"
30 #include "KviLocale.h"
31 #include "KviQString.h"
32 #include "KviCommandFormatter.h"
33 #include "KviError.h"
34 #include "kvi_out.h"
35 #include "KviIconManager.h"
36 #include "KviControlCodes.h"
37 #include "KviConfigurationFile.h"
38 #include "kvi_sourcesdate.h"
39 #include "KviMiscUtils.h"
40 #include "KviFileUtils.h"
41 
42 #include <QFileInfo>
43 #include <QDir>
44 
45 QRect g_rectManagementDialogGeometry(0, 0, 0, 0);
46 
47 /*
48 	@doc: addon.exists
49 	@type:
50 		function
51 	@title:
52 		$addon.exists
53 	@short:
54 		Checks if an addon is currently installed
55 	@syntax:
56 		<boolean> $addon.exists(<id:string>[,<version:string>])
57 	@description:
58 		Returns [b]1[/b] if the addon with the specified <id> is currently installed
59 		and [b]0[/b] otherwise. If <version> is specified then any addon with
60 		a version lower than <version> is ignored (so you can effectively
61 		check if a greater or equal version is present).
62 */
63 
addon_kvs_fnc_exists(KviKvsModuleFunctionCall * c)64 static bool addon_kvs_fnc_exists(KviKvsModuleFunctionCall * c)
65 {
66 	QString szId;
67 	QString szVersion;
68 	KVSM_PARAMETERS_BEGIN(c)
69 	KVSM_PARAMETER("id", KVS_PT_NONEMPTYSTRING, 0, szId)
70 	KVSM_PARAMETER("version", KVS_PT_STRING, KVS_PF_OPTIONAL, szVersion)
71 	KVSM_PARAMETERS_END(c)
72 
73 	KviKvsScriptAddon * a = KviKvsScriptAddonManager::instance()->findAddon(szId);
74 	if(a)
75 	{
76 		if(szVersion.isEmpty())
77 		{
78 			c->returnValue()->setBoolean(true);
79 		}
80 		else
81 		{
82 			c->returnValue()->setBoolean(KviMiscUtils::compareVersions(a->version(), szVersion) < 0);
83 		}
84 	}
85 	else
86 	{
87 		c->returnValue()->setBoolean(false);
88 	}
89 	return true;
90 }
91 
92 /*
93 	@doc: addon.version
94 	@type:
95 		function
96 	@title:
97 		$addon.version
98 	@short:
99 		Returns the version of an installed addon
100 	@syntax:
101 		<string> $addon.version(<id:string>)
102 	@description:
103 		Returns the version of the currently installed addon with the
104 		specified <id>. If the addon with the given <id> does not exist
105 		then an empty string is returned.
106 */
107 
addon_kvs_fnc_version(KviKvsModuleFunctionCall * c)108 static bool addon_kvs_fnc_version(KviKvsModuleFunctionCall * c)
109 {
110 	QString szId;
111 	KVSM_PARAMETERS_BEGIN(c)
112 	KVSM_PARAMETER("id", KVS_PT_NONEMPTYSTRING, 0, szId)
113 	KVSM_PARAMETERS_END(c)
114 
115 	KviKvsScriptAddon * a = KviKvsScriptAddonManager::instance()->findAddon(szId);
116 	if(a)
117 	{
118 		c->returnValue()->setString(a->version());
119 	}
120 	else
121 	{
122 		c->returnValue()->setNothing();
123 	}
124 	return true;
125 }
126 
127 /*
128 	@doc: addon.list
129 	@type:
130 		command
131 	@title:
132 		addon.list
133 	@short:
134 		Lists the installed addons
135 	@syntax:
136 		addon.list
137 	@description:
138 		Lists the currently installed addons
139 	@seealso:
140 		[cmd]addon.register[/cmd]
141 */
142 
addon_kvs_cmd_list(KviKvsModuleCommandCall * c)143 static bool addon_kvs_cmd_list(KviKvsModuleCommandCall * c)
144 {
145 	KviPointerHashTable<QString, KviKvsScriptAddon> * da = KviKvsScriptAddonManager::instance()->addonDict();
146 
147 	int cnt = 0;
148 	KviPointerHashTableIterator<QString, KviKvsScriptAddon> it(*da);
149 	while(KviKvsScriptAddon * a = it.current())
150 	{
151 		c->window()->output(KVI_OUT_SYSTEMMESSAGE, __tr2qs_ctx("%cAddon ID %Q, version %Q%c", "addon"), KviControlCodes::Bold, &(a->name()), &(a->version()), KviControlCodes::Bold);
152 		c->window()->output(KVI_OUT_SYSTEMMESSAGE, __tr2qs_ctx("Name: %Q", "addon"), &(a->visibleName()));
153 		c->window()->output(KVI_OUT_SYSTEMMESSAGE, __tr2qs_ctx("Description: %Q", "addon"), &(a->description()));
154 
155 		++it;
156 		cnt++;
157 	}
158 
159 	c->window()->output(KVI_OUT_SYSTEMMESSAGE, __tr2qs_ctx("Total: %d addons installed", "addon"), cnt);
160 	return true;
161 }
162 
163 /*
164 	@doc: addon.uninstall
165 	@type:
166 		command
167 	@title:
168 		addon.uninstall
169 	@short:
170 		Uninstalls an addon
171 	@syntax:
172 		addon.uninstall [-q] [-n] <id:string>
173 	@switches:
174 		!sw: -n | --no-callback
175 			Doesn't call the uninstall callback but only removes the registration entry.
176 		!sw: -q | --quiet
177 			Makes the command run quietly
178 	@description:
179 		Uninstalls the specified addon by executing its uninstall callback function
180 		and removing its installed files. It also removes the addon's registration entry.
181 		If the [b]-n[/b] switch is specified the uninstall callback is not called,
182 		only the registration entry is removed.
183 	@seealso:
184 		[cmd]addon.register[/cmd]
185 */
186 
addon_kvs_cmd_uninstall(KviKvsModuleCommandCall * c)187 static bool addon_kvs_cmd_uninstall(KviKvsModuleCommandCall * c)
188 {
189 	QString szName;
190 	KVSM_PARAMETERS_BEGIN(c)
191 	KVSM_PARAMETER("name", KVS_PT_NONEMPTYSTRING, 0, szName)
192 	KVSM_PARAMETERS_END(c)
193 
194 	KviKvsScriptAddon * a = KviKvsScriptAddonManager::instance()->findAddon(szName);
195 	if(a)
196 	{
197 		if(!c->switches()->find('q', "quiet"))
198 			c->window()->output(KVI_OUT_SYSTEMMESSAGE, __tr2qs_ctx("Uninstalling existing addon version %Q", "addon"), &(a->version()));
199 
200 		// uninstall the existing version
201 		KviKvsScriptAddonManager::instance()->unregisterAddon(szName, c->window(), !c->switches()->find('n', "no-callback"));
202 	}
203 	else
204 	{
205 		if(!c->switches()->find('q', "quiet"))
206 			c->warning(__tr2qs_ctx("The addon \"%1\" doesn't exist", "addon").arg(szName));
207 	}
208 
209 	return true;
210 }
211 
212 /*
213 	@doc: addon.configure
214 	@type:
215 		command
216 	@title:
217 		addon.configure
218 	@short:
219 		Executes an addon's configuration callback
220 	@syntax:
221 		addon.configure [-q] <id:string>
222 	@switches:
223 		!sw: -q | --quiet
224 			Makes the command run quietly
225 	@description:
226 		Executes the configuration callback of the specified addon.
227 	@seealso:
228 		[cmd]addon.register[/cmd]
229 		[cmd]addon.setconfigurecallback[/cmd]
230 */
231 
addon_kvs_cmd_configure(KviKvsModuleCommandCall * c)232 static bool addon_kvs_cmd_configure(KviKvsModuleCommandCall * c)
233 {
234 	QString szName;
235 	KVSM_PARAMETERS_BEGIN(c)
236 	KVSM_PARAMETER("name", KVS_PT_NONEMPTYSTRING, 0, szName)
237 	KVSM_PARAMETERS_END(c)
238 
239 	KviKvsScriptAddon * a = KviKvsScriptAddonManager::instance()->findAddon(szName);
240 	if(a)
241 	{
242 		QString ss = a->configureCallbackCode();
243 		if(ss.isEmpty())
244 		{
245 			if(!c->switches()->find('q', "quiet"))
246 				c->warning(__tr2qs_ctx("The addon \"%1\" has no configure callback set", "addon").arg(szName));
247 		}
248 		else
249 		{
250 			a->executeConfigureCallback(c->window());
251 		}
252 	}
253 	else
254 	{
255 		if(!c->switches()->find('q', "quiet"))
256 			c->warning(__tr2qs_ctx("The addon \"%1\" doesn't exist", "addon").arg(szName));
257 	}
258 
259 	return true;
260 }
261 
262 /*
263 	@doc: addon.help
264 	@type:
265 		command
266 	@title:
267 		addon.help
268 	@short:
269 		Executes an addon's help callback
270 	@syntax:
271 		addon.help [-q] <id:string>
272 	@switches:
273 		!sw: -q | --quiet
274 			Makes the command run quietly
275 	@description:
276 		Executes the help callback of the specified addon. It will usually
277 		display the addon's documentation in the help viewer.
278 	@seealso:
279 		[cmd]addon.register[/cmd]
280 		[cmd]addon.sethelpcallback[/cmd]
281 */
282 
addon_kvs_cmd_help(KviKvsModuleCommandCall * c)283 static bool addon_kvs_cmd_help(KviKvsModuleCommandCall * c)
284 {
285 	QString szName;
286 	KVSM_PARAMETERS_BEGIN(c)
287 	KVSM_PARAMETER("name", KVS_PT_NONEMPTYSTRING, 0, szName)
288 	KVSM_PARAMETERS_END(c)
289 
290 	KviKvsScriptAddon * a = KviKvsScriptAddonManager::instance()->findAddon(szName);
291 	if(a)
292 	{
293 		QString ss = a->helpCallbackCode();
294 		if(ss.isEmpty())
295 		{
296 			if(!c->switches()->find('q', "quiet"))
297 				c->warning(__tr2qs_ctx("The addon \"%1\" has no help callback set", "addon").arg(szName));
298 		}
299 		else
300 		{
301 			a->executeHelpCallback(c->window());
302 		}
303 	}
304 	else
305 	{
306 		if(!c->switches()->find('q', "quiet"))
307 			c->warning(__tr2qs_ctx("The addon \"%1\" doesn't exist", "addon").arg(szName));
308 	}
309 
310 	return true;
311 }
312 
313 /*
314 	@doc: addon.setconfigurecallback
315 	@type:
316 		command
317 	@title:
318 		addon.setconfigurecallback
319 	@short:
320 		Sets an addon's configuration callback
321 	@syntax:
322 		addon.setconfigurecallback [-q] (<id:string>)
323 		{
324 			<configure_callback>
325 		}
326 	@switches:
327 		!sw: -q
328 		Makes the command run quietly
329 	@description:
330 		Sets the configure callback for the specified addon.
331 		The configure callback will be called by the user either by the
332 		means of [cmd]addon.configure[/cmd] or by accessing the
333 		proper function via the GUI.
334 	@seealso:
335 		[cmd]addon.register[/cmd]
336 		[cmd]addon.configure[/cmd]
337 */
338 
addon_kvs_cmd_setconfigurecallback(KviKvsModuleCallbackCommandCall * c)339 static bool addon_kvs_cmd_setconfigurecallback(KviKvsModuleCallbackCommandCall * c)
340 {
341 	QString szName;
342 
343 	KVSM_PARAMETERS_BEGIN(c)
344 	KVSM_PARAMETER("name", KVS_PT_NONEMPTYSTRING, 0, szName)
345 	KVSM_PARAMETERS_END(c)
346 
347 	KviKvsScriptAddon * a = KviKvsScriptAddonManager::instance()->findAddon(szName);
348 	if(a)
349 	{
350 		a->setConfigureCallback(c->callback()->code());
351 	}
352 	else
353 	{
354 		if(!c->switches()->find('q', "quiet"))
355 			c->warning(__tr2qs_ctx("The addon \"%1\" doesn't exist", "addon").arg(szName));
356 	}
357 
358 	return true;
359 }
360 
361 /*
362 	@doc: addon.sethelpcallback
363 	@type:
364 		command
365 	@title:
366 		addon.sethelpcallback
367 	@short:
368 		Sets an addon's help callback
369 	@syntax:
370 		addon.sethelpcallback(<id:string>)
371 		{
372 			<help_callback>
373 		}
374 	@switches:
375 		!sw: -q
376 		Makes the command run quietly
377 	@description:
378 		Sets the help callback for the specified addon.
379 		The help callback will be called by the user either by the
380 		means of [cmd]addon.help[/cmd] or by accessing the
381 		proper function via the GUI. It should display some sort
382 		of addon documentation, usually in the help browser.
383 	@seealso:
384 		[cmd]addon.register[/cmd]
385 		[cmd]addon.help[/cmd]
386 */
387 
addon_kvs_cmd_sethelpcallback(KviKvsModuleCallbackCommandCall * c)388 static bool addon_kvs_cmd_sethelpcallback(KviKvsModuleCallbackCommandCall * c)
389 {
390 	QString szName;
391 
392 	KVSM_PARAMETERS_BEGIN(c)
393 	KVSM_PARAMETER("name", KVS_PT_NONEMPTYSTRING, 0, szName)
394 	KVSM_PARAMETERS_END(c)
395 
396 	KviKvsScriptAddon * a = KviKvsScriptAddonManager::instance()->findAddon(szName);
397 	if(a)
398 	{
399 		a->setHelpCallback(c->callback()->code());
400 	}
401 	else
402 	{
403 		if(!c->switches()->find('q', "quiet"))
404 			c->warning(__tr2qs_ctx("The addon \"%1\" doesn't exist", "addon").arg(szName));
405 	}
406 
407 	return true;
408 }
409 
410 /*
411 	@doc: addon.register
412 	@type:
413 		command
414 	@title:
415 		addon.register
416 	@short:
417 		Registers a script-based addon
418 	@syntax:
419 		addon.register [-f] [-n] [-q] (<id:string>,<version:string>,<visible_name:string>,<description:string>,<minkvircverion:string>[,<iconid:string>])
420 		{
421 			<uninstall callback>
422 		}
423 	@switches:
424 		!sw: -f | --force
425 		Registers the addon even if an addon with the same <id> and
426 		a higher version already exists. The usage of this flag
427 		is highly discouraged (i.e. [b]use it only for debugging purposes
428 		on your own machine[/b]).
429 		!sw: -n | --no-uninstall
430 		Performs no uninstallation of existing versions of the addon:
431 		it simply replaces the registration entry with the new data.
432 		Again, [b]only use this switch for debugging purposes and on your own machine[/b].
433 		!sw: -q | --quiet
434 		Makes the command run quietly
435 	@description:
436 		Registers a script-based addon.[br][br]
437 		The registration process allows to [i]show[/i] the addon in the script-addon manager
438 		dialog and provides a standard way for the user to manage and uninstall the addons.
439 		You simply register your addon BEFORE attempting to install it.[br][br]
440 		A script-based addon is a set of scripts, icons, translations and possibly
441 		other data files that add functionality to the KVIrc program.
442 		The script-based addons are often simply called [i]scripts[/i] and
443 		we will adhere to that naming in certain parts of the documentation too.[br][br]
444 		Each script-based addon (a set of scripts) is identified by a UNIQUE
445 		<id>. Two addons with the same <id> can't co-exist in the same
446 		KVIrc installation (so be sure to choose a token unique enough
447 		to avoid collisions with others). The <id> itself is used only for
448 		identification purposes and the user will almost always see the <visible_name>
449 		instead, which can contain the [fnc]$tr[/fnc] function that will handle
450 		the translation for it.[br][br]
451 		Each addon also has a <version> which is a string in the form x.y.z
452 		where x, y and z are numbers (yes.. that's the standard major-minor-patch level
453 		version numbering scheme). A <version> of 2.4.23 is greater than 2.4.3
454 		even if 2.4.3 comes after when compared as a string.
455 		When an updated addon is installed over the same or previous version, the current version is first uninstalled.
456 		Installing a lower version over a greater one is not possible, unless
457 		the lower version one is uninstalled first.[br][br]
458 		<description> is another, possibly translated, string that will
459 		be presented to the user in the addon management dialog.[br][br]
460 		<minkvircversion> is the minimum KVIrc version required for the
461 		addon to run. If the version of the running KVIrc executable
462 		is lower than the requested one then the command will abort with an error.
463 		If you want to completely ignore the KVIrc versioning (don't do it),
464 		use [b][i]0.0.0[/i][/b] here. If you need fine tuning on git features you may also add
465 		the sources date tag at the end of the required version string (e.g 3.2.1.20060303).[br][br]
466 		<iconid> is the [doc:image_id]image identifier[/doc] of the icon
467 		that will be displayed in the addon management dialog.
468 		If not specified, a default icon will be used.[br][br]
469 		The <uninstall_callback> is a snippet of code that should
470 		wipe out the addon from the system. It is ALWAYS a good practice
471 		to write a complete uninstallation procedure (think that YOU like
472 		to be able to completely uninstall a program that you don't use anymore).
473 		The <uninstall_callback> will be called by KVIrc when the addon
474 		uninstallation is requested, either explicitly by using the GUI or the
475 		command [cmd]addon.uninstall[/cmd], or implicitly by installing
476 		a newer version of the addon (upgrading).[br][br]
477 		If the user's security configuration doesn't allow your addon to be installed,
478 		or a higher version of an addon with the same name already exists,
479 		the command will fail with an error (aborting the installation process).
480 		If you don't want to fail with an error but handle it gracefully instead,
481 		you should use [fnc]$addon.exists()[/fnc] to check if an
482 		addon with the same name and a greater version already exists.
483 		You can't gracefully handle security error conditions: your installation
484 		will be always aborted with an error in this case.[br][br]
485 		The addon can also define a configuration callback via [cmd]addon.setconfigurecallback[/cmd]
486 		and a help callback via [cmd]addon.sethelpcallback[/cmd]. The first
487 		will usually display a configuration dialog, the second will display
488 		some sort of addon documentation, usually in the help browser.[br][br]
489 		The registration process uninstalls any previous addon version
490 		by executing its uninstall callback routine. This is another reason that
491 		you should call addon.register BEFORE you attempt to install your addon.
492 		Failing to do this may result in the the old version uninstallation wiping
493 		out your newly installed files or code.
494 	@seealso:
495 		[cmd]addon.uninstall[/cmd], [fnc]$addon.exists[/fnc],
496 		[cmd]addon.setconfigurecallback[/cmd], [cmd]addon.configure[/cmd],
497 		[cmd]addon.sethelpcallback[/cmd], [cmd]addon.help[/cmd], [cmd]addon.installfiles[/cmd]
498 	@examples:
499 		[example]
500 		[/example]
501 */
502 
addon_kvs_cmd_register(KviKvsModuleCallbackCommandCall * c)503 static bool addon_kvs_cmd_register(KviKvsModuleCallbackCommandCall * c)
504 {
505 	KviKvsScriptAddonRegistrationData rd;
506 	QString szMinKVIrcVersion;
507 
508 	KVSM_PARAMETERS_BEGIN(c)
509 	KVSM_PARAMETER("name", KVS_PT_NONEMPTYSTRING, 0, (rd.szName))
510 	KVSM_PARAMETER("version", KVS_PT_NONEMPTYSTRING, 0, (rd.szVersion))
511 	KVSM_PARAMETER_IGNORED("visible_text")
512 	KVSM_PARAMETER_IGNORED("description")
513 	KVSM_PARAMETER("min_kvirc_version", KVS_PT_NONEMPTYSTRING, 0, szMinKVIrcVersion)
514 	KVSM_PARAMETER("icon_id", KVS_PT_STRING, KVS_PF_OPTIONAL, (rd.szIconId))
515 	KVSM_PARAMETERS_END(c)
516 
517 	if(!(c->getParameterCode(2, rd.szVisibleNameScript) && c->getParameterCode(3, rd.szDescriptionScript)))
518 	{
519 		c->error(__tr2qs_ctx("Internal error: call a head-shrinker", "addon"));
520 		return false;
521 	}
522 
523 	if(c->callback())
524 		rd.szUninstallCallbackScript = c->callback()->code();
525 
526 	if(!KviMiscUtils::isValidVersionString(rd.szVersion))
527 	{
528 		c->error(__tr2qs_ctx("The specified version \"%Q\" is not a valid version string", "addon"), &(rd.szVersion));
529 		return false;
530 	}
531 
532 	if(!KviMiscUtils::isValidVersionString(szMinKVIrcVersion))
533 	{
534 		c->error(__tr2qs_ctx("The specified KVIrc version \"%Q\" is not a valid version string", "addon"), &szMinKVIrcVersion);
535 		return false;
536 	}
537 
538 	if(KviMiscUtils::compareVersions(szMinKVIrcVersion, KVI_VERSION "." KVI_SOURCES_DATE) < 0)
539 	{
540 		c->error(__tr2qs_ctx("This KVIrc executable is too old to run this addon (minimum version required is %Q)", "addon"), &szMinKVIrcVersion);
541 		return false;
542 	}
543 
544 	if(!c->switches()->find('q', "quiet"))
545 		c->window()->output(KVI_OUT_SYSTEMMESSAGE, __tr2qs_ctx("Attempting to register addon \"%Q\" with version %Q", "addon"), &(rd.szName), &(rd.szVersion));
546 
547 	KviKvsScriptAddon * a = KviKvsScriptAddonManager::instance()->findAddon(rd.szName);
548 	if(a)
549 	{
550 		// the same addon already exists
551 		if(KviMiscUtils::compareVersions(a->version(), rd.szVersion) < 0)
552 		{
553 			// and it has a higher version...
554 			// complain unless -f is used
555 			if(!c->switches()->find('f', "force"))
556 			{
557 				c->error(__tr2qs_ctx("The addon \"%Q\" already exists with version %Q which is higher than %Q", "addon"), &(rd.szName), &(a->version()), &(rd.szVersion));
558 				return false;
559 			}
560 		}
561 
562 		if(!c->switches()->find('q', "quiet"))
563 			c->window()->output(KVI_OUT_SYSTEMMESSAGE, __tr2qs_ctx("Uninstalling existing addon version %Q", "addon"), &(a->version()));
564 
565 		bool bUninstall = !c->switches()->find('n', "no-uninstall");
566 
567 		// uninstall the existing version
568 		KviKvsScriptAddonManager::instance()->unregisterAddon(rd.szName, c->window(), bUninstall, bUninstall);
569 	}
570 
571 	if(!KviKvsScriptAddonManager::instance()->registerAddon(&rd))
572 	{
573 		c->error(__tr2qs_ctx("Addon registration failed", "addon"));
574 		return false;
575 	}
576 
577 	if(!c->switches()->find('q', "quiet"))
578 		c->window()->output(KVI_OUT_SYSTEMMESSAGE, __tr2qs_ctx("Addon successfully registered", "addon"));
579 
580 	return true;
581 }
582 
583 /*
584 	@doc: addon.installfiles
585 	@type:
586 		command
587 	@title:
588 		addon.installfiles
589 	@short:
590 		Installs a set of files for an addon
591 	@syntax:
592 		addon.installfiles <id:string> <target:string> [files]
593 	@switches:
594 		!sw: -q | --quiet
595 		Makes the command run quietly
596 		!sw: -s | --skip-nonexistent
597 		Skip nonexistent entries in the [files] list
598 	@description:
599 		Installs the [files] for the addon identified by the specified <id>.
600 		These files will be automatically removed when the addon is uninstalled.[br][br]
601 		<target> is the target path inside the local KVIrc directory. The following
602 		standard paths should be used:
603 		[ul]
604 		[li]"pics" for image files.[/li]
605 		[li]"locale" for translation *.mo files.[/li]
606 		[li]"audio" for sound files.[/li]
607 		[li]"config" for configuration files.[/li]
608 		[li]"help/<language>" for help files.[/li]
609 		[/ul]
610 		Other target paths are allowed and subdirectories are supported (e.g. [i]pics/myaddon[/i]).[br][br]
611 		[files] is a list of filenames or directory names.
612 		Each file will be copied to the specified target path in the local KVIrc directory.
613 		Filenames can contain wildcard characters in the last component.
614 	@seealso:
615 		[cmd]addon.register[/cmd]
616 		[cmd]addon.uninstall[/cmd]
617 */
618 
addon_kvs_cmd_installfiles(KviKvsModuleCommandCall * c)619 static bool addon_kvs_cmd_installfiles(KviKvsModuleCommandCall * c)
620 {
621 	QString szName;
622 	QString szTarget;
623 	QStringList lEntries;
624 
625 	KVSM_PARAMETERS_BEGIN(c)
626 	KVSM_PARAMETER("name", KVS_PT_NONEMPTYSTRING, 0, szName)
627 	KVSM_PARAMETER("target", KVS_PT_NONEMPTYSTRING, 0, szTarget)
628 	KVSM_PARAMETER("files", KVS_PT_STRINGLIST, 0, lEntries)
629 	KVSM_PARAMETERS_END(c)
630 
631 	bool bQuiet = c->switches()->find('q', "quiet");
632 	// we had a typo here, support the old switch name for backward scripts compatibility
633 	bool bSkipNonExistent = c->switches()->find('i', "skip-nonexistent") || c->switches()->find('i', "skip-nonexisting");
634 
635 	KviKvsScriptAddon * a = KviKvsScriptAddonManager::instance()->findAddon(szName);
636 	if(!a)
637 	{
638 		if(!bQuiet)
639 			c->warning(__tr2qs_ctx("The addon \"%1\" doesn't exist", "addon").arg(szName));
640 		return true;
641 	}
642 
643 	if(szTarget.isEmpty())
644 		return c->error(__tr2qs_ctx("The target path can't be empty", "addon"));
645 
646 	if(szTarget.indexOf("..") != -1)
647 		return c->error(__tr2qs_ctx("The target path can't contain ..", "addon"));
648 
649 	if(lEntries.isEmpty())
650 	{
651 		if(!bQuiet)
652 			c->warning(__tr2qs_ctx("Empty file list", "addon"));
653 		return true; // nothing to do
654 	}
655 
656 	QStringList lExpandedEntries;
657 
658 	foreach(QString szEntry, lEntries)
659 	{
660 		KviFileUtils::adjustFilePath(szEntry);
661 
662 		if(szEntry.contains("*"))
663 		{
664 			// filtered entry
665 			int idx = szEntry.lastIndexOf(QChar(KVI_PATH_SEPARATOR_CHAR));
666 			QString szPath;
667 			QString szFilter;
668 			if(idx < 0)
669 			{
670 				szPath = ".";
671 				szFilter = szEntry;
672 			}
673 			else
674 			{
675 				szPath = szEntry.left(idx);
676 				szFilter = szEntry.mid(idx + 1);
677 			}
678 
679 			QDir dir(szPath);
680 			if(!dir.exists())
681 			{
682 				if(!bSkipNonExistent)
683 					return c->error(__tr2qs_ctx("The directory '%1' doesn't exist", "addon").arg(szPath));
684 				if(!bQuiet)
685 					c->warning(__tr2qs_ctx("Skipping non-existent entry '%1'", "addon").arg(szEntry));
686 				continue;
687 			}
688 
689 			QStringList sl = dir.entryList(
690 			    QStringList(szFilter),
691 			    QDir::Files | QDir::NoSymLinks | QDir::Readable | QDir::Hidden | QDir::System,
692 			    QDir::Unsorted);
693 
694 			foreach(QString sz, sl)
695 			{
696 				lExpandedEntries.append(QString("%1%2%3").arg(szPath).arg(QString(KVI_PATH_SEPARATOR_CHAR)).arg(sz));
697 			}
698 			continue;
699 		}
700 
701 		QFileInfo inf(szEntry);
702 		if(!inf.exists())
703 		{
704 			if(!bSkipNonExistent)
705 				return c->error(__tr2qs_ctx("The file '%1' doesn't exist", "addon").arg(szEntry));
706 			if(!bQuiet)
707 				c->warning(__tr2qs_ctx("Skipping non-existent entry '%1'", "addon").arg(szEntry));
708 			continue;
709 		}
710 
711 		if(!inf.isFile())
712 		{
713 			if(!bSkipNonExistent)
714 				return c->error(__tr2qs_ctx("The entry '%1' is not a file", "addon").arg(szEntry));
715 			if(!bQuiet)
716 				c->warning(__tr2qs_ctx("Skipping invalid entry '%1'", "addon").arg(szEntry));
717 			continue;
718 		}
719 
720 		lExpandedEntries.append(szEntry);
721 	}
722 
723 	// create target path
724 	QString szTargetPath;
725 	g_pApp->getLocalKvircDirectory(szTargetPath, KviApplication::None, szTarget);
726 
727 	KviFileUtils::makeDir(szTargetPath);
728 
729 	for(auto & lExpandedEntrie : lExpandedEntries)
730 	{
731 		QFileInfo inf(lExpandedEntrie);
732 		if(!inf.exists())
733 		{
734 			qDebug("ERROR: file %s doesn't exist, but it should...", inf.fileName().toUtf8().data());
735 			continue; // bleah.. should never happen
736 		}
737 
738 		QString szEntry = QString("%1%2%3").arg(szTarget).arg(QString(KVI_PATH_SEPARATOR_CHAR)).arg(inf.fileName());
739 		g_pApp->getLocalKvircDirectory(szTargetPath, KviApplication::None, szEntry);
740 
741 		if(!bQuiet)
742 			c->window()->output(KVI_OUT_SYSTEMMESSAGE, __tr2qs_ctx("Installing file '%1' into '%2'", "addon").arg(lExpandedEntrie).arg(szTargetPath));
743 
744 		KviFileUtils::copyFile(lExpandedEntrie, szTargetPath);
745 
746 		a->addInstalledFile(szEntry);
747 	}
748 
749 	return true;
750 }
751 
752 /*
753 	@doc: addon.dialog
754 	@type:
755 		command
756 	@title:
757 		addon.dialog
758 	@short:
759 		Shows the addon management editor
760 	@syntax:
761 		addon.dialog [-t]
762 	@description:
763 		Shows the addon management editor.[br][br]
764 		If the [b]-t[/b] switch is used, the dialog is opened as toplevel window,
765 		otherwise it is opened as part of the current frame window.[br]
766 */
767 
addon_kvs_cmd_dialog(KviKvsModuleCommandCall * c)768 static bool addon_kvs_cmd_dialog(KviKvsModuleCommandCall * c)
769 {
770 
771 	AddonManagementDialog::display(c->hasSwitch('t', "toplevel"));
772 	return true;
773 }
774 
775 /*
776 	@doc: addon.install
777 	@type:
778 		command
779 	@title:
780 		addon.install
781 	@short:
782 		Installs the addon
783 	@syntax:
784 		addon.install <package_path:string>
785 	@description:
786 		Attempts to install the addon in the package specified by <package_path>.
787 */
788 
addon_kvs_cmd_install(KviKvsModuleCommandCall * c)789 static bool addon_kvs_cmd_install(KviKvsModuleCommandCall * c)
790 {
791 	QString szAddonPackFile;
792 
793 	KVSM_PARAMETERS_BEGIN(c)
794 	KVSM_PARAMETER("package_path", KVS_PT_STRING, 0, szAddonPackFile)
795 	KVSM_PARAMETERS_END(c)
796 
797 	QString szError;
798 	if(!AddonFunctions::installAddonPackage(szAddonPackFile, szError))
799 	{
800 		c->error(__tr2qs_ctx("Error installing addon package: %Q", "addon"), &szError);
801 		return false;
802 	}
803 
804 	return true;
805 }
806 
807 /*
808 	@doc: addon.pack
809 	@type:
810 		command
811 	@title:
812 		addon.pack
813 	@short:
814 		Creates a kva package containing an addon
815 	@syntax:
816 		addon.pack <package_path> <addon_name> <addon_version> <description> <author> <min_kvirc_version> <image> <addon_path>
817 	@description:
818 		Creates a *.kva package containing a KVIrc addon.[br][br]
819 		<package_path> is the absolute path and file name of the package that should be saved.[br]
820 		<addon_name> is the visible name of the addon (something like [i][b]My Addon[/i][/b]).[br]
821 		<addon_version> is the version of the addon in the form X.Y.Z.[br]
822 		<description> is a textual description of the addon.[br]
823 		<author> is the name of the person that is creating the addon.[br]
824 		<min_kvirc_version> is the minimum KVIrc version that this addon supports. Pass an empty string if you want
825 		this to become the current KVIrc version.[br]
826 		<image> is the path of an image to be used in the installation dialog. Pass an empty string if you
827 		don't want an image to be stored in the package.[br]
828 		<addon_path> is a path to the directory containing the addon. It should contain an install.kvs file
829 		that calls [cmd]addon.register[/cmd] and then installs all the addon aliases, events and files via [cmd]addon.installfiles[/cmd].
830 */
addon_kvs_cmd_pack(KviKvsModuleCommandCall * c)831 static bool addon_kvs_cmd_pack(KviKvsModuleCommandCall * c)
832 {
833 	AddonInfo info;
834 
835 	KVSM_PARAMETERS_BEGIN(c)
836 	KVSM_PARAMETER("package_path", KVS_PT_NONEMPTYSTRING, 0, info.szSavePath)
837 	KVSM_PARAMETER("addon_name", KVS_PT_NONEMPTYSTRING, 0, info.szName)
838 	KVSM_PARAMETER("addon_version", KVS_PT_NONEMPTYSTRING, 0, info.szVersion)
839 	KVSM_PARAMETER("description", KVS_PT_STRING, 0, info.szDescription)
840 	KVSM_PARAMETER("author", KVS_PT_NONEMPTYSTRING, 0, info.szAuthor)
841 	KVSM_PARAMETER("min_kvirc_version", KVS_PT_STRING, 0, info.szMinVersion)
842 	KVSM_PARAMETER("image", KVS_PT_STRING, 0, info.szImage)
843 	KVSM_PARAMETER("addon_path", KVS_PT_NONEMPTYSTRING, 0, info.szDirPath)
844 	KVSM_PARAMETERS_END(c)
845 
846 	QString szError;
847 
848 	if(AddonFunctions::pack(info, szError))
849 		return true;
850 
851 	c->error(szError);
852 	return false;
853 }
854 
addon_module_init(KviModule * m)855 static bool addon_module_init(KviModule * m)
856 {
857 	KVSM_REGISTER_FUNCTION(m, "exists", addon_kvs_fnc_exists);
858 	KVSM_REGISTER_FUNCTION(m, "version", addon_kvs_fnc_version);
859 
860 	KVSM_REGISTER_SIMPLE_COMMAND(m, "dialog", addon_kvs_cmd_dialog);
861 	KVSM_REGISTER_SIMPLE_COMMAND(m, "list", addon_kvs_cmd_list);
862 	KVSM_REGISTER_SIMPLE_COMMAND(m, "install", addon_kvs_cmd_install);
863 	KVSM_REGISTER_SIMPLE_COMMAND(m, "uninstall", addon_kvs_cmd_uninstall);
864 	KVSM_REGISTER_SIMPLE_COMMAND(m, "configure", addon_kvs_cmd_configure);
865 	KVSM_REGISTER_SIMPLE_COMMAND(m, "help", addon_kvs_cmd_help);
866 	KVSM_REGISTER_SIMPLE_COMMAND(m, "installfiles", addon_kvs_cmd_installfiles);
867 	KVSM_REGISTER_SIMPLE_COMMAND(m, "pack", addon_kvs_cmd_pack);
868 
869 	KVSM_REGISTER_CALLBACK_COMMAND(m, "setconfigurecallback", addon_kvs_cmd_setconfigurecallback);
870 	KVSM_REGISTER_CALLBACK_COMMAND(m, "sethelpcallback", addon_kvs_cmd_sethelpcallback);
871 	KVSM_REGISTER_CALLBACK_COMMAND(m, "register", addon_kvs_cmd_register);
872 
873 	QString szBuf;
874 	m->getDefaultConfigFileName(szBuf);
875 	KviConfigurationFile cfg(szBuf, KviConfigurationFile::Read);
876 	g_rectManagementDialogGeometry = cfg.readRectEntry("EditorGeometry", QRect(10, 10, 390, 440));
877 
878 	return true;
879 }
880 
addon_module_cleanup(KviModule * m)881 static bool addon_module_cleanup(KviModule * m)
882 {
883 	AddonManagementDialog::cleanup();
884 
885 	QString szBuf;
886 	m->getDefaultConfigFileName(szBuf);
887 	KviConfigurationFile cfg(szBuf, KviConfigurationFile::Write);
888 	cfg.writeEntry("EditorGeometry", g_rectManagementDialogGeometry);
889 
890 	return true;
891 }
892 
addon_module_can_unload(KviModule *)893 static bool addon_module_can_unload(KviModule *)
894 {
895 	return (!AddonManagementDialog::instance());
896 }
897 
898 KVIRC_MODULE(
899     "Addon", // module name
900     "4.0.0", // module version
901     "Copyright (C) 2005 Szymon Stefanek (pragma at kvirc dot net)\n"
902     "              2008 Elvio Basello (hell at hellvis69 dot netsons dot org)", // author & (C)
903     "Addon management functions for the KVS engine",
904     addon_module_init,
905     addon_module_can_unload,
906     0,
907     addon_module_cleanup,
908     "addon")
909