@ -0,0 +1,46 @@ | |||||
/** | |||||
* 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 <maciek.borzecki (at] gmail.com> | |||||
*/ | |||||
namespace Logging { | |||||
public bool VERBOSE = false; | |||||
/** | |||||
* enable_vdebug: | |||||
* | |||||
* Enable verbose debug logging | |||||
*/ | |||||
void enable_vdebug() { | |||||
VERBOSE = true; | |||||
} | |||||
} | |||||
/** | |||||
* vdebug: | |||||
* @format: format string | |||||
* | |||||
* Same as debug() but looks at verbose debug flag | |||||
*/ | |||||
void vdebug(string format, ...) { | |||||
if (Logging.VERBOSE == true) { | |||||
var l = va_list(); | |||||
logv(null, LogLevelFlags.LEVEL_DEBUG, format, l); | |||||
} | |||||
} | |||||
@ -0,0 +1,85 @@ | |||||
/** | |||||
* 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 <maciek.borzecki (at] gmail.com> | |||||
*/ | |||||
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()); | |||||
dev.message.connect(this.message); | |||||
} | |||||
private ShareHandler() { | |||||
} | |||||
public static ShareHandler instance() { | |||||
if (ShareHandler.DOWNLOADS == null) { | |||||
ShareHandler.DOWNLOADS = Path.build_filename( | |||||
Environment.get_user_special_dir(UserDirectory.DOWNLOAD), | |||||
"mconnect"); | |||||
if (DirUtils.create_with_parents(ShareHandler.DOWNLOADS, | |||||
0700) == -1) { | |||||
warning("failed to create downloads directory: %s", | |||||
Posix.strerror(Posix.errno)); | |||||
} | |||||
} | |||||
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; | |||||
} | |||||
public void release_device(Device dev) { | |||||
debug("release device %s", dev.to_string()); | |||||
dev.message.disconnect(this.message); | |||||
} | |||||
private void message(Device dev, Packet pkt) { | |||||
if (pkt.pkt_type != SHARE_PKT && pkt.pkt_type != SHARE) { | |||||
return; | |||||
} | |||||
if (pkt.payload == null) { | |||||
warning("missing payload info"); | |||||
return; | |||||
} | |||||
string name = pkt.body.get_string_member("filename"); | |||||
debug("file: %s size: %s", name, format_size(pkt.payload.size)); | |||||
var t = new DownloadTransfer( | |||||
new InetSocketAddress(dev.host, | |||||
(uint16) pkt.payload.port), | |||||
pkt.payload.size, | |||||
make_downloads_path(name)); | |||||
t.start(); | |||||
} | |||||
} |
@ -0,0 +1,154 @@ | |||||
/** | |||||
* 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 <maciek.borzecki (at] gmail.com> | |||||
*/ | |||||
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; | |||||
} | |||||
debug("start transfer from %s", this.isa.to_string()); | |||||
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(); | |||||
Utils.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() { | |||||
debug("connected, start transfer"); | |||||
this.transfer = new Transfer(this.conn.input_stream, | |||||
this.foutstream); | |||||
this.transfer.progress.connect((t, done) => { | |||||
int percent = (int) (100.0 * ((double)done / (double)this.size)); | |||||
debug("progress: %s/%s %d%%", | |||||
format_size(done), format_size(this.size), percent); | |||||
this.transferred = done; | |||||
}); | |||||
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 %s", format_size(rcvd_bytes)); | |||||
this.cleanup_success(); | |||||
} catch (Error err) { | |||||
warning("transfer failed: %s", err.message); | |||||
this.cleanup_error(err.message); | |||||
} | |||||
} | |||||
private void cleanup() { | |||||
if (this.foutstream != null) { | |||||
try { | |||||
this.foutstream.close(); | |||||
} catch (IOError e) { | |||||
warning("failed to close file output: %s", | |||||
e.message); | |||||
} | |||||
} | |||||
if (this.conn != null) { | |||||
try { | |||||
this.conn.close(); | |||||
} catch (IOError e) { | |||||
warning("failed to close connection: %s", | |||||
e.message); | |||||
} | |||||
} | |||||
this.file = null; | |||||
this.foutstream = null; | |||||
this.conn = null; | |||||
this.transfer = null; | |||||
} | |||||
private void cleanup_error(string reason) { | |||||
this.file.@delete(); | |||||
this.cleanup(); | |||||
this.error(reason); | |||||
} | |||||
private void cleanup_success() { | |||||
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); | |||||
} |
@ -0,0 +1,69 @@ | |||||
/** | |||||
* 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 <maciek.borzecki (at] gmail.com> | |||||
*/ | |||||
class Transfer : Object { | |||||
private InputStream from = null; | |||||
private OutputStream to = null; | |||||
public Transfer(InputStream from, OutputStream to) { | |||||
this.from = from; | |||||
this.to = to; | |||||
} | |||||
/** | |||||
* transfer_async: | |||||
* @cancel: cancellable | |||||
* | |||||
* Starty asynchronous transfer of data from @from stream to @to stream. | |||||
* | |||||
* @return number of bytes transferred if no error occurred | |||||
*/ | |||||
public async uint64 transfer_async(Cancellable? cancel) throws Error { | |||||
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", format_size(bytes_done)); | |||||
return bytes_done; | |||||
} | |||||
/** | |||||
* progress: | |||||
* @bytes_down: number of bytes transferred | |||||
* | |||||
* Indicate transfer progress | |||||
*/ | |||||
public signal void progress(uint64 bytes_done); | |||||
} |