Merge lp:~tkluck/simple-scan/autosaves into lp:~simple-scan-team/simple-scan/trunk

Proposed by Timo Kluck
Status: Merged
Merged at revision: 602
Proposed branch: lp:~tkluck/simple-scan/autosaves
Merge into: lp:~simple-scan-team/simple-scan/trunk
Diff against target: 635 lines (+545/-2)
6 files modified
.bzrignore (+1/-0)
configure.ac (+1/-0)
src/Makefile.am (+4/-2)
src/autosave-manager.vala (+521/-0)
src/page.vala (+9/-0)
src/ui.vala (+9/-0)
To merge this branch: bzr merge lp:~tkluck/simple-scan/autosaves
Reviewer Review Type Date Requested Status
Robert Ancell Needs Fixing
Review via email: mp+101563@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Robert Ancell (robert-ancell) wrote :

Hi,

Thanks for working on this!

The patch seems generally good, but I'm not able to confirm it working. I can confirm the database gets written, but if I do a "killall -9 simple-scan" and then restart it the existing book is not recovered. What steps should I take to confirm the patch works?

> // FIXME: this only works on linux
> string current_pids;
> Process.spawn_command_line_sync ("pidof simple-scan | sed \"s/ /,/g\"", out current_pids);
This is a bit gross, but don't know if we can get around it.

> public void on_page_changed (Page page)
> {
> /* we don't update the database as it is to slow to do so each time
> * a scan line is received.
> */
> //update_page (page);
> }
Please remove this if it doesn't do anything.

> if (number_of_instances > 0)
> {
> assert_not_reached ();
> }
We don't use curly braces if there's just one line in a branch.

