mirror of
https://github.com/kopia/kopia.git
synced 2026-01-23 22:07:54 -05:00
* refactor(repository): ensure we always parse content.ID and object.ID
This changes the types to be incompatible with string to prevent direct
conversion to and from string.
This has the additional benefit of reducing number of memory allocations
and bytes for all IDs.
content.ID went from 2 allocations to 1:
typical case 32 characters + 16 bytes per-string overhead
worst-case 65 characters + 16 bytes per-string overhead
now: 34 bytes
object.ID went from 2 allocations to 1:
typical case 32 characters + 16 bytes per-string overhead
worst-case 65 characters + 16 bytes per-string overhead
now: 36 bytes
* move index.{ID,IDRange} methods to separate files
* replaced index.IDFromHash with content.IDFromHash externally
* minor tweaks and additional tests
* Update repo/content/index/id_test.go
Co-authored-by: Julio Lopez <1953782+julio-lopez@users.noreply.github.com>
* Update repo/content/index/id_test.go
Co-authored-by: Julio Lopez <1953782+julio-lopez@users.noreply.github.com>
* pr feedback
* post-merge fixes
* pr feedback
* pr feedback
* fixed subtle regression in sortedContents()
This was actually not producing invalid results because of how base36
works, just not sorting as efficiently as it could.
Co-authored-by: Julio Lopez <1953782+julio-lopez@users.noreply.github.com>
114 lines
3.0 KiB
Go
114 lines
3.0 KiB
Go
package server
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/kopia/kopia/internal/gather"
|
|
"github.com/kopia/kopia/internal/remoterepoapi"
|
|
"github.com/kopia/kopia/internal/serverapi"
|
|
"github.com/kopia/kopia/repo"
|
|
"github.com/kopia/kopia/repo/compression"
|
|
"github.com/kopia/kopia/repo/content"
|
|
"github.com/kopia/kopia/repo/manifest"
|
|
)
|
|
|
|
func handleContentGet(ctx context.Context, rc requestContext) (interface{}, *apiError) {
|
|
dr, ok := rc.rep.(repo.DirectRepository)
|
|
if !ok {
|
|
return nil, notFoundError("content not found")
|
|
}
|
|
|
|
cid, err := content.ParseID(rc.muxVar("contentID"))
|
|
if err != nil {
|
|
return nil, notFoundError("content not found")
|
|
}
|
|
|
|
data, err := dr.ContentReader().GetContent(ctx, cid)
|
|
if errors.Is(err, content.ErrContentNotFound) {
|
|
return nil, notFoundError("content not found")
|
|
}
|
|
|
|
return data, nil
|
|
}
|
|
|
|
func handleContentInfo(ctx context.Context, rc requestContext) (interface{}, *apiError) {
|
|
cid, err := content.ParseID(rc.muxVar("contentID"))
|
|
if err != nil {
|
|
return nil, notFoundError("content not found")
|
|
}
|
|
|
|
ci, err := rc.rep.ContentInfo(ctx, cid)
|
|
|
|
switch {
|
|
case err == nil:
|
|
return ci, nil
|
|
|
|
case errors.Is(err, content.ErrContentNotFound):
|
|
return nil, notFoundError("content not found")
|
|
|
|
default:
|
|
return nil, internalServerError(err)
|
|
}
|
|
}
|
|
|
|
func handleContentPut(ctx context.Context, rc requestContext) (interface{}, *apiError) {
|
|
dr, ok := rc.rep.(repo.DirectRepositoryWriter)
|
|
if !ok {
|
|
return nil, repositoryNotWritableError()
|
|
}
|
|
|
|
cid, cerr := content.ParseID(rc.muxVar("contentID"))
|
|
if cerr != nil {
|
|
return nil, requestError(serverapi.ErrorMalformedRequest, "malformed content ID")
|
|
}
|
|
|
|
prefix := cid.Prefix()
|
|
|
|
if strings.HasPrefix(string(prefix), manifest.ContentPrefix) {
|
|
// it's not allowed to create contents prefixed with 'm' since those could be mistaken for manifest contents.
|
|
return nil, accessDeniedError()
|
|
}
|
|
|
|
var comp compression.HeaderID
|
|
|
|
if c := rc.queryParam("compression"); c != "" {
|
|
// nolint:gomnd
|
|
v, err := strconv.ParseInt(c, 16, 32)
|
|
if err != nil {
|
|
return nil, requestError(serverapi.ErrorMalformedRequest, "malformed compression ID")
|
|
}
|
|
|
|
comp = compression.HeaderID(v)
|
|
if _, ok := compression.ByHeaderID[comp]; !ok {
|
|
return nil, requestError(serverapi.ErrorMalformedRequest, "invalid compression ID")
|
|
}
|
|
}
|
|
|
|
actualCID, err := dr.ContentManager().WriteContent(ctx, gather.FromSlice(rc.body), prefix, comp)
|
|
if err != nil {
|
|
return nil, internalServerError(err)
|
|
}
|
|
|
|
if actualCID != cid {
|
|
return nil, requestError(serverapi.ErrorMalformedRequest, "mismatched content ID")
|
|
}
|
|
|
|
return &serverapi.Empty{}, nil
|
|
}
|
|
|
|
func handleContentPrefetch(ctx context.Context, rc requestContext) (interface{}, *apiError) {
|
|
var req remoterepoapi.PrefetchContentsRequest
|
|
|
|
if err := json.Unmarshal(rc.body, &req); err != nil {
|
|
return nil, requestError(serverapi.ErrorMalformedRequest, "malformed request")
|
|
}
|
|
|
|
return &remoterepoapi.PrefetchContentsResponse{
|
|
ContentIDs: rc.rep.PrefetchContents(ctx, req.ContentIDs, req.Hint),
|
|
}, nil
|
|
}
|