Merge lp:~erasmo-marin/foto/fix-import into lp:foto/foto-1.0

Proposed by Erasmo Marín
Status: Merged
Approved by: Erasmo Marín
Approved revision: 113
Merged at revision: 118
Proposed branch: lp:~erasmo-marin/foto/fix-import
Merge into: lp:foto/foto-1.0
Diff against target: 437 lines (+220/-78)
9 files modified
CMakeLists.txt (+2/-0)
src/FotoApp.vala (+8/-5)
src/ViewManager.vala (+2/-1)
src/core/ImportJob.vala (+41/-62)
src/core/Threads/Threads.vala (+145/-0)
src/pages/AlbumPage.vala (+1/-1)
src/pages/CollectionPage.vala (+0/-1)
src/pages/LastImportedPage.vala (+19/-6)
src/pages/WelcomePage.vala (+2/-2)
To merge this branch: bzr merge lp:~erasmo-marin/foto/fix-import
Reviewer Review Type Date Requested Status
Fabio Zaramella Approve
Review via email: mp+238389@code.launchpad.net

Description of the change

-Adds Threads.vala taken from lp:noise, it creates a ThreadPool for thread management.
-Adds a workaround (not sure if this is a definitive solution) to fix the import crash when the thread ends by adding X.init_threads() in main.
-Adds a method to refresh the import page.

With the new Threads class, it will be easier to implement IO operations without blocking GUI.

To post a comment you must log in.
lp:~erasmo-marin/foto/fix-import updated
113. By Erasmo Marín

fix comment documentation

Revision history for this message
Fabio Zaramella (fabiozaramella) wrote :

Looks and works great, no more crashes.

review: Approve
Revision history for this message
Fabio Zaramella (fabiozaramella) wrote :

For the future, in debug messages use english where possible :p

Revision history for this message
Erasmo Marín (erasmo-marin) wrote :

Yep, sorry about that ^^

Revision history for this message
Fabio Zaramella (fabiozaramella) wrote :

Merge it ^^

lp:~erasmo-marin/foto/fix-import updated
114. By Erasmo Marín

