<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE article PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
               "docbook/dtd/xml/4.2/docbookx.dtd">
<book id="general_overview" xmlns="http://docbook.org/ns/docbook" version="5.0" xml:lang="en">
	<bookinfo>
		<title>General overview</title>
	</bookinfo>
	<chapter id="architecture">
		<title>LeechCraft architecture</title>
		<para>LeechCraft is plugin-based: LeechCraft Core only ensures correct loading and initialization order of plugins, initializes them, interconnects them and routes messages in between, while all the real work like BitTorrent implementation is done by plugins. Core also performs some common tasks like showing GUI to the user, merging settings from different plugins into one dialog or sharing cache and cookie base.</para>
		<para>When LeechCraft starts up, it searches for plugins in directories relevant to the platform which it runs under. Plugins are basically just dynamic objects (<filename>libleechcraft_*.so</filename> on *NIX and <filename>libleechcraft_*.dll</filename> on Windows) being loaded by LeechCraft core through <function>dlopen</function> on *NIX and <function>LoadLibrary</function> on Windows at runtime. However, a plugin («virtual plugin») may be provided by another plugin («adaptor plugin») instead of being loaded from a shared library by LeechCraft Core. Virtual plugins are subject to the same plugin behavior rules mentioned a couple of paragraphs below.</para>
		<para>Each plugin should have a class, an object of which is exposed via Qt's <function>Q_EXPORT_PLUGIN2</function> macro. The object of this class is created internally by Qt in that macro and may be obtained via <function>QPluginLoader::instance</function> method, if <classname>QPluginLoader</classname> is used to load the library (and it is in LeechCraft). So let's call that object the <emphasis>plugin instance object</emphasis> or just instance object.</para>
		<para>The exported class should obviously be a <classname>QObject</classname> and also implement at least <classname>IInfo</classname> interface in order to be recognized by LeechCraft.</para>
		<para>When LeechCraft starts up, it initializes the plugins it finds. Initialization is done in two passes: first, <function>IInfo::Init</function> is called on each found plugin, then, after all plugins have passed the first pass of initialization, <function>IInfo::SecondInit</function> is called. So, as a consequence, you basically cannot rely on other plugins before <function>IInfo::SecondInit</function>, particularly in <function>IInfo::Init</function>. So, you must not request the list of plugins, emit messages, etc before <function>IInfo::SecondInit</function> is called. However, after <function>IInfo::Init</function> your plugin must be in a correct and usable state, because other plugins may begin to use it. Basically that means that you need to initialize all your datamembers in <function>IInfo::Init</function>.</para>
	</chapter>
	<chapter id="communication">
		<para>Plugins can communicate with each other either via a set of interfaces (which is based on but not limited to default LeechCraft interfaces found in <filename>/src/interfaces/</filename> directory) or using the messaging system.</para>
		<para>Let's consider each of these methods.</para>
		<sect1 id="message-communication">
			<title>Message-based communication</title>
			<para>This method is more useful when you don't know the exact plugin, or, at least, what this plugin looks like (what interfaces it implements) and just talking by such messages with other plugins is OK for you.</para>
			<para>Communcation is done using <structname>LeechCraft::Entity</structname> (found in <filename>/src/interfaces/structures.h</filename>) structure which can be considered as a packet or a message. Plugins send these <structname>Entity</structname> messages, LeechCraft Core catches them, and… Here the story begins.</para>
			<para>So, you have two types of messages. Same <structname>Entity</structname> is used for both of them, and they differ only in the way of sending them out of your plugin.</para>
			<para>First type of messages is <emphasis>notification</emphasis>. It's used to notify Core or other plugins that a new entity appeared, but your plugin doesn't care what would happen with it after the announcement.</para>
			<para>Let's consider a BitTorrent client that has just finished a download as an example. It would <emphasis>notify</emphasis> other parts of LeechCraft about files it just finished downloading but it doesn't care what will happen with them next, would they be handled or not, etc.</para>
			<para>Because the result of such a message isn't required at the point of notification, the signal used to send this message doesn't block. Instead, it queues the message in the main event loop and returns immediately, handling all the delivery duty later, when control gets back to the event loop.</para>
			<para>To send a <emphasis>notification</emphasis> message, use the
				<funcsynopsis>
					<funcprototype>
						<funcdef>void <function>gotEntity</function></funcdef>
						<paramdef>LeechCraft::Entity *<parameter>entity</parameter></paramdef>
					</funcprototype>
				</funcsynopsis>
				signal. Just declare it in your plugin instance object (the one you export with <function>Q_EXPORT_PLUGIN2</function>), and LeechCraft core will automatically detect it at runtime.</para>
			<para>Second type is <emphasis>delegation request</emphasis>. It's used to <emphasis>delegate</emphasis> a task to another plugin and hope there is such plugin that can handle it. This way you get access to the plugin that would actually handle the task and can communicate with it. The corresponding signal blocks.</para>
			<para>For instance, consider the Aggregator feed reader. It doesn't download feeds itself, instead, it delegates them to another plugin by emitting a <emphasis>delegation request</emphasis>. After returning from the signal used to emit such a message, the sender plugin will know whether the entity was actually delegated, and if it was, which plugin is handling it.</para>
			<para>To send a <emphasis>delegation request</emphasis>, use the
				<funcsynopsis>
					<funcprototype>
						<funcdef>void <function>delegateEntity</function></funcdef>
						<paramdef>LeechCraft::Entity *<parameter>entity</parameter></paramdef>
						<paramdef>int *<parameter>id</parameter> = 0</paramdef>
						<paramdef>QObject **<parameter>handler</parameter> = 0</paramdef>
					</funcprototype>
				</funcsynopsis>
				signal. Here, if the delegation was successful, <parameter>handler</parameter> would point to the object that handles this delegation request and <parameter>id</parameter> would be the ID of the task in that handler. It's guaranteed to be a some non-negative value. To check if the delegation request was successful, the following trick is commonly applied: before emitting the <function>delegateEntity</function> signal, set the <parameter>id</parameter> to some negative value like -1 and after emitting check if it is still -1. If it is, the delegation failed.</para>
				<example>
					<title>Delegating an entity and checking success of delegation</title>
					<para>In this example we emit a delegation entity for downloading a given <parameter>url</parameter> to <parameter>location</parameter> with task parameters commonly used for internal LeechCraft tasks (a user does not have to know about the process of downloading news feeds, for example). We check if the delegation was successful, and if it failed, we use <function>LeechCraft::Util::MakeNotification</function> to create a user-notification (an <structname>Entity</structname> that represents some notification for the user, not to be confused with notification <structname>Entity</structname>) entity with a human-readable error title, message and priority, and we emit it as a notification via <function>gotEntity</function>. Actually, this is a real piece of code from LackMan plugin, the package manager.</para>
					<programlisting language="c++">
