store attachments in holding area; commit to wiki on page save
authorJoey Hess <joey@kitenet.net>
Tue, 14 Jun 2011 17:30:09 +0000 (13:30 -0400)
committerJoey Hess <joey@kitenet.net>
Tue, 14 Jun 2011 17:34:36 +0000 (13:34 -0400)
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.

IkiWiki/Plugin/attachment.pm

index c8cabe18ce23530ad91a1ad904f1b19e9521a120..b7ea1f312793b1ea61e44c890c720b537f6d0a6b 100644 (file)
@@ -116,9 +116,13 @@ sub formbuilder (@) {
        my $filename=Encode::decode_utf8($q->param('attachment'));
        if (defined $filename && length $filename &&
             ($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 $add="";
                foreach my $f ($q->param("attachment_select")) {
@@ -143,7 +147,14 @@ sub formbuilder (@) {
        $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 $form=shift;
        my $q=shift;
@@ -167,29 +178,26 @@ sub attachment_save {
 
        $filename=IkiWiki::basename($filename);
        $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')).
-                       $filename));
-       if (IkiWiki::file_pruned($filename)) {
+               $filename));
+       if (IkiWiki::file_pruned($final_filename)) {
                error(gettext("bad attachment filename"));
        }
-               
-       # Check that the user is allowed to edit a page with the
-       # name of the attachment.
-       IkiWiki::check_canedit($filename, $q, $session);
+       IkiWiki::check_canedit($final_filename, $q, $session);
        # 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.
-       require IkiWiki::Render;
-
-       # Move the attachment into place.
+       # Move the attachment into holding directory.
        # Try to use a fast rename; fall back to copying.
-       IkiWiki::prep_writefile($filename, $config{srcdir});
-       unlink($config{srcdir}."/".$filename);
-       if (rename($tempfile, $config{srcdir}."/".$filename)) {
+       my $dest=attachment_holding_dir($form->field('page'));
+       IkiWiki::prep_writefile($filename, $dest);
+       unlink($dest."/".$filename);
+       if (rename($tempfile, $dest."/".$filename)) {
                # The temp file has tight permissions; loosen up.
-               chmod(0666 & ~umask, $config{srcdir}."/".$filename);
+               chmod(0666 & ~umask, $dest."/".$filename);
        }
        else {
                my $fh=$q->upload('attachment');
@@ -204,19 +212,41 @@ sub attachment_save {
                        }
                }
                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, @_);
                });
        }
+}
+
+# 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 attachment in and trigger a wiki refresh.
+       # Check the attachments in and trigger a wiki refresh.
        if ($config{rcs}) {
-               IkiWiki::rcs_add($filename);
+               IkiWiki::rcs_add($_) foreach @attachments;
                IkiWiki::disable_commit_hook();
-               IkiWiki::rcs_commit(
-                       file => $filename,
+               IkiWiki::rcs_commit_staged(
                        message => gettext("attachment upload"),
-                       token => IkiWiki::rcs_prepedit($filename),
                        session => $session,
                );
                IkiWiki::enable_commit_hook();