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