Entity e = Util::MakeEntity (url,
		location,
		LeechCraft::Internal |
			LeechCraft::DoNotNotifyUser |
			LeechCraft::DoNotSaveInHistory |
			LeechCraft::NotPersistent |
			LeechCraft::DoNotAnnounceEntity);
int id = -1;
QObject *pr;
emit delegateEntity (e, &amp;id, &amp;pr);
if (id == -1)
{
	emit gotEntity (Util::MakeNotification (tr ("Error fetching repository"),
			tr ("Could not find plugin to fetch repository information for %1.")
				.arg (url.toString ()),
			PCritical_));
	return;
}
					</programlisting>
				</example>
			<para>Sometimes you will need to know if LeechCraft can handle a particular entity here and now. For example, Poshuku plugin, the web browser, checks what external resources (like RSS feeds or OpenSearch descriptions) can be handled upon page load and shows actions for handling only those that can be handled.</para>
			<para>In order to check if the particular entity may be handled, use the
				<funcsynopsis>
					<funcprototype>
						<funcdef>void <function>couldHandle</function></funcdef>
						<paramdef>const LeechCraft::Entity&amp; <parameter>entity</parameter></paramdef>
						<paramdef>bool *<parameter>could</parameter></paramdef>
					</funcprototype>
				</funcsynopsis>
				signal. This signal blocks. It returns either after Core finds first plugin that's able to handle the <parameter>entity</parameter>, or all queried plugins refuse to handle it. The result is stored in the variable pointed by <parameter>could</parameter>.</para>
			<para>It's generally a bad idea to store the result of this function for a long time because plugins may dynamically choose to refuse or accept entities, but typically it's safe to assume that the result wouldn't change until the control gets back to the event loop.</para>
		</sect1>
		<sect1>
			<title>Interfaces-based communication</title>
			<para>This is a much simpler approach, but it is much more «static» as well. Once you have a pointer to <classname>QObject</classname> you want to communicate with, you can try to <function>qobject_cast</function> it to the interface you want.</para>
			<para>There are basically three ways to obtain a pointer to another plugin:
				<itemizedlist>
					<listitem><para>It may be set by LeechCraft Core in its <function>delegateEntity</function> handler (see previous section for more information): the third parameter of this signal is the pointer to the pointer to the handler of the entity.</para></listitem>
					<listitem><para>It may be obtained by your plugin in <function>IInfo::SetProvider</function>, have you returned the proper dependencies list from either <function>IInfo::Needs</function> or <function>IInfo::Uses</function>.</para></listitem>
					<listitem><para>You may obtain the list of all plugins yourself via the plugins manager that you can access via <function>ICoreProxy::GetPluginsManager</function>. The <classname>ICoreProxy</classname> is passed to your <function>IInfo::Init</function>.</para></listitem>
				</itemizedlist>
			</para>
			<para>Let us consider a couple of examples illustrating various concepts of working with other plugins.</para>
			<example>
				<title>Getting any available plugin that implements <classname>IWebBrowser</classname>.</title>
				<para>This is the real example from the Aggregator. It's assumed that the <classname>ICoreProxy</classname> passed to your <function>IInfo::Init</function> is stored as <varname>Proxy_</varname>.</para>
				<programlisting language="c++">
