kludge issue 513
[conkeror.git] / modules / session.js
blob806f2606bc62110a0eb9e5ca80eef8836467cbe8
1 /**
2 * (C) Copyright 2009 Nicholas A. Zigarovich
4 * Use, modification, and distribution are subject to the terms specified in the
5 * COPYING file.
6 **/
8 /* TODO
10 * Features:
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.
21 * Bugs, critical:
23 * - This module does not work correctly with daemon mode.
25 * Bugs, deferred:
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.
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, parseInt("0755", 8));
45 define_variable("session_dir", _session_dir_default,
46 "Default directory for save/load interactive commands.");
48 define_variable("session_save_buffer_access_order", false,
49 "Whether to store last-accessed order in the sessions file.");
51 /**
52 * session_token is instantiated for the $opener property of buffers
53 * created by the session. This allows the possibility of knowing,
54 * during buffer init, whether the buffer was created by a session or
55 * by other means.
57 var session_token = function session_token () {}
58 session_token.prototype = {
59 constructor: session_token
62 /**
63 * session_get generates and returns a structure containing session
64 * data for the current group of open windows.
66 var session_get = function session_get () {
67 let windows = {};
68 let x = 0;
69 for_each_window(function (w) {
70 let buffers = [];
71 w.buffers.for_each(function (b) {
72 if (b instanceof content_buffer) {
73 if (session_save_buffer_access_order) {
74 buffers.push({
75 url: b.display_uri_string,
76 access_index: w.buffers.buffer_history.indexOf(b)
77 });
78 } else {
79 buffers.push(b.display_uri_string);
82 });
83 if (buffers.length == 0)
84 return;
85 windows[x] = buffers;
86 x++;
87 });
88 return windows;
91 /**
92 * session_load loads the given session (given as a data structure),
93 * with optional window as the first window to load the session into,
94 * with optional buffer_idx as the buffer index at which to begin
95 * overwriting existing buffers.
97 var session_load = function session_load (session, window, buffer_idx) {
98 if (! (session[0] && session[0][0]))
99 throw new Error("Invalid 'session' argument.");
100 let s = 0;
101 var opener = new session_token();
102 if (window) {
103 let bi = buffer_idx != undefined ?
104 buffer_idx : window.buffers.count;
106 // first kill special buffers slated for recycling.
108 let b, i = (bi == 0 ? 1 : bi),
109 safe2kill = bi > 0;
110 while ((b = window.buffers.get_buffer(i))) {
111 if (b instanceof content_buffer) {
112 safe2kill = true;
113 ++i;
114 } else
115 kill_buffer(b, true);
117 if (bi == 0 &&
118 (b = window.buffers.get_buffer(0)) &&
119 !(b instanceof content_buffer))
121 if (! safe2kill)
122 create_buffer(window,
123 buffer_creator(content_buffer,
124 $opener = opener),
125 OPEN_NEW_BUFFER_BACKGROUND);
126 kill_buffer(b, true);
130 // it is now safe to recycle the remaining buffers.
131 for (let i = 0; session[s][i] != undefined; ++i, ++bi) {
132 let b = window.buffers.get_buffer(bi);
133 if (b) {
134 try {
135 let history = b.web_navigation.sessionHistory;
136 history.PurgeHistory(history.count);
137 } catch (e) {}
138 b.load(session[s][i].url);
139 } else {
140 let c = buffer_creator(content_buffer,
141 $load = session[s][i].url,
142 $opener = opener,
143 $position = buffer_position_end);
144 create_buffer(window, c, OPEN_NEW_BUFFER_BACKGROUND);
147 for (let b = window.buffers.get_buffer(bi); b;
148 b = window.buffers.get_buffer(bi))
150 kill_buffer(b, true);
152 if ('access_index' in session[s][0]) {
153 var ts = session[s].slice(0);
154 ts.sort(function (a, b) { return b.access_index - a.access_index; });
155 for (let i = 0, m = ts.length; i < m; ++i) {
156 for (let j = 0, n = window.buffers.count; j < n; j++) {
157 var b = window.buffers.get_buffer(j);
158 if (ts[i].url == b.display_uri_string) {
159 window.buffers.buffer_history.splice(
160 window.buffers.buffer_history.indexOf(b), 1);
161 window.buffers.buffer_history.unshift(b);
162 break;
166 switch_to_buffer(window, window.buffers.buffer_history[0]);
168 ++s;
171 function make_init_hook (session) {
172 function init_hook (window) {
173 for (let i = 1; session[i] != undefined; ++i) {
174 let c = buffer_creator(content_buffer,
175 $load = session[i].url,
176 $opener = opener,
177 $position = buffer_position_end);
178 create_buffer(window, c, OPEN_NEW_BUFFER_BACKGROUND);
180 if ('access_index' in session[0]) {
181 var ts = session.slice(0);
182 ts.sort(function (a, b) { return b.access_index - a.access_index; });
183 for (let i = 0, n = ts.length; i < n; ++i) {
184 for (let j = 0, m = window.buffers.count; j < m; j++) {
185 var b = window.buffers.get_buffer(j);
186 if (ts[i].url == b.display_uri_string) {
187 window.buffers.buffer_history.splice(
188 window.buffers.buffer_history.indexOf(b), 1);
189 window.buffers.buffer_history.unshift(b);
190 break;
194 switch_to_buffer(window, window.buffers.buffer_history[0]);
197 return init_hook;
200 for (; session[s] != undefined; ++s) {
201 let w = make_window(buffer_creator(content_buffer,
202 $load = session[s][0].url,
203 $opener = opener));
204 add_hook.call(w, "window_initialize_late_hook",
205 make_init_hook(session[s]));
210 * session_load_window_new loads the given session into new windows.
212 var session_load_window_new = function session_load_window_new (session) {
213 session_load(session);
217 * session_load_window_current loads the given session, with the
218 * session's first window being appended to window. No existing
219 * buffers will be overwritten.
221 var session_load_window_current =
222 function session_load_window_current (session, window) {
223 let w = window ? window : get_recent_conkeror_window();
224 session_load(session, w);
228 * session_load_window_current loads the given session, with the
229 * session's first window replacing the given window. All buffers in
230 * the given window will be overwritten.
232 var session_load_window_current_replace =
233 function session_load_window_current_replace (session, window) {
234 let w = window ? window : get_recent_conkeror_window();
235 session_load(session, w, 0);
239 * session_write writes the given session to the file given by path.
241 var session_write = function session_write (path, session) {
242 if (! (path instanceof Ci.nsIFile))
243 path = make_file(path);
244 if (! session)
245 session = session_get();
246 write_text_file(path, JSON.stringify(session));
250 * session_read reads session data from the given file path,
251 * and returns a decoded session structure.
253 var session_read = function session_read (path) {
254 if (! (path instanceof Ci.nsIFile))
255 path = make_file(path);
256 var rv = JSON.parse(read_text_file(path));
257 for (var i in rv) {
258 for (let e = 0, n = rv[i].length; e < n; ++e) {
259 if (typeof rv[i][e] == "string")
260 rv[i][e] = { url: rv[i][e] };
263 return rv;
267 * session_remove deletes the given session file.
269 var session_remove = function session_remove (path) {
270 if (! (path instanceof Ci.nsIFile))
271 path = make_file(path);
272 path.remove(false);
275 let _session_prompt_file = function (I) {
276 yield co_return(
277 yield I.minibuffer.read_file_path(
278 $prompt = "Session file:",
279 $initial_value = session_dir.path,
280 $history = "save"
285 let _session_file_not_found = function (I, file) {
286 let mb = I ? I.minibuffer : get_recent_conkeror_window().minibuffer;
287 let msg = "Session file not found: " + file.path;
288 mb.message(msg);
289 dumpln(msg);
292 interactive("session-save",
293 "Save the current session.",
294 function (I) {
295 session_write(make_file(yield _session_prompt_file(I)),
296 session_get());
299 interactive("session-load-window-new",
300 "Load a session in a new window.",
301 function (I) {
302 let file = make_file(yield _session_prompt_file(I));
303 if (! file.exists())
304 _session_file_not_found(I, file);
305 else
306 session_load_window_new(session_read(file));
309 interactive("session-load-window-current",
310 "Load a session in new buffers in the current window.",
311 function (I) {
312 let file = make_file(yield _session_prompt_file(I));
313 if (! file.exists())
314 _session_file_not_found(I, file);
315 else
316 session_load_window_current(session_read(file), I.window);
319 interactive("session-load-window-current-replace",
320 "Replace all buffers in the current window with buffers "+
321 "in the saved session.",
322 function (I) {
323 let file = make_file(yield _session_prompt_file(I));
324 if (! file.exists())
325 _session_file_not_found(I, file);
326 else
327 session_load_window_current_replace(session_read(file),
328 I.window, 0);
331 interactive("session-remove",
332 "Remove a session file.",
333 function (I) {
334 let file = make_file(yield _session_prompt_file(I));
335 if (! file.exists())
336 _session_file_not_found(I, file);
337 else
338 session_remove(file);
342 //// Auto-save sessions. ////
345 define_variable("session_auto_save_file", "auto-save",
346 "Default filename for the auto-save session.");
348 define_variable("session_auto_save_auto_load", false,
349 'Whether to load the auto-saved session when the browser is started. '+
350 'May be true, false, or "prompt".');
352 var session_auto_save_load_window_new =
353 function session_auto_save_load_window_new () {
354 session_load_window_new(_session_auto_save_cached);
357 var session_auto_save_load_window_current =
358 function session_auto_save_load_window_current (window) {
359 session_load_window_current(_session_auto_save_cached, window);
362 var session_auto_save_load_window_current_replace =
363 function session_auto_save_load_window_current_replace (window) {
364 session_load_window_current_replace(_session_auto_save_cached, window);
367 define_variable("session_auto_save_auto_load_fn",
368 null,
369 "Function to be called to load the auto-saved session at start-up " +
370 "when URLs are given on the command-line. May be " +
371 "session_auto_save_load_window_new, " +
372 "session_auto_save_load_window_current, or null. If null, the" +
373 "session will not be auto-loaded when URLs are given.");
375 // Supported values:
376 // undefined - we have not tried to cache the auto-save.
377 // null - we have tried to cache the auto-save, but it didn't exist.
378 // object - the cached session object for the auto-save.
379 let _session_auto_save_cached = undefined;
381 let _session_auto_save_file_get = function () {
382 if (session_auto_save_file instanceof Ci.nsIFile)
383 return session_auto_save_file;
384 let f = session_dir.clone();
385 f.append(session_auto_save_file);
386 return f;
389 var session_auto_save_save = function session_auto_save_save () {
390 let f = _session_auto_save_file_get();
391 let s = session_get();
392 if (s[0])
393 session_write(f, s);
394 else if (f.exists())
395 f.remove(false);
398 var session_auto_save_remove = function session_auto_save_remove () {
399 let f = _session_auto_save_file_get();
400 if (f.exists())
401 f.remove(false);
404 let _session_auto_save_auto_load = function (user_gave_urls) {
405 if (! session_auto_save_auto_load)
406 return;
407 if (! _session_auto_save_cached) {
408 _session_file_not_found(null, _session_auto_save_file_get());
409 return;
411 let do_load = false;
412 let window = get_recent_conkeror_window();
413 if (session_auto_save_auto_load == true)
414 do_load = true;
415 else if (session_auto_save_auto_load == "prompt" && !user_gave_urls) {
416 do_load = (yield window.minibuffer.read_single_character_option(
417 $prompt = "Load auto-saved session? (y/n)",
418 $options = ["y", "n"]
419 )) == "y";
420 } else
421 throw new Error("Invalid value for session_auto_save_auto_load: " +
422 session_auto_save_auto_load);
423 if (! do_load)
424 return;
425 if (user_gave_urls) {
426 if (session_auto_save_auto_load_fn)
427 session_auto_save_auto_load_fn(window);
428 } else
429 session_auto_save_load_window_current_replace(window);
432 interactive("session-auto-save-load-window-new",
433 "Load the auto-save session in a new window.",
434 function (I) {
435 if (_session_auto_save_cached == null)
436 _session_file_not_found(I, _session_auto_save_file_get());
437 else
438 session_auto_save_load_window_new();
441 interactive("session-auto-save-load-window-current",
442 "Load the auto-save session in new buffers in the current window.",
443 function (I) {
444 if (_session_auto_save_cached == null)
445 _session_file_not_found(I, _session_auto_save_file_get());
446 else
447 session_auto_save_load_window_current(I.window);
450 interactive("session-auto-save-load-window-current-replace",
451 "Replace all buffers in the current window with buffers in the "+
452 "auto-saved session.",
453 function (I) {
454 if (_session_auto_save_cached == null)
455 _session_file_not_found(I, _session_auto_save_file_get());
456 else
457 session_auto_save_load_window_current_replace(I.window);
460 interactive("session-auto-save-remove",
461 "Remove the auto-save session",
462 session_auto_save_remove);
465 //// auto-save-session-mode ////
468 let _session_auto_save_mode_bootstrap = function (b) {
469 remove_hook("window_initialize_late_hook", _session_auto_save_mode_bootstrap);
470 add_hook("create_buffer_hook", session_auto_save_save);
471 add_hook("kill_buffer_hook", session_auto_save_save);
472 add_hook("move_buffer_hook", session_auto_save_save);
473 add_hook("content_buffer_location_change_hook", session_auto_save_save);
474 add_hook("select_buffer_hook", session_auto_save_save);
475 let user_gave_urls = false;
476 for (let i = 0; i < command_line.length; ++i) {
477 if (command_line[i][0] != '-') {
478 user_gave_urls = true;
479 break;
482 spawn(_session_auto_save_auto_load(user_gave_urls));
485 let _session_auto_save_mode_enable = function () {
486 if (_session_auto_save_cached == undefined) {
487 let f = _session_auto_save_file_get();
488 _session_auto_save_cached = f.exists() ? session_read(f) : null;
490 if (conkeror_started) {
491 add_hook("create_buffer_hook", session_auto_save_save);
492 add_hook("kill_buffer_hook", session_auto_save_save);
493 add_hook("move_buffer_hook", session_auto_save_save);
494 add_hook("content_buffer_location_change_hook", session_auto_save_save);
495 add_hook("select_buffer_hook", session_auto_save_save);
496 } else
497 add_hook("window_initialize_late_hook", _session_auto_save_mode_bootstrap);
500 let _session_auto_save_mode_disable = function () {
501 remove_hook("create_buffer_hook", session_auto_save_save);
502 remove_hook("kill_buffer_hook", session_auto_save_save);
503 remove_hook("move_buffer_hook", session_auto_save_save);
504 remove_hook("content_buffer_location_change_hook", session_auto_save_save);
505 remove_hook("select_buffer_hook", session_auto_save_save);
506 // Just in case.
507 remove_hook("window_initialize_late_hook", _session_auto_save_mode_bootstrap);
510 define_global_mode("session_auto_save_mode",
511 _session_auto_save_mode_enable,
512 _session_auto_save_mode_disable);
514 session_auto_save_mode(true);
517 provide("session");