[simple-scan/gnome-3-10: 1/8] Rewrite autosaving. Now uses files for scan data, is more efficient in writing to disk and drops dep



commit 5774f48e7f8cb7bf603f511c7bbd96cad4d28a20
Author: Robert Ancell <robert ancell canonical com>
Date:   Fri Nov 1 12:10:37 2013 -0700

    Rewrite autosaving. Now uses files for scan data, is more efficient in writing to disk and drops 
dependency on sqlite.

 configure.ac              |    1 -
 src/autosave-manager.vala |  693 ++++++++++++++++-----------------------------
 src/book.vala             |    5 +-
 src/page.vala             |   47 +++-
 src/simple-scan.vala      |    9 +-
 src/ui.vala               |   30 ++-
 6 files changed, 313 insertions(+), 472 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index aa152be..0add5a1 100644
--- a/configure.ac
+++ b/configure.ac
@@ -29,7 +29,6 @@ PKG_CHECK_MODULES(SIMPLE_SCAN, [
     cairo
     gdk-pixbuf-2.0
     gudev-1.0
-    sqlite3
 ])
 
 PKG_CHECK_MODULES(COLORD, [
diff --git a/src/autosave-manager.vala b/src/autosave-manager.vala
index eb8f1c5..b8d9534 100644
--- a/src/autosave-manager.vala
+++ b/src/autosave-manager.vala
@@ -1,6 +1,7 @@
 /*
  * Copyright (C) 2011 Timo Kluck
- * Author: Timo Kluck <tkluck infty nl>
+ * Authors: Timo Kluck <tkluck infty nl>
+ *          Robert Ancell <robert ancell canonical com>
  *
  * This program is free software: you can redistribute it and/or modify it under
  * the terms of the GNU General Public License as published by the Free Software
@@ -9,545 +10,353 @@
  * license.
  */
 
-/*
- * We store autosaves in a database named
- *    ~/.cache/simple-scan/autosaves/autosaves.db
- * It contains a single table of pages, each containing the process id (pid) of
- * the simple-scan instance that saved it, and a hash of the Book and Page
- * objects corresponding to it. The pixels are saved as a BLOB.
- * Additionally, the autosaves directory contains a number of tiff files that
- * the user can use for manual recovery.
- *
- * At startup, we check whether autosaves.db contains any records
- * with a pid that does not match a current pid for simple-scan. If so, we take
- * ownership by an UPDATE statement changing to our own pid. Then, we
- * recover the book. We're trying our best to avoid the possible race
- * condition if several instances of simple-scan are started simultaneously.
- *
- * At application exit, we delete the records corresponding to our own pid.
- *
- * Important notes:
- *  - We enforce that there is only one AutosaveManager instance in a given
- *    process by using a create function.
- *  - It should be possible to change the book object at runtime, although this
- *    is not used in the current implementation so it has not been tested.
- */
-
 public class AutosaveManager
 {
     private static string AUTOSAVE_DIR = Path.build_filename (Environment.get_user_cache_dir (), 
"simple-scan", "autosaves");
-    private static string AUTOSAVE_NAME = "autosaves";
-    private static string AUTOSAVE_EXT = ".db";
-    private static string AUTOSAVE_FILENAME = Path.build_filename (AUTOSAVE_DIR, AUTOSAVE_NAME + 
AUTOSAVE_EXT);
-
-    private static string PID = ((int)(Posix.getpid ())).to_string ();
-    private static int number_of_instances = 0;
-
-    private Sqlite.Database database_connection;
-    private Book _book = null;
+    private static string AUTOSAVE_FILENAME = "autosave.book";
+    private static string AUTOSAVE_PATH = Path.build_filename (AUTOSAVE_DIR, AUTOSAVE_FILENAME);
 
     private uint update_timeout = 0;
-    private HashTable<Page, bool> dirty_pages;
 
+    private HashTable<Page, string> page_filenames;
+
+    private Book book_ = null;
     public Book book
     {
         get
         {
-            return _book;
+            return book_;
         }
         set
         {
-            if (_book != null)
+            if (book_ != null)
             {
-                for (var i = 0; i < _book.get_n_pages (); i++)
+                for (var i = 0; i < book_.get_n_pages (); i++)
                 {
-                    var page = _book.get_page (i);
+                    var page = book_.get_page (i);
                     on_page_removed (page);
                 }
-                _book.page_added.disconnect (on_page_added);
-                _book.page_removed.disconnect (on_page_removed);
-                _book.reordered.disconnect (on_reordered);
-                _book.cleared.disconnect (on_cleared);
+                book_.page_added.disconnect (on_page_added);
+                book_.page_removed.disconnect (on_page_removed);
+                book_.reordered.disconnect (on_changed);
+                book_.cleared.disconnect (on_cleared);
             }
-            _book = value;
-            _book.page_added.connect (on_page_added);
-            _book.page_removed.connect (on_page_removed);
-            _book.reordered.connect (on_reordered);
-            _book.cleared.connect (on_cleared);
-            for (var i = 0; i < _book.get_n_pages (); i++)
+            book_ = value;
+            book_.page_added.connect (on_page_added);
+            book_.page_removed.connect (on_page_removed);
+            book_.reordered.connect (on_changed);
+            book_.cleared.connect (on_cleared);
+            for (var i = 0; i < book_.get_n_pages (); i++)
             {
-                var page = book.get_page (i);
+                var page = book_.get_page (i);
                 on_page_added (page);
             }
         }
     }
 
-    public static AutosaveManager? create (ref Book book)
+    public AutosaveManager ()
+    {
+        page_filenames = new HashTable<Page, string> (direct_hash, direct_equal);
+    }
+
+    public void load ()
     {
-        /* compare autosave directories with pids of current instances of simple-scan
-         * take ownership of one of the ones that are unowned by renaming to the
-         * own pid. Then open the database and fill the book with the pages it
-         * contains.
-         */
-        if (number_of_instances > 0)
-            assert_not_reached ();
+        debug ("Loading autosave information");
 
-        var man = new AutosaveManager ();
-        number_of_instances++;
+        book.clear ();
+        page_filenames.remove_all ();
 
+        var file = new KeyFile ();
         try
         {
-            man.database_connection = open_database_connection ();
+            file.load_from_file (AUTOSAVE_PATH, KeyFileFlags.NONE);
         }
-        catch
+        catch (Error e)
         {
-            warning ("Could not connect to the autosave database; no autosaves will be kept.");
-            return null;
+            warning ("Could not load autosave infomation; not restoring any autosaves");
+            return;
         }
-
-        bool any_pages_recovered = false;
-        try
+        var pages = get_value (file, "simple-scan", "pages");
+        foreach (var page_name in pages.split (" "))
         {
-            // FIXME: this only works on linux. We can maybe use Gtk.Application and some session bus id 
instead?
-            string current_pids;
-            Process.spawn_command_line_sync ("pidof simple-scan | sed \"s/ /,/g\"", out current_pids);
-            current_pids = current_pids.strip ();
-            Sqlite.Statement stmt;
-            string query = @"
-                   SELECT process_id, book_hash, book_revision FROM pages
-                   WHERE NOT process_id IN ($current_pids)
-                   LIMIT 1
-                ";
-
-            var result = man.database_connection.prepare_v2 (query, -1, out stmt);
-            if (result == Sqlite.OK)
+            debug ("Loading automatically saved page %s", page_name);
+
+            var scan_width = get_integer (file, page_name, "scan-width");
+            var scan_height = get_integer (file, page_name, "scan-height");
+            var rowstride = get_integer (file, page_name, "rowstride");
+            var n_channels = get_integer (file, page_name, "n-channels");
+            var depth = get_integer (file, page_name, "depth");
+            var dpi = get_integer (file, page_name, "dpi");
+            var scan_direction_name = get_value (file, page_name, "scan-direction");
+            ScanDirection scan_direction = ScanDirection.TOP_TO_BOTTOM;
+            switch (scan_direction_name)
             {
-                while (stmt.step () == Sqlite.ROW)
+            case "TOP_TO_BOTTOM":
+                scan_direction = ScanDirection.TOP_TO_BOTTOM;
+                break;
+            case "LEFT_TO_RIGHT":
+                scan_direction = ScanDirection.LEFT_TO_RIGHT;
+                break;
+            case "BOTTOM_TO_TOP":
+                scan_direction = ScanDirection.BOTTOM_TO_TOP;
+                break;
+            case "RIGHT_TO_LEFT":
+                scan_direction = ScanDirection.RIGHT_TO_LEFT;
+                break;
+            }
+            var color_profile = get_value (file, page_name, "color-profile");
+            if (color_profile == "")
+                color_profile = null;
+            var pixels_filename = get_value (file, page_name, "pixels-filename");
+            var has_crop = get_boolean (file, page_name, "has-crop");
+            var crop_name = get_value (file, page_name, "crop-name");
+            if (crop_name == "")
+                crop_name = null;
+            var crop_x = get_integer (file, page_name, "crop-x");
+            var crop_y = get_integer (file, page_name, "crop-y");
+            var crop_width = get_integer (file, page_name, "crop-width");
+            var crop_height = get_integer (file, page_name, "crop-height");
+
+            uchar[]? pixels = null;
+            if (pixels_filename != "")
+            {
+                var path = Path.build_filename (AUTOSAVE_DIR, pixels_filename);
+                var f = File.new_for_path (path);
+                try
+                {
+                    f.load_contents (null, out pixels, null);
+                }
+                catch (Error e)
                 {
-                    debug ("Found at least one autosave page, taking ownership");
-                    var unowned_pid = stmt.column_int (0);
-                    var book_hash = stmt.column_int (1);
-                    var book_revision = stmt.column_int (2);
-                    /* there's a possible race condition here when several instances
-                     * try to take ownership of the same rows. What would happen is
-                     * that this operations would affect no rows if another process
-                     * has taken ownership in the mean time. In that case, recover_book
-                     * does nothing, so there should be no problem.
-                     */
-                    query = @"
-                        UPDATE pages
-                           SET process_id = $PID
-                         WHERE process_id = ?2
-                           AND book_hash = ?3
-                           AND book_revision = ?4";
-                    Sqlite.Statement stmt2;
-                    result = man.database_connection.prepare_v2(query, -1, out stmt2);
-                    if (result != Sqlite.OK)
-                        warning (@"Error preparing statement: $query");
-
-                    stmt2.bind_int64 (2, unowned_pid);
-                    stmt2.bind_int64 (3, book_hash);
-                    stmt2.bind_int64 (4, book_revision);
-                    result = stmt2.step();
-                    if (result == Sqlite.DONE)
-                    {
-                        any_pages_recovered = true;
-                        man.recover_book (ref book);
-                    }
-                    else
-                        warning ("Error %d while executing query", result);
+                    warning ("Failed to load pixel information");
+                    continue;
                 }
             }
-            else
-                warning ("Error %d while preparing statement", result);
+
+            var page = new Page.from_data (scan_width,
+                                           scan_height,
+                                           rowstride,
+                                           n_channels,
+                                           depth,
+                                           dpi,
+                                           scan_direction,
+                                           color_profile,
+                                           pixels,
+                                           has_crop,
+                                           crop_name,
+                                           crop_x,
+                                           crop_y,
+                                           crop_width,
+                                           crop_height);
+            page_filenames.insert (page, pixels_filename);
+            book.append_page (page);
         }
-        catch (SpawnError e)
+    }
+
+    private string get_value (KeyFile file, string group_name, string key, string default = "")
+    {
+        try
         {
-            warning ("Could not obtain current process ids; not restoring any autosaves");
+            return file.get_value (group_name, key);
         }
-
-        man.book = book;
-        if (!any_pages_recovered)
+        catch (Error e)
         {
-            for (var i = 0; i < book.get_n_pages (); i++)
-            {
-                var page = book.get_page (i);
-                man.on_page_added (page);
-            }
+            return default;
         }
+    }
 
-        return man;
+    private int get_integer (KeyFile file, string group_name, string key, int default = 0)
+    {
+        try
+        {
+            return file.get_integer (group_name, key);
+        }
+        catch (Error e)
+        {
+            return default;
+        }
     }
 
-    private AutosaveManager ()
+    private bool get_boolean (KeyFile file, string group_name, string key, bool default = false)
     {
-        dirty_pages = new HashTable<Page, bool> (direct_hash, direct_equal);
+        try
+        {
+            return file.get_boolean (group_name, key);
+        }
+        catch (Error e)
+        {
+            return default;
+        }
     }
 
     public void cleanup ()
     {
-        debug ("Clean exit; deleting autosave records");
+        debug ("Deleting autosave records");
 
         if (update_timeout > 0)
             Source.remove (update_timeout);
         update_timeout = 0;
 
-        warn_if_fail (database_connection.exec (@"
-            DELETE FROM pages
-                WHERE process_id = $PID
-        ") == Sqlite.OK);
-    }
-
-    static Sqlite.Database open_database_connection () throws Error
-    {
-        var autosaves_dir = File.new_for_path (AUTOSAVE_DIR);
+        Dir dir;
         try
         {
-            autosaves_dir.make_directory_with_parents ();
+            dir = Dir.open (AUTOSAVE_DIR);
         }
-        catch
-        { // the directory already exists
-            // pass
+        catch (Error e)
+        {
+            warning ("Failed to delete autosaves: %s", e.message);
+            return;
+        }
+
+        while (true)
+        {
+            var filename = dir.read_name ();
+            if (filename == null)
+                break;
+            var path = Path.build_filename (AUTOSAVE_DIR, filename);
+            FileUtils.unlink (path);
         }
-        Sqlite.Database connection;
-        if (Sqlite.Database.open (AUTOSAVE_FILENAME, out connection) != Sqlite.OK)
-            throw new IOError.FAILED ("Could not connect to autosave database");
-        string query = @"
-            CREATE TABLE IF NOT EXISTS pages (
-                id integer PRIMARY KEY,
-                process_id integer,
-                page_hash integer,
-                book_hash integer,
-                book_revision integer,
-                page_number integer,
-                dpi integer,
-                width integer,
-                height integer,
-                depth integer,
-                n_channels integer,
-                rowstride integer,
-                color_profile string,
-                crop_x integer,
-                crop_y integer,
-                crop_width integer,
-                crop_height integer,
-                scan_direction integer,
-                pixels binary
-            )";
-        var result = connection.exec(query);
-        if (result != Sqlite.OK)
-            warning ("Error %d while executing query", result);
-        return connection;
     }
 
-    void on_page_added (Page page)
+    public void on_page_added (Page page)
     {
-        insert_page (page);
-        // TODO: save a tiff file
-        page.size_changed.connect (on_page_changed);
-        page.scan_direction_changed.connect (on_page_changed);
-        page.crop_changed.connect (on_page_changed);
-        page.scan_finished.connect (on_page_changed);
+        page.scan_finished.connect (on_scan_finished);
+        page.crop_changed.connect (on_changed);
     }
 
     public void on_page_removed (Page page)
     {
-        page.pixels_changed.disconnect (on_page_changed);
-        page.size_changed.disconnect (on_page_changed);
-        page.scan_direction_changed.disconnect (on_page_changed);
-        page.crop_changed.disconnect (on_page_changed);
-        page.scan_finished.connect (on_page_changed);
-
-        string query = @"
-        DELETE FROM pages
-            WHERE process_id = $PID
-              AND page_hash = ?2
-              AND book_hash = ?3
-              AND book_revision = ?4
-        ";
-        Sqlite.Statement stmt;
-        var result = database_connection.prepare_v2 (query, -1, out stmt);
-        if (result != Sqlite.OK)
-            warning (@"Error $result while preparing query");
-        stmt.bind_int64 (2, direct_hash (page));
-        stmt.bind_int64 (3, direct_hash (book));
-        stmt.bind_int64 (4, cur_book_revision);
-
-        result = stmt.step();
-        if (result != Sqlite.DONE)
-            warning ("Error %d while executing query", result);
-    }
+        page.scan_finished.disconnect (on_scan_finished);
+        page.crop_changed.disconnect (on_changed);
 
-    public void on_reordered ()
-    {
-        for (var i=0; i < book.get_n_pages (); i++)
-        {
-            var page = book.get_page (i);
-            string query = @"
-            UPDATE pages SET page_number = ?5
-            WHERE process_id = $PID
-              AND page_hash = ?2
-              AND book_hash = ?3
-              AND book_revision = ?4
-            ";
-            Sqlite.Statement stmt;
-            var result = database_connection.prepare_v2 (query, -1, out stmt);
-            if (result != Sqlite.OK)
-                warning (@"Error $result while preparing query");
-
-            stmt.bind_int64 (5, i);
-            stmt.bind_int64 (2, direct_hash (page));
-            stmt.bind_int64 (3, direct_hash (book));
-            stmt.bind_int64 (4, cur_book_revision);
-
-            result = stmt.step();
-            if (result != Sqlite.DONE)
-                warning ("Error %d while executing query", result);
-        }
+        var filename = page_filenames.lookup (page);
+        if (filename != null)
+            FileUtils.unlink (filename);
+        page_filenames.remove (page);
     }
 
-    public void on_page_changed (Page page)
+    public void on_scan_finished (Page page)
     {
-        update_page (page);
+        save_pixels (page);
+        save (false);
     }
 
-    public void on_needs_saving_changed (Book book)
+    public void on_changed ()
     {
-        for (var n = 0; n < book.get_n_pages (); n++)
-        {
-            var page = book.get_page (n);
-            update_page (page);
-        }
+        save ();
     }
 
-    private int cur_book_revision = 0;
-
     public void on_cleared ()
     {
-        cur_book_revision++;
+        page_filenames.remove_all ();
+        save ();
     }
 
-    private void insert_page (Page page)
+    private void save (bool do_timeout = true)
     {
-        debug ("Adding an autosave for a new page");
-        string query = @"
-            INSERT INTO pages
-                (process_id,
-                page_hash,
-                book_hash,
-                book_revision)
-                VALUES
-                ($PID,
-                ?2,
-                ?3,
-                ?4)
-        ";
-        Sqlite.Statement stmt;
-        var result = database_connection.prepare_v2 (query, -1, out stmt);
-        if (result != Sqlite.OK)
-            warning (@"Error $result while preparing query");
-
-        stmt.bind_int64 (2, direct_hash (page));
-        stmt.bind_int64 (3, direct_hash (book));
-        stmt.bind_int64 (4, cur_book_revision);
-
-        result = stmt.step();
-        if (result != Sqlite.DONE)
-            warning ("Error %d while executing query", result);
-
-        update_page (page);
-    }
+        if (update_timeout == 0 && do_timeout)
+            debug ("Waiting to autosave...");
 
-    private void update_page (Page page)
-    {
-        dirty_pages.insert (page, true);
+        /* Cancel existing timeout */
         if (update_timeout > 0)
             Source.remove (update_timeout);
-        update_timeout = Timeout.add (100, () =>
-        {
-            var iter = HashTableIter<Page, bool> (dirty_pages);
-            Page p;
-            bool is_dirty;
-            while (iter.next (out p, out is_dirty))
-                real_update_page (p);
-
-            dirty_pages.remove_all ();
-            update_timeout = 0;
-
-            return false;
-        });
-    }
-
-    private void real_update_page (Page page)
-    {
-        debug ("Updating the autosave for a page");
-
-        int crop_x;
-        int crop_y;
-        int crop_width;
-        int crop_height;
-        page.get_crop (out crop_x, out crop_y, out crop_width, out crop_height);
-
-        Sqlite.Statement stmt;
-        string query = @"
-            UPDATE pages
-                SET
-                page_number=$(book.get_page_index (page)),
-                dpi=$(page.get_dpi ()),
-                width=$(page.get_width ()),
-                height=$(page.get_height ()),
-                depth=$(page.get_depth ()),
-                n_channels=$(page.get_n_channels ()),
-                rowstride=$(page.get_rowstride ()),
-                crop_x=$crop_x,
-                crop_y=$crop_y,
-                crop_width=$crop_width,
-                crop_height=$crop_height,
-                scan_direction=$((int)page.get_scan_direction ()),
-                color_profile=?1,
-                pixels=?2
-                WHERE process_id = $PID
-                  AND page_hash = ?4
-                  AND book_hash = ?5
-                  AND book_revision = ?6
-            ";
-
-        var result = database_connection.prepare_v2 (query, -1, out stmt);
-        if (result != Sqlite.OK)
-        {
-            warning ("Error %d while preparing statement", result);
-            return;
-        }
-
-        stmt.bind_int64 (4, direct_hash (page));
-        stmt.bind_int64 (5, direct_hash (book));
-        stmt.bind_int64 (6, cur_book_revision);
-        result = stmt.bind_text (1, page.get_color_profile () ?? "");
-
-        if (result != Sqlite.OK)
-            warning ("Error %d while binding text", result);
+        update_timeout = 0;
 
-        if (page.get_pixels () != null)
+        if (do_timeout)
         {
-            // (-1) is the special value SQLITE_TRANSIENT
-            result = stmt.bind_blob (2, page.get_pixels (), page.get_pixels ().length, (DestroyNotify)(-1));
-            if (result != Sqlite.OK)
-                warning ("Error %d while binding blob", result);
+            update_timeout = Timeout.add (100, () =>
+            {
+                real_save ();
+                update_timeout = 0;
+                return false;
+            });
         }
         else
-            warn_if_fail (stmt.bind_null (2) == Sqlite.OK);
-
-        warn_if_fail (stmt.step () == Sqlite.DONE);
+            real_save();
     }
 
-    private void recover_book (ref Book book)
+    private void real_save ()
     {
-        Sqlite.Statement stmt;
-        string query = @"
-            SELECT process_id,
-                page_hash,
-                book_hash,
-                book_revision,
-                page_number,
-                dpi,
-                width,
-                height,
-                depth,
-                n_channels,
-                rowstride,
-                color_profile,
-                crop_x,
-                crop_y,
-                crop_width,
-                crop_height,
-                scan_direction,
-                pixels,
-                id
-            FROM pages
-            WHERE process_id = $PID
-              AND book_revision = (
-                  SELECT MAX(book_revision) FROM pages WHERE process_id = $PID
-              )
-            ORDER BY page_number
-        ";
-
-        var result = database_connection.prepare_v2 (query, -1, out stmt);
-        if (result != Sqlite.OK)
-            warning ("Error %d while preparing statement", result);
-
-        var first = true;
-        while (Sqlite.ROW == stmt.step ())
+        debug ("Autosaving book information");
+
+        var file = new KeyFile ();
+        var page_names = "";
+        for (var i = 0; i < book.get_n_pages (); i++)
         {
-            debug ("Found a page that needs to be recovered");
-            if (first)
-            {
-                book.clear ();
-                first = false;
-            }
-            var dpi = stmt.column_int (5);
-            var width = stmt.column_int (6);
-            var height = stmt.column_int (7);
-            var depth = stmt.column_int (8);
-            var n_channels = stmt.column_int (9);
-            var scan_direction = (ScanDirection)stmt.column_int (16);
-
-            if (width <= 0 || height <= 0)
+            var page = book.get_page (i);
+
+            /* Skip empty pages */
+            if (!page.has_data ())
                 continue;
 
-            debug (@"Restoring a page of size $(width) x $(height)");
-            var new_page = book.append_page (width, height, dpi, scan_direction);
+            var page_name = "page-%d".printf (i);
+            if (page_names != "")
+                page_names += " ";
+            page_names += page_name;
 
-            if (depth > 0 && n_channels > 0)
-            {
-                var info = new ScanPageInfo ();
-                info.width = width;
-                info.height = height;
-                info.depth = depth;
-                info.n_channels = n_channels;
-                info.dpi = dpi;
-                info.device = "";
-                new_page.set_page_info (info);
-            }
+            debug ("Autosaving page %s", page_name);
 
-            new_page.set_color_profile (stmt.column_text (11));
-            var crop_x = stmt.column_int (12);
-            var crop_y = stmt.column_int (13);
-            var crop_width = stmt.column_int (14);
-            var crop_height = stmt.column_int (15);
-            if (crop_width > 0 && crop_height > 0)
+            file.set_integer (page_name, "scan-width", page.get_scan_width ());
+            file.set_integer (page_name, "scan-height", page.get_scan_height ());
+            file.set_integer (page_name, "rowstride", page.get_rowstride ());
+            file.set_integer (page_name, "n-channels", page.get_n_channels ());
+            file.set_integer (page_name, "depth", page.get_depth ());
+            file.set_integer (page_name, "dpi", page.get_dpi ());
+            switch (page.get_scan_direction ())
             {
-                new_page.set_custom_crop (crop_width, crop_height);
-                new_page.move_crop (crop_x, crop_y);
+            case ScanDirection.TOP_TO_BOTTOM:
+                file.set_value (page_name, "scan-direction", "TOP_TO_BOTTOM");
+                break;
+            case ScanDirection.LEFT_TO_RIGHT:
+                file.set_value (page_name, "scan-direction", "LEFT_TO_RIGHT");
+                break;
+            case ScanDirection.BOTTOM_TO_TOP:
+                file.set_value (page_name, "scan-direction", "BOTTOM_TO_TOP");
+                break;
+            case ScanDirection.RIGHT_TO_LEFT:
+                file.set_value (page_name, "scan-direction", "RIGHT_TO_LEFT");
+                break;
             }
-
-            uchar[] new_pixels = new uchar[stmt.column_bytes (17)];
-            Memory.copy (new_pixels, stmt.column_blob (17), stmt.column_bytes (17));
-            new_page.set_pixels (new_pixels);
-
-            var id = stmt.column_int (18);
-            debug ("Updating autosave to point to our new copy of the page");
-            query = @"
-                UPDATE pages
-                   SET page_hash=?1,
-                       book_hash=?2,
-                       book_revision=?3
-                WHERE id = $id
-            ";
-
-            Sqlite.Statement stmt2;
-            var result2 = database_connection.prepare_v2 (query, -1, out stmt2);
-            if (result2 != Sqlite.OK)
-                warning (@"Error $result2 while preparing query");
-            stmt2.bind_int64 (1, direct_hash (new_page));
-            stmt2.bind_int64 (2, direct_hash (book));
-            stmt2.bind_int64 (3, cur_book_revision);
-
-            result2 = stmt2.step ();
-            if (result2 != Sqlite.DONE)
-                warning ("Error %d while executing query", result);
+            file.set_value (page_name, "color-profile", page.get_color_profile () ?? "");
+            file.set_value (page_name, "pixels-filename", page_filenames.lookup (page) ?? "");
+            file.set_boolean (page_name, "has-crop", page.has_crop ());
+            int cx, cy, cw, ch;
+            page.get_crop (out cx, out cy, out cw, out ch);
+            file.set_value (page_name, "crop-name", page.get_named_crop () ?? "");
+            file.set_integer (page_name, "crop-x", cx);
+            file.set_integer (page_name, "crop-y", cy);
+            file.set_integer (page_name, "crop-width", cw);
+            file.set_integer (page_name, "crop-height", ch);
+        }
+        file.set_value ("simple-scan", "pages", page_names);
+ 
+        try
+        {
+            DirUtils.create_with_parents (AUTOSAVE_DIR, 0777);
+            FileUtils.set_contents (AUTOSAVE_PATH, file.to_data ());
         }
+        catch (Error e)
+        {
+            warning ("Failed to write autosave: %s", e.message);
+        }
+    }
+
+    private void save_pixels (Page page)
+    {
+        var filename = "%u.pixels".printf (direct_hash (page));
+        var path = Path.build_filename (AUTOSAVE_DIR, filename);
+        page_filenames.insert (page, filename);
+
+        debug ("Autosaving page pixels to %s", path);
 
-        if (first)
-            debug ("No pages found to recover");
+        var file = File.new_for_path (path);
+        try
+        {
+            file.replace_contents (page.get_pixels (), null, false, FileCreateFlags.NONE, null);
+        }
+        catch (Error e)
+        {
+            warning ("Failed to autosave page contents: %s", e.message);
+        }
     }
 }
diff --git a/src/book.vala b/src/book.vala
index a4e6cdd..4427393 100644
--- a/src/book.vala
+++ b/src/book.vala
@@ -52,17 +52,14 @@ public class Book
         set_needs_saving (true);
     }
 
-    public Page append_page (int width, int height, int dpi, ScanDirection scan_direction)
+    public void append_page (Page page)
     {
-        var page = new Page (width, height, dpi, scan_direction);
         page.pixels_changed.connect (page_changed_cb);
         page.crop_changed.connect (page_changed_cb);
 
         pages.append (page);
         page_added (page);
         set_needs_saving (true);
-
-        return page;
     }
 
     public void move_page (Page page, uint location)
diff --git a/src/page.vala b/src/page.vala
index f27258c..2153397 100644
--- a/src/page.vala
+++ b/src/page.vala
@@ -81,6 +81,41 @@ public class Page
         this.scan_direction = scan_direction;
     }
 
+    public Page.from_data (int width,
+                           int n_rows,
+                           int rowstride,
+                           int n_channels,
+                           int depth,
+                           int dpi,
+                           ScanDirection scan_direction,
+                           string? color_profile,
+                           uchar[]? pixels,
+                           bool has_crop,
+                           string? crop_name,
+                           int crop_x,
+                           int crop_y,
+                           int crop_width,
+                           int crop_height)
+    {
+        this.width = width;
+        this.n_rows = n_rows;
+        this.expected_rows = n_rows;
+        this.rowstride = rowstride;
+        this.n_channels = n_channels;
+        this.depth = depth;
+        this.dpi = dpi;
+        this.scan_direction = scan_direction;
+        this.color_profile = color_profile;
+        this.pixels = pixels;
+        has_data_ = pixels != null;
+        this.has_crop_ = has_crop;
+        this.crop_name = crop_name;
+        this.crop_x = crop_x;
+        this.crop_y = crop_y;
+        this.crop_width = crop_width;
+        this.crop_height = crop_height;
+    }
+
     public void set_page_info (ScanPageInfo info)
     {
         expected_rows = info.height;
@@ -374,6 +409,11 @@ public class Page
         if (!has_crop_)
             return;
         has_crop_ = false;
+        crop_name = null;
+        crop_x = 0;
+        crop_y = 0;
+        crop_width = 0;
+        crop_height = 0;
         crop_changed ();
     }
 
@@ -543,13 +583,6 @@ public class Page
         return pixels;
     }
 
-    public void set_pixels (uchar[] new_pixels)
-    {
-        pixels = new_pixels;
-        has_data_ = new_pixels != null;
-        pixels_changed ();
-    }
-
     // FIXME: Copied from page-view, should be shared code
     private uchar get_sample (uchar[] pixels, int offset, int x, int depth, int n_channels, int channel)
     {
diff --git a/src/simple-scan.vala b/src/simple-scan.vala
index 14e70ea..689ab36 100644
--- a/src/simple-scan.vala
+++ b/src/simple-scan.vala
@@ -155,7 +155,8 @@ public class SimpleScan : Gtk.Application
             }
         }
 
-        page = book.append_page (width, height, dpi, scan_direction);
+        page = new Page (width, height, dpi, scan_direction);
+        book.append_page (page);
         if (do_crop)
         {
             if (named_crop != null)
@@ -275,11 +276,7 @@ public class SimpleScan : Gtk.Application
     private void remove_empty_page ()
     {
         var page = book.get_page ((int) book.get_n_pages () - 1);
-
-        /* Remove a failed page */
-        if (page.has_data ())
-            page.finish ();
-        else
+        if (!page.has_data ())
             book.delete_page (page);
     }
 
diff --git a/src/ui.vala b/src/ui.vala
index 075c97d..f3e34d7 100644
--- a/src/ui.vala
+++ b/src/ui.vala
@@ -109,7 +109,18 @@ public class UserInterface
 
         load ();
 
-        autosave_manager = AutosaveManager.create (ref book);
+        autosave_manager = new AutosaveManager ();
+        autosave_manager.book = book;
+        autosave_manager.load ();
+
+        if (book.get_n_pages () == 0)
+        {
+            add_default_page ();
+            book.set_needs_saving (false);
+        }
+        else
+            book_view.select_page (book.get_page (0));
+        book.needs_saving_changed.connect (needs_saving_cb);
     }
 
     ~UserInterface ()
@@ -318,10 +329,11 @@ public class UserInterface
 
     private void add_default_page ()
     {
-        var page = book.append_page (default_page_width,
-                                     default_page_height,
-                                     default_page_dpi,
-                                     default_page_scan_direction);
+        var page = new Page (default_page_width,
+                             default_page_height,
+                             default_page_dpi,
+                             default_page_scan_direction);
+        book.append_page (page);
         book_view.select_page (page);
     }
 
@@ -1183,8 +1195,7 @@ public class UserInterface
 
         window.destroy ();
 
-        if (autosave_manager != null)
-            autosave_manager.cleanup ();
+        autosave_manager.cleanup ();
 
         return true;
     }
@@ -1519,11 +1530,6 @@ public class UserInterface
             window.maximize ();
         }
 
-        if (book.get_n_pages () == 0)
-            add_default_page ();
-        book.set_needs_saving (false);
-        book.needs_saving_changed.connect (needs_saving_cb);
-
         progress_dialog = new ProgressBarDialog (window, _("Saving document..."));
         book.saving.connect (book_saving_cb);
     }


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