class FtpDownloader { private Curl.EasyHandle curl; private Cancellable cancellable; private FileStream file; private string dirname; private HashTable file_size; public FtpDownloader (Cancellable? _cancellable) { cancellable = _cancellable; curl = new Curl.EasyHandle (); } [CCode (instance_pos = -1)] size_t write_callback (void *buffer, size_t size, size_t nmemb) { if (cancellable != null && cancellable.is_cancelled ()) return 0; unowned uint8[] buf = (uint8[]) buffer; buf.length = (int) (size * nmemb); file.write (buf); return buf.length; } private int last_dlnow; int progress_callback (double dltotal, double dlnow, double ultotal, double ulnow) { if (cancellable != null && cancellable.is_cancelled ()) return 1; if (last_dlnow != (int) dlnow) { last_dlnow = (int) dlnow; progress ((int) dltotal, last_dlnow); } return 0; } public void download (string url, string filename) throws IOError { print ("download (\"%s\", \"%s\")\n", url, filename); download_dir (Path.get_dirname (url) + "/"); string basename = Path.get_basename (url); int size = file_size.lookup (basename); if (size > 0) { Posix.Stat st; Posix.stat (filename, out st); if (size == st.st_size) { progress (size, size); return; } } curl.setopt (Curl.Option.URL, url); curl.setopt (Curl.Option.WRITEFUNCTION, write_callback); curl.setopt (Curl.Option.WRITEDATA, this); curl.setopt (Curl.Option.NOPROGRESS, 0L); curl.setopt (Curl.Option.PROGRESSFUNCTION, progress_callback); curl.setopt (Curl.Option.PROGRESSDATA, this); last_dlnow = -1; file = FileStream.open (filename, "w"); var res = curl.perform (); if (Curl.Code.ABORTED_BY_CALLBACK == res) { throw new IOError.CANCELLED ("Download cancelled."); } else if (res != 0) { stderr.printf ("cURL performed: %d\n", res); } file = null; } void parse_dir_entry (string line) { try { Regex re_dir_entry = new Regex ("^.* ([0-9]*) [A-Z][a-z]* *[0-9]* [0-9]* [0-9]*:[0-9]* ([^ ]*)$"); MatchInfo match_info; if (re_dir_entry.match (line, 0, out match_info)) { string name = match_info.fetch (2); int size = match_info.fetch (1).to_int (); file_size.insert (name, size); } } catch (RegexError e) { } } string last_line = null; [CCode (instance_pos = -1)] size_t dir_callback (void *buffer, size_t size, size_t nmemb) { if (cancellable != null && cancellable.is_cancelled ()) return 0; unowned char[] buf = (char[]) buffer; buf.length = (int) (size * nmemb); char *p = buf; int i; int j; for (i = 0, j = 0; i < buf.length; i++, j++) { if (buf[i] == '\n') { buf[i] = 0; if (last_line != null) { parse_dir_entry (last_line + (string) p); last_line = null; } else { parse_dir_entry ((string) p); } p += j + 1; j = -1; } } if (j > 0) last_line = ((string) p).ndup (j); return buf.length; } public void download_dir (string url) throws IOError { if (dirname != null && dirname == url) return; print ("download_dir (\"%s\")\n", url); curl.setopt (Curl.Option.URL, url); curl.setopt (Curl.Option.WRITEFUNCTION, dir_callback); curl.setopt (Curl.Option.WRITEDATA, this); curl.setopt (Curl.Option.NOPROGRESS, 1L); curl.setopt (Curl.Option.PROGRESSFUNCTION, null); curl.setopt (Curl.Option.PROGRESSDATA, null); file_size = new HashTable (str_hash, int_equal); var res = curl.perform (); if (Curl.Code.ABORTED_BY_CALLBACK == res) { throw new IOError.CANCELLED ("Dir listing cancelled."); } else if (res != 0) { stderr.printf ("cURL performed: %d\n", res); } dirname = url; } public signal void progress (int dltotal, int dlnow); }