summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Dywan <christian@twotoasts.de>2018-10-31 11:23:18 +0100
committerGitHub <noreply@github.com>2018-10-31 11:23:18 +0100
commitff9ef19b39e746acc6931482f63eb4175ff2be25 (patch)
treeba5346ec3a206fe8c8c8cd33467bba4a8c0718b3
parentb706998fb38a91e6f1327f46ab62d5d2e36c9b6b (diff)
downloadmidori-git-ff9ef19b39e746acc6931482f63eb4175ff2be25.tar.gz
Implement built-in session extension (#65)
- The database schema is compatible with the original tabby. - Every browser is a "session". - Each tab is part of a session and can be closed. - The last closed session (window) is restored as a fallback. - `Midori.Database.insert/ update/ delete` become virtual. - `Midori.Browser.tabs` is made public. - `Midori.Browser.add` ensures there's at least one visible tab. - The new `ClearPrivateDataActivatable` allows adding buttons and clearing data used by an extension, in this case previously open tabs. Follow-ups: - See #70 for closed storing tabs in the session - See #71 for historing tab history - See #142 for re-rodering of tabs Closes: #41
-rw-r--r--core/app.vala14
-rw-r--r--core/browser.vala26
-rw-r--r--core/clear-private-data.vala32
-rw-r--r--core/database.vala11
-rw-r--r--core/settings.vala23
-rw-r--r--data/tabby/Create.sql33
-rw-r--r--data/tabby/Update1.sql4
-rw-r--r--extensions/session.plugin.in5
-rw-r--r--extensions/session.vala406
-rw-r--r--gresource.xml2
-rw-r--r--po/POTFILES.in2
11 files changed, 540 insertions, 18 deletions
diff --git a/core/app.vala b/core/app.vala
index 561b4ad8..404675a9 100644
--- a/core/app.vala
+++ b/core/app.vala
@@ -313,14 +313,21 @@ namespace Midori {
void win_new_activated (Action action, Variant? parameter) {
var browser = new Browser (this);
+ if (!browser.default_tab ()) {
+ browser.add (new Tab (null, browser.web_context));
+ }
string? uri = parameter.get_string () != "" ? parameter.get_string () : null;
- browser.add (new Tab (null, browser.web_context, uri));
+ if (uri != null) {
+ browser.add (new Tab (null, browser.web_context, uri));
+ }
browser.show ();
}
void win_incognito_new_activated () {
var browser = new Browser.incognito (this);
- browser.add (new Tab (null, browser.web_context));
+ if (!browser.default_tab ()) {
+ browser.add (new Tab (null, browser.web_context));
+ }
browser.show ();
}
@@ -390,8 +397,7 @@ namespace Midori {
}
if (app != "") {
- var browser = new Browser (this);
- browser.is_locked = true;
+ var browser = new Browser (this, true);
var tab = new Tab (null, browser.web_context, app);
tab.pinned = true;
browser.add (tab);
diff --git a/core/browser.vala b/core/browser.vala
index 3fc9b4a8..f365fa40 100644
--- a/core/browser.vala
+++ b/core/browser.vala
@@ -24,7 +24,7 @@ namespace Midori {
public Tab? tab { get; protected set; }
public ListStore trash { get; protected set; }
public bool is_fullscreen { get; protected set; default = false; }
- public bool is_locked { get; set; default = false; }
+ public bool is_locked { get; construct set; default = false; }
const ActionEntry[] actions = {
{ "tab-new", tab_new_activated },
@@ -69,7 +69,7 @@ namespace Midori {
[GtkChild]
Navigationbar navigationbar;
[GtkChild]
- Gtk.Stack tabs;
+ public Gtk.Stack tabs;
[GtkChild]
public Gtk.Overlay overlay;
[GtkChild]
@@ -285,8 +285,16 @@ namespace Midori {
navigationbar.pack_end (button);
}
- public Browser (App app) {
+ /*
+ * Requests a default tab to be added to an otherwise empty window.
+ *
+ * Connect, adding one or more windows, and return true to override.
+ */
+ public signal bool default_tab ();
+
+ public Browser (App app, bool is_locked=false) {
Object (application: app,
+ is_locked: is_locked,
web_context: WebKit.WebContext.get_default ());
}
@@ -370,14 +378,20 @@ namespace Midori {
void homepage_activated () {
var settings = CoreSettings.get_default ();
string homepage = settings.homepage;
+ string uri;
if ("://" in homepage) {
- tab.load_uri (homepage);
+ uri = homepage;
} else if ("." in homepage) {
// Prepend http:// if hompepage has no scheme
- tab.load_uri ("http://" + homepage);
+ uri = "http://" + homepage;
} else {
// Fallback to search if URI is about:search or anything else
- tab.load_uri (settings.uri_for_search ());
+ uri = settings.uri_for_search ();
+ }
+ if (tab == null) {
+ add (new Tab (null, web_context, uri));
+ } else {
+ tab.load_uri (uri);
}
}
diff --git a/core/clear-private-data.vala b/core/clear-private-data.vala
index b7d36ef0..96f222d0 100644
--- a/core/clear-private-data.vala
+++ b/core/clear-private-data.vala
@@ -10,6 +10,12 @@
*/
namespace Midori {
+ public interface ClearPrivateDataActivatable : Object {
+ public abstract Gtk.Box box { owned get; set; }
+ public abstract void activate ();
+ public async abstract void clear (TimeSpan timespan);
+ }
+
[GtkTemplate (ui = "/ui/clear-private-data.ui")]
class ClearPrivateData : Gtk.Dialog {
[GtkChild]
@@ -22,6 +28,7 @@ namespace Midori {
Gtk.CheckButton cache;
Cancellable? show_cancellable = null;
+ Peas.ExtensionSet extensions;
public ClearPrivateData (Gtk.Window parent) {
Object (transient_for: parent);
@@ -51,6 +58,11 @@ namespace Midori {
} catch (DatabaseError error) {
debug ("Failed to check history: %s", error.message);
}
+
+ extensions = Plugins.get_default ().plug<ClearPrivateDataActivatable> ("box", history.parent);
+ extensions.extension_added.connect ((info, extension) => { ((ClearPrivateDataActivatable)extension).activate (); });
+ extensions.foreach ((extensions, info, extension) => { extensions.extension_added (info, extension); });
+
base.show ();
}
@@ -79,6 +91,10 @@ namespace Midori {
public override void response (int response_id) {
show_cancellable.cancel ();
+ response_async.begin (response_id);
+ }
+
+ async void response_async (int response_id) {
if (response_id == Gtk.ResponseType.OK) {
// The ID is the number of days as a string; 0 means everything
var timespan = timerange.active_id.to_int () * TimeSpan.DAY;
@@ -91,15 +107,25 @@ namespace Midori {
}
if (types != 0) {
var manager = WebKit.WebContext.get_default ().website_data_manager;
- manager.clear.begin (types, timespan, null);
+ try {
+ yield manager.clear (types, timespan, null);
+ } catch (Error error) {
+ critical ("Failed to clear website data: %s", error.message);
+ }
}
if (history.active) {
try {
- HistoryDatabase.get_default ().clear.begin (timespan);
+ yield HistoryDatabase.get_default ().clear (timespan);
} catch (DatabaseError error) {
- debug ("Failed to clear history: %s", error.message);
+ critical ("Failed to clear history: %s", error.message);
}
}
+
+ var active_extensions = new List<Peas.Extension> ();
+ extensions.foreach ((extensions, info, extension) => { active_extensions.append (extension); });
+ foreach (var extension in active_extensions) {
+ yield ((ClearPrivateDataActivatable)extension).clear (timespan);
+ }
}
close ();
}
diff --git a/core/database.vala b/core/database.vala
index 4c215486..64abeee4 100644
--- a/core/database.vala
+++ b/core/database.vala
@@ -324,7 +324,8 @@ namespace Midori {
}
public bool exec_script (string filename) throws DatabaseError {
- string schema_path = "/data/%s/%s.sql".printf (table, filename);
+ string basename = Path.get_basename (path).split (".")[0];
+ string schema_path = "/data/%s/%s.sql".printf (basename, filename);
try {
var schema = resources_lookup_data (schema_path, ResourceLookupFlags.NONE);
transaction (()=> { return exec ((string)schema.get_data ()); });
@@ -376,7 +377,7 @@ namespace Midori {
/*
* Delete an item from the database.
*/
- public async bool delete (DatabaseItem item) throws DatabaseError {
+ public async virtual bool delete (DatabaseItem item) throws DatabaseError {
string sqlcmd = """
DELETE FROM %s WHERE rowid = :id
""".printf (table);
@@ -544,9 +545,9 @@ namespace Midori {
// Note: TimeSpan is defined in microseconds
int64 maximum_age = new DateTime.now_local ().to_unix () - timespan / 1000000;
- unowned string sqlcmd = """
- DELETE FROM %s WHERE date <= :maximum_age;
- """;
+ string sqlcmd = """
+ DELETE FROM %s WHERE date <= :maximum_age
+ """.printf (table);
var statement = prepare (sqlcmd,
":maximum_age", typeof (int64), maximum_age);
return statement.exec ();
diff --git a/core/settings.vala b/core/settings.vala
index 1cb8896a..0d3f6ef0 100644
--- a/core/settings.vala
+++ b/core/settings.vala
@@ -10,6 +10,14 @@
*/
namespace Midori {
+ [CCode (cprefix = "MIDORI_STARTUP_")]
+ public enum StartupType {
+ SPEED_DIAL,
+ HOMEPAGE,
+ LAST_OPEN_PAGES,
+ DELAYED_PAGES
+ }
+
public class CoreSettings : Settings {
static CoreSettings? _default = null;
@@ -34,6 +42,21 @@ namespace Midori {
set_boolean ("extensions", "lib%s.so".printf (plugin), enabled);
}
+ // Note: Speed Dial is saved as Blank Page for compatibility reasons
+ public StartupType load_on_startup { get {
+ var startup = get_string ("settings", "load-on-startup", "MIDORI_STARTUP_LAST_OPEN_PAGES");
+ if (startup.has_suffix ("BLANK_PAGE")) {
+ return StartupType.SPEED_DIAL;
+ } else if (startup.has_suffix ("HOMEPAGE")) {
+ return StartupType.HOMEPAGE;
+ } else {
+ return StartupType.LAST_OPEN_PAGES;
+ }
+ } set {
+ var startup = value == StartupType.SPEED_DIAL ? "MIDORI_STARTUP_BLANK_PAGE" : value.to_string ();
+ set_string ("settings", "load-on-startup", startup, "MIDORI_STARTUP_LAST_OPEN_PAGES");
+ } }
+
public bool enable_spell_checking { get {
return get_boolean ("settings", "enable-spell-checking", true);
} set {
diff --git a/data/tabby/Create.sql b/data/tabby/Create.sql
new file mode 100644
index 00000000..0b5690bd
--- /dev/null
+++ b/data/tabby/Create.sql
@@ -0,0 +1,33 @@
+CREATE TABLE IF NOT EXISTS sessions
+(
+ id INTEGER PRIMARY KEY,
+ parent_id INTEGER DEFAULT 0,
+ crdate INTEGER DEFAULT 0,
+ tstamp INTEGER DEFAULT 0,
+ closed INTEGER DEFAULT 0,
+ title TEXT DEFAULT NULL,
+ FOREIGN KEY(parent_id) REFERENCES sessions(id)
+);
+
+CREATE TABLE IF NOT EXISTS tabs
+(
+ id INTEGER PRIMARY KEY,
+ session_id INTEGER NOT NULL,
+ uri TEXT DEFAULT NULL,
+ icon TEXT DEFAULT NULL,
+ title TEXT DEFAULT NULL,
+ crdate INTEGER DEFAULT 0,
+ tstamp INTEGER DEFAULT 0,
+ closed INTEGER DEFAULT 0,
+ FOREIGN KEY(session_id) REFERENCES sessions(id)
+);
+
+CREATE TABLE IF NOT EXISTS tab_history
+(
+ id INTEGER PRIMARY KEY,
+ tab_id INTEGER,
+ url TEXT,
+ icon TEXT,
+ title TEXT,
+ FOREIGN KEY(tab_id) REFERENCES tabs(id)
+);
diff --git a/data/tabby/Update1.sql b/data/tabby/Update1.sql
new file mode 100644
index 00000000..26b72103
--- /dev/null
+++ b/data/tabby/Update1.sql
@@ -0,0 +1,4 @@
+ALTER TABLE tabs ADD sorting REAL DEFAULT 0;
+
+CREATE INDEX sorting on tabs (sorting ASC);
+CREATE INDEX tstamp on tabs (tstamp ASC);
diff --git a/extensions/session.plugin.in b/extensions/session.plugin.in
new file mode 100644
index 00000000..e2181ba6
--- /dev/null
+++ b/extensions/session.plugin.in
@@ -0,0 +1,5 @@
+[Plugin]
+Module=session
+IAge=3
+Builtin=true
+Name=Session
diff --git a/extensions/session.vala b/extensions/session.vala
new file mode 100644
index 00000000..92bcee90
--- /dev/null
+++ b/extensions/session.vala
@@ -0,0 +1,406 @@
+/*
+ Copyright (C) 2013-2018 Christian Dywan <christian@twotoats.de>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ See the file COPYING for the full license text.
+*/
+
+namespace Tabby {
+ class SessionDatabase : Midori.Database {
+ static SessionDatabase? _default = null;
+ // Note: Using string instead of int64 because it's a hashable type
+ HashTable<string, Midori.Browser> browsers;
+
+ public static SessionDatabase get_default () throws Midori.DatabaseError {
+ if (_default == null) {
+ _default = new SessionDatabase ();
+ }
+ return _default;
+ }
+
+ SessionDatabase () throws Midori.DatabaseError {
+ Object (path: "tabby.db", table: "tabs");
+ init ();
+ browsers = new HashTable<string, Midori.Browser> (str_hash, str_equal);
+ }
+
+ async List<int64?> get_sessions () throws Midori.DatabaseError {
+ string sqlcmd = """
+ SELECT id, closed FROM sessions WHERE closed = 0
+ UNION
+ SELECT * FROM (SELECT id, closed FROM sessions WHERE closed = 1 ORDER BY tstamp DESC LIMIT 1)
+ ORDER BY closed;
+ """;
+ var sessions = new List<int64?> ();
+ var statement = prepare (sqlcmd);
+ while (statement.step ()) {
+ int64 id = statement.get_int64 ("id");
+ int64 closed = statement.get_int64 ("closed");
+ if (closed == 0 || sessions.length () == 0) {
+ sessions.append (id);
+ }
+ }
+ return sessions;
+ }
+
+ async List<Midori.DatabaseItem>? get_items (int64 session_id, string? filter=null, int64 max_items=15, Cancellable? cancellable=null) throws Midori.DatabaseError {
+ string where = filter != null ? "AND (uri LIKE :filter OR title LIKE :filter)" : "";
+ string sqlcmd = """
+ SELECT id, uri, title, tstamp FROM %s
+ WHERE session_id = :session_id %s
+ ORDER BY tstamp DESC LIMIT :limit
+ """.printf (table, where);
+ var statement = prepare (sqlcmd,
+ ":session_id", typeof (int64), session_id,
+ ":limit", typeof (int64), max_items);
+ if (filter != null) {
+ string real_filter = "%" + filter.replace (" ", "%") + "%";
+ statement.bind (":filter", typeof (string), real_filter);
+ }
+
+ var items = new List<Midori.DatabaseItem> ();
+ while (statement.step ()) {
+ string uri = statement.get_string ("uri");
+ string title = statement.get_string ("title");
+ int64 date = statement.get_int64 ("tstamp");
+ var item = new Midori.DatabaseItem (uri, title, date);
+ item.database = this;
+ item.id = statement.get_int64 ("id");
+ item.set_data<int64> ("session_id", session_id);
+ items.append (item);
+
+ uint src = Idle.add (get_items.callback);
+ yield;
+ Source.remove (src);
+
+ if (cancellable != null && cancellable.is_cancelled ())
+ return null;
+ }
+
+ if (cancellable != null && cancellable.is_cancelled ())
+ return null;
+ return items;
+ }
+
+ public async override List<Midori.DatabaseItem>? query (string? filter=null, int64 max_items=15, Cancellable? cancellable=null) throws Midori.DatabaseError {
+ var items = new List<Midori.DatabaseItem> ();
+ foreach (int64 session_id in yield get_sessions ()) {
+ foreach (var item in yield get_items (session_id, filter, max_items, cancellable)) {
+ items.append (item);
+ }
+ }
+
+ if (cancellable != null && cancellable.is_cancelled ())
+ return null;
+ return items;
+ }
+
+ public async override bool insert (Midori.DatabaseItem item) throws Midori.DatabaseError {
+ item.database = this;
+
+ string sqlcmd = """
+ INSERT INTO %s (crdate, tstamp, session_id, uri, title)
+ VALUES (:crdate, :tstamp, :session_id, :uri, :title)
+ """.printf (table);
+
+ var statement = prepare (sqlcmd,
+ ":crdate", typeof (int64), item.date,
+ ":tstamp", typeof (int64), item.date,
+ ":session_id", typeof (int64), item.get_data<int64> ("session_id"),
+ ":uri", typeof (string), item.uri,
+ ":title", typeof (string), item.title);
+ if (statement.exec ()) {
+ item.id = statement.row_id ();
+ return true;
+ }
+ return false;
+ }
+
+ public async override bool update (Midori.DatabaseItem item) throws Midori.DatabaseError {
+ string sqlcmd = """
+ UPDATE %s SET uri = :uri, title = :title, tstamp = :tstamp WHERE id = :id
+ """.printf (table);
+ try {
+ var statement = prepare (sqlcmd,
+ ":id", typeof (int64), item.id,
+ ":uri", typeof (string), item.uri,
+ ":title", typeof (string), item.title,
+ ":tstamp", typeof (int64), new DateTime.now_local ().to_unix ());
+ if (statement.exec ()) {
+ return true;
+ }
+ } catch (Midori.DatabaseError error) {
+ critical ("Failed to update %s: %s", table, error.message);
+ }
+ return false;
+ }
+
+ public async override bool delete (Midori.DatabaseItem item) throws Midori.DatabaseError {
+ string sqlcmd = """
+ DELETE FROM %s WHERE id = :id
+ """.printf (table);
+ var statement = prepare (sqlcmd,
+ ":id", typeof (int64), item.id);
+ if (statement.exec ()) {
+ return true;
+ }
+ return false;
+ }
+
+ int64 insert_session () {
+ string sqlcmd = """
+ INSERT INTO sessions (tstamp) VALUES (:tstamp)
+ """;
+ try {
+ var statement = prepare (sqlcmd,
+ ":tstamp", typeof (int64), new DateTime.now_local ().to_unix ());
+ statement.exec ();
+ debug ("Added session: %s", statement.row_id ().to_string ());
+ return statement.row_id ();
+ } catch (Midori.DatabaseError error) {
+ critical ("Failed to add session: %s", error.message);
+ }
+ return -1;
+ }
+
+ void update_session (int64 id, bool closed) {
+ string sqlcmd = """
+ UPDATE sessions SET closed=:closed, tstamp=:tstamp WHERE id = :id
+ """;
+ try {
+ var statement = prepare (sqlcmd,
+ ":id", typeof (int64), id,
+ ":tstamp", typeof (int64), new DateTime.now_local ().to_unix (),
+ ":closed", typeof (int64), closed ? 1 : 0);
+ statement.exec ();
+ } catch (Midori.DatabaseError error) {
+ critical ("Failed to update session: %s", error.message);
+ }
+ }
+
+ public async override bool clear (TimeSpan timespan) throws Midori.DatabaseError {
+ // Note: TimeSpan is defined in microseconds
+ int64 maximum_age = new DateTime.now_local ().to_unix () - timespan / 1000000;
+
+ string sqlcmd = """
+ DELETE FROM %s WHERE tstamp >= :maximum_age;
+ DELETE FROM sessions WHERE tstamp >= :maximum_age;
+ """.printf (table);
+ var statement = prepare (sqlcmd,
+ ":maximum_age", typeof (int64), maximum_age);
+ return statement.exec ();
+ }
+
+ public async bool restore_windows (Midori.Browser default_browser) throws Midori.DatabaseError {
+ bool restored = false;
+
+ // Restore existing session(s) that weren't closed, or the last closed one
+ foreach (var item in yield query (null, int64.MAX - 1)) {
+ Midori.Browser browser;
+ int64 id = item.get_data<int64> ("session_id");
+ if (!restored) {
+ browser = default_browser;
+ restored = true;
+ connect_browser (browser, id);
+ foreach (var widget in browser.tabs.get_children ()) {
+ yield tab_added (widget as Midori.Tab, id);
+ }
+ } else {
+ var app = (Midori.App)default_browser.get_application ();
+ browser = browser_for_session (app, id);
+ }
+ var tab = new Midori.Tab (browser.tab, browser.web_context,
+ item.uri, item.title);
+ connect_tab (tab, item);
+ browser.add (tab);
+ }
+ return restored;
+ }
+
+ Midori.Browser browser_for_session (Midori.App app, int64 id) {
+ var browser = browsers.lookup (id.to_string ());
+ if (browser == null) {
+ debug ("Restoring session %s", id.to_string ());
+ browser = new Midori.Browser (app);
+ browser.show ();
+ connect_browser (browser, id);
+ }
+ return browser;
+ }
+
+ public void connect_browser (Midori.Browser browser, int64 id=-1) {
+ if (id < 0) {
+ id = insert_session ();
+ } else {
+ update_session (id, false);
+ }
+
+ browsers.insert (id.to_string (), browser);
+ browser.set_data<bool> ("tabby_connected", true);
+ foreach (var widget in browser.tabs.get_children ()) {
+ tab_added.begin (widget as Midori.Tab, id);
+ }
+ browser.tabs.add.connect ((widget) => { tab_added.begin (widget as Midori.Tab, id); });
+ browser.delete_event.connect ((event) => {
+ debug ("Closing session %s", id.to_string ());
+ update_session (id, true);
+ return false;
+ });
+ }
+
+ void connect_tab (Midori.Tab tab, Midori.DatabaseItem item) {
+ debug ("Connecting %s to session %s", item.uri, item.get_data<int64> ("session_id").to_string ());
+ tab.set_data<Midori.DatabaseItem?> ("tabby-item", item);
+ tab.notify["uri"].connect ((pspec) => { item.uri = tab.uri; update.begin (item); });
+ tab.notify["title"].connect ((pspec) => { item.title = tab.title; });
+ tab.close.connect (() => { tab_removed (tab); });
+ }
+
+ bool tab_is_connected (Midori.Tab tab) {
+ return tab.get_data<Midori.DatabaseItem?> ("tabby-item") != null;
+ }
+
+ async void tab_added (Midori.Tab tab, int64 id) {
+ if (tab_is_connected (tab)) {
+ return;
+ }
+ var item = new Midori.DatabaseItem (tab.display_uri, tab.display_title,
+ new DateTime.now_local ().to_unix ());
+ item.set_data<int64> ("session_id", id);
+ try {
+ yield insert (item);
+ connect_tab (tab, item);
+ } catch (Midori.DatabaseError error) {
+ critical ("Failed add tab to session database: %s", error.message);
+ }
+ }
+
+ void tab_removed (Midori.Tab tab) {
+ var item = tab.get_data<Midori.DatabaseItem?> ("tabby-item");
+ debug ("Trashing tab %s:%s", item.get_data<int64> ("session_id").to_string (), tab.display_uri);
+ item.delete.begin ();
+ }
+ }
+
+ public class Session : Peas.ExtensionBase, Midori.BrowserActivatable {
+ public Midori.Browser browser { owned get; set; }
+
+ static bool session_restored = false;
+
+ public void activate () {
+ // Don't track locked (app) or private windows
+ if (browser.is_locked || browser.web_context.is_ephemeral ()) {
+ return;
+ }
+ // Skip windows already in the session
+ if (browser.get_data<bool> ("tabby_connected")) {
+ return;
+ }
+
+ browser.default_tab.connect (restore_or_connect);
+ try {
+ var session = SessionDatabase.get_default ();
+ if (session_restored) {
+ session.connect_browser (browser);
+ browser.activate_action ("tab-new", null);
+ } else {
+ session_restored = true;
+ restore_session.begin (session);
+ }
+ } catch (Midori.DatabaseError error) {
+ critical ("Failed to restore session: %s", error.message);
+ }
+ }
+
+ bool restore_or_connect () {
+ try {
+ var session = SessionDatabase.get_default ();
+ var settings = Midori.CoreSettings.get_default ();
+ if (settings.load_on_startup == Midori.StartupType.SPEED_DIAL) {
+ session.connect_browser (browser);
+ } else if (settings.load_on_startup == Midori.StartupType.HOMEPAGE) {
+ session.connect_browser (browser);
+ browser.activate_action ("homepage", null);
+ return true;
+ } else {
+ return true;
+ }
+ } catch (Midori.DatabaseError error) {
+ critical ("Failed to restore session: %s", error.message);
+ }
+ return false;
+ }
+
+ async void restore_session (SessionDatabase session) {
+ try {
+ bool restored = yield session.restore_windows (browser);
+ if (!restored) {
+ browser.add (new Midori.Tab (null, browser.web_context));
+ session.connect_browser (browser);
+ }
+ } catch (Midori.DatabaseError error) {
+ critical ("Failed to restore session: %s", error.message);
+ }
+ }
+ }
+
+ public class Preferences : Object, Midori.PreferencesActivatable {
+ public Midori.Preferences preferences { owned get; set; }
+
+ public void activate () {
+ var settings = Midori.CoreSettings.get_default ();
+ var box = new Midori.LabelWidget (_("Startup"));
+ var combo = new Gtk.ComboBoxText ();
+ combo.append ("0", _("Show Speed Dial"));
+ combo.append ("1", _("Show Homepage"));
+ combo.append ("2", _("Show last open tabs"));
+ settings.bind_property ("load-on-startup", combo, "active", BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL);
+ var button = new Midori.LabelWidget (_("When Midori starts:"), combo);
+ box.add (button);
+ box.show_all ();
+ preferences.add (_("Browsing"), box);
+ deactivate.connect (() => {
+ box.destroy ();
+ });
+ }
+ }
+
+ public class ClearSession : Peas.ExtensionBase, Midori.ClearPrivateDataActivatable {
+ public Gtk.Box box { owned get; set; }
+
+ Gtk.CheckButton button;
+
+ public void activate () {
+ button = new Gtk.CheckButton.with_mnemonic (_("Last open _tabs"));
+ button.show ();
+ box.add (button);
+ }
+
+ public async void clear (TimeSpan timespan) {
+ if (!button.active) {
+ return;
+ }
+
+ try {
+ yield SessionDatabase.get_default ().clear (timespan);
+ } catch (Midori.DatabaseError error) {
+ critical ("Failed to clear session: %s", error.message);
+ }
+ }
+ }
+}
+
+[ModuleInit]
+public void peas_register_types(TypeModule module) {
+ ((Peas.ObjectModule)module).register_extension_type (
+ typeof (Midori.BrowserActivatable), typeof (Tabby.Session));
+ ((Peas.ObjectModule)module).register_extension_type (
+ typeof (Midori.PreferencesActivatable), typeof (Tabby.Preferences));
+ ((Peas.ObjectModule)module).register_extension_type (
+ typeof (Midori.ClearPrivateDataActivatable), typeof (Tabby.ClearSession));
+
+}
diff --git a/gresource.xml b/gresource.xml
index ef7361cb..92418a7c 100644
--- a/gresource.xml
+++ b/gresource.xml
@@ -19,6 +19,8 @@
<file compressed="true">data/bookmarks/Create.sql</file>
<file compressed="true">data/history/Create.sql</file>
<file compressed="true">data/history/Day.sql</file>
+ <file compressed="true">data/tabby/Create.sql</file>
+ <file compressed="true">data/tabby/Update1.sql</file>
<file compressed="true" preprocess="xml-stripblanks">ui/about.ui</file>
<file compressed="true" preprocess="xml-stripblanks">ui/bookmarks-button.ui</file>
<file compressed="true" preprocess="xml-stripblanks">ui/browser.ui</file>
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 1f2ba990..d31844ed 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -41,6 +41,8 @@ extensions/statusbar-features.in
extensions/statusbar-features.vala
extensions/colorful-tabs.plugin.in
extensions/colorful-tabs.vala
+extensions/session.plugin.in
+extensions/session.vala
ui/bookmarks-button.ui
ui/browser.ui
ui/clear-private-data.ui