2011-07-03 17:53:02 +00:00
|
|
|
/*
|
|
|
|
Copyright 2011 Google Inc.
|
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
limitations under the License.
|
|
|
|
*/
|
|
|
|
|
2011-10-18 18:12:01 +00:00
|
|
|
package server
|
2011-07-03 17:53:02 +00:00
|
|
|
|
|
|
|
import (
|
pkg/server: add files "zipper" to DownloadHandler
We want to add a feature for clients (the web UI), where they can select
a bunch of files and ask the server for a zip archive of all these files.
This CL modifies the DownloadHandler so it does exactly that upon
reception of a POST request with a query parameter of the form
files=sha1-foo,sha1-bar,sha1-baz
This CL also adds a new button to the contextual sidebar of the web UI,
that takes care of sending the download request to the server.
known limitations: only permanodes with file as camliContent are
accepted as a valid selection (i.e. no sets, or static-dirs, etc) for
now.
Implementation detail:
We're creating an ephemeral DOM form on the fly to send the request.
The reason is: if we sent it as a Go http request, we'd have to read
the response manually and then we'd have no way of writing it to disk.
If we did it with an xhr, we could write the response to disk by
creating a File or Blob and then using URL.createObjectURL(), but we'd
have to keep the response in memory while doing so, which is
unacceptable for large enough archives.
Fixes #899
Change-Id: I104f7c5bd10ab3369e28d33752380dd12b5b3e6b
2017-03-06 20:02:19 +00:00
|
|
|
"archive/zip"
|
2011-07-03 17:53:02 +00:00
|
|
|
"fmt"
|
|
|
|
"io"
|
2015-02-05 05:07:59 +00:00
|
|
|
"log"
|
Update from r60 to [almost] Go 1.
A lot is still broken, but most stuff at least compiles now.
The directory tree has been rearranged now too. Go libraries are now
under "pkg". Fully qualified, they are e.g. "camlistore.org/pkg/jsonsign".
The go tool cannot yet fetch from arbitrary domains, but discussion is
happening now on which mechanism to use to allow that.
For now, put the camlistore root under $GOPATH/src. Typically $GOPATH
is $HOME, so Camlistore should be at $HOME/src/camlistore.org.
Then you can:
$ go build ./server/camlistored
... etc
The build.pl script is currently disabled. It'll be resurrected at
some point, but with a very different role (helping create a fake
GOPATH and running the go build command, if things are installed at
the wrong place, and/or running fileembed generators).
Many things are certainly broken.
Many things are disabled. (MySQL, all indexing, etc).
Many things need to be moved into
camlistore.org/third_party/{code.google.com,github.com} and updated
from their r60 to Go 1 versions, where applicable.
The GoMySQL stuff should be updated to use database/sql and the ziutek
library implementing database/sql/driver.
Help wanted.
Change-Id: If71217dc5c8f0e70dbe46e9504ca5131c6eeacde
2012-02-19 05:53:06 +00:00
|
|
|
"net/http"
|
2015-02-05 05:07:59 +00:00
|
|
|
"os"
|
pkg/server: add files "zipper" to DownloadHandler
We want to add a feature for clients (the web UI), where they can select
a bunch of files and ask the server for a zip archive of all these files.
This CL modifies the DownloadHandler so it does exactly that upon
reception of a POST request with a query parameter of the form
files=sha1-foo,sha1-bar,sha1-baz
This CL also adds a new button to the contextual sidebar of the web UI,
that takes care of sending the download request to the server.
known limitations: only permanodes with file as camliContent are
accepted as a valid selection (i.e. no sets, or static-dirs, etc) for
now.
Implementation detail:
We're creating an ephemeral DOM form on the fly to send the request.
The reason is: if we sent it as a Go http request, we'd have to read
the response manually and then we'd have no way of writing it to disk.
If we did it with an xhr, we could write the response to disk by
creating a File or Blob and then using URL.createObjectURL(), but we'd
have to keep the response in memory while doing so, which is
unacceptable for large enough archives.
Fixes #899
Change-Id: I104f7c5bd10ab3369e28d33752380dd12b5b3e6b
2017-03-06 20:02:19 +00:00
|
|
|
"regexp"
|
2015-04-13 17:45:54 +00:00
|
|
|
"strings"
|
2013-01-25 23:18:32 +00:00
|
|
|
"time"
|
2011-07-03 17:53:02 +00:00
|
|
|
|
2013-08-04 02:54:30 +00:00
|
|
|
"camlistore.org/pkg/blob"
|
Update from r60 to [almost] Go 1.
A lot is still broken, but most stuff at least compiles now.
The directory tree has been rearranged now too. Go libraries are now
under "pkg". Fully qualified, they are e.g. "camlistore.org/pkg/jsonsign".
The go tool cannot yet fetch from arbitrary domains, but discussion is
happening now on which mechanism to use to allow that.
For now, put the camlistore root under $GOPATH/src. Typically $GOPATH
is $HOME, so Camlistore should be at $HOME/src/camlistore.org.
Then you can:
$ go build ./server/camlistored
... etc
The build.pl script is currently disabled. It'll be resurrected at
some point, but with a very different role (helping create a fake
GOPATH and running the go build command, if things are installed at
the wrong place, and/or running fileembed generators).
Many things are certainly broken.
Many things are disabled. (MySQL, all indexing, etc).
Many things need to be moved into
camlistore.org/third_party/{code.google.com,github.com} and updated
from their r60 to Go 1 versions, where applicable.
The GoMySQL stuff should be updated to use database/sql and the ziutek
library implementing database/sql/driver.
Help wanted.
Change-Id: If71217dc5c8f0e70dbe46e9504ca5131c6eeacde
2012-02-19 05:53:06 +00:00
|
|
|
"camlistore.org/pkg/blobserver"
|
pkg/server: add files "zipper" to DownloadHandler
We want to add a feature for clients (the web UI), where they can select
a bunch of files and ask the server for a zip archive of all these files.
This CL modifies the DownloadHandler so it does exactly that upon
reception of a POST request with a query parameter of the form
files=sha1-foo,sha1-bar,sha1-baz
This CL also adds a new button to the contextual sidebar of the web UI,
that takes care of sending the download request to the server.
known limitations: only permanodes with file as camliContent are
accepted as a valid selection (i.e. no sets, or static-dirs, etc) for
now.
Implementation detail:
We're creating an ephemeral DOM form on the fly to send the request.
The reason is: if we sent it as a Go http request, we'd have to read
the response manually and then we'd have no way of writing it to disk.
If we did it with an xhr, we could write the response to disk by
creating a File or Blob and then using URL.createObjectURL(), but we'd
have to keep the response in memory while doing so, which is
unacceptable for large enough archives.
Fixes #899
Change-Id: I104f7c5bd10ab3369e28d33752380dd12b5b3e6b
2017-03-06 20:02:19 +00:00
|
|
|
"camlistore.org/pkg/httputil"
|
Update from r60 to [almost] Go 1.
A lot is still broken, but most stuff at least compiles now.
The directory tree has been rearranged now too. Go libraries are now
under "pkg". Fully qualified, they are e.g. "camlistore.org/pkg/jsonsign".
The go tool cannot yet fetch from arbitrary domains, but discussion is
happening now on which mechanism to use to allow that.
For now, put the camlistore root under $GOPATH/src. Typically $GOPATH
is $HOME, so Camlistore should be at $HOME/src/camlistore.org.
Then you can:
$ go build ./server/camlistored
... etc
The build.pl script is currently disabled. It'll be resurrected at
some point, but with a very different role (helping create a fake
GOPATH and running the go build command, if things are installed at
the wrong place, and/or running fileembed generators).
Many things are certainly broken.
Many things are disabled. (MySQL, all indexing, etc).
Many things need to be moved into
camlistore.org/third_party/{code.google.com,github.com} and updated
from their r60 to Go 1 versions, where applicable.
The GoMySQL stuff should be updated to use database/sql and the ziutek
library implementing database/sql/driver.
Help wanted.
Change-Id: If71217dc5c8f0e70dbe46e9504ca5131c6eeacde
2012-02-19 05:53:06 +00:00
|
|
|
"camlistore.org/pkg/magic"
|
|
|
|
"camlistore.org/pkg/schema"
|
2015-02-05 05:07:59 +00:00
|
|
|
"camlistore.org/pkg/search"
|
2016-02-05 14:56:16 +00:00
|
|
|
"go4.org/readerutil"
|
2016-04-22 04:34:24 +00:00
|
|
|
"golang.org/x/net/context"
|
2011-07-03 17:53:02 +00:00
|
|
|
)
|
|
|
|
|
pkg/server: add files "zipper" to DownloadHandler
We want to add a feature for clients (the web UI), where they can select
a bunch of files and ask the server for a zip archive of all these files.
This CL modifies the DownloadHandler so it does exactly that upon
reception of a POST request with a query parameter of the form
files=sha1-foo,sha1-bar,sha1-baz
This CL also adds a new button to the contextual sidebar of the web UI,
that takes care of sending the download request to the server.
known limitations: only permanodes with file as camliContent are
accepted as a valid selection (i.e. no sets, or static-dirs, etc) for
now.
Implementation detail:
We're creating an ephemeral DOM form on the fly to send the request.
The reason is: if we sent it as a Go http request, we'd have to read
the response manually and then we'd have no way of writing it to disk.
If we did it with an xhr, we could write the response to disk by
creating a File or Blob and then using URL.createObjectURL(), but we'd
have to keep the response in memory while doing so, which is
unacceptable for large enough archives.
Fixes #899
Change-Id: I104f7c5bd10ab3369e28d33752380dd12b5b3e6b
2017-03-06 20:02:19 +00:00
|
|
|
const (
|
|
|
|
oneYear = 365 * 86400 * time.Second
|
|
|
|
downloadTimeLayout = "20060102150405"
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
debugPack = strings.Contains(os.Getenv("CAMLI_DEBUG_X"), "packserve")
|
2013-01-25 23:18:32 +00:00
|
|
|
|
pkg/server: add files "zipper" to DownloadHandler
We want to add a feature for clients (the web UI), where they can select
a bunch of files and ask the server for a zip archive of all these files.
This CL modifies the DownloadHandler so it does exactly that upon
reception of a POST request with a query parameter of the form
files=sha1-foo,sha1-bar,sha1-baz
This CL also adds a new button to the contextual sidebar of the web UI,
that takes care of sending the download request to the server.
known limitations: only permanodes with file as camliContent are
accepted as a valid selection (i.e. no sets, or static-dirs, etc) for
now.
Implementation detail:
We're creating an ephemeral DOM form on the fly to send the request.
The reason is: if we sent it as a Go http request, we'd have to read
the response manually and then we'd have no way of writing it to disk.
If we did it with an xhr, we could write the response to disk by
creating a File or Blob and then using URL.createObjectURL(), but we'd
have to keep the response in memory while doing so, which is
unacceptable for large enough archives.
Fixes #899
Change-Id: I104f7c5bd10ab3369e28d33752380dd12b5b3e6b
2017-03-06 20:02:19 +00:00
|
|
|
// Download URL suffix:
|
|
|
|
// $1: blobref (checked in download handler)
|
|
|
|
// $2: TODO. optional "/filename" to be sent as recommended download name,
|
|
|
|
// if sane looking
|
|
|
|
downloadPattern = regexp.MustCompile(`^download/([^/]+)(/.*)?$`)
|
|
|
|
)
|
2015-04-13 17:45:54 +00:00
|
|
|
|
2011-07-03 17:53:02 +00:00
|
|
|
type DownloadHandler struct {
|
2015-02-05 05:07:59 +00:00
|
|
|
Fetcher blob.Fetcher
|
|
|
|
Cache blobserver.Storage
|
|
|
|
|
|
|
|
// Search is optional. If present, it's used to map a fileref
|
|
|
|
// to a wholeref, if the Fetcher is of a type that knows how
|
|
|
|
// to get at a wholeref more efficiently. (e.g. blobpacked)
|
|
|
|
Search *search.Handler
|
|
|
|
|
2015-02-02 09:41:35 +00:00
|
|
|
ForceMIME string // optional
|
2011-07-03 17:53:02 +00:00
|
|
|
}
|
|
|
|
|
2014-03-14 19:11:08 +00:00
|
|
|
func (dh *DownloadHandler) blobSource() blob.Fetcher {
|
|
|
|
return dh.Fetcher // TODO: use dh.Cache
|
2011-07-03 17:53:02 +00:00
|
|
|
}
|
|
|
|
|
2015-02-02 09:41:35 +00:00
|
|
|
type fileInfo struct {
|
pkg/server: add files "zipper" to DownloadHandler
We want to add a feature for clients (the web UI), where they can select
a bunch of files and ask the server for a zip archive of all these files.
This CL modifies the DownloadHandler so it does exactly that upon
reception of a POST request with a query parameter of the form
files=sha1-foo,sha1-bar,sha1-baz
This CL also adds a new button to the contextual sidebar of the web UI,
that takes care of sending the download request to the server.
known limitations: only permanodes with file as camliContent are
accepted as a valid selection (i.e. no sets, or static-dirs, etc) for
now.
Implementation detail:
We're creating an ephemeral DOM form on the fly to send the request.
The reason is: if we sent it as a Go http request, we'd have to read
the response manually and then we'd have no way of writing it to disk.
If we did it with an xhr, we could write the response to disk by
creating a File or Blob and then using URL.createObjectURL(), but we'd
have to keep the response in memory while doing so, which is
unacceptable for large enough archives.
Fixes #899
Change-Id: I104f7c5bd10ab3369e28d33752380dd12b5b3e6b
2017-03-06 20:02:19 +00:00
|
|
|
mime string
|
|
|
|
name string
|
|
|
|
size int64
|
|
|
|
modtime time.Time
|
|
|
|
rs io.ReadSeeker
|
|
|
|
close func() error // release the rs
|
|
|
|
whyNot string // for testing, why fileInfoPacked failed.
|
2015-02-02 09:41:35 +00:00
|
|
|
}
|
|
|
|
|
pkg/server: add files "zipper" to DownloadHandler
We want to add a feature for clients (the web UI), where they can select
a bunch of files and ask the server for a zip archive of all these files.
This CL modifies the DownloadHandler so it does exactly that upon
reception of a POST request with a query parameter of the form
files=sha1-foo,sha1-bar,sha1-baz
This CL also adds a new button to the contextual sidebar of the web UI,
that takes care of sending the download request to the server.
known limitations: only permanodes with file as camliContent are
accepted as a valid selection (i.e. no sets, or static-dirs, etc) for
now.
Implementation detail:
We're creating an ephemeral DOM form on the fly to send the request.
The reason is: if we sent it as a Go http request, we'd have to read
the response manually and then we'd have no way of writing it to disk.
If we did it with an xhr, we could write the response to disk by
creating a File or Blob and then using URL.createObjectURL(), but we'd
have to keep the response in memory while doing so, which is
unacceptable for large enough archives.
Fixes #899
Change-Id: I104f7c5bd10ab3369e28d33752380dd12b5b3e6b
2017-03-06 20:02:19 +00:00
|
|
|
func (dh *DownloadHandler) fileInfo(r *http.Request, file blob.Ref) (fi fileInfo, packed bool, err error) {
|
2016-04-22 04:34:24 +00:00
|
|
|
ctx := context.TODO()
|
|
|
|
|
2015-02-05 05:07:59 +00:00
|
|
|
// Fast path for blobpacked.
|
pkg/server: add files "zipper" to DownloadHandler
We want to add a feature for clients (the web UI), where they can select
a bunch of files and ask the server for a zip archive of all these files.
This CL modifies the DownloadHandler so it does exactly that upon
reception of a POST request with a query parameter of the form
files=sha1-foo,sha1-bar,sha1-baz
This CL also adds a new button to the contextual sidebar of the web UI,
that takes care of sending the download request to the server.
known limitations: only permanodes with file as camliContent are
accepted as a valid selection (i.e. no sets, or static-dirs, etc) for
now.
Implementation detail:
We're creating an ephemeral DOM form on the fly to send the request.
The reason is: if we sent it as a Go http request, we'd have to read
the response manually and then we'd have no way of writing it to disk.
If we did it with an xhr, we could write the response to disk by
creating a File or Blob and then using URL.createObjectURL(), but we'd
have to keep the response in memory while doing so, which is
unacceptable for large enough archives.
Fixes #899
Change-Id: I104f7c5bd10ab3369e28d33752380dd12b5b3e6b
2017-03-06 20:02:19 +00:00
|
|
|
fi, ok := fileInfoPacked(ctx, dh.Search, dh.Fetcher, r, file)
|
2015-04-13 17:45:54 +00:00
|
|
|
if debugPack {
|
|
|
|
log.Printf("download.go: fileInfoPacked: ok=%v, %+v", ok, fi)
|
|
|
|
}
|
2015-02-05 05:07:59 +00:00
|
|
|
if ok {
|
2015-04-13 19:02:41 +00:00
|
|
|
return fi, true, nil
|
2015-02-05 05:07:59 +00:00
|
|
|
}
|
2015-02-02 09:41:35 +00:00
|
|
|
fr, err := schema.NewFileReader(dh.blobSource(), file)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
mime := dh.ForceMIME
|
|
|
|
if mime == "" {
|
|
|
|
mime = magic.MIMETypeFromReaderAt(fr)
|
|
|
|
}
|
|
|
|
if mime == "" {
|
|
|
|
mime = "application/octet-stream"
|
|
|
|
}
|
|
|
|
return fileInfo{
|
pkg/server: add files "zipper" to DownloadHandler
We want to add a feature for clients (the web UI), where they can select
a bunch of files and ask the server for a zip archive of all these files.
This CL modifies the DownloadHandler so it does exactly that upon
reception of a POST request with a query parameter of the form
files=sha1-foo,sha1-bar,sha1-baz
This CL also adds a new button to the contextual sidebar of the web UI,
that takes care of sending the download request to the server.
known limitations: only permanodes with file as camliContent are
accepted as a valid selection (i.e. no sets, or static-dirs, etc) for
now.
Implementation detail:
We're creating an ephemeral DOM form on the fly to send the request.
The reason is: if we sent it as a Go http request, we'd have to read
the response manually and then we'd have no way of writing it to disk.
If we did it with an xhr, we could write the response to disk by
creating a File or Blob and then using URL.createObjectURL(), but we'd
have to keep the response in memory while doing so, which is
unacceptable for large enough archives.
Fixes #899
Change-Id: I104f7c5bd10ab3369e28d33752380dd12b5b3e6b
2017-03-06 20:02:19 +00:00
|
|
|
mime: mime,
|
|
|
|
name: fr.FileName(),
|
|
|
|
size: fr.Size(),
|
|
|
|
modtime: fr.ModTime(),
|
|
|
|
rs: fr,
|
|
|
|
close: fr.Close,
|
2015-04-13 19:02:41 +00:00
|
|
|
}, false, nil
|
2015-02-02 09:41:35 +00:00
|
|
|
}
|
|
|
|
|
2015-02-05 05:07:59 +00:00
|
|
|
// Fast path for blobpacked.
|
pkg/server: add files "zipper" to DownloadHandler
We want to add a feature for clients (the web UI), where they can select
a bunch of files and ask the server for a zip archive of all these files.
This CL modifies the DownloadHandler so it does exactly that upon
reception of a POST request with a query parameter of the form
files=sha1-foo,sha1-bar,sha1-baz
This CL also adds a new button to the contextual sidebar of the web UI,
that takes care of sending the download request to the server.
known limitations: only permanodes with file as camliContent are
accepted as a valid selection (i.e. no sets, or static-dirs, etc) for
now.
Implementation detail:
We're creating an ephemeral DOM form on the fly to send the request.
The reason is: if we sent it as a Go http request, we'd have to read
the response manually and then we'd have no way of writing it to disk.
If we did it with an xhr, we could write the response to disk by
creating a File or Blob and then using URL.createObjectURL(), but we'd
have to keep the response in memory while doing so, which is
unacceptable for large enough archives.
Fixes #899
Change-Id: I104f7c5bd10ab3369e28d33752380dd12b5b3e6b
2017-03-06 20:02:19 +00:00
|
|
|
func fileInfoPacked(ctx context.Context, sh *search.Handler, src blob.Fetcher, r *http.Request, file blob.Ref) (packFileInfo fileInfo, ok bool) {
|
2015-04-13 17:45:54 +00:00
|
|
|
if sh == nil {
|
|
|
|
return fileInfo{whyNot: "no search"}, false
|
2015-02-05 05:07:59 +00:00
|
|
|
}
|
2015-04-13 17:45:54 +00:00
|
|
|
wf, ok := src.(blobserver.WholeRefFetcher)
|
2015-02-05 05:07:59 +00:00
|
|
|
if !ok {
|
2015-04-13 17:45:54 +00:00
|
|
|
return fileInfo{whyNot: "fetcher type"}, false
|
2015-02-05 05:07:59 +00:00
|
|
|
}
|
pkg/server: add files "zipper" to DownloadHandler
We want to add a feature for clients (the web UI), where they can select
a bunch of files and ask the server for a zip archive of all these files.
This CL modifies the DownloadHandler so it does exactly that upon
reception of a POST request with a query parameter of the form
files=sha1-foo,sha1-bar,sha1-baz
This CL also adds a new button to the contextual sidebar of the web UI,
that takes care of sending the download request to the server.
known limitations: only permanodes with file as camliContent are
accepted as a valid selection (i.e. no sets, or static-dirs, etc) for
now.
Implementation detail:
We're creating an ephemeral DOM form on the fly to send the request.
The reason is: if we sent it as a Go http request, we'd have to read
the response manually and then we'd have no way of writing it to disk.
If we did it with an xhr, we could write the response to disk by
creating a File or Blob and then using URL.createObjectURL(), but we'd
have to keep the response in memory while doing so, which is
unacceptable for large enough archives.
Fixes #899
Change-Id: I104f7c5bd10ab3369e28d33752380dd12b5b3e6b
2017-03-06 20:02:19 +00:00
|
|
|
if r != nil && r.Header.Get("Range") != "" {
|
2015-02-05 05:07:59 +00:00
|
|
|
// TODO: not handled yet. Maybe not even important,
|
|
|
|
// considering rarity.
|
2015-04-13 17:45:54 +00:00
|
|
|
return fileInfo{whyNot: "range header"}, false
|
2015-02-05 05:07:59 +00:00
|
|
|
}
|
2016-04-22 04:34:24 +00:00
|
|
|
des, err := sh.Describe(ctx, &search.DescribeRequest{BlobRef: file})
|
2015-02-05 05:07:59 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Printf("ui: fileInfoPacked: skipping fast path due to error from search: %v", err)
|
2015-04-13 17:45:54 +00:00
|
|
|
return fileInfo{whyNot: "search error"}, false
|
2015-02-05 05:07:59 +00:00
|
|
|
}
|
|
|
|
db, ok := des.Meta[file.String()]
|
|
|
|
if !ok || db.File == nil {
|
2015-04-13 17:45:54 +00:00
|
|
|
return fileInfo{whyNot: "search index doesn't know file"}, false
|
2015-02-05 05:07:59 +00:00
|
|
|
}
|
|
|
|
fi := db.File
|
|
|
|
if !fi.WholeRef.Valid() {
|
2015-04-13 17:45:54 +00:00
|
|
|
return fileInfo{whyNot: "no wholeref from search index"}, false
|
2015-02-05 05:07:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
offset := int64(0)
|
|
|
|
rc, wholeSize, err := wf.OpenWholeRef(fi.WholeRef, offset)
|
|
|
|
if err == os.ErrNotExist {
|
2015-04-13 17:45:54 +00:00
|
|
|
return fileInfo{whyNot: "WholeRefFetcher returned ErrNotexist"}, false
|
2015-02-05 05:07:59 +00:00
|
|
|
}
|
|
|
|
if wholeSize != fi.Size {
|
|
|
|
log.Printf("ui: fileInfoPacked: OpenWholeRef size %d != index size %d; ignoring fast path", wholeSize, fi.Size)
|
2015-04-13 17:45:54 +00:00
|
|
|
return fileInfo{whyNot: "WholeRefFetcher and index don't agree"}, false
|
2015-02-05 05:07:59 +00:00
|
|
|
}
|
|
|
|
if err != nil {
|
2015-04-13 17:45:54 +00:00
|
|
|
log.Printf("ui: fileInfoPacked: skipping fast path due to error from WholeRefFetcher (%T): %v", src, err)
|
|
|
|
return fileInfo{whyNot: "WholeRefFetcher error"}, false
|
2015-02-05 05:07:59 +00:00
|
|
|
}
|
pkg/server: add files "zipper" to DownloadHandler
We want to add a feature for clients (the web UI), where they can select
a bunch of files and ask the server for a zip archive of all these files.
This CL modifies the DownloadHandler so it does exactly that upon
reception of a POST request with a query parameter of the form
files=sha1-foo,sha1-bar,sha1-baz
This CL also adds a new button to the contextual sidebar of the web UI,
that takes care of sending the download request to the server.
known limitations: only permanodes with file as camliContent are
accepted as a valid selection (i.e. no sets, or static-dirs, etc) for
now.
Implementation detail:
We're creating an ephemeral DOM form on the fly to send the request.
The reason is: if we sent it as a Go http request, we'd have to read
the response manually and then we'd have no way of writing it to disk.
If we did it with an xhr, we could write the response to disk by
creating a File or Blob and then using URL.createObjectURL(), but we'd
have to keep the response in memory while doing so, which is
unacceptable for large enough archives.
Fixes #899
Change-Id: I104f7c5bd10ab3369e28d33752380dd12b5b3e6b
2017-03-06 20:02:19 +00:00
|
|
|
modtime := fi.ModTime
|
|
|
|
if modtime.IsAnyZero() {
|
|
|
|
modtime = fi.Time
|
|
|
|
}
|
2015-02-05 05:07:59 +00:00
|
|
|
return fileInfo{
|
pkg/server: add files "zipper" to DownloadHandler
We want to add a feature for clients (the web UI), where they can select
a bunch of files and ask the server for a zip archive of all these files.
This CL modifies the DownloadHandler so it does exactly that upon
reception of a POST request with a query parameter of the form
files=sha1-foo,sha1-bar,sha1-baz
This CL also adds a new button to the contextual sidebar of the web UI,
that takes care of sending the download request to the server.
known limitations: only permanodes with file as camliContent are
accepted as a valid selection (i.e. no sets, or static-dirs, etc) for
now.
Implementation detail:
We're creating an ephemeral DOM form on the fly to send the request.
The reason is: if we sent it as a Go http request, we'd have to read
the response manually and then we'd have no way of writing it to disk.
If we did it with an xhr, we could write the response to disk by
creating a File or Blob and then using URL.createObjectURL(), but we'd
have to keep the response in memory while doing so, which is
unacceptable for large enough archives.
Fixes #899
Change-Id: I104f7c5bd10ab3369e28d33752380dd12b5b3e6b
2017-03-06 20:02:19 +00:00
|
|
|
mime: fi.MIMEType,
|
|
|
|
name: fi.FileName,
|
|
|
|
size: fi.Size,
|
|
|
|
modtime: modtime.Time(),
|
|
|
|
rs: readerutil.NewFakeSeeker(rc, fi.Size-offset),
|
|
|
|
close: rc.Close,
|
2015-02-05 05:07:59 +00:00
|
|
|
}, true
|
|
|
|
}
|
|
|
|
|
pkg/server: add files "zipper" to DownloadHandler
We want to add a feature for clients (the web UI), where they can select
a bunch of files and ask the server for a zip archive of all these files.
This CL modifies the DownloadHandler so it does exactly that upon
reception of a POST request with a query parameter of the form
files=sha1-foo,sha1-bar,sha1-baz
This CL also adds a new button to the contextual sidebar of the web UI,
that takes care of sending the download request to the server.
known limitations: only permanodes with file as camliContent are
accepted as a valid selection (i.e. no sets, or static-dirs, etc) for
now.
Implementation detail:
We're creating an ephemeral DOM form on the fly to send the request.
The reason is: if we sent it as a Go http request, we'd have to read
the response manually and then we'd have no way of writing it to disk.
If we did it with an xhr, we could write the response to disk by
creating a File or Blob and then using URL.createObjectURL(), but we'd
have to keep the response in memory while doing so, which is
unacceptable for large enough archives.
Fixes #899
Change-Id: I104f7c5bd10ab3369e28d33752380dd12b5b3e6b
2017-03-06 20:02:19 +00:00
|
|
|
// ServeHTTP answers the following queries:
|
|
|
|
//
|
|
|
|
// POST:
|
|
|
|
// ?files=sha1-foo,sha1-bar,sha1-baz
|
|
|
|
// Creates a zip archive of the provided files and serves it in the response.
|
|
|
|
//
|
|
|
|
// GET:
|
|
|
|
// /<file-schema-blobref>
|
|
|
|
// Serves the file described by the requested file schema blobref.
|
|
|
|
func (dh *DownloadHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
|
|
if r.Method == "POST" {
|
|
|
|
dh.serveZip(w, r)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
suffix := httputil.PathSuffix(r)
|
|
|
|
m := downloadPattern.FindStringSubmatch(suffix)
|
|
|
|
if m == nil {
|
|
|
|
httputil.ErrorRouting(w, r)
|
2011-07-03 17:53:02 +00:00
|
|
|
return
|
|
|
|
}
|
pkg/server: add files "zipper" to DownloadHandler
We want to add a feature for clients (the web UI), where they can select
a bunch of files and ask the server for a zip archive of all these files.
This CL modifies the DownloadHandler so it does exactly that upon
reception of a POST request with a query parameter of the form
files=sha1-foo,sha1-bar,sha1-baz
This CL also adds a new button to the contextual sidebar of the web UI,
that takes care of sending the download request to the server.
known limitations: only permanodes with file as camliContent are
accepted as a valid selection (i.e. no sets, or static-dirs, etc) for
now.
Implementation detail:
We're creating an ephemeral DOM form on the fly to send the request.
The reason is: if we sent it as a Go http request, we'd have to read
the response manually and then we'd have no way of writing it to disk.
If we did it with an xhr, we could write the response to disk by
creating a File or Blob and then using URL.createObjectURL(), but we'd
have to keep the response in memory while doing so, which is
unacceptable for large enough archives.
Fixes #899
Change-Id: I104f7c5bd10ab3369e28d33752380dd12b5b3e6b
2017-03-06 20:02:19 +00:00
|
|
|
file, ok := blob.Parse(m[1])
|
|
|
|
if !ok {
|
|
|
|
http.Error(w, "Invalid blobref", http.StatusBadRequest)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
// TODO(mpl): make use of m[2] (the optional filename).
|
|
|
|
dh.ServeFile(w, r, file)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (dh *DownloadHandler) ServeFile(w http.ResponseWriter, r *http.Request, file blob.Ref) {
|
|
|
|
if r.Method != "GET" && r.Method != "HEAD" {
|
|
|
|
http.Error(w, "Invalid download method", http.StatusBadRequest)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if r.Header.Get("If-Modified-Since") != "" {
|
2013-01-25 23:18:32 +00:00
|
|
|
// Immutable, so any copy's a good copy.
|
pkg/server: add files "zipper" to DownloadHandler
We want to add a feature for clients (the web UI), where they can select
a bunch of files and ask the server for a zip archive of all these files.
This CL modifies the DownloadHandler so it does exactly that upon
reception of a POST request with a query parameter of the form
files=sha1-foo,sha1-bar,sha1-baz
This CL also adds a new button to the contextual sidebar of the web UI,
that takes care of sending the download request to the server.
known limitations: only permanodes with file as camliContent are
accepted as a valid selection (i.e. no sets, or static-dirs, etc) for
now.
Implementation detail:
We're creating an ephemeral DOM form on the fly to send the request.
The reason is: if we sent it as a Go http request, we'd have to read
the response manually and then we'd have no way of writing it to disk.
If we did it with an xhr, we could write the response to disk by
creating a File or Blob and then using URL.createObjectURL(), but we'd
have to keep the response in memory while doing so, which is
unacceptable for large enough archives.
Fixes #899
Change-Id: I104f7c5bd10ab3369e28d33752380dd12b5b3e6b
2017-03-06 20:02:19 +00:00
|
|
|
w.WriteHeader(http.StatusNotModified)
|
2013-01-25 23:18:32 +00:00
|
|
|
return
|
|
|
|
}
|
2011-07-03 17:53:02 +00:00
|
|
|
|
pkg/server: add files "zipper" to DownloadHandler
We want to add a feature for clients (the web UI), where they can select
a bunch of files and ask the server for a zip archive of all these files.
This CL modifies the DownloadHandler so it does exactly that upon
reception of a POST request with a query parameter of the form
files=sha1-foo,sha1-bar,sha1-baz
This CL also adds a new button to the contextual sidebar of the web UI,
that takes care of sending the download request to the server.
known limitations: only permanodes with file as camliContent are
accepted as a valid selection (i.e. no sets, or static-dirs, etc) for
now.
Implementation detail:
We're creating an ephemeral DOM form on the fly to send the request.
The reason is: if we sent it as a Go http request, we'd have to read
the response manually and then we'd have no way of writing it to disk.
If we did it with an xhr, we could write the response to disk by
creating a File or Blob and then using URL.createObjectURL(), but we'd
have to keep the response in memory while doing so, which is
unacceptable for large enough archives.
Fixes #899
Change-Id: I104f7c5bd10ab3369e28d33752380dd12b5b3e6b
2017-03-06 20:02:19 +00:00
|
|
|
fi, packed, err := dh.fileInfo(r, file)
|
2011-07-03 17:53:02 +00:00
|
|
|
if err != nil {
|
pkg/server: add files "zipper" to DownloadHandler
We want to add a feature for clients (the web UI), where they can select
a bunch of files and ask the server for a zip archive of all these files.
This CL modifies the DownloadHandler so it does exactly that upon
reception of a POST request with a query parameter of the form
files=sha1-foo,sha1-bar,sha1-baz
This CL also adds a new button to the contextual sidebar of the web UI,
that takes care of sending the download request to the server.
known limitations: only permanodes with file as camliContent are
accepted as a valid selection (i.e. no sets, or static-dirs, etc) for
now.
Implementation detail:
We're creating an ephemeral DOM form on the fly to send the request.
The reason is: if we sent it as a Go http request, we'd have to read
the response manually and then we'd have no way of writing it to disk.
If we did it with an xhr, we could write the response to disk by
creating a File or Blob and then using URL.createObjectURL(), but we'd
have to keep the response in memory while doing so, which is
unacceptable for large enough archives.
Fixes #899
Change-Id: I104f7c5bd10ab3369e28d33752380dd12b5b3e6b
2017-03-06 20:02:19 +00:00
|
|
|
http.Error(w, "Can't serve file: "+err.Error(), http.StatusInternalServerError)
|
2011-07-03 17:53:02 +00:00
|
|
|
return
|
|
|
|
}
|
2015-02-02 09:41:35 +00:00
|
|
|
defer fi.close()
|
2011-07-03 17:53:02 +00:00
|
|
|
|
pkg/server: add files "zipper" to DownloadHandler
We want to add a feature for clients (the web UI), where they can select
a bunch of files and ask the server for a zip archive of all these files.
This CL modifies the DownloadHandler so it does exactly that upon
reception of a POST request with a query parameter of the form
files=sha1-foo,sha1-bar,sha1-baz
This CL also adds a new button to the contextual sidebar of the web UI,
that takes care of sending the download request to the server.
known limitations: only permanodes with file as camliContent are
accepted as a valid selection (i.e. no sets, or static-dirs, etc) for
now.
Implementation detail:
We're creating an ephemeral DOM form on the fly to send the request.
The reason is: if we sent it as a Go http request, we'd have to read
the response manually and then we'd have no way of writing it to disk.
If we did it with an xhr, we could write the response to disk by
creating a File or Blob and then using URL.createObjectURL(), but we'd
have to keep the response in memory while doing so, which is
unacceptable for large enough archives.
Fixes #899
Change-Id: I104f7c5bd10ab3369e28d33752380dd12b5b3e6b
2017-03-06 20:02:19 +00:00
|
|
|
h := w.Header()
|
2015-02-02 09:41:35 +00:00
|
|
|
h.Set("Content-Length", fmt.Sprint(fi.size))
|
2013-01-25 23:18:32 +00:00
|
|
|
h.Set("Expires", time.Now().Add(oneYear).Format(http.TimeFormat))
|
2015-02-02 09:41:35 +00:00
|
|
|
h.Set("Content-Type", fi.mime)
|
2015-04-13 19:02:41 +00:00
|
|
|
if packed {
|
|
|
|
h.Set("X-Camlistore-Packed", "1")
|
|
|
|
}
|
2011-07-03 17:53:02 +00:00
|
|
|
|
2015-02-02 09:41:35 +00:00
|
|
|
if fi.mime == "application/octet-stream" {
|
2011-12-11 01:52:35 +00:00
|
|
|
// Chrome seems to silently do nothing on
|
|
|
|
// application/octet-stream unless this is set.
|
|
|
|
// Maybe it's confused by lack of URL it recognizes
|
|
|
|
// along with lack of mime type?
|
2015-02-02 09:41:35 +00:00
|
|
|
fileName := fi.name
|
2014-11-06 22:40:25 +00:00
|
|
|
if fileName == "" {
|
|
|
|
fileName = "file-" + file.String() + ".dat"
|
|
|
|
}
|
pkg/server: add files "zipper" to DownloadHandler
We want to add a feature for clients (the web UI), where they can select
a bunch of files and ask the server for a zip archive of all these files.
This CL modifies the DownloadHandler so it does exactly that upon
reception of a POST request with a query parameter of the form
files=sha1-foo,sha1-bar,sha1-baz
This CL also adds a new button to the contextual sidebar of the web UI,
that takes care of sending the download request to the server.
known limitations: only permanodes with file as camliContent are
accepted as a valid selection (i.e. no sets, or static-dirs, etc) for
now.
Implementation detail:
We're creating an ephemeral DOM form on the fly to send the request.
The reason is: if we sent it as a Go http request, we'd have to read
the response manually and then we'd have no way of writing it to disk.
If we did it with an xhr, we could write the response to disk by
creating a File or Blob and then using URL.createObjectURL(), but we'd
have to keep the response in memory while doing so, which is
unacceptable for large enough archives.
Fixes #899
Change-Id: I104f7c5bd10ab3369e28d33752380dd12b5b3e6b
2017-03-06 20:02:19 +00:00
|
|
|
w.Header().Set("Content-Disposition", "attachment; filename="+fileName)
|
2011-12-11 01:52:35 +00:00
|
|
|
}
|
2011-07-03 19:28:39 +00:00
|
|
|
|
pkg/server: add files "zipper" to DownloadHandler
We want to add a feature for clients (the web UI), where they can select
a bunch of files and ask the server for a zip archive of all these files.
This CL modifies the DownloadHandler so it does exactly that upon
reception of a POST request with a query parameter of the form
files=sha1-foo,sha1-bar,sha1-baz
This CL also adds a new button to the contextual sidebar of the web UI,
that takes care of sending the download request to the server.
known limitations: only permanodes with file as camliContent are
accepted as a valid selection (i.e. no sets, or static-dirs, etc) for
now.
Implementation detail:
We're creating an ephemeral DOM form on the fly to send the request.
The reason is: if we sent it as a Go http request, we'd have to read
the response manually and then we'd have no way of writing it to disk.
If we did it with an xhr, we could write the response to disk by
creating a File or Blob and then using URL.createObjectURL(), but we'd
have to keep the response in memory while doing so, which is
unacceptable for large enough archives.
Fixes #899
Change-Id: I104f7c5bd10ab3369e28d33752380dd12b5b3e6b
2017-03-06 20:02:19 +00:00
|
|
|
if r.Method == "HEAD" && r.FormValue("verifycontents") != "" {
|
|
|
|
vbr, ok := blob.Parse(r.FormValue("verifycontents"))
|
2013-08-04 02:54:30 +00:00
|
|
|
if !ok {
|
2011-07-03 17:53:02 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
hash := vbr.Hash()
|
|
|
|
if hash == nil {
|
|
|
|
return
|
|
|
|
}
|
2015-02-02 09:41:35 +00:00
|
|
|
io.Copy(hash, fi.rs) // ignore errors, caught later
|
2011-07-03 17:53:02 +00:00
|
|
|
if vbr.HashMatches(hash) {
|
pkg/server: add files "zipper" to DownloadHandler
We want to add a feature for clients (the web UI), where they can select
a bunch of files and ask the server for a zip archive of all these files.
This CL modifies the DownloadHandler so it does exactly that upon
reception of a POST request with a query parameter of the form
files=sha1-foo,sha1-bar,sha1-baz
This CL also adds a new button to the contextual sidebar of the web UI,
that takes care of sending the download request to the server.
known limitations: only permanodes with file as camliContent are
accepted as a valid selection (i.e. no sets, or static-dirs, etc) for
now.
Implementation detail:
We're creating an ephemeral DOM form on the fly to send the request.
The reason is: if we sent it as a Go http request, we'd have to read
the response manually and then we'd have no way of writing it to disk.
If we did it with an xhr, we could write the response to disk by
creating a File or Blob and then using URL.createObjectURL(), but we'd
have to keep the response in memory while doing so, which is
unacceptable for large enough archives.
Fixes #899
Change-Id: I104f7c5bd10ab3369e28d33752380dd12b5b3e6b
2017-03-06 20:02:19 +00:00
|
|
|
w.Header().Set("X-Camli-Contents", vbr.String())
|
2011-07-03 17:53:02 +00:00
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
pkg/server: add files "zipper" to DownloadHandler
We want to add a feature for clients (the web UI), where they can select
a bunch of files and ask the server for a zip archive of all these files.
This CL modifies the DownloadHandler so it does exactly that upon
reception of a POST request with a query parameter of the form
files=sha1-foo,sha1-bar,sha1-baz
This CL also adds a new button to the contextual sidebar of the web UI,
that takes care of sending the download request to the server.
known limitations: only permanodes with file as camliContent are
accepted as a valid selection (i.e. no sets, or static-dirs, etc) for
now.
Implementation detail:
We're creating an ephemeral DOM form on the fly to send the request.
The reason is: if we sent it as a Go http request, we'd have to read
the response manually and then we'd have no way of writing it to disk.
If we did it with an xhr, we could write the response to disk by
creating a File or Blob and then using URL.createObjectURL(), but we'd
have to keep the response in memory while doing so, which is
unacceptable for large enough archives.
Fixes #899
Change-Id: I104f7c5bd10ab3369e28d33752380dd12b5b3e6b
2017-03-06 20:02:19 +00:00
|
|
|
http.ServeContent(w, r, "", time.Now(), fi.rs)
|
|
|
|
}
|
|
|
|
|
|
|
|
// statFiles stats the given refs and returns an error if any one of them is not
|
|
|
|
// found.
|
|
|
|
// It is the responsibility of the caller to check that dh.blobSource() is a
|
|
|
|
// blobserver.BlobStatter.
|
|
|
|
func (dh *DownloadHandler) statFiles(refs []blob.Ref) error {
|
|
|
|
statter, _ := dh.blobSource().(blobserver.BlobStatter)
|
|
|
|
statted := make(map[blob.Ref]bool)
|
|
|
|
ch := make(chan (blob.SizedRef))
|
|
|
|
errc := make(chan (error))
|
|
|
|
go func() {
|
|
|
|
err := statter.StatBlobs(ch, refs)
|
|
|
|
close(ch)
|
|
|
|
errc <- err
|
|
|
|
|
|
|
|
}()
|
|
|
|
for sbr := range ch {
|
|
|
|
statted[sbr.Ref] = true
|
|
|
|
}
|
|
|
|
if err := <-errc; err != nil {
|
|
|
|
log.Printf("Error statting blob files for download archive: %v", err)
|
|
|
|
return fmt.Errorf("error looking for files")
|
|
|
|
}
|
|
|
|
for _, v := range refs {
|
|
|
|
if _, ok := statted[v]; !ok {
|
|
|
|
return fmt.Errorf("%q was not found", v)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// serveZip creates a zip archive from the files provided as
|
|
|
|
// ?files=sha1-foo,sha1-bar,... and serves it as the response.
|
|
|
|
func (dh *DownloadHandler) serveZip(w http.ResponseWriter, r *http.Request) {
|
|
|
|
if r.Method != "POST" {
|
|
|
|
http.Error(w, "Invalid download method", http.StatusBadRequest)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
filesValue := r.FormValue("files")
|
|
|
|
if filesValue == "" {
|
|
|
|
http.Error(w, "No files blobRefs specified", http.StatusBadRequest)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
files := strings.Split(filesValue, ",")
|
|
|
|
|
|
|
|
var refs []blob.Ref
|
|
|
|
for _, file := range files {
|
|
|
|
br, ok := blob.Parse(file)
|
|
|
|
if !ok {
|
|
|
|
http.Error(w, fmt.Sprintf("%q is not a valid blobRef", file), http.StatusBadRequest)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
refs = append(refs, br)
|
|
|
|
}
|
|
|
|
|
|
|
|
// We check as many things as we can before writing the zip, because
|
|
|
|
// once we start sending a response we can't http.Error anymore.
|
|
|
|
// TODO(mpl): instead of just statting, read the files (from the
|
|
|
|
// blobSource, which should be Cache then Fetcher), and write them to the
|
|
|
|
// Cache.
|
|
|
|
_, ok := dh.blobSource().(blobserver.BlobStatter)
|
|
|
|
if ok {
|
|
|
|
if err := dh.statFiles(refs); err != nil {
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO(mpl): do not zip if only one file is requested?
|
|
|
|
h := w.Header()
|
|
|
|
h.Set("Content-Type", "application/zip")
|
|
|
|
zipName := "camli-download-" + time.Now().Format(downloadTimeLayout) + ".zip"
|
|
|
|
h.Set("Content-Disposition", "attachment; filename="+zipName)
|
|
|
|
zw := zip.NewWriter(w)
|
|
|
|
|
|
|
|
zipFile := func(br blob.Ref) error {
|
|
|
|
fi, _, err := dh.fileInfo(r, br)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer fi.close()
|
|
|
|
zh := &zip.FileHeader{
|
|
|
|
Name: fi.name,
|
|
|
|
Method: zip.Store,
|
|
|
|
}
|
|
|
|
zh.SetModTime(fi.modtime.UTC())
|
|
|
|
zfh, err := zw.CreateHeader(zh)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
_, err = io.Copy(zfh, fi.rs)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
for _, br := range refs {
|
|
|
|
if err := zipFile(br); err != nil {
|
|
|
|
log.Printf("error zipping %v: %v", br, err)
|
|
|
|
// http.Error is of no use since we've already started sending a response
|
|
|
|
panic(http.ErrAbortHandler)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if err := zw.Close(); err != nil {
|
|
|
|
log.Printf("error closing zip stream: %v", err)
|
|
|
|
panic(http.ErrAbortHandler)
|
|
|
|
}
|
2011-07-03 17:53:02 +00:00
|
|
|
}
|