803 lines
34 KiB
Vala
803 lines
34 KiB
Vala
|
/*
|
||
|
Copyright (C) 2013 André Stösel <andre@stoesel.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 {
|
||
|
int IDLE_RESTORE_COUNT = 13;
|
||
|
/* FixMe: don't use a global object */
|
||
|
Midori.App? APP;
|
||
|
|
||
|
/* function called from Manager object */
|
||
|
public interface IStorage : GLib.Object {
|
||
|
public abstract Katze.Array get_saved_sessions ();
|
||
|
public abstract Base.Session get_new_session ();
|
||
|
public abstract void restore_last_sessions ();
|
||
|
public abstract void import_session (Katze.Array tabs);
|
||
|
}
|
||
|
|
||
|
public interface ISession : GLib.Object {
|
||
|
public abstract Katze.Array get_tabs ();
|
||
|
/* Add one tab to the database */
|
||
|
public abstract void add_item (Katze.Item item);
|
||
|
/* Attach to a browser */
|
||
|
public abstract void attach (Midori.Browser browser);
|
||
|
/* Attach to a browser and populate it with tabs from the database */
|
||
|
public abstract void restore (Midori.Browser browser);
|
||
|
/* Remove all tabs from the database */
|
||
|
public abstract void remove ();
|
||
|
/* Run when a browser is closed */
|
||
|
public abstract void close ();
|
||
|
}
|
||
|
|
||
|
public enum SessionState {
|
||
|
OPEN,
|
||
|
CLOSED,
|
||
|
RESTORING
|
||
|
}
|
||
|
|
||
|
namespace Base {
|
||
|
/* each base class should connect to all necessary signals and provide an abstract function to handle them */
|
||
|
|
||
|
public abstract class Storage : GLib.Object, IStorage {
|
||
|
public Midori.App app { get; construct; }
|
||
|
|
||
|
public abstract Katze.Array get_saved_sessions ();
|
||
|
public abstract Base.Session get_new_session ();
|
||
|
|
||
|
public void start_new_session () {
|
||
|
Katze.Array sessions = new Katze.Array (typeof (Session));
|
||
|
this.init_sessions (sessions);
|
||
|
}
|
||
|
|
||
|
public void restore_last_sessions () {
|
||
|
Katze.Array sessions = this.get_saved_sessions ();
|
||
|
this.init_sessions (sessions);
|
||
|
}
|
||
|
|
||
|
private void init_sessions (Katze.Array sessions) {
|
||
|
if (sessions.is_empty ()) {
|
||
|
sessions.add_item (this.get_new_session ());
|
||
|
}
|
||
|
|
||
|
GLib.List<unowned Katze.Item> items = sessions.get_items ();
|
||
|
foreach (Katze.Item item in items) {
|
||
|
Session session = item as Session;
|
||
|
Midori.Browser browser = this.app.create_browser ();
|
||
|
|
||
|
/* FixMe: tabby-session should be set in .restore and .attch */
|
||
|
browser.set_data<Base.Session> ("tabby-session", session as Base.Session);
|
||
|
|
||
|
app.add_browser (browser);
|
||
|
browser.show ();
|
||
|
|
||
|
session.restore (browser);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public virtual void import_session (Katze.Array tabs) {
|
||
|
Session session = this.get_new_session ();
|
||
|
GLib.List<unowned Katze.Item> items = tabs.get_items ();
|
||
|
double i = 0;
|
||
|
foreach (Katze.Item item in items) {
|
||
|
item.set_meta_string ("sorting", i.to_string());
|
||
|
// See midori_browser_step_history: don't add to history
|
||
|
item.set_meta_string ("history-step", "ignore");
|
||
|
i += 1024;
|
||
|
session.add_item (item);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public abstract class Session : GLib.Object, ISession {
|
||
|
protected GLib.SList<double?> tab_sorting;
|
||
|
|
||
|
public Midori.Browser? browser { get; protected set; default = null; }
|
||
|
public SessionState state { get; protected set; default = SessionState.CLOSED; }
|
||
|
|
||
|
public abstract void add_item (Katze.Item item);
|
||
|
public abstract void uri_changed (Midori.View view, string uri);
|
||
|
public abstract void data_changed (Midori.View view);
|
||
|
public abstract void tab_added (Midori.Browser browser, Midori.View view);
|
||
|
public abstract void tab_removed (Midori.Browser browser, Midori.View view);
|
||
|
public abstract void tab_switched (Midori.View? old_view, Midori.View? new_view);
|
||
|
public abstract void tab_reordered (Gtk.Widget tab, uint pos);
|
||
|
|
||
|
public abstract void remove ();
|
||
|
|
||
|
public abstract Katze.Array get_tabs ();
|
||
|
public abstract double get_max_sorting ();
|
||
|
|
||
|
public void attach (Midori.Browser browser) {
|
||
|
this.browser = browser;
|
||
|
|
||
|
browser.add_tab.connect_after (this.tab_added);
|
||
|
browser.add_tab.connect (this.helper_data_changed);
|
||
|
|
||
|
browser.remove_tab.connect (this.tab_removed);
|
||
|
browser.switch_tab.connect (this.tab_switched);
|
||
|
browser.delete_event.connect_after(this.delete_event);
|
||
|
browser.notebook.page_reordered.connect_after (this.tab_reordered);
|
||
|
|
||
|
this.state = SessionState.OPEN;
|
||
|
|
||
|
foreach (Midori.View view in browser.get_tabs ()) {
|
||
|
this.tab_added (browser, view);
|
||
|
this.helper_data_changed (browser, view);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void restore (Midori.Browser browser) {
|
||
|
this.browser = browser;
|
||
|
|
||
|
Katze.Array tabs = this.get_tabs ();
|
||
|
unowned Katze.Array? open_uris = browser.get_data ("tabby-open-uris");
|
||
|
|
||
|
if(tabs.is_empty () && open_uris == null) {
|
||
|
/* Using get here to avoid MidoriMidoriStartup in generated C with Vala 0.20.1 */
|
||
|
int load_on_startup;
|
||
|
APP.settings.get ("load-on-startup", out load_on_startup);
|
||
|
|
||
|
Katze.Item item = new Katze.Item ();
|
||
|
|
||
|
if (load_on_startup == Midori.MidoriStartup.BLANK_PAGE) {
|
||
|
item.uri = "about:dial";
|
||
|
} else {
|
||
|
item.uri = "about:home";
|
||
|
}
|
||
|
|
||
|
tabs.add_item (item);
|
||
|
}
|
||
|
|
||
|
browser.add_tab.connect_after (this.tab_added);
|
||
|
browser.add_tab.connect (this.helper_data_changed);
|
||
|
|
||
|
browser.remove_tab.connect (this.tab_removed);
|
||
|
browser.switch_tab.connect (this.tab_switched);
|
||
|
browser.delete_event.connect_after(this.delete_event);
|
||
|
browser.notebook.page_reordered.connect_after (this.tab_reordered);
|
||
|
|
||
|
GLib.List<unowned Katze.Item> items = new GLib.List<unowned Katze.Item> ();
|
||
|
if (open_uris != null) {
|
||
|
items.concat (open_uris.get_items ());
|
||
|
}
|
||
|
items.concat (tabs.get_items ());
|
||
|
unowned GLib.List<unowned Katze.Item> u_items = items;
|
||
|
|
||
|
bool delay = false;
|
||
|
bool should_delay = false;
|
||
|
|
||
|
int load_on_startup;
|
||
|
APP.settings.get ("load-on-startup", out load_on_startup);
|
||
|
should_delay = load_on_startup == Midori.MidoriStartup.DELAYED_PAGES;
|
||
|
|
||
|
if (APP.crashed == true) {
|
||
|
delay = true;
|
||
|
should_delay = true;
|
||
|
}
|
||
|
|
||
|
this.state = SessionState.RESTORING;
|
||
|
|
||
|
GLib.Idle.add (() => {
|
||
|
/* Note: we need to use `items` for something to maintain a valid reference */
|
||
|
GLib.PtrArray new_tabs = new GLib.PtrArray ();
|
||
|
if (items.length () > 0) {
|
||
|
for (int i = 0; i < IDLE_RESTORE_COUNT; i++) {
|
||
|
if (u_items == null) {
|
||
|
this.helper_reorder_tabs (new_tabs);
|
||
|
this.state = SessionState.OPEN;
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
Katze.Item t_item = u_items.data<Katze.Item>;
|
||
|
|
||
|
t_item.set_meta_integer ("append", 1);
|
||
|
|
||
|
if (delay && should_delay)
|
||
|
t_item.set_meta_integer ("delay", Midori.Delay.DELAYED);
|
||
|
else
|
||
|
delay = true;
|
||
|
|
||
|
unowned Gtk.Widget tab = browser.add_item (t_item);
|
||
|
new_tabs.add (tab);
|
||
|
|
||
|
u_items = u_items.next;
|
||
|
}
|
||
|
this.helper_reorder_tabs (new_tabs);
|
||
|
}
|
||
|
if (u_items == null) {
|
||
|
this.state = SessionState.OPEN;
|
||
|
return false;
|
||
|
}
|
||
|
return true;
|
||
|
});
|
||
|
}
|
||
|
|
||
|
public virtual void close () {
|
||
|
if (this.state == SessionState.CLOSED) {
|
||
|
assert (this.browser == null);
|
||
|
} else {
|
||
|
this.state = SessionState.CLOSED;
|
||
|
|
||
|
this.browser.add_tab.disconnect (this.tab_added);
|
||
|
this.browser.add_tab.disconnect (this.helper_data_changed);
|
||
|
this.browser.remove_tab.disconnect (this.tab_removed);
|
||
|
this.browser.switch_tab.disconnect (this.tab_switched);
|
||
|
this.browser.delete_event.disconnect (this.delete_event);
|
||
|
this.browser.notebook.page_reordered.disconnect (this.tab_reordered);
|
||
|
|
||
|
this.browser = null;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#if HAVE_GTK3
|
||
|
protected bool delete_event (Gtk.Widget widget, Gdk.EventAny event) {
|
||
|
#else
|
||
|
protected bool delete_event (Gtk.Widget widget, Gdk.Event event) {
|
||
|
#endif
|
||
|
|
||
|
this.close ();
|
||
|
return false;
|
||
|
|
||
|
}
|
||
|
|
||
|
protected double get_tab_sorting (Midori.View view) {
|
||
|
int this_pos = this.browser.notebook.page_num (view);
|
||
|
Midori.View prev_view = this.browser.notebook.get_nth_page (this_pos - 1) as Midori.View;
|
||
|
Midori.View next_view = this.browser.notebook.get_nth_page (this_pos + 1) as Midori.View;
|
||
|
|
||
|
string prev_meta_sorting = null;
|
||
|
string next_meta_sorting = null;
|
||
|
double prev_sorting, next_sorting, this_sorting;
|
||
|
|
||
|
if (prev_view != null) {
|
||
|
unowned Katze.Item prev_item = prev_view.get_proxy_item ();
|
||
|
prev_meta_sorting = prev_item.get_meta_string ("sorting");
|
||
|
}
|
||
|
|
||
|
if (prev_meta_sorting == null)
|
||
|
if (this.state == SessionState.RESTORING)
|
||
|
prev_sorting = this.get_max_sorting ();
|
||
|
else
|
||
|
prev_sorting = double.parse ("0");
|
||
|
else
|
||
|
prev_sorting = double.parse (prev_meta_sorting);
|
||
|
|
||
|
if (next_view != null) {
|
||
|
unowned Katze.Item next_item = next_view.get_proxy_item ();
|
||
|
next_meta_sorting = next_item.get_meta_string ("sorting");
|
||
|
}
|
||
|
|
||
|
if (next_meta_sorting == null)
|
||
|
next_sorting = prev_sorting + 2048;
|
||
|
else
|
||
|
next_sorting = double.parse (next_meta_sorting);
|
||
|
|
||
|
this_sorting = prev_sorting + (next_sorting - prev_sorting) / 2;
|
||
|
|
||
|
return this_sorting;
|
||
|
}
|
||
|
|
||
|
private void load_status (GLib.Object _view, ParamSpec pspec) {
|
||
|
Midori.View view = (Midori.View)_view;
|
||
|
|
||
|
if (view.load_status == Midori.LoadStatus.PROVISIONAL) {
|
||
|
unowned Katze.Item item = view.get_proxy_item ();
|
||
|
|
||
|
int64 delay = item.get_meta_integer ("delay");
|
||
|
if (delay == Midori.Delay.UNDELAYED) {
|
||
|
view.web_view.notify["uri"].connect ( () => {
|
||
|
this.uri_changed (view, view.web_view.uri);
|
||
|
});
|
||
|
view.web_view.notify["title"].connect ( () => {
|
||
|
this.data_changed (view);
|
||
|
});
|
||
|
|
||
|
}
|
||
|
|
||
|
view.notify["load-status"].disconnect (load_status);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void helper_data_changed (Midori.Browser browser, Midori.View view) {
|
||
|
view.notify["load-status"].connect (load_status);
|
||
|
|
||
|
view.new_view.connect (this.helper_duplicate_tab);
|
||
|
}
|
||
|
|
||
|
private void helper_reorder_tabs (GLib.PtrArray new_tabs) {
|
||
|
CompareDataFunc<double?> helper_compare_data = (a, b) => {
|
||
|
if (a > b)
|
||
|
return 1;
|
||
|
else if(a < b)
|
||
|
return -1;
|
||
|
return 0;
|
||
|
};
|
||
|
|
||
|
GLib.CompareFunc<double?> helper_compare_func = (a,b) => {
|
||
|
return a == b ? 0 : -1;
|
||
|
};
|
||
|
|
||
|
this.browser.notebook.page_reordered.disconnect (this.tab_reordered);
|
||
|
for(var i = 0; i < new_tabs.len; i++) {
|
||
|
Midori.View tab = new_tabs.index(i) as Midori.View;
|
||
|
|
||
|
unowned Katze.Item item = tab.get_proxy_item ();
|
||
|
|
||
|
double sorting;
|
||
|
string? sorting_string = item.get_meta_string ("sorting");
|
||
|
if (sorting_string != null) { /* we have to use a seperate if condition to avoid a `possibly unassigned local variable` error */
|
||
|
if (double.try_parse (item.get_meta_string ("sorting"), out sorting)) {
|
||
|
this.tab_sorting.insert_sorted_with_data (sorting, helper_compare_data);
|
||
|
|
||
|
int index = this.tab_sorting.position (this.tab_sorting.find_custom (sorting, helper_compare_func));
|
||
|
|
||
|
this.browser.notebook.reorder_child (tab, index);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
this.browser.notebook.page_reordered.connect_after (this.tab_reordered);
|
||
|
}
|
||
|
|
||
|
private void helper_duplicate_tab (Midori.View view, Midori.View new_view, Midori.NewView where, bool user_initiated) {
|
||
|
unowned Katze.Item item = view.get_proxy_item ();
|
||
|
unowned Katze.Item new_item = new_view.get_proxy_item ();
|
||
|
int64 tab_id = item.get_meta_integer ("tabby-id");
|
||
|
int64 new_tab_id = new_item.get_meta_integer ("tabby-id");
|
||
|
|
||
|
if (tab_id > 0 && tab_id == new_tab_id) {
|
||
|
new_item.set_meta_integer ("tabby-id", 0);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
construct {
|
||
|
this.tab_sorting = new GLib.SList<double?> ();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
namespace Local {
|
||
|
private class Session : Base.Session {
|
||
|
public int64 id { get; private set; }
|
||
|
private Midori.Database database;
|
||
|
|
||
|
public override void add_item (Katze.Item item) {
|
||
|
GLib.DateTime time = new DateTime.now_local ();
|
||
|
string? sorting = item.get_meta_string ("sorting") ?? "1";
|
||
|
string sqlcmd = "INSERT INTO `tabs` (`crdate`, `tstamp`, `session_id`, `uri`, `title`, `sorting`) VALUES (:crdate, :tstamp, :session_id, :uri, :title, :sorting);";
|
||
|
|
||
|
int64 tstamp = item.get_meta_integer ("tabby-tstamp");
|
||
|
if (tstamp < 0) { // new tab without focus
|
||
|
tstamp = 0;
|
||
|
}
|
||
|
|
||
|
try {
|
||
|
var statement = database.prepare (sqlcmd,
|
||
|
":crdate", typeof (int64), time.to_unix (),
|
||
|
":tstamp", typeof (int64), tstamp,
|
||
|
":session_id", typeof (int64), this.id,
|
||
|
":uri", typeof (string), item.uri,
|
||
|
":title", typeof (string), item.name,
|
||
|
":sorting", typeof (double), double.parse (sorting));
|
||
|
statement.exec ();
|
||
|
int64 tab_id = statement.row_id ();
|
||
|
item.set_meta_integer ("tabby-id", tab_id);
|
||
|
} catch (Error error) {
|
||
|
critical (_("Failed to update database: %s"), error.message);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
protected override void uri_changed (Midori.View view, string uri) {
|
||
|
unowned Katze.Item item = view.get_proxy_item ();
|
||
|
int64 tab_id = item.get_meta_integer ("tabby-id");
|
||
|
string sqlcmd = "UPDATE `tabs` SET uri = :uri WHERE session_id = :session_id AND id = :tab_id;";
|
||
|
try {
|
||
|
database.prepare (sqlcmd,
|
||
|
":uri", typeof (string), uri,
|
||
|
":session_id", typeof (int64), this.id,
|
||
|
":tab_id", typeof (int64), tab_id).exec ();
|
||
|
} catch (Error error) {
|
||
|
critical (_("Failed to update database: %s"), error.message);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
protected override void data_changed (Midori.View view) {
|
||
|
unowned Katze.Item item = view.get_proxy_item ();
|
||
|
int64 tab_id = item.get_meta_integer ("tabby-id");
|
||
|
string sqlcmd = "UPDATE `tabs` SET title = :title WHERE session_id = :session_id AND id = :tab_id;";
|
||
|
try {
|
||
|
database.prepare (sqlcmd,
|
||
|
":title", typeof (string), view.get_display_title (),
|
||
|
":session_id", typeof (int64), this.id,
|
||
|
":tab_id", typeof (int64), tab_id).exec ();
|
||
|
} catch (Error error) {
|
||
|
critical (_("Failed to update database: %s"), error.message);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
protected override void tab_added (Midori.Browser browser, Midori.View view) {
|
||
|
unowned Katze.Item item = view.get_proxy_item ();
|
||
|
int64 tab_id = item.get_meta_integer ("tabby-id");
|
||
|
if (tab_id < 1) {
|
||
|
double sorting = this.get_tab_sorting (view);
|
||
|
item.set_meta_string ("sorting", sorting.to_string ());
|
||
|
this.add_item (item);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
protected override void tab_removed (Midori.Browser browser, Midori.View view) {
|
||
|
unowned Katze.Item item = view.get_proxy_item ();
|
||
|
int64 tab_id = item.get_meta_integer ("tabby-id");
|
||
|
/* FixMe: mark as deleted */
|
||
|
string sqlcmd = "DELETE FROM `tabs` WHERE session_id = :session_id AND id = :tab_id;";
|
||
|
try {
|
||
|
database.prepare (sqlcmd,
|
||
|
":session_id", typeof (int64), this.id,
|
||
|
":tab_id", typeof (int64), tab_id).exec ();
|
||
|
} catch (Error error) {
|
||
|
critical (_("Failed to update database: %s"), error.message);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
protected override void tab_switched (Midori.View? old_view, Midori.View? new_view) {
|
||
|
GLib.DateTime time = new DateTime.now_local ();
|
||
|
unowned Katze.Item item = new_view.get_proxy_item ();
|
||
|
int64 tab_id = item.get_meta_integer ("tabby-id");
|
||
|
int64 tstamp = time.to_unix();
|
||
|
item.set_meta_integer ("tabby-tstamp", tstamp);
|
||
|
string sqlcmd = "UPDATE `tabs` SET tstamp = :tstamp WHERE session_id = :session_id AND id = :tab_id;";
|
||
|
try {
|
||
|
database.prepare (sqlcmd,
|
||
|
":session_id", typeof (int64), this.id,
|
||
|
":tab_id", typeof (int64), tab_id,
|
||
|
":tstamp", typeof (int64), tstamp).exec ();
|
||
|
} catch (Error error) {
|
||
|
critical (_("Failed to update database: %s"), error.message);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
protected override void tab_reordered (Gtk.Widget tab, uint pos) {
|
||
|
Midori.View view = tab as Midori.View;
|
||
|
|
||
|
double sorting = this.get_tab_sorting (view);
|
||
|
unowned Katze.Item item = view.get_proxy_item ();
|
||
|
int64 tab_id = item.get_meta_integer ("tabby-id");
|
||
|
string sqlcmd = "UPDATE `tabs` SET sorting = :sorting WHERE session_id = :session_id AND id = :tab_id;";
|
||
|
try {
|
||
|
database.prepare (sqlcmd,
|
||
|
":session_id", typeof (int64), this.id,
|
||
|
":tab_id", typeof (int64), tab_id,
|
||
|
":sorting", typeof (double), sorting).exec ();
|
||
|
} catch (Error error) {
|
||
|
critical (_("Failed to update database: %s"), error.message);
|
||
|
}
|
||
|
|
||
|
item.set_meta_string ("sorting", sorting.to_string ());
|
||
|
}
|
||
|
|
||
|
public override void remove() {
|
||
|
string sqlcmd = """
|
||
|
DELETE FROM `tabs` WHERE session_id = :session_id;
|
||
|
DELETE FROM `sessions` WHERE id = :session_id;
|
||
|
""";
|
||
|
try {
|
||
|
database.prepare (sqlcmd,
|
||
|
":session_id", typeof (int64), this.id). exec ();
|
||
|
} catch (Error error) {
|
||
|
critical (_("Failed to update database: %s"), error.message);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public override void close () {
|
||
|
/* base.close may unset this.browser, so hold onto it */
|
||
|
Midori.Browser? my_browser = this.browser;
|
||
|
base.close ();
|
||
|
|
||
|
bool should_break = true;
|
||
|
if (my_browser != null && !my_browser.destroy_with_parent) {
|
||
|
foreach (Midori.Browser browser in APP.get_browsers ()) {
|
||
|
if (browser != my_browser && !browser.destroy_with_parent) {
|
||
|
should_break = false;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (should_break) {
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
GLib.DateTime time = new DateTime.now_local ();
|
||
|
string sqlcmd = "UPDATE `sessions` SET closed = 1, tstamp = :tstamp WHERE id = :session_id;";
|
||
|
try {
|
||
|
database.prepare (sqlcmd,
|
||
|
":session_id", typeof (int64), this.id,
|
||
|
":tstamp", typeof (int64), time.to_unix ()).exec ();
|
||
|
} catch (Error error) {
|
||
|
critical (_("Failed to update database: %s"), error.message);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public override Katze.Array get_tabs() {
|
||
|
Katze.Array tabs = new Katze.Array (typeof (Katze.Item));
|
||
|
|
||
|
string sqlcmd = "SELECT id, uri, title, sorting FROM tabs WHERE session_id = :session_id ORDER BY tstamp DESC";
|
||
|
try {
|
||
|
var statement = database.prepare (sqlcmd,
|
||
|
":session_id", typeof (int64), this.id);
|
||
|
while (statement.step ()) {
|
||
|
Katze.Item item = new Katze.Item ();
|
||
|
int64 id = statement.get_int64 ("id");
|
||
|
string uri = statement.get_string ("uri");
|
||
|
string title = statement.get_string ("title");
|
||
|
double sorting = statement.get_double ("sorting");
|
||
|
item.uri = uri;
|
||
|
item.name = title;
|
||
|
item.set_meta_integer ("tabby-id", id);
|
||
|
item.set_meta_string ("sorting", sorting.to_string ());
|
||
|
// See midori_browser_step_history: don't add to history
|
||
|
item.set_meta_string ("history-step", "ignore");
|
||
|
tabs.add_item (item);
|
||
|
}
|
||
|
} catch (Error error) {
|
||
|
critical (_("Failed to select from database: %s"), error.message);
|
||
|
}
|
||
|
return tabs;
|
||
|
}
|
||
|
|
||
|
public override double get_max_sorting () {
|
||
|
string sqlcmd = "SELECT MAX(sorting) FROM tabs WHERE session_id = :session_id";
|
||
|
try {
|
||
|
var statement = database.prepare (sqlcmd,
|
||
|
":session_id", typeof (int64), this.id);
|
||
|
statement.step ();
|
||
|
double sorting = statement.get_double ("MAX(sorting)");
|
||
|
if (!sorting.is_nan ()) {
|
||
|
return sorting;
|
||
|
}
|
||
|
} catch (Error error) {
|
||
|
critical (_("Failed to select from database: %s"), error.message);
|
||
|
}
|
||
|
|
||
|
return 0.0;
|
||
|
}
|
||
|
|
||
|
internal Session (Midori.Database database) {
|
||
|
this.database = database;
|
||
|
|
||
|
GLib.DateTime time = new DateTime.now_local ();
|
||
|
|
||
|
string sqlcmd = "INSERT INTO `sessions` (`tstamp`) VALUES (:tstamp);";
|
||
|
|
||
|
try {
|
||
|
var statement = database.prepare (sqlcmd,
|
||
|
":tstamp", typeof (int64), time.to_unix ());
|
||
|
statement.exec ();
|
||
|
this.id = statement.row_id ();
|
||
|
} catch (Error error) {
|
||
|
critical (_("Failed to update database: %s"), error.message);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal Session.with_id (Midori.Database database, int64 id) {
|
||
|
this.database = database;
|
||
|
this.id = id;
|
||
|
|
||
|
GLib.DateTime time = new DateTime.now_local ();
|
||
|
string sqlcmd = "UPDATE `sessions` SET closed = 0, tstamp = :tstamp WHERE id = :session_id;";
|
||
|
|
||
|
try {
|
||
|
database.prepare (sqlcmd,
|
||
|
":session_id", typeof (int64), this.id,
|
||
|
":tstamp", typeof (int64), time.to_unix ()).exec ();
|
||
|
} catch (Error error) {
|
||
|
critical (_("Failed to update database: %s"), error.message);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private class Storage : Base.Storage {
|
||
|
private Midori.Database database;
|
||
|
|
||
|
public override Katze.Array get_saved_sessions () {
|
||
|
Katze.Array sessions = new Katze.Array (typeof (Session));
|
||
|
|
||
|
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;
|
||
|
""";
|
||
|
try {
|
||
|
var statement = database.prepare (sqlcmd);
|
||
|
while (statement.step ()) {
|
||
|
int64 id = statement.get_int64 ("id");
|
||
|
int64 closed = statement.get_int64 ("closed");
|
||
|
if (closed == 0 || sessions.is_empty ()) {
|
||
|
sessions.add_item (new Session.with_id (this.database, id));
|
||
|
}
|
||
|
}
|
||
|
} catch (Error error) {
|
||
|
critical (_("Failed to select from database: %s"), error.message);
|
||
|
}
|
||
|
|
||
|
if (sessions.is_empty ()) {
|
||
|
sessions.add_item (new Session (this.database));
|
||
|
}
|
||
|
|
||
|
return sessions;
|
||
|
}
|
||
|
|
||
|
public override void import_session (Katze.Array tabs) {
|
||
|
try {
|
||
|
database.transaction (()=>{
|
||
|
base.import_session(tabs); return true;
|
||
|
});
|
||
|
} catch (Error error) {
|
||
|
critical (_("Failed to select from database: %s"), error.message);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public override Base.Session get_new_session () {
|
||
|
return new Session (this.database) as Base.Session;
|
||
|
}
|
||
|
|
||
|
internal Storage (Midori.App app) {
|
||
|
GLib.Object (app: app);
|
||
|
|
||
|
try {
|
||
|
database = new Midori.Database ("tabby.db");
|
||
|
} catch (Midori.DatabaseError schema_error) {
|
||
|
error (schema_error.message);
|
||
|
}
|
||
|
|
||
|
if (database.first_use) {
|
||
|
string config_file = Midori.Paths.get_config_filename_for_reading ("session.xbel");
|
||
|
try {
|
||
|
Katze.Array old_session = new Katze.Array (typeof (Katze.Item));
|
||
|
Midori.array_from_file (old_session, config_file, "xbel-tiny");
|
||
|
this.import_session (old_session);
|
||
|
} catch (GLib.FileError file_error) {
|
||
|
/* no old session.xbel -> could be a new profile -> ignore it */
|
||
|
} catch (GLib.Error error) {
|
||
|
critical (_("Failed to import legacy session: %s"), error.message);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private class Manager : Midori.Extension {
|
||
|
private Base.Storage storage;
|
||
|
private bool load_session () {
|
||
|
/* Using get here to avoid MidoriMidoriStartup in generated C with Vala 0.20.1 */
|
||
|
int load_on_startup;
|
||
|
APP.settings.get ("load-on-startup", out load_on_startup);
|
||
|
if (load_on_startup == Midori.MidoriStartup.BLANK_PAGE
|
||
|
|| load_on_startup == Midori.MidoriStartup.HOMEPAGE) {
|
||
|
this.storage.start_new_session ();
|
||
|
} else {
|
||
|
this.storage.restore_last_sessions ();
|
||
|
}
|
||
|
|
||
|
/* FIXME: execute_commands should be called before session creation */
|
||
|
GLib.Idle.add (this.execute_commands);
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
private bool execute_commands () {
|
||
|
Midori.App app = this.get_app ();
|
||
|
unowned string?[] commands = app.get_data ("execute-commands");
|
||
|
|
||
|
if (commands != null) {
|
||
|
app.send_command (commands);
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
private void set_open_uris (Midori.Browser browser) {
|
||
|
Midori.App app = this.get_app ();
|
||
|
unowned string?[] uris = app.get_data ("open-uris");
|
||
|
|
||
|
if (uris != null) {
|
||
|
Katze.Array tabs = new Katze.Array (typeof (Katze.Item));
|
||
|
|
||
|
for(int i = 0; uris[i] != null; i++) {
|
||
|
Katze.Item item = new Katze.Item ();
|
||
|
item.name = uris[i];
|
||
|
item.uri = Midori.Sokoke.magic_uri (uris[i], true, true);
|
||
|
if (item.uri != null) {
|
||
|
tabs.add_item (item);
|
||
|
}
|
||
|
}
|
||
|
if (!tabs.is_empty()) {
|
||
|
browser.set_data ("tabby-open-uris", tabs);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
app.add_browser.disconnect (this.set_open_uris);
|
||
|
}
|
||
|
|
||
|
private void browser_added (Midori.Browser browser) {
|
||
|
Base.Session? session = browser.get_data<Base.Session> ("tabby-session");
|
||
|
if (session == null) {
|
||
|
session = this.storage.get_new_session () as Base.Session;
|
||
|
browser.set_data<Base.Session> ("tabby-session", session);
|
||
|
session.attach (browser);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void browser_removed (Midori.Browser browser) {
|
||
|
Base.Session? session = browser.get_data<Base.Session> ("tabby-session");
|
||
|
if (session == null) {
|
||
|
GLib.warning ("missing session");
|
||
|
} else {
|
||
|
session.close ();
|
||
|
|
||
|
/* Using get here to avoid MidoriMidoriStartup in generated C with Vala 0.20.1 */
|
||
|
int load_on_startup;
|
||
|
APP.settings.get ("load-on-startup", out load_on_startup);
|
||
|
|
||
|
if (browser.destroy_with_parent
|
||
|
|| load_on_startup < Midori.MidoriStartup.LAST_OPEN_PAGES) {
|
||
|
/* Remove js popups and close if not restoring on startup */
|
||
|
session.remove ();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void activated (Midori.App app) {
|
||
|
APP = app;
|
||
|
unowned string? restore_count = GLib.Environment.get_variable ("TABBY_RESTORE_COUNT");
|
||
|
if (restore_count != null) {
|
||
|
int count = int.parse (restore_count);
|
||
|
if (count >= 1) {
|
||
|
IDLE_RESTORE_COUNT = count;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* FixMe: provide an option to replace Local.Storage with IStorage based Objects */
|
||
|
this.storage = new Local.Storage (this.get_app ()) as Base.Storage;
|
||
|
|
||
|
app.add_browser.connect (this.set_open_uris);
|
||
|
app.add_browser.connect (this.browser_added);
|
||
|
app.remove_browser.connect (this.browser_removed);
|
||
|
|
||
|
GLib.Idle.add (this.load_session);
|
||
|
}
|
||
|
|
||
|
private void deactivated () {
|
||
|
/* set_open_uris will disconnect itself if called,
|
||
|
but it may have been called before we are deactivated */
|
||
|
APP.add_browser.disconnect (this.set_open_uris);
|
||
|
APP.add_browser.disconnect (this.browser_added);
|
||
|
APP.remove_browser.disconnect (this.browser_removed);
|
||
|
APP = null;
|
||
|
|
||
|
this.storage = null;
|
||
|
}
|
||
|
|
||
|
internal Manager () {
|
||
|
GLib.Object (name: _("Tabby"),
|
||
|
description: _("Tab and session management."),
|
||
|
version: "0.1",
|
||
|
authors: "André Stösel <andre@stoesel.de>");
|
||
|
|
||
|
this.activate.connect (this.activated);
|
||
|
this.deactivate.connect (this.deactivated);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public Midori.Extension extension_init () {
|
||
|
return new Tabby.Manager ();
|
||
|
}
|