When and how to use folders in Tinymail (dealing with folder removals)

Folders and folder stores, what to keep in mind

A TnyFolder in Tinymail usually comes from a TnyFolderStore which can again also be a TnyFolder. Making it recursive. A danger lies in the fact that the TnyFolderStore that is the parent of the TnyFolder, can make the decision to remove the folder.

The danger here is that if you use the TnyFolder as an instance, while the TnyFolderStore is removing it or has removed it, that you'll end up attempting things on a TnyFolder that is removed. This removal can have happened locally or remotely already. Or both. As a matter of fact, you don't know that, as the user of the instance. Actually you can know some things, but for that let's move to the Solutions section of this documentation.

Solutions

One solution is, as the user of the TnyFolder, become an observer of the TnyFolderStore that is the parent of the TnyFolder. Let's summarize which types are involved: TnyFolder, TnyFolderStore, TnyFolderStoreObserver and TnyFolderStoreChange. Using the API for getting the removed folders from a TnyFolderStoreChange, which will be given to you during the update method of TnyFolderStoreObserver, you can get which folder has been removed from a TnyFolderStore. Being the user of a TnyFolder, your using-code should therefore become a TnyFolderStoreObserver of the parent of the TnyFolder. You can get the parent of a folder using the get_folder_store API of TnyFolder. You can register yourself as an observer of it using the add_observer API of TnyFolderStore.

Reasoning

That was a lot of text and linking to types, I know. In short it means utilizing the well known observer design pattern: You become an observer of the folder's parent. Waiting for a removal of the folder to be notified of. Once removed, you deal with it. For example by letting the folder go. Releasing your reference. Stopping your usage of it.

Because let's all agree, once a folder is removed: you shouldn't be using any more. Right? There aren't a lot ways for a folder to get removed. One is for example the the remove_folder API of TnyFolderStore. However and although not implemented today, because it's not possible with today's IMAP servers, it's possible that a TnyFolderStore will spontaneously trigger the notifies that informs users of a folder about the removal of it. Perhaps in future it will be possible to detect a removal of a folder, that might have happened remotely by another E-mail client or by the administrator of the IMAP server?

Renaming a folder. It's the same problem, different name

A rename of a folder is, in Tinymail as far as the TnyFolderStoreObserver instances know, a remove and a creation of a new folder. Therefore will any renaming, which you do using the copy API of TnyFolder with the delete-originals boolean set to TRUE, instruct your TnyFolderStoreObserver to do the same thing: to deal with the two events: the removal and the creation of the new folder.

It's your task

It's your task, as the application developer, to deal with these situations. The Tinymail framework can't really fully automate this for you, as it's you who will decide when you'll add a reference to a TnyFolder instance. Forcing it to be kept alive. There's very few things a framework can do about such (your) decisions. The framework does, however, help you with dealing with it: by allowing you to observe the removals.

Creating a folder. You want to become a user of the new folder?

A creation of a folder can in Tinymail be caught by being a TnyFolderStoreObserver of a TnyFolderStore. Creations can (in future) happen spontaneously too. Right now there are no IMAP servers that support being notified of folder creations, but the Tinymail API allows for spontaneous create notifies to occur.

Sample code

First you need to make your user of the TnyFolder a TnyFolderStoreObserver. You do that this way:

static void
my_update (TnyFolderStoreObserver *self, TnyFolderStoreChange *change)
{
	TnyFolderStoreChangeChanged changed = tny_folder_store_change_get_changed (change);

	/* If we have created folders in the delta */
	if (changed & TNY_FOLDER_STORE_CHANGE_CHANGED_CREATED_FOLDERS)
	{
		TnyList *created = tny_simple_list_new ();
		TnyIterator *miter;
		tny_folder_store_change_get_created_folders (change, created);
		miter = tny_list_create_iterator (created);
		while (!tny_iterator_is_done (miter))
		{
			TnyFolder *folder = TNY_FOLDER (tny_iterator_get_current (miter));

			/* Handle creation of folder. For example becoming a user
			 * if (!priv->my_folder)
			 *     priv->my_folder = g_object_ref (folder); */

			g_object_unref (folder);
			tny_iterator_next (miter);
		}
		g_object_unref (miter);
		g_object_unref (created);
	}

	/* If we have removed folders in the delta */
	if (changed & TNY_FOLDER_STORE_CHANGE_CHANGED_REMOVED_FOLDERS)
	{
		TnyList *deleted = tny_simple_list_new ();
		TnyIterator *miter;
		tny_folder_store_change_get_removed_folders (change, deleted);
		miter = tny_list_create_iterator (deleted);
		while (!tny_iterator_is_done (miter))
		{
			TnyFolder *folder = TNY_FOLDER (tny_iterator_get_current (miter));

			/* Handle deletion of folder. For example stop using the folder
			 * if (folder == priv->my_folder) {
			 * 	g_object_unref (priv->my_folder);
			 * 	priv->my_folder = NULL; 
			 * } */

			g_object_unref (folder);
			tny_iterator_next (miter);
		}
		g_object_unref (miter);
		g_object_unref (deleted);
	}
}

static void
tny_folder_store_observer_init (TnyFolderStoreObserverIface *klass)
{
	klass->update_func = my_update;
}

GType
my_get_type (void)
{
	static GType type = 0;

	if (G_UNLIKELY(type == 0)) {
		...
		static const GInterfaceInfo tny_folder_store_observer_info = {
			(GInterfaceInitFunc) tny_folder_store_observer_init,
			NULL,
			NULL
		};
		...
		g_type_add_interface_static (type, TNY_TYPE_FOLDER_STORE_OBSERVER,
		    &my_info);
	}
	return type;
}

You must also register self, who's now a TnyFolderStoreObserver, to the TnyFolder's parent TnyFolderStore as an observer:

static void 
kill_fstore (gpointer data, gpointer user_data)
{
	My *self = user_data;
	TnyFolderStore *fstore = data;

	tny_folder_store_remove_observer (fstore, self);
	g_object_unref (fstore);
}

static void
my_get_rid_of_folder_stores (My *self)
{
	g_list_foreach (self->fstores, kill_fstore, self);
	g_list_free (self->fstores);
}

static void 
my_needs_folder (My *self, TnyFolder *folder)
{
	TnyFolderStore *fstore = tny_folder_get_folder_store (folder);

	/* Some folders might not have a parent folder store, we must cope
	 * with such folders too. Here I'm assuming that they can't spontan-
	 * eously get removed (which in fact, is true. More or less) */

	if (fstore)
	{
		/* Note that this adds a reference to self, until you called
		 * tny_folder_store_remove_observer. This means that the 
		 * finalize of self might not be the right location to do the
		 * observer removal. */

		tny_folder_store_add_observer (fstore, 
			TNY_FOLDER_STORE_OBSERVER (self));

		self->fstores = g_list_prepend (self->fstores, 
			g_object_ref (fstore));

		/* Although the ref makes the unref obsolete, I consider
		 * it to be good coding style to nevertheless do it this
		 * way: it avoids that a code refactor causes a leak, by 
		 * forgetting the original reference, and it makes it
		 * more clear that the get_folder_store does add a
		 * reference that you do have to deal with */

		g_object_unref (fstore);
	}
}

The TnyGtkFolderStoreTreeModel

The TnyGtkFolderStoreTreeModel is such a user, and deals with all the specifics of folder creations, renames and deletions already.

References to related other documentation