From ae5f97517599942136ae8bf3c93734b21e8f5c04 Mon Sep 17 00:00:00 2001 From: Maciek Borzecki Date: Mon, 26 Jun 2017 22:33:05 +0200 Subject: [PATCH] mconnect/{share,transfer,download): file sharing - download support --- src/mconnect/share.vala | 28 ++++++ src/mconnect/transfer-download.vala | 136 ++++++++++++++++++++++++++++ src/mconnect/transfer.vala | 58 ++++++++++++ 3 files changed, 222 insertions(+) create mode 100644 src/mconnect/transfer-download.vala create mode 100644 src/mconnect/transfer.vala diff --git a/src/mconnect/share.vala b/src/mconnect/share.vala index 9736fcd..436b1d7 100644 --- a/src/mconnect/share.vala +++ b/src/mconnect/share.vala @@ -20,6 +20,7 @@ class ShareHandler : Object, PacketHandlerInterface { private const string SHARE = "kdeconnect.share.request"; private const string SHARE_PKT = "kdeconnect.share"; + private static string DOWNLOADS = null; public void use_device(Device dev) { debug("use device %s for sharing", dev.to_string()); @@ -30,9 +31,28 @@ class ShareHandler : Object, PacketHandlerInterface { } public static ShareHandler instance() { + if (ShareHandler.DOWNLOADS == null) { + + GLib.warning("tutaj"); + ShareHandler.DOWNLOADS = Path.build_filename( + Environment.get_user_special_dir(UserDirectory.DOWNLOAD), + "mconnect"); + try { + DirUtils.create_with_parents(ShareHandler.DOWNLOADS, + 0700); + } catch (IOError e) { + warning("failed to create downloads directory: %s", e.message); + } + } + + info("downloads will be saved to %s", ShareHandler.DOWNLOADS); return new ShareHandler(); } + private static string make_downloads_path(string name) { + return Path.build_filename(ShareHandler.DOWNLOADS, name); + } + public string get_pkt_type() { return SHARE; } @@ -56,5 +76,13 @@ class ShareHandler : Object, PacketHandlerInterface { string name = pkt.body.get_string_member("filename"); debug("file: %s size: %lld", name, pkt.payload.size); + + var t = new DownloadTransfer( + new InetSocketAddress(dev.host, + (uint16) pkt.payload.port), + pkt.payload.size, + make_downloads_path(name)); + + t.start(); } } \ No newline at end of file diff --git a/src/mconnect/transfer-download.vala b/src/mconnect/transfer-download.vala new file mode 100644 index 0000000..bff0bbc --- /dev/null +++ b/src/mconnect/transfer-download.vala @@ -0,0 +1,136 @@ +/** + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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. + * + * AUTHORS + * Maciek Borzecki + */ + +class DownloadTransfer : Object { + + private InetSocketAddress isa = null; + private File file = null; + private FileOutputStream foutstream = null; + private Cancellable cancel = null; + private SocketConnection conn = null; + public uint64 size = 0; + public uint64 transferred = 0; + public string destination = ""; + private Transfer transfer = null; + + public DownloadTransfer(InetSocketAddress isa, uint64 size, string dest) { + this.isa = isa; + this.cancel = new Cancellable(); + this.destination = dest; + this.size = size; + } + + public bool start() { + try { + this.file = File.new_for_path(this.destination + ".part"); + this.foutstream = this.file.replace(null, false, + FileCreateFlags.PRIVATE | FileCreateFlags.REPLACE_DESTINATION); + } catch (Error e) { + warning("failed to open destination path %s: %s", + this.destination, e.message); + return false; + } + + var client = new SocketClient(); + client.connect_async.begin(this.isa, null, this.connected); + return true; + } + + private void connected(Object? obj, AsyncResult res) { + try { + var sc = (SocketClient)obj; + this.conn = sc.connect_async.end(res); + + var sock = this.conn.get_socket(); + socket_set_keepalive(sock); + this.start_transfer(); + + } catch (Error e) { + var err ="failed to connect: %s".printf(e.message); + warning(err); + this.cleanup_error(err); + } + } + + private void start_transfer() { + this.transfer = new Transfer(this.conn.input_stream, + this.foutstream); + this.transfer.progress.connect((t, p) => { + int percent = (int) (100.0 * ((double)p / (double)this.size)); + debug("progress: %llu/%llu %d %%", p, this.size, percent); + }); + this.transfer.transfer_async.begin(this.cancel, + this.transfer_complete); + } + + private void transfer_complete(Object? obj, AsyncResult res) { + info("transfer finished"); + try { + var rcvd_bytes = this.transfer.transfer_async.end(res); + debug("transfer done, got %llu bytes", rcvd_bytes); + + this.cleanup_success(); + + } catch (IOError err) { + warning("transfer failed: %s", err.message); + + this.cleanup_error(err.message); + } + } + + private void cleanup() { + if (this.foutstream != null) + this.foutstream.close(); + + this.file = null; + this.foutstream = null; + this.transfer = null; + } + + private void cleanup_error(string reason) { + + this.file.@delete(); + + this.cleanup(); + + this.error(reason); + } + + private void cleanup_success() { + + var failed = false; + try { + var dest = File.new_for_path(this.destination); + this.file.move(dest, FileCopyFlags.OVERWRITE); + + this.cleanup(); + + this.finished(); + + } catch (IOError e) { + var err = "failed to rename temporary file %s to %s: %s".printf(this.file.get_path(), + this.destination, + e.message); + warning(err); + this.cleanup_error(err); + } + } + + public signal void finished(); + public signal void error(string reason); +} \ No newline at end of file diff --git a/src/mconnect/transfer.vala b/src/mconnect/transfer.vala new file mode 100644 index 0000000..57db937 --- /dev/null +++ b/src/mconnect/transfer.vala @@ -0,0 +1,58 @@ +/** + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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. + * + * AUTHORS + * Maciek Borzecki + */ + +class Transfer : Object { + + private InputStream from = null; + private OutputStream to = null; + + public Transfer(InputStream from, OutputStream to) { + this.from = from; + this.to = to; + } + + public async uint64 transfer_async(Cancellable? cancel) throws IOError { + uint64 bytes_done = 0; + var chunk_size = 4096; + var max_chunk_size = 64 * 1024; + while (true) { + var data = yield this.from.read_bytes_async(chunk_size); + debug("read %d bytes", data.length); + if (data.length == 0) { + break; + } + yield this.to.write_bytes_async(data); + bytes_done += data.length; + this.progress(bytes_done); + + if (data.length == chunk_size) + chunk_size = 2 * chunk_size; + + if (chunk_size > max_chunk_size) + chunk_size = max_chunk_size; + } + + debug("transfer done, got %llu bytes", bytes_done); + this.from.close(); + this.to.close(); + + return bytes_done; + } + + public signal void progress(uint64 bytes_done); +} \ No newline at end of file