4 use MIME::Base64 qw/decode_base64 encode_base64 encoded_base64_length/;
5 use Digest::MD5 qw/md5_hex/;
6 use Gtk2 qw/-init -threads-init/;
8 use Glib qw/TRUE FALSE/;
9 use Number::Bytes::Human qw/format_bytes/;
10 use Time::HiRes qw/gettimeofday/;
11 use constant {STAT_DEVNO=>0, STAT_INO=>1, STAT_MODE=>2, STAT_LINKS=>3, STAT_UID=>4, STAT_GID=>5, STAT_DEVIDENT=>6, STAT_SIZE=>7, STAT_ATIME=>8, STAT_MTIME=>9, STAT_CTIME=>10, STAT_PREFBLKSZ=>11, STAT_BLOCKS=>12};
12 use constant {RXVT_CAPTURE=>1, RXVT_PASS=>()};
14 my $is_download_in_progress = 0;
15 my $is_upload_in_progress = 0;
22 my $last_used_folder = '.';
23 my $pbar_updated_time = 0;
28 'decode' => sub{ return decode_base64($_[0]); },
29 'closeline' => '^=====',
42 'wind' => Gtk2::Window->new,
43 'box1' => Gtk2::VBox->new,
44 'box3' => Gtk2::HBox->new,
45 'pbar' => Gtk2::ProgressBar->new,
46 'lab1' => Gtk2::Label->new,
48 'ibtn' => Gtk2::Button->new_with_label(" I "),
49 'iwnd' => Gtk2::Window->new,
50 'scrl' => Gtk2::ScrolledWindow->new,
51 'info' => Gtk2::TextView->new,
53 $gui->{'wind'}->add($gui->{'box1'});
54 $gui->{'wind'}->set_title("rxvt In-Band File Transfer");
55 $gui->{'wind'}->set_default_size(400, 50);
56 $gui->{'wind'}->signal_connect('delete_event' => \&hide_win);
57 $gui->{'lab1'}->set_alignment(0, 0.5);
58 $gui->{'lab1'}->set_justify('left');
59 $gui->{'ibtn'}->signal_connect('clicked' => \&show_log_win);
60 $gui->{'box1'}->pack_start($gui->{'pbar'}, TRUE, FALSE, 0);
61 $gui->{'box1'}->pack_start($gui->{'box3'}, TRUE, TRUE, 0);
62 $gui->{'box3'}->pack_start($gui->{'lab1'}, TRUE, TRUE, 0);
63 $gui->{'box3'}->pack_start($gui->{'ibtn'}, FALSE, FALSE, 0);
65 $gui->{'iwnd'}->add($gui->{'scrl'});
66 $gui->{'iwnd'}->set_title("rxvt In-Band File Transfer - Log");
67 $gui->{'iwnd'}->set_default_size(370, 450);
68 $gui->{'iwnd'}->signal_connect('delete_event' => \&hide_win);
69 $gui->{'scrl'}->set_border_width(5);
70 $gui->{'scrl'}->set_policy('automatic', 'automatic');
71 $gui->{'scrl'}->add($gui->{'info'});
72 $gui->{'info'}->set_editable(FALSE);
73 my $g_thread = threads->new(\&rungui);
79 my ($rxvt, $osc, $resp) = @_;
81 if($osc =~ /^inbandfiletransfer;(.+)/)
83 my ($action, $encoding, $filename, $size) = split /;/, $1;
85 if(grep {$encoding eq $_} keys %$Conv)
87 $data_encoding = $encoding;
89 if($action eq 'download')
91 $filename =~ s{.*/}{};
94 my $file_chooser = Gtk2::FileChooserDialog->new("Download", undef, 'save', 'gtk-cancel'=>'cancel', 'gtk-save'=>'ok');
95 $file_chooser->set_do_overwrite_confirmation(TRUE);
96 $file_chooser->set_current_folder($last_used_folder);
97 $file_chooser->set_current_name($filename);
98 my $response = $file_chooser->run();
101 $filename = $file_chooser->get_filename();
102 $last_used_folder = $file_chooser->get_current_folder();
104 $file_chooser->destroy();
107 if($response eq 'ok')
109 $rxvt->tt_write('y');
110 $actual_filename = $filename;
111 $expect_filesize = $size || -1;
112 add_log($actual_filename);
113 if(open $file_handle, '>', $actual_filename)
115 $is_download_in_progress = 1;
116 #$rxvt->enable('add_lines');
117 $digest_handle = Digest::MD5->new;
119 message("Download started");
124 error("File open error: $!");
129 $rxvt->tt_write('n');
132 elsif($action eq 'upload')
135 my $file_chooser = Gtk2::FileChooserDialog->new("Upload", undef, 'open', 'gtk-cancel'=>'cancel', 'gtk-open'=>'ok');
136 $file_chooser->set_current_folder($last_used_folder);
137 my $response = $file_chooser->run();
138 if($response eq 'ok')
140 $filename = $file_chooser->get_filename();
141 $last_used_folder = $file_chooser->get_current_folder();
143 $file_chooser->destroy();
146 if($response eq 'ok')
148 $rxvt->tt_write('y');
150 if(open($file_handle, '<', $filename))
152 $digest_handle = Digest::MD5->new;
153 $expect_filesize = (stat $filename)[STAT_SIZE];
155 $is_upload_in_progress = 1;
156 #$rxvt->enable('add_lines');
157 $rxvt->tt_write($filename . "\n");
158 message("Upload started");
160 send_a_file_chunk($rxvt);
164 error("File open error: $!");
169 $rxvt->tt_write('n');
174 error("Unknown action: $action");
179 error("Unknown encoding: $encoding");
188 my ($rxvt, $data) = @_;
189 if($is_download_in_progress)
191 $data = $buffer . $data;
192 my $closeline = $Conv->{$data_encoding}->{'closeline'};
193 while($data =~ s/(.*\n)//)
196 if($line =~ /$closeline/)
198 $is_download_in_progress = 0;
199 #$rxvt->disable('add_lines');
200 my $size = tell $file_handle;
201 my $md5 = uc $digest_handle->hexdigest;
203 $gui->{'pbar'}->set_fraction(1);
204 $gui->{'pbar'}->set_text(sprintf "%sB (100%%)", size4human($size));
208 if(close $file_handle)
210 message("Downloaded");
214 error("File close error: $!");
217 $rxvt->scr_add_lines($data);
222 if(defined $file_handle)
224 my $filedata = $Conv->{$data_encoding}->{'decode'}->($line);
225 my $part_size = tell($file_handle) + length($filedata);
229 if($pbar_updated_time + 0.5 < gettimeofday())
231 if($expect_filesize >= 0)
233 my $prc = $part_size / $expect_filesize;
234 $gui->{'pbar'}->set_fraction($prc);
235 $gui->{'pbar'}->set_text(sprintf "%sB (%d%%)", size4human($part_size), $prc*100);
239 $gui->{'pbar'}->pulse;
240 $gui->{'pbar'}->set_text(sprintf "%sB", size4human($part_size));
242 $pbar_updated_time = gettimeofday();
246 $digest_handle->add($filedata);
247 my $ok = print {$file_handle} $filedata;
250 error("File write error: $!");
260 elsif($is_upload_in_progress)
262 $data = $buffer . $data;
263 while($data =~ s/(.*\n)//)
266 if($line =~ /^=(.+)$/)
270 if($msg eq 'received')
272 my $sent_size = tell $file_handle;
273 my $prc = $sent_size / $expect_filesize;
275 $gui->{'pbar'}->set_fraction($prc);
276 $gui->{'pbar'}->set_text(sprintf "%sB (%d%%)", size4human($sent_size), $prc*100);
280 if($now < $chunk_sent + 1.0)
284 elsif($now > $chunk_sent + 1.8)
286 $chunk_size = int($chunk_size / 2);
288 send_a_file_chunk($rxvt);
292 print STDERR Dumper $msg;
297 $is_upload_in_progress = 0;
298 #$rxvt->disable('add_lines');
301 error("Upload is interruped");
318 sub send_a_file_chunk
320 if(defined $file_handle)
324 if(read($file_handle, $buf, $chunk_size*57))
326 $digest_handle->add($buf);
327 $rxvt->tt_write(encoded_base64_length($buf) . "\n");
328 $rxvt->tt_write(encode_base64($buf));
333 $is_upload_in_progress = 0;
334 #$rxvt->disable('add_lines');
335 my $md5 = uc $digest_handle->hexdigest;
338 if(eof($file_handle))
340 $rxvt->tt_write("0\n");
341 #$rxvt->tt_write(chr(0x04));
346 $rxvt->tt_write("\n");
347 error("File read error: $!");
358 upd_status('<span foreground="darkred"><b>'.$_[0].'</b></span>');
371 $gui->{'lab1'}->set_markup($_[0]);
378 my $buffer = $gui->{'info'}->get_buffer;
379 my (undef, $end) = $buffer->get_bounds;
380 $buffer->insert($end, $_[0] . "\n");
386 $gui->{'wind'}->show_all;
391 $gui->{'iwnd'}->show_all;
397 return Gtk2::EVENT_STOP;
411 Gtk2::Gdk::Threads->enter;
416 Gtk2::Gdk::Threads->leave;
421 Gtk2->main_iteration while Gtk2->events_pending;
432 $gui->{'pbar'}->set_fraction(0);
433 $gui->{'pbar'}->set_text("(0%)");
439 my ($n, $u) = format_bytes(int $_[0]) =~ /([\d\.,]+)(.*)/;