/
githubmirror
/
client
Обзор
Документация
Войти
/
githubmirror
/
client
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
go/chat/attachments/utils.go
355 строк
8 KB
chrisnojima-zoom
Version 670 - clean2 (#29122)
08 июн 2026, 19:31
Не верифицирован
08 июн 2026, 19:31
1943197
Код
Авторство
О чём код?
package attachments import ( "bufio" "bytes" "context" "errors" "image/gif" "image/png" "io" "os" "strings" "sync/atomic" "time" "github.com/keybase/client/go/chat/globals" "github.com/keybase/client/go/libkb" "github.com/keybase/client/go/protocol/chat1" "github.com/keybase/client/go/protocol/gregor1" "github.com/keybase/client/go/protocol/keybase1" "github.com/keybase/go-framed-msgpack-rpc/rpc" ) func AssetFromMessage(ctx context.Context, g *globals.Context, uid gregor1.UID, convID chat1.ConversationID, msgID chat1.MessageID, preview bool, ) (res chat1.Asset, err error) { reason := chat1.GetThreadReason_GENERAL msgs, err := g.ChatHelper.GetMessages(ctx, uid, convID, []chat1.MessageID{msgID}, true, &reason) if err != nil { return res, err } if len(msgs) == 0 { return res, libkb.NotFoundError{} } first := msgs[0] st, err := first.State() if err != nil { return res, err } if st == chat1.MessageUnboxedState_ERROR { em := first.Error().ErrMsg return res, errors.New(em) } if st != chat1.MessageUnboxedState_VALID { // given a message id that doesn't exist, msgs can come back // with an empty message in it (and st == 0). // this check prevents a panic, but perhaps the server needs // a fix as well. return res, libkb.NotFoundError{} } msg := first.Valid() body := msg.MessageBody t, err := body.MessageType() if err != nil { return res, err } var attachment chat1.MessageAttachment switch t { case chat1.MessageType_ATTACHMENT: attachment = msg.MessageBody.Attachment() case chat1.MessageType_ATTACHMENTUPLOADED: uploaded := msg.MessageBody.Attachmentuploaded() attachment = chat1.MessageAttachment{ Object: uploaded.Object, Previews: uploaded.Previews, Metadata: uploaded.Metadata, //nolint:govet // keeping metadata for completeness } default: return res, errors.New("not an attachment message") } res = attachment.Object if preview { if len(attachment.Previews) > 0 { res = attachment.Previews[0] } else if attachment.Preview != nil { res = *attachment.Preview } else { return res, errors.New("no preview in attachment") } } return res, nil } // ReadCloseResetter is io.ReadCloser plus a Reset method. This is used by // attachment uploads. type ReadCloseResetter interface { io.ReadCloser Reset() error } type FileReadCloseResetter struct { filename string file *os.File buf *bufio.Reader } // NewFileReadCloseResetter creates a ReadCloseResetter that uses an on-disk file as // source of data. func NewFileReadCloseResetter(name string) (ReadCloseResetter, error) { f := &FileReadCloseResetter{filename: name} if err := f.open(); err != nil { return nil, err } return f, nil } func (f *FileReadCloseResetter) open() error { ff, err := os.Open(f.filename) if err != nil { return err } f.file = ff f.buf = bufio.NewReader(f.file) return nil } func (f *FileReadCloseResetter) Read(p []byte) (int, error) { return f.buf.Read(p) } func (f *FileReadCloseResetter) Reset() error { _, err := f.file.Seek(0, io.SeekStart) if err != nil { return err } f.buf.Reset(f.file) return nil } func (f *FileReadCloseResetter) Close() error { f.buf = nil if f.file != nil { return f.file.Close() } return nil } // KbfsReadCloseResetter is an implementation of ReadCloseResetter that uses // SimpleFS as source of data. type KbfsReadCloseResetter struct { client *keybase1.SimpleFSClient opid keybase1.OpID offset int64 ctx context.Context } const ( kbfsPrefix = "/keybase" kbfsPrefixPrivate = kbfsPrefix + "/private/" kbfsPrefixPublic = kbfsPrefix + "/public/" kbfsPrefixTeam = kbfsPrefix + "/team/" ) func isKbfsPath(p string) bool { return strings.HasPrefix(p, kbfsPrefixPrivate) || strings.HasPrefix(p, kbfsPrefixPublic) || strings.HasPrefix(p, kbfsPrefixTeam) } func makeSimpleFSClientFromGlobalContext( g *libkb.GlobalContext, ) (*keybase1.SimpleFSClient, error) { xp := g.ConnectionManager.LookupByClientType(keybase1.ClientType_KBFS) if xp == nil { return nil, libkb.KBFSNotRunningError{} } return &keybase1.SimpleFSClient{ Cli: rpc.NewClient(xp, libkb.NewContextifiedErrorUnwrapper(g), nil), }, nil } // NewKbfsReadCloseResetter creates a ReadCloseResetter that uses SimpleFS as source // of data. kbfsPath must start with "/keybase/<tlf-type>/". func NewKbfsReadCloseResetter(ctx context.Context, g *libkb.GlobalContext, kbfsPath string, ) (ReadCloseResetter, error) { if !isKbfsPath(kbfsPath) { return nil, errors.New("not a kbfs path") } client, err := makeSimpleFSClientFromGlobalContext(g) if err != nil { return nil, err } opid, err := client.SimpleFSMakeOpid(ctx) if err != nil { return nil, err } if err = client.SimpleFSOpen(ctx, keybase1.SimpleFSOpenArg{ OpID: opid, Dest: keybase1.NewPathWithKbfsPath(kbfsPath[len(kbfsPrefix):]), Flags: keybase1.OpenFlags_READ | keybase1.OpenFlags_EXISTING, }); err != nil { return nil, err } return &KbfsReadCloseResetter{ client: client, ctx: ctx, opid: opid, }, nil } // Read implements the ReadCloseResetter interface. func (f *KbfsReadCloseResetter) Read(p []byte) (int, error) { content, err := f.client.SimpleFSRead(f.ctx, keybase1.SimpleFSReadArg{ OpID: f.opid, Offset: atomic.LoadInt64(&f.offset), Size: len(p), }) if err != nil { return 0, err } if len(content.Data) == 0 { // Unfortunately SimpleFSRead doesn't return EOF error. return 0, io.EOF } atomic.AddInt64(&f.offset, int64(len(content.Data))) copy(p, content.Data) return len(content.Data), nil } // Reset implements the ReadCloseResetter interface. func (f *KbfsReadCloseResetter) Reset() error { atomic.StoreInt64(&f.offset, 0) return nil } // Close implements the ReadCloseResetter interface. func (f *KbfsReadCloseResetter) Close() error { return f.client.SimpleFSClose(f.ctx, f.opid) } // NewReadCloseResetter creates a ReadCloseResetter using either on-disk file // or SimpleFS depending on if p is a KBFS path. func NewReadCloseResetter(ctx context.Context, g *libkb.GlobalContext, p string, ) (ReadCloseResetter, error) { if isKbfsPath(p) { return NewKbfsReadCloseResetter(ctx, g, p) } return NewFileReadCloseResetter(p) } type kbfsFileInfo struct { dirent *keybase1.Dirent } func (fi kbfsFileInfo) Name() string { return fi.dirent.Name } func (fi kbfsFileInfo) Size() int64 { return int64(fi.dirent.Size) } func (fi kbfsFileInfo) Mode() (mode os.FileMode) { mode |= 0o400 if fi.dirent.Writable { mode |= 0o200 } switch fi.dirent.DirentType { case keybase1.DirentType_DIR: mode |= os.ModeDir case keybase1.DirentType_SYM: mode |= os.ModeSymlink case keybase1.DirentType_EXEC: mode |= 0o100 } return mode } func (fi kbfsFileInfo) ModTime() time.Time { return keybase1.FromTime(fi.dirent.Time) } func (fi kbfsFileInfo) IsDir() bool { return fi.dirent.DirentType == keybase1.DirentType_DIR } func (fi kbfsFileInfo) Sys() any { return fi.dirent } // StatOSOrKbfsFile stats the file located at p, using SimpleFSStat if it's a // KBFS path, or os.Stat if not. func StatOSOrKbfsFile(ctx context.Context, g *libkb.GlobalContext, p string) ( fi os.FileInfo, err error, ) { if !isKbfsPath(p) { return os.Stat(p) } client, err := makeSimpleFSClientFromGlobalContext(g) if err != nil { return nil, err } dirent, err := client.SimpleFSStat(ctx, keybase1.SimpleFSStatArg{ Path: keybase1.NewPathWithKbfsPath(p[len(kbfsPrefix):]), }) if err != nil { return nil, err } return kbfsFileInfo{dirent: &dirent}, nil } type BufReadResetter struct { buf []byte r *bytes.Reader } func NewBufReadResetter(buf []byte) *BufReadResetter { return &BufReadResetter{ buf: buf, r: bytes.NewReader(buf), } } func (b *BufReadResetter) Read(p []byte) (int, error) { return b.r.Read(p) } func (b *BufReadResetter) Reset() error { b.r.Reset(b.buf) return nil } func (b *BufReadResetter) Close() error { return nil } func AddPendingPreview(ctx context.Context, g *globals.Context, obr *chat1.OutboxRecord) error { pre, err := NewPendingPreviews(g).Get(ctx, obr.OutboxID) if err != nil { return err } mpr, err := pre.Export(func() *chat1.PreviewLocation { loc := chat1.NewPreviewLocationWithUrl(g.AttachmentURLSrv.GetPendingPreviewURL(ctx, obr.OutboxID)) return &loc }) if err != nil { return err } obr.Preview = &mpr return nil } func GIFToPNG(ctx context.Context, src io.Reader, dest io.Writer) error { g, err := gif.DecodeAll(src) if err != nil { return err } if len(g.Image) == 0 { return errors.New("no frames in gif") } return png.Encode(dest, g.Image[0]) }