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 | 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 |
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.