@ -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); | |||
} |