[folks] Block flushing of Kf.PersonaStore on any pending file operations



commit 964ed5760f9dabe6562cca7bdc2f564ee7a8a8a1
Author: Philip Withnall <philip withnall collabora co uk>
Date:   Thu Sep 9 16:07:08 2010 +0100

    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.

 backends/key-file/kf-persona-store.vala |   40 ++++++++++++++++++++++++++++--
 folks/persona-store.vala                |   17 +++++++++++++
 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



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]