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