> /* FIXME: we would like to connect to a scan_fished signal on a page,
> * but it does not exist. Updating the database every time a scanline
> * has changed is much to slow. We choose to update the database every
> * now and then, instead.
Let's add that signal then.

review: Needs Information
Revision history for this message
Timo Kluck (tkluck) wrote :

Hi Robert,

Thanks for your detailed feedback.

> The patch seems generally good, but I'm not able to confirm it working. I can confirm the database gets written, but if I do a "killall -9 simple-scan" and then restart it the existing book is not recovered. What steps should I take to confirm the patch works?

I don't have access to a scanner atm, so I cannot make a document like you're doing. However, it did work for me as far as I remember. What does work for me right now, is to add an autosave page manually, by

sqlite3 ~/.cache/simple-scan/autosaves/autosaves.db
INSERT INTO "pages" VALUES(1,1,1,1,1,1,150,150,150,8,3,450,'',0,0,10,10,0,randomblob(150*150*3));

and then starting simple-scan. The window will open with a very colourful page. Can you confirm that this works for you?

I will put your other remarks into work soon.

Revision history for this message
Robert Ancell (robert-ancell) wrote :

Hi Timo,

If you ever want to test simple-scan you can pass the name of the scanner as an argument to simple-scan. Conveniently SANE has a test driver so the following:

$ simple-scan test

Works without any scanner attached!

Revision history for this message
Timo Kluck (tkluck) wrote :

If you ever want to test simple-scan you can pass the name of the scanner as an argument to simple-scan. Conveniently SANE has a test driver so the following:

$ simple-scan test

Works without any scanner attached!
Thanks, that's very helpful! I now see that the first scanned page is not correctly stored in the database. The next one is, and it is also being correctly recovered on my machine. A clue is that I'm getting some of those warn_if_fails failing. Are you seeing the same behaviour?

I will have to look into it later.

Revision history for this message
Timo Kluck (tkluck) wrote :

I've pushed some changes that seem to deal with the issues I could reproduce. Can you test the new version?

lp:~tkluck/simple-scan/autosaves updated
563. By Timo Kluck

Correctly deal with the pages that exist already when the autosave manager is started

564. By Timo Kluck

use a scan_finished signal to know when to autosave

565. By Timo Kluck

do not use curly braces for single line branches

Revision history for this message
Robert Ancell (robert-ancell) wrote :

I tried the following:

1. Run simple-scan with test driver:
bob@alchemy:~/bzr/simple-scan$ ./src/simple-scan test
2. Scan a single page with text mode
3. Kill simple-scan with 'killall simple-scan':
Terminated
4. Run simple-scan again
bob@alchemy:~/bzr/simple-scan$ ./src/simple-scan

** (simple-scan:8023): CRITICAL **: autosave_manager_recover_book: assertion `SQLITE_OK == _tmp8_' failed

Doesn't recover the book and gives the above error.

review: Needs Fixing
Revision history for this message
Timo Kluck (tkluck) wrote :

> I tried the following:
>
> 1. Run simple-scan with test driver:
> bob@alchemy:~/bzr/simple-scan$ ./src/simple-scan test
> 2. Scan a single page with text mode
> 3. Kill simple-scan with 'killall simple-scan':
> Terminated
> 4. Run simple-scan again
> bob@alchemy:~/bzr/simple-scan$ ./src/simple-scan
>
> ** (simple-scan:8023): CRITICAL **: autosave_manager_recover_book: assertion
> `SQLITE_OK == _tmp8_' failed
>
> Doesn't recover the book and gives the above error.

I'm doing the exact same thing (either first removing ~/.cache/simple-scan/autosaves/autosaves.db or not) and it works for me. I'm also puzzled by the error you're getting. It seems to be the prepare_v2 call that's failing. I cannot see how that can work on one machine and fail on the other. Are you on Precise with sqlite3 version 3.7.9-2ubuntu1 ?

Could you help me debug this? There's no way I can do that without seeing it happen for myself. Can you recover the exit code of the prepare_v2 call? And can you recover the sql string after it has been formatted?

Thanks!

Revision history for this message
Robert Ancell (robert-ancell) wrote :

Could you update it to log the error codes? That way it will be safer in the future.

lp:~tkluck/simple-scan/autosaves updated
566. By Timo Kluck

add verbose logging output for autosave manager database access

Revision history for this message
Timo Kluck (tkluck) wrote :

I've just pushed a version that logs all the query strings and the resulting error codes. It would be great if you could send me the resulting logfile. (don't forget to specify --debug on the command line).

Revision history for this message
Robert Ancell (robert-ancell) wrote :

It doesn't crash anymore but I get the following error:

[+0.00s] DEBUG: simple-scan.vala:582: Starting Simple Scan 3.6.0, PID=5391
[+0.00s] DEBUG: Connecting to session manager
[+0.03s] WARNING: g_object_set_valist: invalid object type `GtkAdjustment' for value type `GtkWidget'
[+0.03s] DEBUG: ui.vala:1463: Restoring window to 774x607 pixels
[+0.03s] DEBUG: ui.vala:1468: Restoring window to maximized
[+0.03s] DEBUG: autosave-manager.vala:214: Executing 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
            )"
[+0.04s] DEBUG: autosave-manager.vala:113: preparing query "
                   SELECT process_id, book_hash, book_revision FROM pages
                   WHERE NOT process_id IN (5391)
                   LIMIT 1
                "
[+0.04s] DEBUG: autosave-manager.vala:134: Executing query "
                        UPDATE pages
                           SET process_id = 5391
                         WHERE process_id = 5359
                           AND book_hash = 11118128
                           AND book_revision = 0"
[+0.23s] DEBUG: autosave-manager.vala:396: preparing 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 = 5391
              AND book_revision = (
                  SELECT MAX(book_revision) WHERE process_id = 5391
              )
            ORDER BY page_number
        "
[+0.23s] WARNING: autosave-manager.vala:399: error 1 while preparing statement
[+0.28s] DEBUG: scanner.vala:1419: sane_init () -> SANE_STATUS_GOOD
[+0.28s] DEBUG: scanner.vala:1425: SANE version 1.0.23
[+0.28s] DEBUG: scanner.vala:1486: Requesting redetection of scan devices
[+0.28s] DEBUG: scanner.vala:776: Processing request
[+2.69s] DEBUG: scanner.vala:338: sane_get_devices () -> SANE_STATUS_GOOD
[+3.30s] DEBUG: scanner.vala:1559: Stopping scan thread
[+3.30s] DEBUG: scanner.vala:776: Processing request
[+3.31s] DEBUG: scanner.vala:1567: sane_exit ()

review: Needs Fixing
Revision history for this message
Robert Ancell (robert-ancell) wrote :

It seems to be this part - removing it makes it work.
AND book_revision = ( SELECT MAX(book_revision) WHERE process_id = 5391 )

Is a particular version of SQLite required for this syntax to be supported?

lp:~tkluck/simple-scan/autosaves updated
567. By Timo Kluck

Fix subquery

Revision history for this message
Timo Kluck (tkluck) wrote :

I could reproduce this error now (!). I'm surprised that it worked before, because the subquery misses a FROM clause. I just pushed a fix. Can you let me know your results?

Revision history for this message
Robert Ancell (robert-ancell) wrote :

That fixes that error, but still not books being restored.

I am running the following:
1. $ simple-scan test -d
2. Pressing scan
3. Killing simple-scan with ctrl-C (or killall simple-scan)
4. $ simple-scan

No pages are restored.

Can you add some debugging messages that note what the the autosave manager is doing? I would expect to see the following in the log

"Restoring book with 5 pages"
"No book to restore"
"Failed to restore book: Error Sqlite.ERROR when getting pages"
"Autosaving book to ~/.cache/simple-scan/autosaves/autosaves.db"

lp:~tkluck/simple-scan/autosaves updated
568. By Timo Kluck

even more verbose logging

Revision history for this message
Timo Kluck (tkluck) wrote :

I just pushed some extra verbose logging. Could you post your log for when it is failing? And, just to make sure, can you also

    rm -rf ~/.cache/simple-scan/autosaves

before testing?

Revision history for this message
Robert Ancell (robert-ancell) wrote :
Download full text (3.5 KiB)

Did the following:
0. $ ~/.cache/simple-scan/autosaves/autosaves.db
1. $ simple-scan test
2. Pressing scan
3. Ctrl+C
4. $ simple-scan

It worked some times but not always. The log from a failure is:
[+0.00s] DEBUG: simple-scan.vala:582: Starting Simple Scan 3.6.0, PID=11515
[+0.00s] DEBUG: Connecting to session manager
[+0.04s] WARNING: g_object_set_valist: invalid object type `GtkAdjustment' for value type `GtkWidget'
[+0.04s] DEBUG: ui.vala:1463: Restoring window to 774x607 pixels
[+0.04s] DEBUG: autosave-manager.vala:87: creating a new instance of the autosave manager
[+0.04s] DEBUG: autosave-manager.vala:220: Executing 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
            )"
[+0.06s] DEBUG: autosave-manager.vala:117: preparing query "
                   SELECT process_id, book_hash, book_revision FROM pages
                   WHERE NOT process_id IN (11515)
                   LIMIT 1
                "
[+0.06s] DEBUG: autosave-manager.vala:123: found at least one autosave page, taking ownership
[+0.06s] DEBUG: autosave-manager.vala:139: Executing query "
                        UPDATE pages
                           SET process_id = 11515
                         WHERE process_id = 11505
                           AND book_hash = -671049168
                           AND book_revision = 0"
[+0.06s] DEBUG: autosave-manager.vala:404: preparing 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 = 11515
              AND book_revision = (
                  SELECT MAX(book_revision) FROM pages WHERE process_id = 11515
              )
            ORDER BY page_number
        "
[+0.06s] DEBUG: autosave-manager.vala:474: no pages found to recover
[+0.06s] DEBUG: autosave-manager.vala:57: setting book to autosave
[+0.06s] DEBUG: autosave-manager.vala:72: connecting to signals of new book
[+0.10s] DEBUG: scanner.vala:1419: sane_init () -> SANE_STATUS_GOOD
[+0.10s] DEBUG: scanner.vala:1425: SANE version 1.0.23
[+0.10s] DEBUG: scanner.vala:1486: Requesting redetection of scan devices
[+0....

Read more...

Revision history for this message
Robert Ancell (robert-ancell) wrote :
Download full text (32.0 KiB)

Log when the database was created:
[+0.00s] DEBUG: simple-scan.vala:582: Starting Simple Scan 3.6.0, PID=11458
[+0.00s] DEBUG: Connecting to session manager
[+0.03s] WARNING: g_object_set_valist: invalid object type `GtkAdjustment' for value type `GtkWidget'
[+0.03s] DEBUG: ui.vala:1463: Restoring window to 774x607 pixels
[+0.03s] DEBUG: autosave-manager.vala:87: creating a new instance of the autosave manager
[+0.03s] DEBUG: autosave-manager.vala:220: Executing 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
            )"
[+0.23s] DEBUG: autosave-manager.vala:117: preparing query "
                   SELECT process_id, book_hash, book_revision FROM pages
                   WHERE NOT process_id IN (11458)
                   LIMIT 1
                "
[+0.23s] DEBUG: autosave-manager.vala:57: setting book to autosave
[+0.23s] DEBUG: autosave-manager.vala:72: connecting to signals of new book
[+0.23s] DEBUG: autosave-manager.vala:300: adding an autosave for a new page
[+0.23s] DEBUG: autosave-manager.vala:313: Executing query "
            INSERT INTO pages
                (process_id,
                page_hash,
                book_hash,
                book_revision)
                VALUES
                (11458,
                20112144,
                1744869936,
                0)
        "
[+0.39s] DEBUG: autosave-manager.vala:322: updating the autosave for a page
[+0.39s] DEBUG: autosave-manager.vala:351: preparing query "
            UPDATE pages
                SET
                page_number=0,
                dpi=300,
                width=2362,
                height=2362,
                depth=0,
                n_channels=0,
                rowstride=0,
                crop_x=0,
                crop_y=0,
                crop_width=0,
                crop_height=0,
                scan_direction=0,
                color_profile=?1,
                pixels=?2
                WHERE process_id = 11458
                  AND page_hash = 20112144
                  AND book_hash = 1744869936
                  AND book_revision = 0
            "
[+0.58s] DEBUG: scanner.vala:1419: sane_init () -> SANE_STATUS_GOOD
[+0.58s] DEBUG: scanner.vala:1425: SANE version 1.0.23
[+0.58s] DEBUG: scanner.vala:1486: Requesting redetection of scan devices
[+0.58s] DEBUG: scanner.vala:776: Processing request
[+1.73s] DEBUG: simple-scan.vala:310: Requesting scan at 150 dpi from device 'test'
[+1.73s] DEBUG: scanner.vala:1532: Scanner.scan ("test", dpi=150, scan_mode=ScanMode.GRAY, depth=2, type=ScanType...

Revision history for this message
Timo Kluck (tkluck) wrote :

I think I've got it. The book_hash parameter may be formatted as a signed
int by vala into the query, and then it doesn't match unsigned value in the
record.

I'll do the proper thing and use bind_int for these things. I'll let you
know as soon as I've pushed an updated version.

Thanks for the extensive logs and testing!

lp:~tkluck/simple-scan/autosaves updated
569. By Timo Kluck

use bind_int64 for the direct_hash values

Revision history for this message
Timo Kluck (tkluck) wrote :

Could you test again? I could reproduce the problems with your database but I can't generate the same erroneous database on my machine, so I can't be 100% sure this last commit tackled the problem.

Revision history for this message
Robert Ancell (robert-ancell) wrote :

Pushed with some coding style changes, thanks for the patience!

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file '.bzrignore'
2--- .bzrignore 2011-06-12 10:13:06 +0000
3+++ .bzrignore 2012-12-18 23:23:21 +0000
4@@ -32,3 +32,4 @@
5 src/scanner.c
6 src/simple-scan.c
7 src/ui.c
8+src/autosave-manager.c
9
10=== modified file 'configure.ac'
11--- configure.ac 2012-10-14 20:16:55 +0000
12+++ configure.ac 2012-12-18 23:23:21 +0000
13@@ -27,6 +27,7 @@
14 cairo
15 gdk-pixbuf-2.0
16 gudev-1.0
17+ sqlite3
18 ])
19
20 PKG_CHECK_MODULES(COLORD, [
21
22=== modified file 'src/Makefile.am'
23--- src/Makefile.am 2011-07-10 06:59:21 +0000
24+++ src/Makefile.am 2012-12-18 23:23:21 +0000
25@@ -11,13 +11,15 @@
26 sane.vapi \
27 simple-scan.vala \
28 scanner.vala \
29- ui.vala
30+ ui.vala \
31+ autosave-manager.vala
32
33 simple_scan_VALAFLAGS = \
34 --pkg=zlib \
35 --pkg=gudev-1.0 \
36 --pkg=gio-2.0 \
37- --pkg=gtk+-3.0
38+ --pkg=gtk+-3.0 \
39+ --pkg=sqlite3
40
41 if HAVE_COLORD
42 simple_scan_VALAFLAGS += -D HAVE_COLORD
43
44=== added file 'src/autosave-manager.vala'
45--- src/autosave-manager.vala 1970-01-01 00:00:00 +0000
46+++ src/autosave-manager.vala 2012-12-18 23:23:21 +0000
47@@ -0,0 +1,521 @@
48+/*
49+ * Copyright (C) 2011 Timo Kluck
50+ * Author: Timo Kluck <tkluck@infty.nl>
51+ *
52+ * This program is free software: you can redistribute it and/or modify it under
53+ * the terms of the GNU General Public License as published by the Free Software
54+ * Foundation, either version 3 of the License, or (at your option) any later
55+ * version. See http://www.gnu.org/copyleft/gpl.html the full text of the
56+ * license.
57+ */
58+
59+/*
60+ * We store autosaves in a database named
61+ * ~/.cache/simple-scan/autosaves/autosaves.db
62+ * It contains a single table of pages, each containing the process id (pid) of
63+ * the simple-scan instance that saved it, and a hash of the Book and Page
64+ * objects corresponding to it. The pixels are saved as a BLOB.
65+ * Additionally, the autosaves directory contains a number of tiff files that
66+ * the user can use for manual recovery.
67+ *
68+ * At startup, we check whether autosaves.db contains any records
69+ * with a pid that does not match a current pid for simple-scan. If so, we take
70+ * ownership by an UPDATE statement changing to our own pid. Then, we
71+ * recover the book. We're trying our best to avoid the possible race
72+ * condition if several instances of simple-scan are started simultaneously.
73+ *
74+ * At application exit, we delete the records corresponding to our own pid.
75+ *
76+ * Important notes:
77+ * - We enforce that there is only one AutosaveManager instance in a given
78+ * process by using a create function.
79+ * - It should be possible to change the book object at runtime, although this
80+ * is not used in the current implementation so it has not been tested.
81+ */
82+
83+public class AutosaveManager
84+{
85+ private static string AUTOSAVE_DIR = Path.build_filename (Environment.get_user_cache_dir (), "simple-scan", "autosaves");
86+ private static string AUTOSAVE_NAME = "autosaves";
87+ private static string AUTOSAVE_EXT = ".db";
88+ private static string AUTOSAVE_FILENAME = Path.build_filename (AUTOSAVE_DIR, AUTOSAVE_NAME + AUTOSAVE_EXT);
89+
90+ private static string PID = ((int)(Posix.getpid ())).to_string ();
91+ private static int number_of_instances = 0;
92+
93+ private Sqlite.Database database_connection;
94+ private Book _book = null;
95+
96+ public Book book
97+ {
98+ get
99+ {
100+ return _book;
101+ }
102+ set
103+ {
104+ debug("setting book to autosave");
105+ if (_book != null)
106+ {
107+ debug("disconnecting from signals of old book");
108+ for (var i = 0; i < _book.get_n_pages (); i++)
109+ {
110+ var page = _book.get_page (i);
111+ on_page_removed (page);
112+ }
113+ _book.page_added.disconnect (on_page_added);
114+ _book.page_removed.disconnect (on_page_removed);
115+ _book.reordered.disconnect (on_reordered);
116+ _book.cleared.disconnect (on_cleared);
117+ }
118+ _book = value;
119+ debug("connecting to signals of new book");
120+ _book.page_added.connect (on_page_added);
121+ _book.page_removed.connect (on_page_removed);
122+ _book.reordered.connect (on_reordered);
123+ _book.cleared.connect (on_cleared);
124+ }
125+ }
126+
127+ public static AutosaveManager? create (ref Book book)
128+ {
129+ /* compare autosave directories with pids of current instances of simple-scan
130+ * take ownership of one of the ones that are unowned by renaming to the
131+ * own pid. Then open the database and fill the book with the pages it
132+ * contains.
133+ */
134+ debug("creating a new instance of the autosave manager");
135+ if (number_of_instances > 0)
136+ assert_not_reached ();
137+
138+ var man = new AutosaveManager ();
139+ number_of_instances++;
140+
141+ try
142+ {
143+ man.database_connection = open_database_connection ();
144+ }
145+ catch
146+ {
147+ warning ("Could not connect to the autosave database; no autosaves will be kept.");
148+ return null;
149+ }
150+
151+ bool any_pages_recovered = false;
152+ try
153+ {
154+ // FIXME: this only works on linux. We can maybe use Gtk.Application and some session bus id instead?
155+ string current_pids;
156+ Process.spawn_command_line_sync ("pidof simple-scan | sed \"s/ /,/g\"", out current_pids);
157+ current_pids = current_pids.strip ();
158+ Sqlite.Statement stmt;
159+ string query = @"
160+ SELECT process_id, book_hash, book_revision FROM pages
161+ WHERE NOT process_id IN ($current_pids)
162+ LIMIT 1
163+ ";
164+ debug("preparing query \"%s\"", query);
165+ int result = man.database_connection.prepare_v2 (query, -1, out stmt);
166+ if(Sqlite.OK == result)
167+ {
168+ while (Sqlite.ROW == stmt.step ())
169+ {
170+ debug("found at least one autosave page, taking ownership");
171+ var unowned_pid = stmt.column_int (0);
172+ var book_hash = stmt.column_int (1);
173+ var book_revision = stmt.column_int (2);
174+ /* there's a possible race condition here when several instances
175+ * try to take ownership of the same rows. What would happen is
176+ * that this operations would affect no rows if another process
177+ * has taken ownership in the mean time. In that case, recover_book
178+ * does nothing, so there should be no problem.
179+ */
180+ query = @"
181+ UPDATE pages
182+ SET process_id = $PID
183+ WHERE process_id = ?2
184+ AND book_hash = ?3
185+ AND book_revision = ?4";
186+ debug("Preparing query \"%s\"", query);
187+ Sqlite.Statement stmt2;
188+ result = man.database_connection.prepare_v2(query, -1, out stmt2);
189+ if (Sqlite.OK != result)
190+ {
191+ warning (@"Error preparing statement: $query");
192+ }
193+ stmt2.bind_int64 (2, unowned_pid);
194+ stmt2.bind_int64 (3, book_hash);
195+ stmt2.bind_int64 (4, book_revision);
196+ result = stmt2.step();
197+ if (Sqlite.DONE == result)
198+ {
199+ any_pages_recovered = true;
200+ man.recover_book (ref book);
201+ }
202+ else
203+ {
204+ warning ("error %d while executing query", result);
205+ }
206+ }
207+ }
208+ else
209+ warning("error %d while preparing statement", result);
210+ }
211+ catch (SpawnError e)
212+ {
213+ warning ("Could not obtain current process ids; not restoring any autosaves");
214+ }
215+
216+ man.book = book;
217+ if (!any_pages_recovered) {
218+ for (var i = 0; i < book.get_n_pages (); i++)
219+ {
220+ var page = book.get_page (i);
221+ man.on_page_added (page);
222+ }
223+ }
224+
225+ return man;
226+ }
227+
228+ private AutosaveManager ()
229+ {
230+ }
231+
232+ public void cleanup () {
233+ // delete autosave records
234+ debug("clean exit; deleting autosave records");
235+ warn_if_fail (Sqlite.OK == database_connection.exec (@"
236+ DELETE FROM pages
237+ WHERE process_id = $PID
238+ "));
239+ }
240+
241+
242+ static Sqlite.Database open_database_connection () throws Error
243+ {
244+ var autosaves_dir = File.new_for_path (AUTOSAVE_DIR);
245+ try
246+ {
247+ autosaves_dir.make_directory_with_parents ();
248+ }
249+ catch
250+ { // the directory already exists
251+ // pass
252+ }
253+ Sqlite.Database connection;
254+ if (Sqlite.OK != Sqlite.Database.open (AUTOSAVE_FILENAME, out connection))
255+ throw new IOError.FAILED ("Could not connect to autosave database");
256+ string query = @"
257+ CREATE TABLE IF NOT EXISTS pages (
258+ id integer PRIMARY KEY,
259+ process_id integer,
260+ page_hash integer,
261+ book_hash integer,
262+ book_revision integer,
263+ page_number integer,
264+ dpi integer,
265+ width integer,
266+ height integer,
267+ depth integer,
268+ n_channels integer,
269+ rowstride integer,
270+ color_profile string,
271+ crop_x integer,
272+ crop_y integer,
273+ crop_width integer,
274+ crop_height integer,
275+ scan_direction integer,
276+ pixels binary
277+ )";
278+ debug("Executing query \"%s\"", query);
279+ int result = connection.exec(query);
280+ if (Sqlite.OK != result)
281+ warning("error %d while executing query", result);
282+ return connection;
283+ }
284+
285+ void on_page_added (Page page)
286+ {
287+ insert_page (page);
288+ // TODO: save a tiff file
289+ page.size_changed.connect (on_page_changed);
290+ page.scan_direction_changed.connect (on_page_changed);
291+ page.crop_changed.connect (on_page_changed);
292+ page.scan_finished.connect (on_page_changed);
293+ }
294+
295+ public void on_page_removed (Page page)
296+ {
297+ page.pixels_changed.disconnect (on_page_changed);
298+ page.size_changed.disconnect (on_page_changed);
299+ page.scan_direction_changed.disconnect (on_page_changed);
300+ page.crop_changed.disconnect (on_page_changed);
301+ page.scan_finished.connect (on_page_changed);
302+
303+ string query = @"
304+ DELETE FROM pages
305+ WHERE process_id = $PID
306+ AND page_hash = ?2
307+ AND book_hash = ?3
308+ AND book_revision = ?4
309+ ";
310+ debug("Executing query \"%s\"", query);
311+ Sqlite.Statement stmt;
312+ int result = database_connection.prepare_v2 (query, -1, out stmt);
313+ if (Sqlite.OK != result)
314+ warning(@"error $result while preparing query");
315+ stmt.bind_int64 (2, direct_hash (page));
316+ stmt.bind_int64 (3, direct_hash (book));
317+ stmt.bind_int64 (4, cur_book_revision);
318+
319+ result = stmt.step();
320+ if (Sqlite.DONE != result)
321+ warning("error %d while executing query", result);
322+ }
323+
324+ public void on_reordered ()
325+ {
326+ for (var i=0; i < book.get_n_pages (); i++)
327+ {
328+ var page = book.get_page (i);
329+ string query = @"
330+ UPDATE pages SET page_number = ?5
331+ WHERE process_id = $PID
332+ AND page_hash = ?2
333+ AND book_hash = ?3
334+ AND book_revision = ?4
335+ ";
336+ debug("Executing query \"%s\"", query);
337+ Sqlite.Statement stmt;
338+ int result = database_connection.prepare_v2 (query, -1, out stmt);
339+ if (Sqlite.OK != result)
340+ warning(@"error $result while preparing query");
341+ stmt.bind_int64 (5, i);
342+ stmt.bind_int64 (2, direct_hash (page));
343+ stmt.bind_int64 (3, direct_hash (book));
344+ stmt.bind_int64 (4, cur_book_revision);
345+
346+ result = stmt.step();
347+ if (Sqlite.DONE != result)
348+ warning("error %d while executing query", result);
349+ }
350+ }
351+
352+ public void on_page_changed (Page page)
353+ {
354+ update_page (page);
355+ }
356+
357+ public void on_needs_saving_changed (Book book)
358+ {
359+ for (int n = 0; n < book.get_n_pages (); n++)
360+ {
361+ var page = book.get_page (n);
362+ update_page (page);
363+ }
364+ }
365+
366+ private int cur_book_revision = 0;
367+
368+ public void on_cleared ()
369+ {
370+ cur_book_revision++;
371+ }
372+
373+ private void insert_page (Page page)
374+ {
375+ debug("adding an autosave for a new page");
376+ string query = @"
377+ INSERT INTO pages
378+ (process_id,
379+ page_hash,
380+ book_hash,
381+ book_revision)
382+ VALUES
383+ ($PID,
384+ ?2,
385+ ?3,
386+ ?4)
387+ ";
388+ debug("Executing query \"%s\"", query);
389+ Sqlite.Statement stmt;
390+ int result = database_connection.prepare_v2 (query, -1, out stmt);
391+ if (Sqlite.OK != result)
392+ warning(@"error $result while preparing query");
393+ stmt.bind_int64 (2, direct_hash (page));
394+ stmt.bind_int64 (3, direct_hash (book));
395+ stmt.bind_int64 (4, cur_book_revision);
396+ result = stmt.step();
397+ if (Sqlite.DONE != result)
398+ warning("error %d while executing query", result);
399+ update_page (page);
400+ }
401+
402+ private void update_page (Page page)
403+ {
404+ debug("updating the autosave for a page");
405+ int crop_x;
406+ int crop_y;
407+ int crop_width;
408+ int crop_height;
409+ page.get_crop (out crop_x, out crop_y, out crop_width, out crop_height);
410+ Sqlite.Statement stmt;
411+ string query = @"
412+ UPDATE pages
413+ SET
414+ page_number=$(book.get_page_index (page)),
415+ dpi=$(page.get_dpi ()),
416+ width=$(page.get_width ()),
417+ height=$(page.get_height ()),
418+ depth=$(page.get_depth ()),
419+ n_channels=$(page.get_n_channels ()),
420+ rowstride=$(page.get_rowstride ()),
421+ crop_x=$crop_x,
422+ crop_y=$crop_y,
423+ crop_width=$crop_width,
424+ crop_height=$crop_height,
425+ scan_direction=$((int)page.get_scan_direction ()),
426+ color_profile=?1,
427+ pixels=?2
428+ WHERE process_id = $PID
429+ AND page_hash = ?4
430+ AND book_hash = ?5
431+ AND book_revision = ?6
432+ ";
433+ debug("preparing query \"%s\"", query);
434+ int result = database_connection.prepare_v2 (query, -1, out stmt);
435+ if(Sqlite.OK != result) {
436+ warning("error %d while preparing statement", result);
437+ return_if_reached();
438+ }
439+ stmt.bind_int64 (4, direct_hash (page));
440+ stmt.bind_int64 (5, direct_hash (book));
441+ stmt.bind_int64 (6, cur_book_revision);
442+ result = stmt.bind_text (1, page.get_color_profile () ?? "");
443+ if(Sqlite.OK != result) {
444+ warning("error %d while binding text", result);
445+ }
446+ if (page.get_pixels () != null) {
447+ // (-1) is the special value SQLITE_TRANSIENT
448+ result = stmt.bind_blob (2, page.get_pixels (), page.get_pixels ().length, (DestroyNotify)(-1));
449+ if(Sqlite.OK != result) {
450+ warning("error %d while binding blob", result);
451+ }
452+ } else
453+ warn_if_fail (Sqlite.OK == stmt.bind_null (2));
454+
455+ warn_if_fail (Sqlite.DONE == stmt.step ());
456+ }
457+
458+ private void recover_book (ref Book book)
459+ {
460+ Sqlite.Statement stmt;
461+ string query = @"
462+ SELECT process_id,
463+ page_hash,
464+ book_hash,
465+ book_revision,
466+ page_number,
467+ dpi,
468+ width,
469+ height,
470+ depth,
471+ n_channels,
472+ rowstride,
473+ color_profile,
474+ crop_x,
475+ crop_y,
476+ crop_width,
477+ crop_height,
478+ scan_direction,
479+ pixels,
480+ id
481+ FROM pages
482+ WHERE process_id = $PID
483+ AND book_revision = (
484+ SELECT MAX(book_revision) FROM pages WHERE process_id = $PID
485+ )
486+ ORDER BY page_number
487+ ";
488+ debug("preparing query \"%s\"", query);
489+ int result = database_connection.prepare_v2 (query, -1, out stmt);
490+ if (Sqlite.OK != result) {
491+ warning("error %d while preparing statement", result);
492+ }
493+ bool first = true;
494+ while (Sqlite.ROW == stmt.step ())
495+ {
496+ debug("found a page that needs to be recovered");
497+ if (first)
498+ {
499+ book.clear ();
500+ first = false;
501+ }
502+ var dpi = stmt.column_int (5);
503+ var width = stmt.column_int (6);
504+ var height = stmt.column_int (7);
505+ var depth = stmt.column_int (8);
506+ var n_channels = stmt.column_int (9);
507+ var scan_direction = (ScanDirection)stmt.column_int (16);
508+
509+ if (width <= 0 || height <= 0)
510+ continue;
511+
512+ debug(@"restoring a page of size $(width) x $(height)");
513+ var new_page = book.append_page (width, height, dpi, scan_direction);
514+
515+ if (depth > 0 && n_channels > 0)
516+ {
517+ var info = new ScanPageInfo ();
518+ info.width = width;
519+ info.height = height;
520+ info.depth = depth;
521+ info.n_channels = n_channels;
522+ info.dpi = dpi;
523+ info.device = "";
524+ new_page.set_page_info (info);
525+ }
526+
527+ new_page.set_color_profile (stmt.column_text (11));
528+ var crop_x = stmt.column_int (12);
529+ var crop_y = stmt.column_int (13);
530+ var crop_width = stmt.column_int (14);
531+ var crop_height = stmt.column_int (15);
532+ if (crop_width > 0 && crop_height > 0)
533+ {
534+ new_page.set_custom_crop (crop_width, crop_height);
535+ new_page.move_crop (crop_x, crop_y);
536+ }
537+
538+ uchar[] new_pixels = new uchar[stmt.column_bytes (17)];
539+ Memory.copy (new_pixels, stmt.column_blob (17), stmt.column_bytes (17));
540+ new_page.set_pixels (new_pixels);
541+
542+ var id = stmt.column_int (18);
543+ debug("updating autosave to point to our new copy of the page");
544+ query = @"
545+ UPDATE pages
546+ SET page_hash=?1,
547+ book_hash=?2,
548+ book_revision=?3
549+ WHERE id = $id
550+ ";
551+ debug("executing query \"%s\"", query);
552+ Sqlite.Statement stmt2;
553+ int result2 = database_connection.prepare_v2 (query, -1, out stmt2);
554+ if (Sqlite.OK != result2)
555+ warning(@"error $result2 while preparing query");
556+ stmt2.bind_int64 (1, direct_hash (new_page));
557+ stmt2.bind_int64 (2, direct_hash (book));
558+ stmt2.bind_int64 (3, cur_book_revision);
559+
560+ result2 = stmt2.step();
561+ if (Sqlite.DONE != result2)
562+ warning("error %d while executing query", result);
563+ }
564+ if (first) {
565+ debug("no pages found to recover");
566+ }
567+ }
568+}
569
570=== modified file 'src/page.vala'
571--- src/page.vala 2012-08-16 02:09:28 +0000
572+++ src/page.vala 2012-12-18 23:23:21 +0000
573@@ -63,6 +63,7 @@
574 public signal void scan_line_changed ();
575 public signal void scan_direction_changed ();
576 public signal void crop_changed ();
577+ public signal void scan_finished ();
578
579 public Page (int width, int height, int dpi, ScanDirection scan_direction)
580 {
581@@ -199,6 +200,7 @@
582 if (size_has_changed)
583 size_changed ();
584 scan_line_changed ();
585+ scan_finished ();
586 }
587
588 public ScanDirection get_scan_direction ()
589@@ -541,6 +543,13 @@
590 return pixels;
591 }
592
593+ public void set_pixels (uchar[] new_pixels)
594+ {
595+ pixels = new_pixels;
596+ has_data_ = new_pixels != null;
597+ pixels_changed ();
598+ }
599+
600 // FIXME: Copied from page-view, should be shared code
601 private uchar get_sample (uchar[] pixels, int offset, int x, int depth, int n_channels, int channel)
602 {
603
604=== modified file 'src/ui.vala'
605--- src/ui.vala 2012-11-01 07:45:01 +0000
606+++ src/ui.vala 2012-12-18 23:23:21 +0000
607@@ -74,6 +74,8 @@
608 private Book book;
609 private string? book_uri = null;
610
611+ private AutosaveManager? autosave_manager;
612+
613 private BookView book_view;
614 private bool updating_page_menu;
615 private int default_page_width;
616@@ -103,6 +105,8 @@
617 settings = new Settings ("org.gnome.SimpleScan");
618
619 load ();
620+
621+ autosave_manager = AutosaveManager.create (ref book);
622 }
623
624 ~UserInterface ()
625@@ -1139,6 +1143,11 @@
626
627 window.destroy ();
628
629+ if (autosave_manager != null)
630+ {
631+ autosave_manager.cleanup ();
632+ }
633+
634 return true;
635 }
636

Subscribers

People subscribed via source and target branches