CREDITS: update
[conkeror.git] / modules / session.js
blob08f9682393c78c67f93a905c87297f1859484c80
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     function session_get() {
51         let windows = {};
52         let x = 0;
53         for_each_window(function (w) {
54             let buffers = {};
55             let y = 0;
56             w.buffers.for_each(function (b) {
57                 if (! b.browser || ! b instanceof content_buffer) return;
58                 buffers[y] = b.browser.contentDocument.location.href;
59                 y++;
60             });
61             if (y == 0) return;
62             windows[x] = buffers;
63             x++;
64         });
65         return windows;
66     }
68     function session_load(session, window, buffer_idx) {
69         if (! (session[0] && session[0][0]))
70             throw new Error("Invalid 'session' argument.");
71         let s = 0;
72         if (window) {
73             let bi = buffer_idx != undefined ?
74                 buffer_idx : window.buffers.count;
75             for (let i = 0; session[s][i]; ++i, ++bi) {
76                 let b = window.buffers.get_buffer(bi);
77                 if (! b instanceof content_buffer) continue;
78                 if (b) b.load(session[s][i]);
79                 else {
80                     let c = buffer_creator(content_buffer, $load = session[s][i]);
81                     create_buffer(window, c, OPEN_NEW_BUFFER_BACKGROUND);
82                 }
83             }
84             for (let b = window.buffers.get_buffer(bi); b;
85                  b = window.buffers.get_buffer(bi)) {
86                 if (b instanceof content_buffer) kill_buffer(b, true);
87                 else bi++;
88             }
89             ++s;
90         }
92         function make_init_hook(session) {
93             function init_hook(window) {
94                 for (let i = 1; session[i]; ++i) {
95                     let c = buffer_creator(content_buffer, $load = session[i]);
96                     create_buffer(window, c, OPEN_NEW_BUFFER_BACKGROUND);
97                 }
98             }
99             return init_hook;
100         }
102         for (; session[s]; ++s) {
103             let w = make_window(buffer_creator(content_buffer,
104                                                $load = session[s][0]));
105             add_hook.call(w, "window_initialize_late_hook",
106                           make_init_hook(session[s]));
107         }
108     }
110     function session_load_window_new(session) {
111         session_load(session);
112     }
114     function session_load_window_current(session, window) {
115         let w = window ? window : get_recent_conkeror_window();
116         session_load(session, w);
117     }
119     function session_load_window_current_replace(session, window) {
120         let w = window ? window : get_recent_conkeror_window();
121         session_load(session, w, 0);
122     }
124     function session_write(path, session) {
125         if (! (path instanceof Ci.nsIFile))
126             path = make_file(path);
127         if (! session) session = session_get();
128         write_text_file(path, _json.encode(session));
129     }
131     function session_read(path) {
132         if (! (path instanceof Ci.nsIFile))
133             path = make_file(path);
134         return _json.decode(read_text_file(path));
135     }
137     function session_remove(path) {
138         if (! (path instanceof Ci.nsIFile))
139             path = make_file(path);
140         path.remove(false);
141     }
143     let _session_prompt_file = function (I) {
144         yield co_return(
145             yield I.minibuffer.read_file_path(
146                 $prompt = "Session file:",
147                 $initial_value = session_dir.path,
148                 $history = "save"
149             )
150         );
151     };
153     let _session_file_not_found = function (I, file) {
154         let mb = I ? I.minibuffer : get_recent_conkeror_window().minibuffer;
155         let msg = "Session file not found: " + file.path;
156         mb.message(msg);
157         dumpln(msg);
158     }
160     interactive("session-save", "Save the current session.", function (I) {
161         session_write(make_file(yield _session_prompt_file(I)), session_get());
162     });
164     interactive("session-load-window-new", "Load a session in a new window.", 
165         function (I) {
166             let file = make_file(yield _session_prompt_file(I));
167             if (! file.exists()) _session_file_not_found(I, file);
168             else session_load_window_new(session_read(file));
169         });
171     interactive("session-load-window-current",
172         "Load a session in new buffers in the current window.",
173         function (I) {
174             let file = make_file(yield _session_prompt_file(I));
175             if (! file.exists()) _session_file_not_found(I, file);
176             else session_load_window_current(session_read(file), I.window);
177         });
179     interactive("session-load-window-current-replace", 
180         "Replace all buffers in the current window with buffers in the saved session.",
181         function (I) {
182             let file = make_file(yield _session_prompt_file(I));
183             if (! file.exists()) _session_file_not_found(I, file);
184             else session_load_window_current_replace(session_read(file), I.window, 0)
185         });
187     interactive("session-remove", "Remove a session file.", function (I) {
188         let file = make_file(yield _session_prompt_file(I));
189         if (! file.exists()) _session_file_not_found(I, file);
190         else session_remove(file);
191     });
194     //// Auto-save sessions. ////
197     define_variable("session_auto_save_file", "auto-save",
198         "Default filename for the auto-save session.");
200     define_variable("session_auto_save_auto_load", false,
201         'Whether to load the auto-saved session when the browser is started. ' +
202         'May be true, false, or "prompt".');
204     function session_auto_save_load_window_new() {
205         session_load_window_new(_session_auto_save_cached);
206     }
208     function session_auto_save_load_window_current(window) {
209         session_load_window_current(_session_auto_save_cached, window);
210     }
212     function session_auto_save_load_window_current_replace(window) {
213         session_load_window_current_replace(_session_auto_save_cached, window);
214     }
215     
216     define_variable("session_auto_save_auto_load_fn",
217         null,
218         "Function to be called to load the auto-saved session at start-up " +
219         "when URLs are given on the command-line. May be " +
220         "session_auto_save_load_window_new, " + 
221         "session_auto_save_load_window_current, or null. If null, the" +
222         "session will not be auto-loaded when URLs are given.");
224     // Supported values:
225     //   undefined - we have not tried to cache the auto-save.
226     //   null      - we have tried to cache the auto-save, but it didn't exist.
227     //   object    - the cached session object for the auto-save.
228     let _session_auto_save_cached = undefined;
230     let _session_auto_save_file_get = function () {
231         if (session_auto_save_file instanceof Ci.nsIFile)
232             return session_auto_save_file;
233         let f = session_dir.clone();
234         f.append(session_auto_save_file);
235         return f;
236     };
238     function session_auto_save_save() {
239         let f = _session_auto_save_file_get();
240         let s = session_get();
241         if (s[0]) session_write(f, s);
242         else if (f.exists()) f.remove(false);
243     }
245     function session_auto_save_remove() {
246         let f = _session_auto_save_file_get();
247         if (f.exists()) f.remove(false);
248     }
250     let _session_auto_save_auto_load = function (user_gave_urls) {
251         if (! session_auto_save_auto_load) return;
252         if (! _session_auto_save_cached) {
253             _session_file_not_found(null, _session_auto_save_file_get());
254             return;
255         }
256         let do_load = false;
257         let window = get_recent_conkeror_window();
258         if (session_auto_save_auto_load == true) do_load = true;
259         else if (session_auto_save_auto_load == "prompt" && !user_gave_urls) {
260             do_load = (yield window.minibuffer.read_single_character_option(
261                 $prompt = "Load auto-saved session? (y/n)",
262                 $options = ["y", "n"]
263             )) == "y";
264         }
265         else
266             throw new Error("Invalid value for session_auto_save_auto_load: " +
267                             session_auto_save_auto_load);
268         if (! do_load) return;
269         if (user_gave_urls) {
270             if (session_auto_save_auto_load_fn)
271                 session_auto_save_auto_load_fn(window);
272         }
273         else session_auto_save_load_window_current_replace(window);
274     };
276     interactive("session-auto-save-load-window-new",
277         "Load the auto-save session in a new window.",
278         function (I) { 
279             if (_session_auto_save_cached == null)
280                 _session_file_not_found(I, _session_auto_save_file_get());
281             else session_auto_save_load_window_new();
282         });
284     interactive("session-auto-save-load-window-current",
285         "Load the auto-save session in new buffers in the current window.",
286         function (I) {
287             if (_session_auto_save_cached == null)
288                 _session_file_not_found(I, session_auto_save_file_get());
289             else session_auto_save_load_window_current(I.window);
290         });
292     interactive("session-auto-save-load-window-current-replace",
293         "Replace all buffers in the current window with buffers in the auto-saved session.",
294         function (I) {
295             if (_session_auto_save_cached == null)
296                 _session_file_not_found(I, session_auto_save_file_get());
297             else session_auto_save_load_window_current_replace(I.window);
298         });
300     interactive("session-auto-save-remove", "Remove the auto-save session",
301                 session_auto_save_remove);
304     //// auto-save-session-mode ////
307     let _session_auto_save_mode_bootstrap = function (b) {
308         remove_hook("window_initialize_late_hook", _session_auto_save_mode_bootstrap);
309         add_hook("create_buffer_hook", session_auto_save_save);
310         add_hook("kill_buffer_hook", session_auto_save_save);
311         add_hook("content_buffer_location_change_hook", session_auto_save_save);
312         let user_gave_urls = false;
313         for (let i = 0; i < command_line.length; ++i) {
314             if (command_line[i][0] != '-') {
315                 user_gave_urls = true;
316                 break;
317             }
318         }
319         co_call(_session_auto_save_auto_load(user_gave_urls));
320     };
322     let _session_auto_save_mode_enable = function () {
323         if (_session_auto_save_cached == undefined) {
324             let f = _session_auto_save_file_get();
325             _session_auto_save_cached = f.exists() ? session_read(f) : null;
326         }
327         if (conkeror_started) {
328             add_hook("create_buffer_hook", session_auto_save_save);
329             add_hook("kill_buffer_hook", session_auto_save_save);
330             add_hook("content_buffer_location_change_hook", session_auto_save_save);
331         }
332         else
333             add_hook("window_initialize_late_hook", _session_auto_save_mode_bootstrap);
334     };
336     let _session_auto_save_mode_disable = function () {
337         remove_hook("create_buffer_hook", session_auto_save_save);
338         remove_hook("kill_buffer_hook", session_auto_save_save);
339         remove_hook("content_buffer_location_change_hook", session_auto_save_save);
340         // Just in case.
341         remove_hook("window_initialize_late_hook", _session_auto_save_mode_bootstrap);
342     };
344     define_global_mode("session_auto_save_mode",
345                        _session_auto_save_mode_enable,
346                        _session_auto_save_mode_disable);
348     session_auto_save_mode(true);