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

Subscribers

People subscribed via source and target branches