content_handler_save_in: new content-handler generator
[conkeror.git] / modules / session.js
blobdfdde64bc43a841071029a7ebbe932b5edcc2540
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             let y = 0;
62             w.buffers.for_each(function (b) {
63                 if (! b.browser || ! (b instanceof content_buffer))
64                     return;
65                 buffers[y] = b.display_uri_string;
66                 y++;
67             });
68             if (y == 0)
69                 return;
70             windows[x] = buffers;
71             x++;
72         });
73         return windows;
74     }
76     /**
77      * session_load loads the given session (given as a data structure),
78      * with optional window as the first window to load the session into,
79      * with optional buffer_idx as the buffer index at which to begin
80      * overwriting existing buffers.
81      */
82     function session_load (session, window, buffer_idx) {
83         if (! (session[0] && session[0][0]))
84             throw new Error("Invalid 'session' argument.");
85         let s = 0;
86         if (window) {
87             let bi = buffer_idx != undefined ?
88                 buffer_idx : window.buffers.count;
90             // first kill special buffers slated for recycling.
91             let (b, i = (bi == 0 ? 1 : bi),
92                  safe2kill = bi > 0)
93             {
94                 while ((b = window.buffers.get_buffer(i))) {
95                     if (b instanceof content_buffer) {
96                         safe2kill = true;
97                         ++i;
98                     } else
99                         kill_buffer(b, true);
100                 }
101                 if (bi == 0 &&
102                     (b = window.buffers.get_buffer(0)) &&
103                     !(b instanceof content_buffer))
104                 {
105                     if (! safe2kill)
106                         create_buffer(window,
107                                       buffer_creator(content_buffer),
108                                       OPEN_NEW_BUFFER_BACKGROUND);
109                     kill_buffer(b, true);
110                 }
111             }
113             // it is now safe to recycle the remaining buffers.
114             for (let i = 0; session[s][i] != undefined; ++i, ++bi) {
115                 let b = window.buffers.get_buffer(bi);
116                 if (b)
117                     b.load(session[s][i]);
118                 else {
119                     let c = buffer_creator(content_buffer, $load = session[s][i]);
120                     create_buffer(window, c, OPEN_NEW_BUFFER_BACKGROUND);
121                 }
122             }
123             for (let b = window.buffers.get_buffer(bi); b;
124                  b = window.buffers.get_buffer(bi))
125             {
126                 kill_buffer(b, true);
127             }
128             ++s;
129         }
131         function make_init_hook (session) {
132             function init_hook (window) {
133                 for (let i = 1; session[i] != undefined; ++i) {
134                     let c = buffer_creator(content_buffer, $load = session[i]);
135                     create_buffer(window, c, OPEN_NEW_BUFFER_BACKGROUND);
136                 }
137             }
138             return init_hook;
139         }
141         for (; session[s] != undefined; ++s) {
142             let w = make_window(buffer_creator(content_buffer,
143                                                $load = session[s][0]));
144             add_hook.call(w, "window_initialize_late_hook",
145                           make_init_hook(session[s]));
146         }
147     }
149     /**
150      * session_load_window_new loads the given session into new windows.
151      */
152     function session_load_window_new (session) {
153         session_load(session);
154     }
156     /**
157      * session_load_window_current loads the given session, with the
158      * session's first window being appended to window.  No existing
159      * buffers will be overwritten.
160      */
161     function session_load_window_current (session, window) {
162         let w = window ? window : get_recent_conkeror_window();
163         session_load(session, w);
164     }
166     /**
167      * session_load_window_current loads the given session, with the
168      * session's first window replacing the given window.  All buffers in
169      * the given window will be overwritten.
170      */
171     function session_load_window_current_replace (session, window) {
172         let w = window ? window : get_recent_conkeror_window();
173         session_load(session, w, 0);
174     }
176     /**
177      * session_write writes the given session to the file given by path.
178      */
179     function session_write (path, session) {
180         if (! (path instanceof Ci.nsIFile))
181             path = make_file(path);
182         if (! session)
183             session = session_get();
184         write_text_file(path, _json.encode(session));
185     }
187     /**
188      * session_read reads session data from the given file path,
189      * and returns a decoded session structure.
190      */
191     function session_read (path) {
192         if (! (path instanceof Ci.nsIFile))
193             path = make_file(path);
194         return _json.decode(read_text_file(path));
195     }
197     /**
198      * session_remove deletes the given session file.
199      */
200     function session_remove (path) {
201         if (! (path instanceof Ci.nsIFile))
202             path = make_file(path);
203         path.remove(false);
204     }
206     let _session_prompt_file = function (I) {
207         yield co_return(
208             yield I.minibuffer.read_file_path(
209                 $prompt = "Session file:",
210                 $initial_value = session_dir.path,
211                 $history = "save"
212             )
213         );
214     };
216     let _session_file_not_found = function (I, file) {
217         let mb = I ? I.minibuffer : get_recent_conkeror_window().minibuffer;
218         let msg = "Session file not found: " + file.path;
219         mb.message(msg);
220         dumpln(msg);
221     }
223     interactive("session-save",
224         "Save the current session.",
225         function (I) {
226             session_write(make_file(yield _session_prompt_file(I)),
227                           session_get());
228         });
230     interactive("session-load-window-new",
231         "Load a session in a new window.", 
232         function (I) {
233             let file = make_file(yield _session_prompt_file(I));
234             if (! file.exists())
235                 _session_file_not_found(I, file);
236             else
237                 session_load_window_new(session_read(file));
238         });
240     interactive("session-load-window-current",
241         "Load a session in new buffers in the current window.",
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_load_window_current(session_read(file), I.window);
248         });
250     interactive("session-load-window-current-replace", 
251         "Replace all buffers in the current window with buffers "+
252         "in the saved session.",
253         function (I) {
254             let file = make_file(yield _session_prompt_file(I));
255             if (! file.exists())
256                 _session_file_not_found(I, file);
257             else
258                 session_load_window_current_replace(session_read(file),
259                                                     I.window, 0);
260         });
262     interactive("session-remove",
263         "Remove a session file.",
264         function (I) {
265             let file = make_file(yield _session_prompt_file(I));
266             if (! file.exists())
267                 _session_file_not_found(I, file);
268             else
269                 session_remove(file);
270         });
273     //// Auto-save sessions. ////
276     define_variable("session_auto_save_file", "auto-save",
277         "Default filename for the auto-save session.");
279     define_variable("session_auto_save_auto_load", false,
280         'Whether to load the auto-saved session when the browser is started. '+
281         'May be true, false, or "prompt".');
283     function session_auto_save_load_window_new () {
284         session_load_window_new(_session_auto_save_cached);
285     }
287     function session_auto_save_load_window_current (window) {
288         session_load_window_current(_session_auto_save_cached, window);
289     }
291     function session_auto_save_load_window_current_replace (window) {
292         session_load_window_current_replace(_session_auto_save_cached, window);
293     }
294     
295     define_variable("session_auto_save_auto_load_fn",
296         null,
297         "Function to be called to load the auto-saved session at start-up " +
298         "when URLs are given on the command-line. May be " +
299         "session_auto_save_load_window_new, " + 
300         "session_auto_save_load_window_current, or null. If null, the" +
301         "session will not be auto-loaded when URLs are given.");
303     // Supported values:
304     //   undefined - we have not tried to cache the auto-save.
305     //   null      - we have tried to cache the auto-save, but it didn't exist.
306     //   object    - the cached session object for the auto-save.
307     let _session_auto_save_cached = undefined;
309     let _session_auto_save_file_get = function () {
310         if (session_auto_save_file instanceof Ci.nsIFile)
311             return session_auto_save_file;
312         let f = session_dir.clone();
313         f.append(session_auto_save_file);
314         return f;
315     };
317     function session_auto_save_save () {
318         let f = _session_auto_save_file_get();
319         let s = session_get();
320         if (s[0])
321             session_write(f, s);
322         else if (f.exists())
323             f.remove(false);
324     }
326     function session_auto_save_remove () {
327         let f = _session_auto_save_file_get();
328         if (f.exists())
329             f.remove(false);
330     }
332     let _session_auto_save_auto_load = function (user_gave_urls) {
333         if (! session_auto_save_auto_load)
334             return;
335         if (! _session_auto_save_cached) {
336             _session_file_not_found(null, _session_auto_save_file_get());
337             return;
338         }
339         let do_load = false;
340         let window = get_recent_conkeror_window();
341         if (session_auto_save_auto_load == true)
342             do_load = true;
343         else if (session_auto_save_auto_load == "prompt" && !user_gave_urls) {
344             do_load = (yield window.minibuffer.read_single_character_option(
345                 $prompt = "Load auto-saved session? (y/n)",
346                 $options = ["y", "n"]
347             )) == "y";
348         } else
349             throw new Error("Invalid value for session_auto_save_auto_load: " +
350                             session_auto_save_auto_load);
351         if (! do_load)
352             return;
353         if (user_gave_urls) {
354             if (session_auto_save_auto_load_fn)
355                 session_auto_save_auto_load_fn(window);
356         } else
357             session_auto_save_load_window_current_replace(window);
358     };
360     interactive("session-auto-save-load-window-new",
361         "Load the auto-save session in a new window.",
362         function (I) { 
363             if (_session_auto_save_cached == null)
364                 _session_file_not_found(I, _session_auto_save_file_get());
365             else
366                 session_auto_save_load_window_new();
367         });
369     interactive("session-auto-save-load-window-current",
370         "Load the auto-save session in new buffers in the current window.",
371         function (I) {
372             if (_session_auto_save_cached == null)
373                 _session_file_not_found(I, _session_auto_save_file_get());
374             else
375                 session_auto_save_load_window_current(I.window);
376         });
378     interactive("session-auto-save-load-window-current-replace",
379         "Replace all buffers in the current window with buffers in the "+
380         "auto-saved session.",
381         function (I) {
382             if (_session_auto_save_cached == null)
383                 _session_file_not_found(I, _session_auto_save_file_get());
384             else
385                 session_auto_save_load_window_current_replace(I.window);
386         });
388     interactive("session-auto-save-remove",
389                 "Remove the auto-save session",
390                 session_auto_save_remove);
393     //// auto-save-session-mode ////
396     let _session_auto_save_mode_bootstrap = function (b) {
397         remove_hook("window_initialize_late_hook", _session_auto_save_mode_bootstrap);
398         add_hook("create_buffer_hook", session_auto_save_save);
399         add_hook("kill_buffer_hook", session_auto_save_save);
400         add_hook("content_buffer_location_change_hook", session_auto_save_save);
401         let user_gave_urls = false;
402         for (let i = 0; i < command_line.length; ++i) {
403             if (command_line[i][0] != '-') {
404                 user_gave_urls = true;
405                 break;
406             }
407         }
408         co_call(_session_auto_save_auto_load(user_gave_urls));
409     };
411     let _session_auto_save_mode_enable = function () {
412         if (_session_auto_save_cached == undefined) {
413             let f = _session_auto_save_file_get();
414             _session_auto_save_cached = f.exists() ? session_read(f) : null;
415         }
416         if (conkeror_started) {
417             add_hook("create_buffer_hook", session_auto_save_save);
418             add_hook("kill_buffer_hook", session_auto_save_save);
419             add_hook("content_buffer_location_change_hook", session_auto_save_save);
420         } else
421             add_hook("window_initialize_late_hook", _session_auto_save_mode_bootstrap);
422     };
424     let _session_auto_save_mode_disable = function () {
425         remove_hook("create_buffer_hook", session_auto_save_save);
426         remove_hook("kill_buffer_hook", session_auto_save_save);
427         remove_hook("content_buffer_location_change_hook", session_auto_save_save);
428         // Just in case.
429         remove_hook("window_initialize_late_hook", _session_auto_save_mode_bootstrap);
430     };
432     define_global_mode("session_auto_save_mode",
433                        _session_auto_save_mode_enable,
434                        _session_auto_save_mode_disable);
436     session_auto_save_mode(true);
439 provide("session");