- // The data object X exists, but recent/X is missing.
- err = v.bucket.PutReader("recent/"+loc, nil, 0, "application/octet-stream", s3ACL, s3.Options{})
- if err != nil {
- log.Printf("error: creating %q: %s", "recent/"+loc, err)
- return zeroTime, v.translateError(err)
- }
- log.Printf("info: created %q to migrate existing block to new storage scheme", "recent/"+loc)
- resp, err = v.bucket.Head("recent/"+loc, nil)
- if err != nil {
- log.Printf("error: created %q but HEAD failed: %s", "recent/"+loc, err)
- return zeroTime, v.translateError(err)
- }
- } else if err != nil {
- // HEAD recent/X failed for some other reason.
- return zeroTime, err
- }
- return v.lastModified(resp)
-}
-
-// IndexTo writes a complete list of locators with the given prefix
-// for which Get() can retrieve data.
-func (v *S3Volume) IndexTo(prefix string, writer io.Writer) error {
- // Use a merge sort to find matching sets of X and recent/X.
- dataL := s3Lister{
- Bucket: v.bucket.Bucket(),
- Prefix: prefix,
- PageSize: v.IndexPageSize,
- Stats: &v.bucket.stats,
- }
- recentL := s3Lister{
- Bucket: v.bucket.Bucket(),
- Prefix: "recent/" + prefix,
- PageSize: v.IndexPageSize,
- Stats: &v.bucket.stats,
- }
- for data, recent := dataL.First(), recentL.First(); data != nil && dataL.Error() == nil; data = dataL.Next() {
- if data.Key >= "g" {
- // Conveniently, "recent/*" and "trash/*" are
- // lexically greater than all hex-encoded data
- // hashes, so stopping here avoids iterating
- // over all of them needlessly with dataL.
- break
- }
- if !v.isKeepBlock(data.Key) {
- continue
- }
-
- // stamp is the list entry we should use to report the
- // last-modified time for this data block: it will be
- // the recent/X entry if one exists, otherwise the
- // entry for the data block itself.
- stamp := data
-
- // Advance to the corresponding recent/X marker, if any
- for recent != nil && recentL.Error() == nil {
- if cmp := strings.Compare(recent.Key[7:], data.Key); cmp < 0 {
- recent = recentL.Next()
- continue
- } else if cmp == 0 {
- stamp = recent
- recent = recentL.Next()
- break
- } else {
- // recent/X marker is missing: we'll
- // use the timestamp on the data
- // object.
- break
- }
- }
- if err := recentL.Error(); err != nil {
- return err
- }
- t, err := time.Parse(time.RFC3339, stamp.LastModified)
- if err != nil {
- return err
- }
- fmt.Fprintf(writer, "%s+%d %d\n", data.Key, data.Size, t.UnixNano())
- }
- return dataL.Error()
-}
-
-// Trash a Keep block.
-func (v *S3Volume) Trash(loc string) error {
- if v.volume.ReadOnly {
- return MethodDisabledError
- }
- if t, err := v.Mtime(loc); err != nil {
- return err
- } else if time.Since(t) < v.cluster.Collections.BlobSigningTTL.Duration() {
- return nil
- }
- if v.cluster.Collections.BlobTrashLifetime == 0 {
- if !v.UnsafeDelete {
- return ErrS3TrashDisabled
- }
- return v.translateError(v.bucket.Del(loc))
- }
- err := v.checkRaceWindow(loc)
- if err != nil {
- return err
- }
- err = v.safeCopy("trash/"+loc, loc)
- if err != nil {