store attachments in holding area; commit to wiki on page save

This makes uploading a lot of attachments somewhat faster, because
the user does not need to wait for a long website refresh after each
upload. Still probably somewhat slow, since ikiwiki has to run for each
upload.

More importantly, this opens the door for integration of things like
the jquery file upload interface, which allow drag-n-drop and multiple
file uploads to be queued and then ran.

It uses rcs_commit_staged, which leaves out tla and mercurual which lack
that, but since rename, remove, autoindex, etc also use that, I think it's
fine for attachments to also depend on it.

The attachment list is currently broken; it does not look in the holding
area yet, and its links to the attached files won't work since they're not
yet in the wiki. previewing is also currently broken.

Work sponsored by TOVA.
master
Joey Hess 2011-06-14 13:30:09 -04:00
parent 4c96fce144
commit e08daac239
1 changed files with 55 additions and 25 deletions

View File

@ -116,9 +116,13 @@ sub formbuilder (@) {
my $filename=Encode::decode_utf8($q->param('attachment')); my $filename=Encode::decode_utf8($q->param('attachment'));
if (defined $filename && length $filename && if (defined $filename && length $filename &&
($form->submitted eq "Upload Attachment" || $form->submitted eq "Save Page")) { ($form->submitted eq "Upload Attachment" || $form->submitted eq "Save Page")) {
attachment_save($filename, $form, $q, $params{session}); attachment_store($filename, $form, $q, $params{session});
} }
elsif ($form->submitted eq "Insert Links") { if ($form->submitted eq "Save Page") {
attachments_save($form, $params{session});
}
if ($form->submitted eq "Insert Links") {
my $page=quotemeta(Encode::decode_utf8($q->param("page"))); my $page=quotemeta(Encode::decode_utf8($q->param("page")));
my $add=""; my $add="";
foreach my $f ($q->param("attachment_select")) { foreach my $f ($q->param("attachment_select")) {
@ -143,7 +147,14 @@ sub formbuilder (@) {
$form->tmpl_param("attachment_list" => [attachment_list($form->field('page'))]); $form->tmpl_param("attachment_list" => [attachment_list($form->field('page'))]);
} }
sub attachment_save { sub attachment_holding_dir {
my $page=shift;
return $config{wikistatedir}."/attachments/$page";
}
# Stores the attachment in a holding area, not yet in the wiki proper.
sub attachment_store {
my $filename=shift; my $filename=shift;
my $form=shift; my $form=shift;
my $q=shift; my $q=shift;
@ -167,29 +178,26 @@ sub attachment_save {
$filename=IkiWiki::basename($filename); $filename=IkiWiki::basename($filename);
$filename=~s/.*\\+(.+)/$1/; # hello, windows $filename=~s/.*\\+(.+)/$1/; # hello, windows
$filename=linkpage(IkiWiki::possibly_foolish_untaint(
# Check that the user is allowed to edit the attachment.
my $final_filename=linkpage(IkiWiki::possibly_foolish_untaint(
attachment_location($form->field('page')). attachment_location($form->field('page')).
$filename)); $filename));
if (IkiWiki::file_pruned($filename)) { if (IkiWiki::file_pruned($final_filename)) {
error(gettext("bad attachment filename")); error(gettext("bad attachment filename"));
} }
IkiWiki::check_canedit($final_filename, $q, $session);
# Check that the user is allowed to edit a page with the
# name of the attachment.
IkiWiki::check_canedit($filename, $q, $session);
# And that the attachment itself is acceptable. # And that the attachment itself is acceptable.
check_canattach($session, $filename, $tempfile); check_canattach($session, $final_filename, $tempfile);
# Needed for fast_file_copy and for rendering below. # Move the attachment into holding directory.
require IkiWiki::Render;
# Move the attachment into place.
# Try to use a fast rename; fall back to copying. # Try to use a fast rename; fall back to copying.
IkiWiki::prep_writefile($filename, $config{srcdir}); my $dest=attachment_holding_dir($form->field('page'));
unlink($config{srcdir}."/".$filename); IkiWiki::prep_writefile($filename, $dest);
if (rename($tempfile, $config{srcdir}."/".$filename)) { unlink($dest."/".$filename);
if (rename($tempfile, $dest."/".$filename)) {
# The temp file has tight permissions; loosen up. # The temp file has tight permissions; loosen up.
chmod(0666 & ~umask, $config{srcdir}."/".$filename); chmod(0666 & ~umask, $dest."/".$filename);
} }
else { else {
my $fh=$q->upload('attachment'); my $fh=$q->upload('attachment');
@ -204,19 +212,41 @@ sub attachment_save {
} }
} }
binmode($fh); binmode($fh);
writefile($filename, $config{srcdir}, undef, 1, sub { # Needed for fast_file_copy.
require IkiWiki::Render;
writefile($filename, $dest, undef, 1, sub {
IkiWiki::fast_file_copy($tempfile, $filename, $fh, @_); IkiWiki::fast_file_copy($tempfile, $filename, $fh, @_);
}); });
} }
}
# Check the attachment in and trigger a wiki refresh. # Save all stored attachments for a page.
sub attachments_save {
my $form=shift;
my $session=shift;
# Move attachments out of holding directory.
my @attachments;
my $dir=attachment_holding_dir($form->field('page'));
foreach my $filename (glob("$dir/*")) {
next unless -f $filename;
my $dest=$config{srcdir}."/".
linkpage(IkiWiki::possibly_foolish_untaint(
attachment_location($form->field('page')).
$filename));
unlink($dest);
rename($filename, $dest);
push @attachments, $dest;
}
return unless @attachments;
rmdir($dir);
# Check the attachments in and trigger a wiki refresh.
if ($config{rcs}) { if ($config{rcs}) {
IkiWiki::rcs_add($filename); IkiWiki::rcs_add($_) foreach @attachments;
IkiWiki::disable_commit_hook(); IkiWiki::disable_commit_hook();
IkiWiki::rcs_commit( IkiWiki::rcs_commit_staged(
file => $filename,
message => gettext("attachment upload"), message => gettext("attachment upload"),
token => IkiWiki::rcs_prepedit($filename),
session => $session, session => $session,
); );
IkiWiki::enable_commit_hook(); IkiWiki::enable_commit_hook();