1/* 2 Copyright (C) 2013-2018 Christian Dywan <christian@twotoats.de> 3 4 This library is free software; you can redistribute it and/or 5 modify it under the terms of the GNU Lesser General Public 6 License as published by the Free Software Foundation; either 7 version 2.1 of the License, or (at your option) any later version. 8 9 See the file COPYING for the full license text. 10*/ 11 12namespace Bookmarks { 13 class BookmarksDatabase : Midori.Database { 14 static BookmarksDatabase? _default = null; 15 public static BookmarksDatabase get_default () throws Midori.DatabaseError { 16 if (_default == null) { 17 _default = new BookmarksDatabase (); 18 } 19 return _default; 20 } 21 22 BookmarksDatabase () throws Midori.DatabaseError { 23 Object (path: "bookmarks.db"); 24 init (); 25 } 26 27 public async override Midori.DatabaseItem? lookup (string uri) throws Midori.DatabaseError { 28 string sqlcmd = """ 29 SELECT id, title FROM %s WHERE uri = :uri LIMIT 1 30 """.printf (table); 31 var statement = prepare (sqlcmd, 32 ":uri", typeof (string), uri); 33 if (statement.step ()) { 34 string title = statement.get_string ("title"); 35 var item = new Midori.DatabaseItem (uri, title); 36 item.database = this; 37 item.id = statement.get_int64 ("id"); 38 return item; 39 } 40 return null; 41 } 42 43 public async override List<Midori.DatabaseItem>? query (string? filter=null, int64 max_items=15, Cancellable? cancellable=null) throws Midori.DatabaseError { 44 string where = filter != null ? "WHERE uri LIKE :filter OR title LIKE :filter" : ""; 45 string sqlcmd = """ 46 SELECT id, uri, title, visit_count AS ct FROM %s 47 %s 48 GROUP BY uri 49 ORDER BY ct DESC LIMIT :limit 50 """.printf (table, where); 51 52 try { 53 var statement = prepare (sqlcmd, 54 ":limit", typeof (int64), max_items); 55 if (filter != null) { 56 string real_filter = "%" + filter.replace (" ", "%") + "%"; 57 statement.bind (":filter", typeof (string), real_filter); 58 } 59 60 var items = new List<Midori.DatabaseItem> (); 61 while (statement.step ()) { 62 string uri = statement.get_string ("uri"); 63 string title = statement.get_string ("title"); 64 var item = new Midori.DatabaseItem (uri, title); 65 item.database = this; 66 item.id = statement.get_int64 ("id"); 67 items.append (item); 68 69 uint src = Idle.add (query.callback); 70 yield; 71 Source.remove (src); 72 73 if (cancellable != null && cancellable.is_cancelled ()) 74 return null; 75 } 76 if (cancellable != null && cancellable.is_cancelled ()) 77 return null; 78 return items; 79 } catch (Midori.DatabaseError error) { 80 critical ("Failed to query bookmarks: %s", error.message); 81 } 82 return null; 83 } 84 85 public async override bool update (Midori.DatabaseItem item) throws Midori.DatabaseError { 86 string sqlcmd = """ 87 UPDATE %s SET uri = :uri, title = :title WHERE id = :id 88 """.printf (table); 89 try { 90 var statement = prepare (sqlcmd, 91 ":id", typeof (int64), item.id, 92 ":uri", typeof (string), item.uri, 93 ":title", typeof (string), item.title); 94 if (statement.exec ()) { 95 return true; 96 } 97 } catch (Error error) { 98 critical ("Failed to update %s: %s", table, error.message); 99 } 100 return false; 101 } 102 103 public async override bool insert (Midori.DatabaseItem item) throws Midori.DatabaseError { 104 item.database = this; 105 106 string sqlcmd = """ 107 INSERT INTO %s (uri, title) VALUES (:uri, :title) 108 """.printf (table); 109 var statement = prepare (sqlcmd, 110 ":uri", typeof (string), item.uri, 111 ":title", typeof (string), item.title); 112 if (statement.exec ()) { 113 item.id = statement.row_id (); 114 return true; 115 } 116 return false; 117 } 118 } 119 120 [GtkTemplate (ui = "/ui/bookmarks-button.ui")] 121 public class Button : Gtk.Button { 122 [GtkChild] 123 Gtk.Popover popover; 124 [GtkChild] 125 Gtk.Entry entry_title; 126 [GtkChild] 127 Gtk.Button button_remove; 128 129 Midori.Browser browser; 130 131 construct { 132 popover.relative_to = this; 133 entry_title.changed.connect (() => { 134 var item = browser.tab.get_data<Midori.DatabaseItem?> ("bookmarks-item"); 135 if (item != null) { 136 item.title = entry_title.text; 137 } 138 }); 139 button_remove.clicked.connect (() => { 140 popover.hide (); 141 var item = browser.tab.get_data<Midori.DatabaseItem?> ("bookmarks-item"); 142 item.delete.begin (); 143 browser.tab.set_data<Midori.DatabaseItem?> ("bookmarks-item", null); 144 }); 145 } 146 147 async Midori.DatabaseItem item_for_tab (Midori.Tab tab) { 148 var item = tab.get_data<Midori.DatabaseItem?> ("bookmarks-item"); 149 if (item == null) { 150 try { 151 item = yield BookmarksDatabase.get_default ().lookup (tab.display_uri); 152 } catch (Midori.DatabaseError error) { 153 critical ("Failed to lookup %s in bookmarks database: %s", tab.display_uri, error.message); 154 } 155 if (item == null) { 156 item = new Midori.DatabaseItem (tab.display_uri, tab.display_title); 157 try { 158 yield BookmarksDatabase.get_default ().insert (item); 159 } catch (Midori.DatabaseError error) { 160 critical ("Failed to add %s to bookmarks database: %s", item.uri, error.message); 161 } 162 } 163 entry_title.text = item.title; 164 tab.set_data<Midori.DatabaseItem?> ("bookmarks-item", item); 165 } 166 return item; 167 } 168 169 public virtual signal void add_bookmark () { 170 var tab = browser.tab; 171 item_for_tab.begin (tab); 172 popover.show (); 173 } 174 175 public Button (Midori.Browser browser) { 176 this.browser = browser; 177 178 var action = new SimpleAction ("bookmark-add", null); 179 action.activate.connect (bookmark_add_activated); 180 browser.notify["uri"].connect (() => { 181 action.set_enabled (browser.uri.has_prefix ("http")); 182 }); 183 browser.add_action (action); 184 browser.application.set_accels_for_action ("win.bookmark-add", { "<Primary>d" }); 185 } 186 187 void bookmark_add_activated () { 188 add_bookmark (); 189 } 190 } 191 192 public class Frontend : Object, Midori.BrowserActivatable { 193 public Midori.Browser browser { owned get; set; } 194 195 public void activate () { 196 // No bookmarks in app mode 197 if (browser.is_locked) { 198 return; 199 } 200 201 browser.add_button (new Button (browser)); 202 } 203 } 204 205 public class Completion : Peas.ExtensionBase, Midori.CompletionActivatable { 206 public Midori.Completion completion { owned get; set; } 207 208 public void activate () { 209 try { 210 completion.add (BookmarksDatabase.get_default ()); 211 } catch (Midori.DatabaseError error) { 212 critical ("Failed to add bookmarks completion: %s", error.message); 213 } 214 } 215 } 216} 217 218[ModuleInit] 219public void peas_register_types(TypeModule module) { 220 ((Peas.ObjectModule)module).register_extension_type ( 221 typeof (Midori.BrowserActivatable), typeof (Bookmarks.Frontend)); 222 ((Peas.ObjectModule)module).register_extension_type ( 223 typeof (Midori.CompletionActivatable), typeof (Bookmarks.Completion)); 224 225} 226