Skip to content
This repository has been archived by the owner on Apr 2, 2022. It is now read-only.

Implement downloads #201

Open
wants to merge 26 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
645a844
Draft downloads UI.
alcinnz Oct 15, 2019
63f5f75
Integrate and fix downloads button.
alcinnz Oct 16, 2019
d1032d9
Fix download click handling.
alcinnz Oct 18, 2019
70fc006
Fix 'Show in folder' button.
alcinnz Oct 18, 2019
0b9ba67
Merge branch 'master' into master
cassidyjames Oct 18, 2019
6d06bd3
Merge branch 'master' into master
cassidyjames Oct 18, 2019
c266dd0
Update src/Widgets/DownloadsButton.vala
alcinnz Oct 18, 2019
8c6eece
Update src/Widgets/DownloadsButton.vala
alcinnz Oct 18, 2019
efee568
Update src/Widgets/DownloadsButton.vala
alcinnz Oct 18, 2019
8f8607f
Update src/Widgets/DownloadsButton.vala
alcinnz Oct 18, 2019
9c770a2
Update src/Widgets/DownloadsButton.vala
alcinnz Oct 18, 2019
144cede
Update src/Widgets/DownloadsButton.vala
alcinnz Oct 18, 2019
b777d07
Update src/Widgets/DownloadsButton.vala
alcinnz Oct 18, 2019
43d95d9
Update src/Widgets/DownloadsButton.vala
alcinnz Oct 18, 2019
37a5c55
Update src/Widgets/DownloadsButton.vala
alcinnz Oct 18, 2019
55ec83f
Update src/MainWindow.vala
alcinnz Oct 18, 2019
ea467c9
Update src/MainWindow.vala
alcinnz Oct 18, 2019
8790da9
Update src/MainWindow.vala
alcinnz Oct 18, 2019
5cadfc7
Update src/Widgets/DownloadsButton.vala
alcinnz Oct 18, 2019
b60300b
Update src/Widgets/DownloadsButton.vala
alcinnz Oct 18, 2019
a918ec7
Update src/Widgets/DownloadsButton.vala
alcinnz Oct 18, 2019
d6d6b1e
Update src/Widgets/DownloadsButton.vala
alcinnz Oct 18, 2019
9694abb
Write namespace in Downloads class names, for consistancy.
alcinnz Oct 18, 2019
c98e482
Codestyle fix (space before function parens).
alcinnz Oct 21, 2019
3cbf1e9
Merge branch 'master' into master
cassidyjames Feb 17, 2020
09b6e4c
Merge branch 'master' into master
cassidyjames Apr 17, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ executable(
join_paths('src', 'Views', 'ErrorView.vala'),
join_paths('src', 'Views', 'WelcomeView.vala'),
join_paths('src', 'Widgets', 'BrowserButton.vala'),
join_paths('src', 'Widgets', 'DownloadsButton.vala'),
join_paths('src', 'Widgets', 'FindBar.vala'),
join_paths('src', 'Widgets', 'UrlEntry.vala'),
join_paths('src', 'Widgets', 'WebView.vala'),
Expand Down
42 changes: 41 additions & 1 deletion src/MainWindow.vala
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,11 @@ public class Ephemeral.MainWindow : Gtk.Window {
private Gtk.Button zoom_default_button;
private Gtk.Stack stack;
private WebView web_view;
private Gtk.Overlay web_overlay;
private FindBar find_bar;
private Gtk.Stack refresh_stop_stack;
private Gtk.Button back_button;
private DownloadsButton downloads_button;
private Gtk.Button forward_button;
private Gtk.Button refresh_button;
private Gtk.Button stop_button;
Expand Down Expand Up @@ -65,7 +67,7 @@ public class Ephemeral.MainWindow : Gtk.Window {
var suggestion_toast = new Granite.Widgets.Toast ("");
suggestion_toast.set_default_action (_("Undo"));

var web_overlay = new Gtk.Overlay ();
web_overlay = new Gtk.Overlay ();
web_overlay.add (web_view);
web_overlay.add_overlay (suggestion_toast);

Expand Down Expand Up @@ -111,6 +113,8 @@ public class Ephemeral.MainWindow : Gtk.Window {
browser_button = new BrowserButton (this, web_view);
browser_button.sensitive = false;

downloads_button = new DownloadsButton ();

var settings_button = new Gtk.MenuButton ();
settings_button.image = new Gtk.Image.from_icon_name ("open-menu", Application.instance.icon_size);
settings_button.tooltip_text = _("Menu");
Expand Down Expand Up @@ -307,6 +311,7 @@ public class Ephemeral.MainWindow : Gtk.Window {
header.pack_start (new Gtk.Separator (Gtk.Orientation.VERTICAL));
header.pack_end (settings_button);
header.pack_end (browser_button);
header.pack_end (downloads_button);
header.pack_end (erase_button);
header.pack_end (new Gtk.Separator (Gtk.Orientation.VERTICAL));

Expand Down Expand Up @@ -460,6 +465,13 @@ public class Ephemeral.MainWindow : Gtk.Window {
web_view.notify["estimated-load-progress"].connect (update_progress);
web_view.notify["is-loading"].connect (update_progress);

web_view.web_context.download_started.connect ((download) => {
downloads_button.add_download (download);
download.finished.connect (() => {
download_finished (download);
});
});

web_view.decide_policy.connect ((decision, type) => {
switch (type) {
case WebKit.PolicyDecisionType.NAVIGATION_ACTION:
Expand Down Expand Up @@ -500,6 +512,15 @@ public class Ephemeral.MainWindow : Gtk.Window {
if (is_location (uri)) {
web_view.load_uri (uri);
}
break;
case WebKit.PolicyDecisionType.RESPONSE:
// Download files not supported by WebKit
var action = (WebKit.ResponsePolicyDecision) decision;
if (!action.is_mime_type_supported ()) {
decision.download ();
decision.ignore ();
return true;
}
}
return false;
});
Expand Down Expand Up @@ -809,6 +830,25 @@ public class Ephemeral.MainWindow : Gtk.Window {
external_dialog.destroy ();
}

private void download_finished (WebKit.Download download) {
if (download.estimated_progress != 1.0) return; // Don't get triggered by cancellations.

var filename = Filename.display_basename (download.destination);
var toast = new Granite.Widgets.Toast (_("%s Downloaded").printf (filename));
toast.set_default_action (_("Open"));

toast.default_action.connect (() => {
new DownloadRow (download).open ();
});
toast.closed.connect (() => {
toast.destroy ();
});

toast.show_all ();
web_overlay.add_overlay (toast);
toast.send_notification ();
}

private bool is_location (string uri) {
return
uri.has_prefix ("about:") ||
Expand Down
207 changes: 207 additions & 0 deletions src/Widgets/DownloadsButton.vala
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
/*
* Copyright © 2019 Adrian Cochrane (https://adrian.geek.nz)
alcinnz marked this conversation as resolved.
Show resolved Hide resolved
* 2019 Cassidy James Blaede (https://cassidyjames.com)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program; if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA
*
* Authored by: Adrian Cochrane <alcinnz@lavabit.com>
*/
public class Ephemeral.DownloadsButton : Gtk.Revealer {
Gtk.ListBox downloads_list;

public DownloadsButton () {
Object (
transition_type: Gtk.RevealerTransitionType.SLIDE_LEFT
);
}

construct {
tooltip_text = _("Downloads");
tooltip_markup = Granite.markup_accel_tooltip ({"<Ctrl>j"}, tooltip_text);

reveal_child = false;

var button = new Gtk.MenuButton ();
button.image = new Gtk.Image.from_icon_name (
"folder-download", Application.instance.icon_size
);

var popover = new Gtk.Popover (null);
button.popover = popover;

downloads_list = new Gtk.ListBox ();
downloads_list.activate_on_single_click = true;
downloads_list.selection_mode = Gtk.SelectionMode.SINGLE;
downloads_list.show ();

downloads_list.row_activated.connect ((row) => {
var download = (DownloadRow) row;
if (download == null) return;
download.open ();
});

popover.add (downloads_list);
add (button);
}

public void add_download (WebKit.Download download) {
var row = new DownloadRow (download);
row.build_ui ();

row.show_all ();
downloads_list.add (row);
reveal_child = true;
}
}

public class Ephemeral.DownloadRow : Gtk.ListBoxRow {
public WebKit.Download download;

public DownloadRow (WebKit.Download download) {
this.download = download;
}

public void build_ui () {
var image = new Gtk.Image.from_icon_name (
"document-save-as", Gtk.IconSize.DND
);

var label = new Gtk.Label ("");
label.hexpand = true;
label.xalign = 0;

var progressbar = new Gtk.ProgressBar ();
progressbar.get_style_context ().add_class ("osd");
progressbar.fraction = 0;
download.received_data.connect (() => {
progressbar.fraction = download.estimated_progress;
});

var overlay = new Gtk.Overlay ();
overlay.add (label);
overlay.add_overlay (progressbar);

var folder_button = new Gtk.Button.from_icon_name (
"folder-open", Gtk.IconSize.MENU
);
folder_button.tooltip_text = _("Open in folder");
folder_button.clicked.connect (() => {
if (download.estimated_progress == 1.0) {
DBus.Files.show_files ({download.destination});
}
});

var cancel_button = new Gtk.Button.from_icon_name (
"process-stop", Gtk.IconSize.MENU
);
cancel_button.tooltip_text = _("Cancel download");
var cancelled = false;
cancel_button.clicked.connect (() => {
download.cancel ();
cancelled = true;
this.destroy ();
});

var secondary_stack = new Gtk.Stack ();
secondary_stack.halign = Gtk.Align.END;
secondary_stack.margin_start = secondary_stack.margin_end = 6;
secondary_stack.add (folder_button);
secondary_stack.add (cancel_button);
secondary_stack.show_all ();
secondary_stack.visible_child = cancel_button;

download.finished.connect (() => {
download.received_data (uint64.MAX); // To ensure initial data is displayed.
if (cancelled) return;

this.selectable = this.activatable = true;
secondary_stack.visible_child = folder_button;
});

var grid = new Gtk.Grid ();
grid.orientation = Gtk.Orientation.HORIZONTAL;
grid.column_spacing = 3;
grid.add (image);
grid.add (overlay);
grid.add (secondary_stack);

add (grid);
tooltip_text = download.destination;
selectable = activatable = false;

ulong download_started = 0;
download_started = download.received_data.connect (() => {
var mimetype = download.response.mime_type;
if (mimetype == "application/octet-stream") {
mimetype = ContentType.guess (download.response.uri, null, null);
}

image.gicon = ContentType.get_icon (mimetype);
image.tooltip_text = ContentType.get_description (mimetype);

label.label = Filename.display_basename (download.destination);

download.disconnect (download_started);
});
}

public void open () {
var mimetype = download.response.mime_type;
if (mimetype == "application/octet-stream") {
mimetype = ContentType.guess (download.response.uri, null, null);
}

var app = AppInfo.get_default_for_type (mimetype, false);
if (app == null) {
// TODO: It'd be nice to integrate AppCenter here
return;
}

var uris = new List<string> ();
uris.append (download.destination);
try {
app.launch_uris (uris, null);
} catch (Error err) {
warning ("%s", err.message);
}
}
}

[DBus (name = "org.freedesktop.FileManager1")]
interface Ephemeral.DBus.Files : Object {
const string FILES_DBUS_ID = "org.freedesktop.FileManager1";
const string FILES_DBUS_PATH = "/org/freedesktop/FileManager1";

public abstract void show_items (string[] uris, string startup_id) throws IOError, DBusError;
public abstract void show_folders (string[] uris, string startup_id) throws IOError, DBusError;

public static void show_files (string[] uris) {
try {

DBus.Files files = Bus.get_proxy_sync (BusType.SESSION, FILES_DBUS_ID, FILES_DBUS_PATH);
files.show_items (uris, "ephemeral");

} catch (DBusError err) {

// Fallback to just opening the Downloads folder
var path = Environment.get_user_special_dir (UserDirectory.DOWNLOAD);
AppInfo.launch_default_for_uri (File.new_for_path (path).get_uri (), null);

} catch (Error err) {
warning ("Failed to open file manager: %s", err.message);
}
}
}
5 changes: 5 additions & 0 deletions src/Widgets/WebView.vala
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,11 @@ public class Ephemeral.WebView : WebKit.WebView {
WebKit.ContextMenuAction.COPY_LINK_TO_CLIPBOARD
)
);
context_menu.append (
new WebKit.ContextMenuItem.from_stock_action (
WebKit.ContextMenuAction.DOWNLOAD_LINK_TO_DISK
)
);

new_window_action.activate.connect (() => {
Application.new_window (hit_test_result.link_uri);
Expand Down