返回 DeepSeek-Reasonix
attachment_read.go
根目录 / internal / session / attachment_read.go
1 package session
2
3 import (
4 "context"
5 "database/sql"
6 "errors"
7 "path/filepath"
8 "strings"
9
10 "reasonix/internal/attachment"
11 "reasonix/internal/projectiondb"
12 "reasonix/internal/sessioncontent"
13 )
14
15 const sessionAttachmentReadLimit = 1 << 20
16
17 // ReadSessionAttachment returns one bounded range of an original attachment.
18 // The caller supplies only the digest; size and integrity fields are looked up
19 // from this session's content graph and are never taken from the client.
20 func (q *Query) ReadSessionAttachment(ctx context.Context, ref SessionRef, digest string, offset, length int64) ([]byte, int64, error) {
21 if q == nil {
22 return nil, 0, errors.New("session: nil query")
23 }
24 if err := ctx.Err(); err != nil {
25 return nil, 0, err
26 }
27 if err := ref.validate(q.hostID); err != nil {
28 return nil, 0, err
29 }
30 digest, ok := canonicalContentDigest(digest)
31 if !ok {
32 return nil, 0, errors.New("session: attachment digest is invalid")
33 }
34 filesystem, ok := q.persistence.(*FilesystemPersistence)
35 if !ok {
36 return nil, 0, errors.New("session: content reads require filesystem persistence")
37 }
38 authorized, err := q.lookupAuthorizedAttachment(ctx, filesystem, ref, digest)
39 if err != nil {
40 return nil, 0, err
41 }
42 if offset < 0 || length < 0 || length > sessionAttachmentReadLimit || offset > authorized.Bytes {
43 return nil, 0, errors.New("session: invalid or oversized attachment range")
44 }
45 if offset == authorized.Bytes {
46 return []byte{}, authorized.Bytes, nil
47 }
48 if length == 0 || length > authorized.Bytes-offset {
49 length = min(int64(sessionAttachmentReadLimit), authorized.Bytes-offset)
50 }
51 data, err := contentStoreForSessionDir(filepath.Join(filesystem.Root, ref.SessionID)).ReadRange(ctx, authorized, offset, length)
52 if err != nil {
53 return nil, 0, err
54 }
55 return data, authorized.Bytes, nil
56 }
57
58 func (q *Query) lookupAuthorizedAttachment(ctx context.Context, filesystem *FilesystemPersistence, ref SessionRef, digest string) (sessioncontent.Ref, error) {
59 if found, ok, err := q.lookupHistoryAttachment(ctx, filesystem, ref, digest); err != nil {
60 return sessioncontent.Ref{}, err
61 } else if ok {
62 return found, nil
63 }
64 if found, ok := q.lookupLiveAttachment(ctx, ref, digest); ok {
65 return found, nil
66 }
67 sessionDir := filepath.Join(filesystem.Root, ref.SessionID)
68 for _, extra := range collectInboxContentRefs(sessionDir) {
69 if extra.Digest == digest {
70 return withIntegrityBlock(extra), nil
71 }
72 }
73 return sessioncontent.Ref{}, errors.New("session: attachment is not authorized for this session")
74 }
75
76 func (q *Query) lookupHistoryAttachment(ctx context.Context, filesystem *FilesystemPersistence, ref SessionRef, digest string) (sessioncontent.Ref, bool, error) {
77 path := historyIndexPath(filesystem.Root, ref.SessionID)
78 if err := ensureHistoryIndex(ctx, filesystem, ref.SessionID, path); err != nil {
79 return sessioncontent.Ref{}, false, err
80 }
81 handle, err := projectiondb.Open(ctx, projectiondb.OpenOptions{Path: path, Migrations: historyMigrations, RequireDisk: true, MaxOpenConns: 1})
82 if err != nil {
83 return sessioncontent.Ref{}, false, err
84 }
85 defer handle.DB.Close()
86 var bytes int64
87 var indexDigest string
88 err = handle.DB.QueryRowContext(ctx, `SELECT bytes, index_digest FROM content_refs WHERE digest=? LIMIT 1`, digest).Scan(&bytes, &indexDigest)
89 if errors.Is(err, sql.ErrNoRows) {
90 return sessioncontent.Ref{}, false, nil
91 }
92 if err != nil {
93 return sessioncontent.Ref{}, false, err
94 }
95 found := sessioncontent.Ref{Digest: digest, Bytes: bytes, IndexDigest: indexDigest}
96 return withIntegrityBlock(found), true, nil
97 }
98
99 func (q *Query) lookupLiveAttachment(ctx context.Context, ref SessionRef, digest string) (sessioncontent.Ref, bool) {
100 snapshot, err := q.Snapshot(ctx, ref)
101 if err != nil {
102 return sessioncontent.Ref{}, false
103 }
104 for _, message := range snapshot.Projection.Messages {
105 for _, extra := range attachment.CollectContentRefs(message.ImageInputs) {
106 if extra.Digest == digest {
107 return withIntegrityBlock(extra), true
108 }
109 }
110 }
111 return sessioncontent.Ref{}, false
112 }
113
114 func canonicalContentDigest(digest string) (string, bool) {
115 digest = strings.ToLower(strings.TrimSpace(digest))
116 if len(digest) != 64 {
117 return "", false
118 }
119 for _, c := range digest {
120 if (c < '0' || c > '9') && (c < 'a' || c > 'f') {
121 return "", false
122 }
123 }
124 return digest, true
125 }
126
127 func withIntegrityBlock(ref sessioncontent.Ref) sessioncontent.Ref {
128 if ref.IndexDigest != "" && ref.IntegrityBlock == 0 {
129 ref.IntegrityBlock = sessioncontent.IntegrityBlockBytes
130 }
131 return ref
132 }
133
133 lines GO