session.js: some comments
[conkeror/arlinius.git] / modules / session.js
blob8074e12544a4e2232fe79335d14376ef14332040
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  */
36     //// Manual sessions. ////
39     let _session_dir_default = Cc["@mozilla.org/file/directory_service;1"]
40         .getService(Ci.nsIProperties).get("ProfD", Ci.nsIFile);
41     _session_dir_default.append("sessions");
42     if (! _session_dir_default.exists())
43         _session_dir_default.create(Ci.nsIFile.DIRECTORY_TYPE, 0755);
45     define_variable("session_dir", _session_dir_default,
46         "Default directory for save/load interactive commands.");
48     let _json = Cc["@mozilla.org/dom/json;1"].createInstance(Ci.nsIJSON);
50     /**
51      * session_get generates and returns a structure containing session
52      * data for the current group of open windows.
53      */
54     function session_get () {
55         let windows = {};
56         let x = 0;
57         for_each_window(function (w) {
58             let buffers = {};
59             let y = 0;
60             w.buffers.for_each(function (b) {
61                 if (! b.browser || ! (b instanceof content_buffer))
62                     return;
63                 buffers[y] = b.browser.contentDocument.location.href;
64                 y++;
65             });
66             if (y == 0)
67                 return;
68             windows[x] = buffers;
69             x++;
70         });
71         return windows;
72     }
74     /**
75      * session_load loads the given session (given as a data structure),
76      * with optional window as the first window to load the session into,
77      * with optional buffer_idx as the buffer index at which to begin
78      * overwriting existing buffers.
79      */
80     function session_load (session, window, buffer_idx) {
81         if (! (session[0] && session[0][0]))
82             throw new Error("Invalid 'session' argument.");
83         let s = 0;
84         if (window) {
85             let bi = buffer_idx != undefined ?
86                 buffer_idx : window.buffers.count;
87             for (let i = 0; session[s][i] != undefined; ++i, ++bi) {
88                 let b = window.buffers.get_buffer(bi);
89                 if (! (b instanceof content_buffer))
90                     continue;
91                 if (b)
92                     b.load(session[s][i]);
93                 else {
94                     let c = buffer_creator(content_buffer, $load = session[s][i]);
95                     create_buffer(window, c, OPEN_NEW_BUFFER_BACKGROUND);
96                 }
97             }
98             for (let b = window.buffers.get_buffer(bi); b;
99                  b = window.buffers.get_buffer(bi))
100             {
101                 if (b instanceof content_buffer)
102                     kill_buffer(b, true);
103                 else
104                     bi++;
105             }
106             ++s;
107         }
109         function make_init_hook (session) {
110             function init_hook (window) {
111                 for (let i = 1; session[i] != undefined; ++i) {
112                     let c = buffer_creator(content_buffer, $load = session[i]);
113                     create_buffer(window, c, OPEN_NEW_BUFFER_BACKGROUND);
114                 }
115             }
116             return init_hook;
117         }
119         for (; session[s] != undefined; ++s) {
120             let w = make_window(buffer_creator(content_buffer,
121                                                $load = session[s][0]));
122             add_hook.call(w, "window_initialize_late_hook",
123                           make_init_hook(session[s]));
124         }
125     }
127     /**
128      * session_load_window_new loads the given session into new windows.
129      */
130     function session_load_window_new (session) {
131         session_load(session);
132     }
134     /**
135      * session_load_window_current loads the given session, with the
136      * session's first window being appended to window.  No existing
137      * buffers will be overwritten.
138      */
139     function session_load_window_current (session, window) {
140         let w = window ? window : get_recent_conkeror_window();
141         session_load(session, w);
142     }
144     /**
145      * session_load_window_current loads the given session, with the
146      * session's first window replacing the given window.  All buffers in
147      * the given window will be overwritten.
148      */
149     function session_load_window_current_replace (session, window) {
150         let w = window ? window : get_recent_conkeror_window();
151         session_load(session, w, 0);
152     }
154     /**
155      * session_write writes the given session to the file given by path.
156      */
157     function session_write (path, session) {
158         if (! (path instanceof Ci.nsIFile))
159             path = make_file(path);
160         if (! session)
161             session = session_get();
162         write_text_file(path, _json.encode(session));
163     }
165     /**
166      * session_read reads session data from the given file path,
167      * and returns a decoded session structure.
168      */
169     function session_read (path) {
170         if (! (path instanceof Ci.nsIFile))
171             path = make_file(path);
172         return _json.decode(read_text_file(path));
173     }
175     /**
176      * session_remove deletes the given session file.
177      */
178     function session_remove (path) {
179         if (! (path instanceof Ci.nsIFile))
180             path = make_file(path);
181         path.remove(false);
182     }
184     let _session_prompt_file = function (I) {
185         yield co_return(
186             yield I.minibuffer.read_file_path(
187                 $prompt = "Session file:",
188                 $initial_value = session_dir.path,
189                 $history = "save"
190             )
191         );
192     };
194     let _session_file_not_found = function (I, file) {
195         let mb = I ? I.minibuffer : get_recent_conkeror_window().minibuffer;
196         let msg = "Session file not found: " + file.path;
197         mb.message(msg);
198         dumpln(msg);
199     }
201     interactive("session-save",
202         "Save the current session.",
203         function (I) {
204             session_write(make_file(yield _session_prompt_file(I)),
205                           session_get());
206         });
208     interactive("session-load-window-new",
209         "Load a session in a new window.", 
210         function (I) {
211             let file = make_file(yield _session_prompt_file(I));
212             if (! file.exists())
213                 _session_file_not_found(I, file);
214             else
215                 session_load_window_new(session_read(file));
216         });
218     interactive("session-load-window-current",
219         "Load a session in new buffers in the current window.",
220         function (I) {
221             let file = make_file(yield _session_prompt_file(I));
222             if (! file.exists())
223                 _session_file_not_found(I, file);
224             else
225                 session_load_window_current(session_read(file), I.window);
226         });
228     interactive("session-load-window-current-replace", 
229         "Replace all buffers in the current window with buffers "+
230         "in the saved session.",
231         function (I) {
232             let file = make_file(yield _session_prompt_file(I));
233             if (! file.exists())
234                 _session_file_not_found(I, file);
235             else
236                 session_load_window_current_replace(session_read(file),
237                                                     I.window, 0);
238         });
240     interactive("session-remove",
241         "Remove a session file.",
242         function (I) {
243             let file = make_file(yield _session_prompt_file(I));
244             if (! file.exists())
245                 _session_file_not_found(I, file);
246             else
247                 session_remove(file);
248         });
251     //// Auto-save sessions. ////
254     define_variable("session_auto_save_file", "auto-save",
255         "Default filename for the auto-save session.");
257     define_variable("session_auto_save_auto_load", false,
258         'Whether to load the auto-saved session when the browser is started. '+
259         'May be true, false, or "prompt".');
261     function session_auto_save_load_window_new () {
262         session_load_window_new(_session_auto_save_cached);
263     }
265     function session_auto_save_load_window_current (window) {
266         session_load_window_current(_session_auto_save_cached, window);
267     }
269     function session_auto_save_load_window_current_replace (window) {
270         session_load_window_current_replace(_session_auto_save_cached, window);
271     }
272     
273     define_variable("session_auto_save_auto_load_fn",
274         null,
275         "Function to be called to load the auto-saved session at start-up " +
276         "when URLs are given on the command-line. May be " +
277         "session_auto_save_load_window_new, " + 
278         "session_auto_save_load_window_current, or null. If null, the" +
279         "session will not be auto-loaded when URLs are given.");
281     // Supported values:
282     //   undefined - we have not tried to cache the auto-save.
283     //   null      - we have tried to cache the auto-save, but it didn't exist.
284     //   object    - the cached session object for the auto-save.
285     let _session_auto_save_cached = undefined;
287     let _session_auto_save_file_get = function () {
288         if (session_auto_save_file instanceof Ci.nsIFile)
289             return session_auto_save_file;
290         let f = session_dir.clone();
291         f.append(session_auto_save_file);
292         return f;
293     };
295     function session_auto_save_save () {
296         let f = _session_auto_save_file_get();
297         let s = session_get();
298         if (s[0])
299             session_write(f, s);
300         else if (f.exists())
301             f.remove(false);
302     }
304     function session_auto_save_remove () {
305         let f = _session_auto_save_file_get();
306         if (f.exists())
307             f.remove(false);
308     }
310     let _session_auto_save_auto_load = function (user_gave_urls) {
311         if (! session_auto_save_auto_load)
312             return;
313         if (! _session_auto_save_cached) {
314             _session_file_not_found(null, _session_auto_save_file_get());
315             return;
316         }
317         let do_load = false;
318         let window = get_recent_conkeror_window();
319         if (session_auto_save_auto_load == true)
320             do_load = true;
321         else if (session_auto_save_auto_load == "prompt" && !user_gave_urls) {
322             do_load = (yield window.minibuffer.read_single_character_option(
323                 $prompt = "Load auto-saved session? (y/n)",
324                 $options = ["y", "n"]
325             )) == "y";
326         } else
327             throw new Error("Invalid value for session_auto_save_auto_load: " +
328                             session_auto_save_auto_load);
329         if (! do_load)
330             return;
331         if (user_gave_urls) {
332             if (session_auto_save_auto_load_fn)
333                 session_auto_save_auto_load_fn(window);
334         } else
335             session_auto_save_load_window_current_replace(window);
336     };
338     interactive("session-auto-save-load-window-new",
339         "Load the auto-save session in a new window.",
340         function (I) { 
341             if (_session_auto_save_cached == null)
342                 _session_file_not_found(I, _session_auto_save_file_get());
343             else
344                 session_auto_save_load_window_new();
345         });
347     interactive("session-auto-save-load-window-current",
348         "Load the auto-save session in new buffers in the current window.",
349         function (I) {
350             if (_session_auto_save_cached == null)
351                 _session_file_not_found(I, _session_auto_save_file_get());
352             else
353                 session_auto_save_load_window_current(I.window);
354         });
356     interactive("session-auto-save-load-window-current-replace",
357         "Replace all buffers in the current window with buffers in the "+
358         "auto-saved session.",
359         function (I) {
360             if (_session_auto_save_cached == null)
361                 _session_file_not_found(I, _session_auto_save_file_get());
362             else
363                 session_auto_save_load_window_current_replace(I.window);
364         });
366     interactive("session-auto-save-remove",
367                 "Remove the auto-save session",
368                 session_auto_save_remove);
371     //// auto-save-session-mode ////
374     let _session_auto_save_mode_bootstrap = function (b) {
375         remove_hook("window_initialize_late_hook", _session_auto_save_mode_bootstrap);
376         add_hook("create_buffer_hook", session_auto_save_save);
377         add_hook("kill_buffer_hook", session_auto_save_save);
378         add_hook("content_buffer_location_change_hook", session_auto_save_save);
379         let user_gave_urls = false;
380         for (let i = 0; i < command_line.length; ++i) {
381             if (command_line[i][0] != '-') {
382                 user_gave_urls = true;
383                 break;
384             }
385         }
386         co_call(_session_auto_save_auto_load(user_gave_urls));
387     };
389     let _session_auto_save_mode_enable = function () {
390         if (_session_auto_save_cached == undefined) {
391             let f = _session_auto_save_file_get();
392             _session_auto_save_cached = f.exists() ? session_read(f) : null;
393         }
394         if (conkeror_started) {
395             add_hook("create_buffer_hook", session_auto_save_save);
396             add_hook("kill_buffer_hook", session_auto_save_save);
397             add_hook("content_buffer_location_change_hook", session_auto_save_save);
398         } else
399             add_hook("window_initialize_late_hook", _session_auto_save_mode_bootstrap);
400     };
402     let _session_auto_save_mode_disable = function () {
403         remove_hook("create_buffer_hook", session_auto_save_save);
404         remove_hook("kill_buffer_hook", session_auto_save_save);
405         remove_hook("content_buffer_location_change_hook", session_auto_save_save);
406         // Just in case.
407         remove_hook("window_initialize_late_hook", _session_auto_save_mode_bootstrap);
408     };
410     define_global_mode("session_auto_save_mode",
411                        _session_auto_save_mode_enable,
412                        _session_auto_save_mode_disable);
414     session_auto_save_mode(true);