fix debug messages

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'CMakeLists.txt'
2--- CMakeLists.txt 2014-10-14 01:35:45 +0000
3+++ CMakeLists.txt 2014-10-15 21:31:58 +0000
4@@ -159,6 +159,8 @@
5 src/core/PhotoFormat/PhotoDriver.vala
6 src/core/PhotoFormat/PhotoFormat.vala
7
8+ src/core/Threads/Threads.vala
9+
10 src/dialogs/AlbumDialog.vala
11 src/dialogs/PropertiesDialog.vala
12 src/dialogs/AddToAlbumDialog.vala
13
14=== modified file 'src/FotoApp.vala'
15--- src/FotoApp.vala 2014-09-07 20:33:48 +0000
16+++ src/FotoApp.vala 2014-10-15 21:31:58 +0000
17@@ -18,10 +18,6 @@
18
19 namespace Foto {
20
21- // Settings
22- //public SavedState saved_state;
23- //public Settings settings;
24-
25 public class FotoApp : Granite.Application {
26
27 public AppWindow window = null;
28@@ -73,7 +69,6 @@
29 if(new_instance)
30 flags |= ApplicationFlags.NON_UNIQUE;
31 set_flags (flags);
32-
33 Utils.Cache.init();
34 }
35
36@@ -173,6 +168,12 @@
37
38 public static int main(string[] args) {
39
40+ if (!Thread.supported ()) {
41+ error ("Cannot run without thread support.");
42+ }
43+
44+ X.init_threads();
45+
46 _app_cmd_name = "foto";
47 var context = new OptionContext ("File");
48 context.add_main_entries (entries, Build.GETTEXT_PACKAGE);
49@@ -193,7 +194,9 @@
50
51 return Posix.EXIT_SUCCESS;
52 }
53+
54 var app = new FotoApp();
55+
56 GtkClutter.init(ref args);
57 Gtk.Settings.get_default ().gtk_application_prefer_dark_theme = true;
58
59
60=== modified file 'src/ViewManager.vala'
61--- src/ViewManager.vala 2014-09-17 04:41:18 +0000
62+++ src/ViewManager.vala 2014-10-15 21:31:58 +0000
63@@ -87,8 +87,9 @@
64 public Page? get_page(PageType page_type) {
65
66 Page page = check_page(page_type);
67- if(page != null)
68+ if(page != null) {
69 return page;
70+ }
71
72 return instantiate(page_type);
73 }
74
75=== modified file 'src/core/ImportJob.vala'
76--- src/core/ImportJob.vala 2014-08-27 08:35:50 +0000
77+++ src/core/ImportJob.vala 2014-10-15 21:31:58 +0000
78@@ -16,10 +16,11 @@
79 using GExiv2;
80 using GLib;
81
82-public class ImportJob : GLib.Object, Thredeable {
83+public class ImportJob : GLib.Object {
84
85 private static ImportJob self;
86 private static File[] files;
87+ public signal void thread_end();
88
89 private ImportJob(File[] files) {
90 this.files = files;
91@@ -34,66 +35,44 @@
92 }
93
94 public void start_import() {
95- start_job();
96- }
97-
98- //TODO implement filter
99- public void job_func () {
100-
101- var date = new DateTime.now_local ();
102- int import_date = (int)date.to_unix();
103- int duplicated = 0;
104- PictureDAO picdao = PictureDAO.get_instance();
105- var metadata = new Metadata();
106-
107- foreach(File file in files) {
108-
109- debug("Importing %s\n", file.get_parse_name ());
110- var photo = new Photo.from_file(file);
111-
112- if(!photo.is_supported()) {
113- debug("File not supported found");
114- continue;
115- }
116-
117- Picture pic = photo.get_picture();
118- pic.import_date = import_date;
119-
120- if(!picdao.insert(pic)) {
121- debug("No se pudo insertar %s, archivo duplicado?", pic.file_path);
122- duplicated++;
123- }
124-
125- }
126-
127- }
128-
129-}
130-
131-
132-
133-//An interface that makes easier to use simple threads. Just implement job_func() to go.
134-public interface Thredeable : GLib.Object {
135-
136- public signal void thread_end();
137-
138- public bool start_job() {
139- try {
140- Thread<void*> thread_a = new Thread<void*>.try ("thread_a", this.thread_func);
141- } catch (Error e) {
142- stderr.printf ("%s\n", e.message);
143- return false;
144- }
145- return true;
146- }
147-
148-
149- public void* thread_func () {
150- job_func();
151- thread_end();
152- return null;
153- }
154-
155- public abstract void job_func();
156+ job_func.begin();
157+ }
158+
159+ private async void job_func () {
160+
161+ SourceFunc callback = job_func.callback;
162+
163+ Threads.add (() => {
164+ var date = new DateTime.now_local ();
165+ int import_date = (int)date.to_unix();
166+ int duplicated = 0;
167+ PictureDAO picdao = PictureDAO.get_instance();
168+ var metadata = new Metadata();
169+
170+ foreach(File file in files) {
171+
172+ var photo = new Photo.from_file(file);
173+
174+ if(!photo.is_supported()) {
175+ debug("File %s is not supported, not importing it", file.get_parse_name ());
176+ continue;
177+ }
178+
179+ debug("Importing %s\n", file.get_parse_name ());
180+
181+ Picture pic = photo.get_picture();
182+ pic.import_date = import_date;
183+
184+ if(!picdao.insert(pic)) {
185+ debug("Could not insert file %s, file already exists?", pic.file_path);
186+ duplicated++;
187+ }
188+ }
189+ thread_end();
190+ Idle.add ((owned) callback);
191+ });
192+
193+ yield;
194+ }
195
196 }
197\ No newline at end of file
198
199=== added directory 'src/core/Threads'
200=== added file 'src/core/Threads/Threads.vala'
201--- src/core/Threads/Threads.vala 1970-01-01 00:00:00 +0000
202+++ src/core/Threads/Threads.vala 2014-10-15 21:31:58 +0000
203@@ -0,0 +1,145 @@
204+//REWRITE
205+// -*- Mode: vala; indent-tabs-mode: nil; tab-width: 4 -*-
206+/*-
207+ * Copyright (c) 2012 Noise Developers (http://launchpad.net/noise)
208+ *
209+ * This library is free software; you can redistribute it and/or
210+ * modify it under the terms of the GNU Library General Public
211+ * License as published by the Free Software Foundation; either
212+ * version 2 of the License, or (at your option) any later version.
213+ *
214+ * This library is distributed in the hope that it will be useful,
215+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
216+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
217+ * Library General Public License for more details.
218+ *
219+ * You should have received a copy of the GNU Library General Public
220+ * License along with this library; if not, write to the
221+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
222+ * Boston, MA 02111-1307, USA.
223+ *
224+ * The Noise authors hereby grant permission for non-GPL compatible
225+ * GStreamer plugins to be used and distributed together with GStreamer
226+ * and Noise. This permission is above and beyond the permissions granted
227+ * by the GPL license by which Noise is covered. If you modify this code
228+ * you may extend this exception to your version of the code, but you are not
229+ * obligated to do so. If you do not wish to do so, delete this exception
230+ * statement from your version.
231+ */
232+
233+/**
234+ * A central place for spawning threads.
235+ *
236+ * The main purpose of this class is to have a central place to track and control all the
237+ * threads created by the application, in a thread-safe environment. Second, but not less
238+ * important, is the ability to re-use existing threads in order to minimize the overhead
239+ * generated by the creation/destruction of new threads under certain conditions.
240+ */
241+public class Threads {
242+
243+ public delegate void TaskFunc ();
244+
245+ /**
246+ * An object that wraps a TaskFunc delegate.
247+ *
248+ * We need this class because delegates with targets cannot be pushed directly into
249+ * a thread pool, nor used as generic type arguments. This doesn't mean the class is
250+ * merely a workaround though. Its existence has the bonus of allowing easy tracking
251+ * of task creation/destruction, and the internal counter exposes the number of tasks
252+ * that have been processed during the entire execution time of the application.
253+ */
254+ private class TaskFuncWrapper : Object {
255+ public unowned TaskFunc func { get; private set; }
256+ public uint id { get; private set; }
257+ private static uint count = 0;
258+
259+ public TaskFuncWrapper (TaskFunc func) {
260+ lock (count)
261+ id = count++;
262+
263+ debug ("Creating task [%u]", id);
264+ this.func = func;
265+ }
266+
267+ ~TaskFuncWrapper () {
268+ debug ("~Destroying task [%u]", id);
269+ }
270+ }
271+
272+ /**
273+ * Maximum number of threads allowed; -1 equals "unlimited".
274+ */
275+ private const int MAX_THREADS = -1;
276+
277+ /**
278+ * Maximum number of threads that will be kept alive while not used.
279+ */
280+ private const int MAX_UNUSED_THREADS = 5;
281+
282+ private ThreadPool<TaskFuncWrapper>? thread_pool;
283+ private static Threads? instance;
284+
285+
286+ private Threads () {
287+ assert (instance == null); // We only want a single instance
288+
289+ lock (thread_pool) {
290+ try {
291+ thread_pool = new ThreadPool<TaskFuncWrapper>.with_owned_data (
292+ task_func,
293+ MAX_THREADS,
294+ MAX_THREADS > 0);
295+ } catch (Error err) {
296+ error ("Couldn't create default thread pool: %s", err.message);
297+ }
298+
299+ ThreadPool.set_max_unused_threads (MAX_UNUSED_THREADS);
300+ }
301+ }
302+
303+ /**
304+ * Adds //task// to be processed on a separate thread. The task function is not
305+ * owned by the method and should be kept alive by the owner until it is actually
306+ * called from a different thread.
307+ */
308+ public static bool add (TaskFunc task) {
309+ lock (instance) {
310+ if (instance == null)
311+ instance = new Threads ();
312+ }
313+
314+ try {
315+ instance.push_task (task);
316+ } catch (Error err) {
317+ critical ("Could not add task: %s", err.message);
318+ return false;
319+ }
320+
321+ return true;
322+ }
323+
324+ /**
325+ * Pushes //task// into the thread pool.
326+ * @see Threads.add
327+ */
328+ private void push_task (TaskFunc task) throws Error {
329+ lock (thread_pool) {
330+ var wrapper = new TaskFuncWrapper (task);
331+ thread_pool.add ((owned) wrapper);
332+ }
333+ }
334+
335+ /**
336+ * Called by the thread pool right after preparing a thread. The method is
337+ * responsible for executing the actual task function.
338+ */
339+ private void task_func (owned TaskFuncWrapper wrapper) {
340+ var id = wrapper.id;
341+ debug ("-- Dispatching task [%u]", id);
342+ if (wrapper.func != null) {
343+ debug ("-- starting [%u]", id);
344+ wrapper.func ();
345+ debug ("-- finished [%u]", id);
346+ }
347+ }
348+}
349\ No newline at end of file
350
351=== modified file 'src/pages/AlbumPage.vala'
352--- src/pages/AlbumPage.vala 2014-10-13 19:02:59 +0000
353+++ src/pages/AlbumPage.vala 2014-10-15 21:31:58 +0000
354@@ -71,7 +71,7 @@
355 collection = picdao.get_for_album(album);
356
357 foreach(Picture pic in collection) {
358- var thumb = new PicThumb.from_picture (this, pic, IconSize.NORMAL);
359+ var thumb = new PicThumb.from_picture (this, pic, IconSize.HUGE);
360 thumb.icon_double_clicked_event.connect(() => {
361 ViewerPage page = (container.get_page(PageType.VIEWER_PAGE) as ViewerPage);
362 page.set_collection(collection, collection.index_of(pic));
363
364=== modified file 'src/pages/CollectionPage.vala'
365--- src/pages/CollectionPage.vala 2014-10-13 19:02:59 +0000
366+++ src/pages/CollectionPage.vala 2014-10-15 21:31:58 +0000
367@@ -381,7 +381,6 @@
368 </item>
369 </menu>
370 </interface>""";
371-
372 }
373
374
375
376=== modified file 'src/pages/LastImportedPage.vala'
377--- src/pages/LastImportedPage.vala 2014-09-29 15:16:48 +0000
378+++ src/pages/LastImportedPage.vala 2014-10-15 21:31:58 +0000
379@@ -27,15 +27,28 @@
380 sorter.set_sort_priorities(PictureSorter.SortMode.ALPHABETICALLY,
381 PictureSorter.SortMode.BY_RATING,
382 PictureSorter.SortMode.BY_IMPORT_DATE);
383- update_last_import();
384 build_sort_bar ();
385+ load_thumbs ();
386 }
387
388- private void update_last_import() {
389-
390+ public void update_last_import () {
391+ print("removing pics");
392 remove_all_items();
393-
394- var picdao = PictureDAO.get_instance();
395+ print("loading thumbs");
396+ load_thumbs();
397+
398+ if(collection.size > 0) {
399+ search_button.set_sensitive(true);
400+ select_btn.set_sensitive(true);
401+ } else {
402+ search_button.set_sensitive(false);
403+ select_btn.set_sensitive(false);
404+ }
405+
406+ }
407+
408+ private void load_thumbs () {
409+ PictureDAO picdao = PictureDAO.get_instance();
410 collection = picdao.get_last_import();
411
412 foreach(Picture pic in collection) {
413@@ -51,7 +64,7 @@
414 debug("Resize item at %f:", val);
415 });
416 }
417-
418+ return;
419 }
420
421 public void build_sort_bar () {
422
423=== modified file 'src/pages/WelcomePage.vala'
424--- src/pages/WelcomePage.vala 2014-09-06 17:19:54 +0000
425+++ src/pages/WelcomePage.vala 2014-10-15 21:31:58 +0000
426@@ -42,9 +42,9 @@
427 import.start_import();
428 import.thread_end.connect(()=>{
429 print("Import finished\n");
430+ var import_page = (LastImportedPage)container.get_page(PageType.LAST_IMPORTED_PAGE);
431+ import_page.update_last_import();
432 container.switch_to_page(PageType.LAST_IMPORTED_PAGE);
433- while(Gtk.events_pending())
434- Gtk.main_iteration();
435 stack.set_visible_child(drop_area);
436 });
437 });

Subscribers

People subscribed via source and target branches

to all changes: