whitespace
[conkeror.git] / modules / session.js
blobd19a603a37c096d0352d9990c54980e373cdf8ef
1 /**
2  * (C) Copyright 2009 Nicholas A. Zigarovich
3  *
4  * Use, modification, and distribution are subject to the terms specified in the
5  * COPYING file.
6 **/
8 /* TODO
9  *
10  * Features:
11  *
12  * - A session-load-window-replace command which is like
13  *   window-current-replace, but which also recycles existing windows.
14  * - A session-load-window-current-flatten command which loads all buffers in a
15  *   multi-window section in the current window.
16  * - Session files should be more human readable.
17  * - Ability to store arbitrary session data, for example, a buffer's history.
18  * - The previous two features depend on a parser/generator for retroj's
19  *   structured plaintext format.
20  *
21  * Bugs, critical:
22  *
23  * - This module does not work correctly with daemon mode.
24  *
25  * Bugs, deferred:
26  *
27  * - Inhibit loading of the homepage in windows' initial buffers when auto-
28  *   loading a session.
29  * - Auto-save the session when the last conkeror window is closed by a
30  *   window manager event. Currently no session is saved.
31  * - Consider how and which errors should be handled. Too often we silently
32  *   fail and return without telling the user why we are doing so.
33  */
35 in_module(null);
38     //// Manual sessions. ////
41     let _session_dir_default = Cc["@mozilla.org/file/directory_service;1"]
42         .getService(Ci.nsIProperties).get("ProfD", Ci.nsIFile);
43     _session_dir_default.append("sessions");
44     if (! _session_dir_default.exists())
45         _session_dir_default.create(Ci.nsIFile.DIRECTORY_TYPE, 0755);
47     define_variable("session_dir", _session_dir_default,
48         "Default directory for save/load interactive commands.");
50     let _json = Cc["@mozilla.org/dom/json;1"].createInstance(Ci.nsIJSON);
52     /**
53      * session_get generates and returns a structure containing session
54      * data for the current group of open windows.
55      */
56     function session_get () {
57         let windows = {};
58         let x = 0;
59         for_each_window(function (w) {
60             let buffers = [];
61             w.buffers.for_each(function (b) {
62                 if (b instanceof content_buffer)
63                     buffers.push(b.display_uri_string);
64             });
65             if (buffers.length == 0)
66                 return;
67             windows[x] = buffers;
68             x++;
69         });
70         return windows;
71     }
73     /**
74      * session_load loads the given session (given as a data structure),
75      * with optional window as the first window to load the session into,
76      * with optional buffer_idx as the buffer index at which to begin
77      * overwriting existing buffers.
78      */
79     function session_load (session, window, buffer_idx) {
80         if (! (session[0] && session[0][0]))
81             throw new Error("Invalid 'session' argument.");
82         let s = 0;
83         if (window) {
84             let bi = buffer_idx != undefined ?
85                 buffer_idx : window.buffers.count;
87             // first kill special buffers slated for recycling.
88             let (b, i = (bi == 0 ? 1 : bi),
89                  safe2kill = bi > 0)
90             {
91                 while ((b = window.buffers.get_buffer(i))) {
92                     if (b instanceof content_buffer) {
93                         safe2kill = true;
94                         ++i;
95                     } else
96                         kill_buffer(b, true);
97                 }
98                 if (bi == 0 &&
99                     (b = window.buffers.get_buffer(0)) &&
100                     !(b instanceof content_buffer))
101                 {
102                     if (! safe2kill)
103                         create_buffer(window,
104                                       buffer_creator(content_buffer),
105                                       OPEN_NEW_BUFFER_BACKGROUND);
106                     kill_buffer(b, true);
107                 }
108             }
110             // it is now safe to recycle the remaining buffers.
111             for (let i = 0; session[s][i] != undefined; ++i, ++bi) {
112                 let b = window.buffers.get_buffer(bi);
113                 if (b) {
114                     try {
115                         let history = b.web_navigation.sessionHistory;
116                         history.PurgeHistory(history.count);
117                     } catch (e) {}
118                     b.load(session[s][i]);
119                 } else {
120                     let c = buffer_creator(content_buffer, $load = session[s][i]);
121                     create_buffer(window, c, OPEN_NEW_BUFFER_BACKGROUND);
122                 }
123             }
124             for (let b = window.buffers.get_buffer(bi); b;
125                  b = window.buffers.get_buffer(bi))
126             {
127                 kill_buffer(b, true);
128             }
129             ++s;
130         }
132         function make_init_hook (session) {
133             function init_hook (window) {
134                 for (let i = 1; session[i] != undefined; ++i) {
135                     let c = buffer_creator(content_buffer, $load = session[i]);
136                     create_buffer(window, c, OPEN_NEW_BUFFER_BACKGROUND);
137                 }
138             }
139             return init_hook;
140         }
142         for (; session[s] != undefined; ++s) {
143             let w = make_window(buffer_creator(content_buffer,
144                                                $load = session[s][0]));
145             add_hook.call(w, "window_initialize_late_hook",
146                           make_init_hook(session[s]));
147         }
148     }
150     /**
151      * session_load_window_new loads the given session into new windows.
152      */
153     function session_load_window_new (session) {
154         session_load(session);
155     }
157     /**
158      * session_load_window_current loads the given session, with the
159      * session's first window being appended to window.  No existing
160      * buffers will be overwritten.
161      */
162     function session_load_window_current (session, window) {
163         let w = window ? window : get_recent_conkeror_window();
164         session_load(session, w);
165     }
167     /**
168      * session_load_window_current loads the given session, with the
169      * session's first window replacing the given window.  All buffers in
170      * the given window will be overwritten.
171      */
172     function session_load_window_current_replace (session, window) {
173         let w = window ? window : get_recent_conkeror_window();
174         session_load(session, w, 0);
175     }
177     /**
178      * session_write writes the given session to the file given by path.
179      */
180     function session_write (path, session) {
181         if (! (path instanceof Ci.nsIFile))
182             path = make_file(path);
183         if (! session)
184             session = session_get();
185         write_text_file(path, _json.encode(session));
186     }
188     /**
189      * session_read reads session data from the given file path,
190      * and returns a decoded session structure.
191      */
192     function session_read (path) {
193         if (! (path instanceof Ci.nsIFile))
194             path = make_file(path);
195         return _json.decode(read_text_file(path));
196     }
198     /**
199      * session_remove deletes the given session file.
200      */
201     function session_remove (path) {
202         if (! (path instanceof Ci.nsIFile))
203             path = make_file(path);
204         path.remove(false);
205     }
207     let _session_prompt_file = function (I) {
208         yield co_return(
209             yield I.minibuffer.read_file_path(
210                 $prompt = "Session file:",
211                 $initial_value = session_dir.path,
212                 $history = "save"
213             )
214         );
215     };
217     let _session_file_not_found = function (I, file) {
218         let mb = I ? I.minibuffer : get_recent_conkeror_window().minibuffer;
219         let msg = "Session file not found: " + file.path;
220         mb.message(msg);
221         dumpln(msg);
222     }
224     interactive("session-save",
225         "Save the current session.",
226         function (I) {
227             session_write(make_file(yield _session_prompt_file(I)),
228                           session_get());
229         });
231     interactive("session-load-window-new",
232         "Load a session in a new window.", 
233         function (I) {
234             let file = make_file(yield _session_prompt_file(I));
235             if (! file.exists())
236                 _session_file_not_found(I, file);
237             else
238                 session_load_window_new(session_read(file));
239         });
241     interactive("session-load-window-current",
242         "Load a session in new buffers in the current window.",
243         function (I) {
244             let file = make_file(yield _session_prompt_file(I));
245             if (! file.exists())
246                 _session_file_not_found(I, file);
247             else
248                 session_load_window_current(session_read(file), I.window);
249         });
251     interactive("session-load-window-current-replace", 
252         "Replace all buffers in the current window with buffers "+
253         "in the saved session.",
254         function (I) {
255             let file = make_file(yield _session_prompt_file(I));
256             if (! file.exists())
257                 _session_file_not_found(I, file);
258             else
259                 session_load_window_current_replace(session_read(file),
260                                                     I.window, 0);
261         });
263     interactive("session-remove",
264         "Remove a session file.",
265         function (I) {
266             let file = make_file(yield _session_prompt_file(I));
267             if (! file.exists())
268                 _session_file_not_found(I, file);
269             else
270                 session_remove(file);
271         });
274     //// Auto-save sessions. ////
277     define_variable("session_auto_save_file", "auto-save",
278         "Default filename for the auto-save session.");
280     define_variable("session_auto_save_auto_load", false,
281         'Whether to load the auto-saved session when the browser is started. '+
282         'May be true, false, or "prompt".');
284     function session_auto_save_load_window_new () {
285         session_load_window_new(_session_auto_save_cached);
286     }
288     function session_auto_save_load_window_current (window) {
289         session_load_window_current(_session_auto_save_cached, window);
290     }
292     function session_auto_save_load_window_current_replace (window) {
293         session_load_window_current_replace(_session_auto_save_cached, window);
294     }
295     
296     define_variable("session_auto_save_auto_load_fn",
297         null,
298         "Function to be called to load the auto-saved session at start-up " +
299         "when URLs are given on the command-line. May be " +
300         "session_auto_save_load_window_new, " + 
301         "session_auto_save_load_window_current, or null. If null, the" +
302         "session will not be auto-loaded when URLs are given.");
304     // Supported values:
305     //   undefined - we have not tried to cache the auto-save.
306     //   null      - we have tried to cache the auto-save, but it didn't exist.
307     //   object    - the cached session object for the auto-save.
308     let _session_auto_save_cached = undefined;
310     let _session_auto_save_file_get = function () {
311         if (session_auto_save_file instanceof Ci.nsIFile)
312             return session_auto_save_file;
313         let f = session_dir.clone();
314         f.append(session_auto_save_file);
315         return f;
316     };
318     function session_auto_save_save () {
319         let f = _session_auto_save_file_get();
320         let s = session_get();
321         if (s[0])
322             session_write(f, s);
323         else if (f.exists())
324             f.remove(false);
325     }
327     function session_auto_save_remove () {
328         let f = _session_auto_save_file_get();
329         if (f.exists())
330             f.remove(false);
331     }
333     let _session_auto_save_auto_load = function (user_gave_urls) {
334         if (! session_auto_save_auto_load)
335             return;
336         if (! _session_auto_save_cached) {
337             _session_file_not_found(null, _session_auto_save_file_get());
338             return;
339         }
340         let do_load = false;
341         let window = get_recent_conkeror_window();
342         if (session_auto_save_auto_load == true)
343             do_load = true;
344         else if (session_auto_save_auto_load == "prompt" && !user_gave_urls) {
345             do_load = (yield window.minibuffer.read_single_character_option(
346                 $prompt = "Load auto-saved session? (y/n)",
347                 $options = ["y", "n"]
348             )) == "y";
349         } else
350             throw new Error("Invalid value for session_auto_save_auto_load: " +
351                             session_auto_save_auto_load);
352         if (! do_load)
353             return;
354         if (user_gave_urls) {
355             if (session_auto_save_auto_load_fn)
356                 session_auto_save_auto_load_fn(window);
357         } else
358             session_auto_save_load_window_current_replace(window);
359     };
361     interactive("session-auto-save-load-window-new",
362         "Load the auto-save session in a new window.",
363         function (I) { 
364             if (_session_auto_save_cached == null)
365                 _session_file_not_found(I, _session_auto_save_file_get());
366             else
367                 session_auto_save_load_window_new();
368         });
370     interactive("session-auto-save-load-window-current",
371         "Load the auto-save session in new buffers in the current window.",
372         function (I) {
373             if (_session_auto_save_cached == null)
374                 _session_file_not_found(I, _session_auto_save_file_get());
375             else
376                 session_auto_save_load_window_current(I.window);
377         });
379     interactive("session-auto-save-load-window-current-replace",
380         "Replace all buffers in the current window with buffers in the "+
381         "auto-saved session.",
382         function (I) {
383             if (_session_auto_save_cached == null)
384                 _session_file_not_found(I, _session_auto_save_file_get());
385             else
386                 session_auto_save_load_window_current_replace(I.window);
387         });
389     interactive("session-auto-save-remove",
390                 "Remove the auto-save session",
391                 session_auto_save_remove);
394     //// auto-save-session-mode ////
397     let _session_auto_save_mode_bootstrap = function (b) {
398         remove_hook("window_initialize_late_hook", _session_auto_save_mode_bootstrap);
399         add_hook("create_buffer_hook", session_auto_save_save);
400         add_hook("kill_buffer_hook", session_auto_save_save);
401         add_hook("content_buffer_location_change_hook", session_auto_save_save);
402         let user_gave_urls = false;
403         for (let i = 0; i < command_line.length; ++i) {
404             if (command_line[i][0] != '-') {
405                 user_gave_urls = true;
406                 break;
407             }
408         }
409         co_call(_session_auto_save_auto_load(user_gave_urls));
410     };
412     let _session_auto_save_mode_enable = function () {
413         if (_session_auto_save_cached == undefined) {
414             let f = _session_auto_save_file_get();
415             _session_auto_save_cached = f.exists() ? session_read(f) : null;
416         }
417         if (conkeror_started) {
418             add_hook("create_buffer_hook", session_auto_save_save);
419             add_hook("kill_buffer_hook", session_auto_save_save);
420             add_hook("content_buffer_location_change_hook", session_auto_save_save);
421         } else
422             add_hook("window_initialize_late_hook", _session_auto_save_mode_bootstrap);
423     };
425     let _session_auto_save_mode_disable = function () {
426         remove_hook("create_buffer_hook", session_auto_save_save);
427         remove_hook("kill_buffer_hook", session_auto_save_save);
428         remove_hook("content_buffer_location_change_hook", session_auto_save_save);
429         // Just in case.
430         remove_hook("window_initialize_late_hook", _session_auto_save_mode_bootstrap);
431     };
433     define_global_mode("session_auto_save_mode",
434                        _session_auto_save_mode_enable,
435                        _session_auto_save_mode_disable);
437     session_auto_save_mode(true);
440 provide("session");