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