summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPhilip Withnall <philip.withnall@collabora.co.uk>2010-09-09 16:07:08 +0100
committerPhilip Withnall <philip.withnall@collabora.co.uk>2010-09-10 14:01:06 +0100
commit964ed5760f9dabe6562cca7bdc2f564ee7a8a8a1 (patch)
treea38371598ec398e34bcd5cb52e22e2036ae53d44
parent88df560e88cf14e3fc6b0b6060173dd94e3ecaed (diff)
Block flushing of Kf.PersonaStore on any pending file operations
If a file operation is still underway, don't allow the Kf.PersonaStore to be finalized until it's finished. This prevents libfolks from being closed before changes to relationships.ini have been written out. We also prevent multiple file operations from happening (pseudo-) concurrently, by cancelling any pending operation when we schedule a new one.
-rw-r--r--backends/key-file/kf-persona-store.vala40
-rw-r--r--folks/persona-store.vala17
2 files changed, 54 insertions, 3 deletions
diff --git a/backends/key-file/kf-persona-store.vala b/backends/key-file/kf-persona-store.vala
index 3a81bdd..153810e 100644
--- a/backends/key-file/kf-persona-store.vala
+++ b/backends/key-file/kf-persona-store.vala
@@ -33,6 +33,7 @@ public class Folks.Backends.Kf.PersonaStore : Folks.PersonaStore
private File file;
private GLib.KeyFile key_file;
private uint first_unused_id = 0;
+ private unowned Cancellable save_key_file_cancellable = null;
/**
* {@inheritDoc}
@@ -173,6 +174,21 @@ public class Folks.Backends.Kf.PersonaStore : Folks.PersonaStore
/**
* {@inheritDoc}
*/
+ public override async void flush ()
+ {
+ /* If there are any ongoing file operations, wait for them to finish
+ * before returning. We have to iterate the main context manually to
+ * achieve this, as all the code in this file is run in the main loop (in
+ * the main thread). We would cause a deadlock if we used anything as
+ * fancy/useful as a GCond. */
+ MainContext context = MainContext.default ();
+ while (this.save_key_file_cancellable != null)
+ context.iteration (true);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
public override async void remove_persona (Folks.Persona persona)
{
debug ("Removing Persona '%s' (IID '%s', group '%s')", persona.uid,
@@ -242,20 +258,38 @@ public class Folks.Backends.Kf.PersonaStore : Folks.PersonaStore
internal async void save_key_file ()
{
string key_file_data = this.key_file.to_data ();
+ Cancellable cancellable = new Cancellable ();
debug ("Saving key file '%s'.", this.file.get_path ());
+ /* There's no point in having two competing file write operations.
+ * We can ensure that only one is running by just checking if a
+ * cancellable is set. This is thread safe because the code in this file
+ * is all run in the main thread (inside the main loop), so only we touch
+ * this.save_key_file_cancellable (albeit in many weird and wonderful
+ * orders due to idle handler queuing). */
+ if (this.save_key_file_cancellable != null)
+ this.save_key_file_cancellable.cancel ();
+ this.save_key_file_cancellable = cancellable;
+
try
{
/* Note: We have to use key_file_data.size () here to get its length
* in _bytes_ rather than _characters_. bgo#628930 */
yield this.file.replace_contents_async (key_file_data,
- key_file_data.size (), null, false, FileCreateFlags.PRIVATE);
+ key_file_data.size (), null, false, FileCreateFlags.PRIVATE,
+ cancellable);
}
catch (Error e)
{
- warning ("Could not write updated key file '%s': %s",
- this.file.get_path (), e.message);
+ if (!(e is IOError.CANCELLED))
+ {
+ warning ("Could not write updated key file '%s': %s",
+ this.file.get_path (), e.message);
+ }
}
+
+ if (this.save_key_file_cancellable == cancellable)
+ this.save_key_file_cancellable = null;
}
}
diff --git a/folks/persona-store.vala b/folks/persona-store.vala
index 48dbf39..0d1a10a 100644
--- a/folks/persona-store.vala
+++ b/folks/persona-store.vala
@@ -194,6 +194,23 @@ public abstract class Folks.PersonaStore : Object
public abstract async void prepare () throws GLib.Error;
/**
+ * Flush any pending changes to the PersonaStore's backing store.
+ *
+ * PersonaStores may (transparently) implement caching or I/O queueing which
+ * means that changes to their {@link Persona}s may not be immediately written
+ * to the PersonaStore's backing store. Calling this function will force all
+ * pending changes to be flushed to the backing store.
+ *
+ * This must not be called before {@link PersonaStore.prepare}.
+ *
+ * @since 0.1.17
+ */
+ public virtual async void flush ()
+ {
+ /* Default implementation doesn't have to do anything */
+ }
+
+ /**
* Add a new {@link Persona} to the PersonaStore.
*
* The {@link Persona} will be created by the PersonaStore backend from the