<![CDATA[
IWebBrowser* Core::GetWebBrowser () const
{
    IPluginsManager *pm = Proxy_->GetPluginsManager ();
    QObjectList browsers = pm->Filter<IWebBrowser*> (pm->GetAllPlugins ());
    return browsers.size () ?
        qobject_cast<IWebBrowser*> (browsers.at (0)) :
        0;
}
]]>
				</programlisting>
			</example>
			<example>
				<title>More sophisticated task delegation and error handling.</title>
				<para>This is another bunch of code taken from the Aggregator. This is how it delegates the downloading of fresh versions of feed files to another plugin.</para>
				<programlisting language="c++">
<![CDATA[
void Core::UpdateFeed (const QString& url)
{
    QList<int> keys = PendingJobs_.keys ();
    Q_FOREACH (int key, keys)
        if (PendingJobs_ [key].URL_ == url)
        {
            QObject *provider = ID2Downloader_ [key];
            IDownload *downloader = qobject_cast<IDownload*> (provider);
            if (downloader)
            {
                qWarning () << Q_FUNC_INFO
                    << "stalled task detected from"
                    << downloader
                    << "trying to kill...";
                downloader->KillTask (key);
                ID2Downloader_.remove (key);
                PendingJobs_.remove (key);
                qWarning () << Q_FUNC_INFO
                    << "killed!";
            }
            else
                qWarning () << Q_FUNC_INFO
                    << "provider is not a downloader:"
                    << provider
                    << "; cannot kill the task";
            return;
        }

    QString filename = LeechCraft::Util::GetTemporaryName ();

    LeechCraft::Entity e = LeechCraft::Util::MakeEntity (QUrl (url),
            filename,
            LeechCraft::Internal |
                LeechCraft::DoNotNotifyUser |
                LeechCraft::DoNotSaveInHistory |
                LeechCraft::NotPersistent |
                LeechCraft::DoNotAnnounceEntity);

    PendingJob pj =
    {
        PendingJob::RFeedUpdated,
        url,
        filename,
        QStringList ()
    };

    int id = -1;
    QObject *pr;
    emit delegateEntity (e, &id, &pr);
    if (id == -1)
    {
        qWarning () << Q_FUNC_INFO << url << "wasn't delegated";
        return;
    }

    HandleProvider (pr, id);
    PendingJobs_ [id] = pj;
    Updates_ [url] = QDateTime::currentDateTime ();
}

void Core::HandleProvider (QObject *provider, int id)
{
    if (Downloaders_.contains (provider))
        return;

    Downloaders_ << provider;
    connect (provider,
            SIGNAL (jobFinished (int)),
            this,
            SLOT (handleJobFinished (int)));
    connect (provider,
            SIGNAL (jobRemoved (int)),
            this,
            SLOT (handleJobRemoved (int)));
    connect (provider,
            SIGNAL (jobError (int, IDownload::Error)),
            this,
            SLOT (handleJobError (int, IDownload::Error)));

    ID2Downloader_ [id] = provider;
}
]]>
			</programlisting>
			</example>
		</sect1>
	</chapter>
 </book>
