1 /*
2 * Copyright (C) 2006 2009, Magnus Hjorth
3 *
4 * This file is part of mhWaveEdit.
5 *
6 * mhWaveEdit is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * mhWaveEdit is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with mhWaveEdit; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19 */
20
21
22 #include <config.h>
23
24 #include <sys/types.h>
25 #include <dirent.h>
26 #include <sys/stat.h>
27 #include <signal.h>
28 #include <errno.h>
29 #include <unistd.h>
30
31 #include "session.h"
32 #include "inifile.h"
33 #include "um.h"
34 #include "tempfile.h"
35 #include "gettext.h"
36 #include "document.h"
37 #include "mainloop.h"
38
39 /* Session states */
40 #define SESSION_RUNNING 0 /* Currently running in another process */
41 #define SESSION_SUSPENDED 1 /* Suspended by user */
42 #define SESSION_CRASHED 2 /* Crash - session file there but process no
43 * longer existing */
44 #define SESSION_LEFTOVER 3 /* Temporary files found, but no session file */
45 #define SESSION_OLD 4 /* Temporary files from version <1.4.8 found */
46 #define SESSION_UNKNOWN 5 /* Session either still running or crashed */
47
48 struct session {
49 int id;
50 int pid;
51 int state;
52 off_t size;
53 time_t date;
54 gchar *logfile;
55 GList *datafiles;
56 };
57
58 static int current_id = 0;
59 static gchar *current_filename;
60 static gchar *session_dir;
61 static EFILE *current_file;
62 static GList *session_list = NULL;
63 /* static EFILE *current_session = NULL; */
64
session_compare_func(gconstpointer a,gconstpointer b)65 static gint session_compare_func(gconstpointer a, gconstpointer b)
66 {
67 struct session *as = (struct session *)a, *bs = (struct session *)b;
68 return (as->date > bs->date)?1:0;
69 }
70
session_init(int * argc,char ** argv)71 void session_init(int *argc, char **argv)
72 {
73 int highest_id = 0;
74 gchar *c,*p,*q;
75 DIR *d;
76 struct dirent *de;
77 int i,j,k,l,m,n;
78 char r;
79 struct session *s;
80 struct stat st;
81 GList *list,*list2;
82 gboolean b;
83 /* Check for session files on the system */
84 session_dir = g_strjoin(NULL,get_home_directory(),"/.mhwaveedit",NULL);
85 d = opendir(session_dir);
86 if (d == NULL) {
87 user_perror(_("Error opening session directory"));
88 return;
89 }
90 while (1) {
91 de = readdir(d);
92 if (de == NULL) break;
93 i = sscanf(de->d_name,"mhwaveedit-session-%d-%d-%c",&j,&k,&r);
94 if (i < 3) continue;
95 /* Stat the session file once to check mod. date and size */
96 p = g_strdup_printf("%s/%s",session_dir,de->d_name);
97
98 i = stat(p,&st);
99 if (i < 0) {
100 console_perror(p);
101 g_free(p);
102 continue;
103 } else if (!S_ISREG(st.st_mode)) {
104 q = g_strdup_printf(_("%s: Wrong file type"),p);
105 console_message(q);
106 g_free(q);
107 g_free(p);
108 continue;
109 }
110
111 s = g_malloc(sizeof(*s));
112 s->id = k;
113 if (k > highest_id) highest_id = k;
114 s->pid = j;
115 s->datafiles = NULL;
116 s->size = st.st_size;
117 s->date = st.st_mtime;
118 s->logfile = p;
119
120
121 /* Choose state */
122 if (r == 's') {
123 s->state = SESSION_SUSPENDED;
124 } else {
125 /* Is there a process with the PID running? */
126 i = kill(s->pid, 0);
127 if (i != 0 && errno == ESRCH) {
128 /* Must be a crash */
129 s->state = SESSION_CRASHED;
130 } else {
131 /* Could be running */
132 /* Could check date here to see if reasonable */
133 s->state = SESSION_UNKNOWN;
134 }
135 }
136
137 session_list = g_list_append(session_list, s);
138 }
139 closedir(d);
140
141 /* Search for leftover tempfiles */
142 for (i=0; ; i++) {
143 c = get_temp_directory(i);
144 if (c == NULL) break;
145 d = opendir(c);
146 if (d == NULL) {
147 console_perror(c);
148 continue;
149 }
150 while (1) {
151 de = readdir(d);
152 if (de == NULL) break;
153 j = sscanf(de->d_name,"mhwaveedit-temp-%d-%d-%d",&k,&l,&m);
154 if (j < 2)
155 continue;
156 /* Try to stat the file */
157 p = g_strdup_printf("%s/%s",c,de->d_name);
158 n = stat(p,&st);
159 if (n < 0) {
160 console_perror(p);
161 g_free(p);
162 continue;
163 } else if (!S_ISREG(st.st_mode)) {
164 q = g_strdup_printf(_("%s: Wrong file type"),p);
165 console_message(q);
166 g_free(p);
167 g_free(q);
168 continue;
169 }
170 /* Try to add to known session */
171 for (list=session_list; list!=NULL; list=list->next) {
172 s = (struct session *)list->data;
173 if ((j>2 && m==s->id) ||
174 (j<3 && s->pid == k && s->state==SESSION_OLD))
175 break;
176 }
177 /* Create new session */
178 if (list == NULL) {
179 s = g_malloc0(sizeof(*s));
180 s->pid = k;
181 if (j>2) {
182 s->state = SESSION_LEFTOVER;
183 s->id = m;
184 } else {
185 s->state = SESSION_OLD;
186 s->id = ++highest_id;
187 }
188 session_list = g_list_append(session_list, s);
189 }
190 /* Add file to data file list */
191 s->datafiles = g_list_append(s->datafiles, p);
192 /* Update session info */
193 s->size += st.st_size;
194 if (s->date < st.st_mtime) s->date = st.st_mtime;
195 }
196 closedir(d);
197 }
198
199 current_id = highest_id+1;
200
201 /* Remove empty sessions unless the session was suspended by user */
202 for (list=session_list; list!=NULL; list=list2) {
203 s = (struct session *) list->data;
204 list2 = list->next;
205 if (s->state != SESSION_SUSPENDED && s->datafiles == NULL) {
206 if (s->logfile != NULL) {
207 xunlink(s->logfile);
208 g_free(s->logfile);
209 }
210 session_list = g_list_remove(session_list, s);
211 g_free(s);
212 if (session_list == NULL) break;
213 }
214 }
215
216 /* Sort list by date */
217 session_list = g_list_sort(session_list, session_compare_func);
218
219 /* Debug */
220 /*
221 puts("Sessions:");
222 for (list=session_list; list!=NULL; list=list->next) {
223 s = (struct session *)list->data;
224 printf("#%d: state=%d, size=%d\n",s->id,s->state,(int)s->size);
225 for (list2=s->datafiles; list2!=NULL; list2=list2->next) {
226 printf(" %s\n",(char *)list2->data);
227 }
228 } */
229
230 current_filename = g_strdup_printf("%s/mhwaveedit-session-%d-%d-r",
231 session_dir,(int)getpid(),current_id);
232
233 b = report_write_errors;
234 report_write_errors = FALSE;
235
236 current_file = e_fopen(current_filename,EFILE_WRITE);
237 if (current_file == NULL)
238 console_perror(_("Could not create session file"));
239
240 report_write_errors = b;
241
242 }
243
session_get_id(void)244 gint session_get_id(void)
245 {
246 return current_id;
247 }
248
session_resume(struct session * s)249 static void session_resume(struct session *s)
250 {
251 GList *l;
252 gchar *fn;
253 Mainwindow *w;
254
255 g_assert(s->state == SESSION_CRASHED || s->state == SESSION_LEFTOVER ||
256 s->state == SESSION_OLD);
257 for (l=s->datafiles; l!=NULL; l=l->next) {
258 fn = (gchar *) l->data;
259 w = MAINWINDOW(mainwindow_new_with_file(fn,FALSE));
260 gtk_widget_show(GTK_WIDGET(w));
261 if (w->doc == NULL) continue;
262 /* This will rename the file to a regular tempfile name for this
263 * session and change the reference from a regular to a tempfile.
264 * If the file was converted into a new tempfile by the loader so
265 * that the original file is no longer being used, it will just be
266 * removed */
267 datasource_backup_unlink(fn);
268 /* Sets the title name to "untitled X" and makes the document appear
269 * "changed" so the "save?" dialog pops up when closing etc. */
270 document_forget_filename(w->doc);
271 }
272 /* Remove the session. */
273 g_list_foreach(s->datafiles, (GFunc)g_free, NULL);
274 g_list_free(s->datafiles);
275 g_free(s->logfile);
276 session_list = g_list_remove(session_list, s);
277
278 /* Popup notification */
279 if (!inifile_get_gboolean("crashMsgShown",FALSE)) {
280 user_message(_("The files that belonged to the crashed session have been "
281 "recovered. Any files that are not saved will be "
282 "removed permanently.\n\n(This notice is only shown once)"),
283 UM_OK);
284 inifile_set_gboolean("crashMsgShown",TRUE);
285 }
286 }
287
session_delete(struct session * s)288 static gboolean session_delete(struct session *s)
289 {
290 GList *l;
291 gchar *fn;
292 gboolean b;
293
294 for (l=s->datafiles; l!=NULL; l=l->next) {
295 fn = (gchar *)l->data;
296 b = xunlink(fn);
297 if (b) return TRUE;
298 }
299 /* Remove the session. */
300 g_list_foreach(s->datafiles, (GFunc)g_free, NULL);
301 g_list_free(s->datafiles);
302 g_free(s->logfile);
303 session_list = g_list_remove(session_list, s);
304
305 return FALSE;
306 }
307
session_quit(void)308 void session_quit(void)
309 {
310 if (current_file != NULL)
311 e_fclose_remove(current_file);
312
313 }
314
315 struct session_dialog_data {
316 GtkList *listwid;
317 struct session **listmap;
318 GtkWidget *resume_button,*delete_button;
319 gboolean destroy_flag, resume_click_flag;
320 gint resume_index;
321 };
322
session_dialog_destroy(GtkObject * obj,gpointer user_data)323 static void session_dialog_destroy(GtkObject *obj, gpointer user_data)
324 {
325 struct session_dialog_data *ddata =
326 (struct session_dialog_data *)user_data;
327 ddata->destroy_flag = TRUE;
328 }
329
session_dialog_select_child(GtkList * list,GtkWidget * widget,gpointer user_data)330 static void session_dialog_select_child(GtkList *list, GtkWidget *widget,
331 gpointer user_data)
332 {
333 struct session_dialog_data *ddata =
334 (struct session_dialog_data *)user_data;
335 ddata->resume_index = gtk_list_child_position(list,widget);
336 gtk_widget_set_sensitive(ddata->resume_button,TRUE);
337 gtk_widget_set_sensitive(ddata->delete_button,TRUE);
338 }
339
340
session_dialog_exit(GtkWidget * widget,gpointer user_data)341 static void session_dialog_exit(GtkWidget *widget, gpointer user_data)
342 {
343 quitflag = TRUE;
344 }
345
session_dialog_resume_click(GtkWidget * widget,gpointer user_data)346 static void session_dialog_resume_click(GtkWidget *widget, gpointer user_data)
347 {
348 struct session_dialog_data *ddata =
349 (struct session_dialog_data *)user_data;
350 ddata->resume_click_flag = TRUE;
351 }
352
session_dialog_delete_click(GtkWidget * widget,gpointer user_data)353 static void session_dialog_delete_click(GtkWidget *widget, gpointer user_data)
354 {
355 struct session_dialog_data *ddata =
356 (struct session_dialog_data *)user_data;
357 int i;
358 gboolean b;
359 i = user_message("Delete session?",UM_OKCANCEL);
360 if (i != MR_OK) return;
361 b = session_delete(ddata->listmap[ddata->resume_index]);
362 if (b) return;
363 gtk_list_clear_items(ddata->listwid,ddata->resume_index,
364 ddata->resume_index+1);
365 for (i=ddata->resume_index; ddata->listmap[i]!=NULL; i++)
366 ddata->listmap[i] = ddata->listmap[i+1];
367 gtk_widget_set_sensitive(ddata->resume_button,FALSE);
368 gtk_widget_set_sensitive(ddata->delete_button,FALSE);
369 }
370
session_dialog(void)371 gboolean session_dialog(void)
372 {
373 struct session_dialog_data ddata;
374 GtkWidget *a,*b,*c,*d;
375 GList *l = NULL,*m;
376 int i;
377 struct session *s;
378 gchar *ch,*p;
379 /* Running and unknown are never shown. Old is also called crash */
380 gchar *state_names[] = { NULL, _("Suspended"), _("Crash"),
381 _("Left files"), _("Crash"), NULL };
382
383 if (session_list == NULL) return FALSE;
384
385 ddata.listmap = g_malloc((g_list_length(session_list)+1) *
386 sizeof(struct session *));
387 for (m=session_list,i=0; m!=NULL; m=m->next) {
388 s = (struct session *) m->data;
389 if (s->state == SESSION_RUNNING || s->state == SESSION_UNKNOWN)
390 continue;
391 ch = g_strdup_printf(_("%s on %s(%d files, %ld bytes)"),
392 state_names[s->state],ctime(&(s->date)),
393 g_list_length(s->datafiles),(long)s->size);
394 /* Replace the newline returned by ctime */
395 p = strchr(ch,'\n');
396 if (p) *p=' ';
397 l = g_list_append(l,gtk_list_item_new_with_label(ch));
398 g_free(ch);
399 ddata.listmap[i++] = s;
400 }
401 ddata.listmap[i] = NULL;
402
403 if (l == NULL) {
404 g_free(ddata.listmap);
405 return FALSE;
406 }
407
408 a = gtk_window_new(GTK_WINDOW_DIALOG);
409 gtk_window_set_title(GTK_WINDOW(a),_("Sessions"));
410 gtk_window_set_modal(GTK_WINDOW(a),TRUE);
411 gtk_window_set_default_size(GTK_WINDOW(a),400,200);
412 gtk_container_set_border_width(GTK_CONTAINER(a),5);
413 gtk_signal_connect(GTK_OBJECT(a),"destroy",
414 GTK_SIGNAL_FUNC(session_dialog_destroy),&ddata);
415
416 b = gtk_vbox_new(FALSE,5);
417 gtk_container_add(GTK_CONTAINER(a),b);
418
419 c = gtk_label_new(_("Earlier sessions were found. Choose one to resume or"
420 " start a new session."));
421 gtk_label_set_line_wrap(GTK_LABEL(c),TRUE);
422 gtk_box_pack_start(GTK_BOX(b),c,FALSE,FALSE,0);
423
424 c = gtk_scrolled_window_new(NULL,NULL);
425 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(c),GTK_POLICY_NEVER,
426 GTK_POLICY_ALWAYS);
427 gtk_box_pack_start(GTK_BOX(b),c,TRUE,TRUE,0);
428
429 d = gtk_list_new();
430 ddata.listwid = GTK_LIST(d);
431 gtk_list_insert_items(GTK_LIST(d),l,0);
432 gtk_signal_connect(GTK_OBJECT(d),"select_child",
433 GTK_SIGNAL_FUNC(session_dialog_select_child),&ddata);
434 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(c),d);
435
436 c = gtk_hbutton_box_new();
437 gtk_box_pack_end(GTK_BOX(b),c,FALSE,TRUE,0);
438
439 d = gtk_button_new_with_label(_("Resume selected"));
440 ddata.resume_button = d;
441 gtk_widget_set_sensitive(d,FALSE);
442 gtk_signal_connect(GTK_OBJECT(d),"clicked",
443 GTK_SIGNAL_FUNC(session_dialog_resume_click),&ddata);
444 gtk_signal_connect_object(GTK_OBJECT(d),"clicked",
445 GTK_SIGNAL_FUNC(gtk_widget_destroy),
446 (GtkObject *)a);
447 gtk_container_add(GTK_CONTAINER(c),d);
448
449 d = gtk_button_new_with_label(_("Delete selected"));
450 ddata.delete_button = d;
451 gtk_widget_set_sensitive(d,FALSE);
452 gtk_signal_connect(GTK_OBJECT(d),"clicked",
453 GTK_SIGNAL_FUNC(session_dialog_delete_click),&ddata);
454 gtk_container_add(GTK_CONTAINER(c),d);
455
456 d = gtk_button_new_with_label(_("Start new session"));
457 gtk_signal_connect_object(GTK_OBJECT(d),"clicked",
458 GTK_SIGNAL_FUNC(gtk_widget_destroy),
459 (GtkObject *)a);
460 gtk_container_add(GTK_CONTAINER(c),d);
461
462 d = gtk_button_new_with_label(_("Exit"));
463 gtk_signal_connect(GTK_OBJECT(d),"clicked",
464 GTK_SIGNAL_FUNC(session_dialog_exit),&ddata);
465 gtk_signal_connect_object(GTK_OBJECT(d),"clicked",
466 GTK_SIGNAL_FUNC(gtk_widget_destroy),
467 (GtkObject *)a);
468 gtk_container_add(GTK_CONTAINER(c),d);
469
470 c = gtk_hseparator_new();
471 gtk_box_pack_end(GTK_BOX(b),c,FALSE,TRUE,0);
472
473 ddata.destroy_flag = ddata.resume_click_flag = FALSE;
474 gtk_widget_show_all(a);
475 while (!ddata.destroy_flag) mainloop();
476
477 if (ddata.resume_click_flag)
478 session_resume(ddata.listmap[ddata.resume_index]);
479
480 g_free(ddata.listmap);
481
482 return ddata.resume_click_flag;
483 }
484