storage

package
v0.26.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 11, 2026 License: MIT Imports: 16 Imported by: 18

Documentation

Overview

Package storage contains common structures for iterating over peer storage.

Index

Examples

Constants

View Source
const LatestVersion = 2

LatestVersion is a latest supported version of data.

Variables

View Source
var ErrPeerNotFound = errors.New("peer not found")

ErrPeerNotFound is a special error to return when peer not found.

View Source
var ErrPeerUnmarshalMustInvalidate = errors.New("outdated data for Peer (cache miss, must invalidate)")

ErrPeerUnmarshalMustInvalidate means that persisted Peer is outdated and must be invalidated.

View Source
var PeerKeyPrefix = []byte("peer") // nolint:gochecknoglobals

PeerKeyPrefix is a key prefix of peer key.

Functions

func DecodePeer added in v0.26.0

func DecodePeer(data []byte, p *Peer) error

DecodePeer decodes peer encoded by EncodePeer or Peer.MarshalJSON.

Like Peer.UnmarshalJSON, it returns ErrPeerUnmarshalMustInvalidate for outdated data, which must be treated as a cache miss.

Data of an unknown format, e.g. written by a newer version, also gives ErrPeerUnmarshalMustInvalidate.

func EncodePeer added in v0.26.0

func EncodePeer(p Peer) ([]byte, error)

EncodePeer encodes peer in the compact binary format.

Use DecodePeer to decode it: it also accepts the JSON format of Peer.MarshalJSON, so values stored before stay readable.

func ForEach added in v0.5.0

func ForEach(ctx context.Context, iterator PeerIterator, cb func(Peer) error) error

ForEach calls callback on every iterator element.

func PeerUnchanged added in v0.26.0

func PeerUnchanged(stored []byte, value Peer, enc PeerEncoding) bool

PeerUnchanged reports whether stored is the encoding of value in enc, ignoring CreatedAt, which is set to the current time on every peer conversion. A value stored in another format is reported as changed, so it is rewritten.

Storages use it to skip writing peers that did not change, e.g. users repeated in every update. So the stored CreatedAt is when this content was first stored, not when the peer was last seen.

func UpdateHook added in v0.3.0

func UpdateHook(next telegram.UpdateHandler, storage PeerStorage) telegram.UpdateHandler

UpdateHook creates update hook, to collect peer data from updates.

Example
package main

import (
	"context"
	"fmt"
	"os"
	"os/signal"

	pebbledb "github.com/cockroachdb/pebble"
	"github.com/go-faster/errors"

	"github.com/gotd/td/telegram"
	"github.com/gotd/td/telegram/message"
	"github.com/gotd/td/tg"

	"github.com/gotd/contrib/pebble"
	"github.com/gotd/contrib/storage"
)

func updatesHook(ctx context.Context) error {
	db, err := pebbledb.Open("pebble.db", &pebbledb.Options{})
	if err != nil {
		return errors.Wrap(err, "create pebble storage")
	}
	s := pebble.NewPeerStorage(db)

	dispatcher := tg.NewUpdateDispatcher()
	handler := storage.UpdateHook(dispatcher, s)
	client, err := telegram.ClientFromEnvironment(telegram.Options{
		UpdateHandler: handler,
	})
	if err != nil {
		return errors.Wrap(err, "create client")
	}
	raw := tg.NewClient(client)
	sender := message.NewSender(raw)

	dispatcher.OnNewMessage(func(ctx context.Context, e tg.Entities, update *tg.UpdateNewMessage) error {
		msg, ok := update.Message.(*tg.Message)
		if !ok {
			return nil
		}

		// Use PeerID to find peer because *Short updates does not contain any entities, so it necessary to
		// store some entities.
		// Storage can be filled using PeerCollector.
		p, err := storage.FindPeer(ctx, s, msg.GetPeerID())
		if err != nil {
			return err
		}

		_, err = sender.To(p.AsInputPeer()).Text(ctx, msg.GetMessage())
		return err
	})

	return client.Run(ctx, func(ctx context.Context) error {
		return telegram.RunUntilCanceled(ctx, client)
	})
}

func main() {
	ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
	defer cancel()

	if err := updatesHook(ctx); err != nil {
		_, _ = fmt.Fprintf(os.Stderr, "%+v\n", err)
		os.Exit(1)
	}
}

Types

type BatchPeerStorage added in v0.26.0

type BatchPeerStorage interface {
	PeerStorage
	// AddMany adds given peers to the storage.
	AddMany(ctx context.Context, values []Peer) error
}

BatchPeerStorage is a PeerStorage that can add many peers at once, e.g. in a single transaction.

UpdateHook and PeerCollector use it, if implemented, to write all peers of an update or of a batch with one write.

type Peer

type Peer struct {
	Version int
	Key     dialogs.DialogKey
	// CreatedAt is when this content of the peer was first stored. Storages
	// skip writing a peer that did not change, so it is not updated when the
	// same peer is seen again: don't use it as "last seen".
	CreatedAt time.Time
	User      *tg.User
	Chat      *tg.Chat
	Channel   *tg.Channel
	Metadata  map[string]any
}

Peer is abstraction for persisted peer object.

Note: unmarshal error ErrPeerUnmarshalMustInvalidate MUST be considered as cache miss and cache entry MUST be invalidated.

The only valid ways to marshal and unmarshal Peer are EncodePeer and DecodePeer (compact, used by storages), or MarshalJSON and UnmarshalJSON. DecodePeer also reads the JSON format.

func FindPeer added in v0.3.0

func FindPeer(ctx context.Context, s PeerStorage, p tg.PeerClass) (Peer, error)

FindPeer finds peer using given storage.

func (Peer) AsInputChannel

func (p Peer) AsInputChannel() (*tg.InputChannel, bool)

AsInputChannel tries to convert peer to *tg.InputChannel.

func (Peer) AsInputPeer

func (p Peer) AsInputPeer() tg.InputPeerClass

AsInputPeer tries to convert peer to tg.InputPeerClass.

func (Peer) AsInputUser

func (p Peer) AsInputUser() (*tg.InputUser, bool)

AsInputUser tries to convert peer to *tg.InputUser.

func (*Peer) FromChat added in v0.3.0

func (p *Peer) FromChat(chat tg.ChatClass) bool

FromChat fills Peer object using given tg.ChatClass.

func (*Peer) FromInputPeer

func (p *Peer) FromInputPeer(input tg.InputPeerClass) error

FromInputPeer fills Peer object using given tg.InputPeerClass.

func (*Peer) FromUser added in v0.3.0

func (p *Peer) FromUser(user tg.UserClass) bool

FromUser fills Peer object using given tg.UserClass.

Returns false for empty and min users: min users have no usable access hash and only part of the fields, so they must not replace stored peers. See https://core.telegram.org/api/min.

func (*Peer) Keys added in v0.3.0

func (p *Peer) Keys() []string

Keys returns list of all associated keys (phones, usernames, etc.) stored in the peer.

func (Peer) Marshal added in v0.16.0

func (p Peer) Marshal(e *jx.Encoder) error

func (Peer) MarshalJSON added in v0.16.0

func (p Peer) MarshalJSON() ([]byte, error)

func (Peer) String added in v0.16.0

func (p Peer) String() string

func (*Peer) Unmarshal added in v0.16.0

func (p *Peer) Unmarshal(d *jx.Decoder) error

func (*Peer) UnmarshalJSON added in v0.16.0

func (p *Peer) UnmarshalJSON(data []byte) error

type PeerCollector added in v0.5.0

type PeerCollector struct {
	// contains filtered or unexported fields
}

PeerCollector is a simple helper to collect peers from different sources.

func CollectPeers added in v0.5.0

func CollectPeers(storage PeerStorage) PeerCollector

CollectPeers creates new PeerCollector.

Example
package main

import (
	"context"
	"fmt"
	"os"
	"os/signal"

	pebbledb "github.com/cockroachdb/pebble"
	"github.com/go-faster/errors"

	"github.com/gotd/td/telegram"
	"github.com/gotd/td/telegram/query"
	"github.com/gotd/td/tg"

	"github.com/gotd/contrib/pebble"
	"github.com/gotd/contrib/storage"
)

func peerCollector(ctx context.Context) error {
	db, err := pebbledb.Open("pebble.db", &pebbledb.Options{})
	if err != nil {
		return errors.Wrap(err, "create pebble storage")
	}
	s := pebble.NewPeerStorage(db)
	collector := storage.CollectPeers(s)

	client, err := telegram.ClientFromEnvironment(telegram.Options{})
	if err != nil {
		return errors.Wrap(err, "create client")
	}
	raw := tg.NewClient(client)

	return client.Run(ctx, func(ctx context.Context) error {
		// Fills storage with user dialogs peers metadata.
		return collector.Dialogs(ctx, query.GetDialogs(raw).Iter())
	})
}

func main() {
	ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
	defer cancel()

	if err := peerCollector(ctx); err != nil {
		_, _ = fmt.Fprintf(os.Stderr, "%+v\n", err)
		os.Exit(1)
	}
}

func (PeerCollector) Contacts added in v0.5.0

func (c PeerCollector) Contacts(ctx context.Context, contacts *tg.ContactsContacts) error

Contacts collects peers from contacts iterator.

func (PeerCollector) Dialogs added in v0.5.0

func (c PeerCollector) Dialogs(ctx context.Context, iter *dialogs.Iterator) error

Dialogs collects peers from dialog iterator.

func (PeerCollector) Participants added in v0.5.0

func (c PeerCollector) Participants(ctx context.Context, iter *participants.Iterator) error

Participants collects peers from participants iterator.

type PeerEncoding added in v0.26.0

type PeerEncoding uint8

PeerEncoding is the format storages write peers in.

DecodePeer reads both formats, so changing it keeps stored values readable. Versions of this package before the binary format can't read binary values: downgrading after binary values were written needs the peer storage cleared.

const (
	// PeerEncodingJSON is the JSON format of Peer.MarshalJSON. Default.
	PeerEncodingJSON PeerEncoding = iota
	// PeerEncodingBinary is the compact binary format of EncodePeer.
	PeerEncodingBinary
)

func (PeerEncoding) Encode added in v0.26.0

func (e PeerEncoding) Encode(p Peer) ([]byte, error)

Encode encodes peer in the format.

type PeerIterator added in v0.5.0

type PeerIterator interface {
	Next(ctx context.Context) bool
	Err() error
	Value() Peer
	io.Closer
}

PeerIterator is a peer iterator.

type PeerKey added in v0.5.0

type PeerKey struct {
	Kind dialogs.PeerKind
	ID   int64
}

PeerKey is unique key of peer object.

func KeyFromPeer

func KeyFromPeer(p Peer) PeerKey

KeyFromPeer creates key from peer.

func (PeerKey) Bytes added in v0.5.0

func (k PeerKey) Bytes(r []byte) []byte

Bytes returns bytes representation of key.

func (*PeerKey) Parse added in v0.5.0

func (k *PeerKey) Parse(r []byte) error

Parse parses bytes representation from given slice.

func (PeerKey) String added in v0.5.0

func (k PeerKey) String() string

String returns string representation of key.

type PeerStorage

type PeerStorage interface {
	// Add adds given peer to the storage.
	Add(ctx context.Context, value Peer) error
	// Find finds peer using given key.
	// If peer not found, it returns ErrPeerNotFound error.
	Find(ctx context.Context, key PeerKey) (Peer, error)

	// Assign adds given peer to the storage and associates it to the given key.
	Assign(ctx context.Context, key string, value Peer) error
	// Resolve finds peer using associated key.
	// If peer not found, it returns ErrPeerNotFound error.
	Resolve(ctx context.Context, key string) (Peer, error)

	// Iterate creates and returns new PeerIterator.
	Iterate(ctx context.Context) (PeerIterator, error)
}

PeerStorage is abstraction for peer storage.

type ResolverCache

type ResolverCache struct {
	// contains filtered or unexported fields
}

ResolverCache is a peer.Resolver cache implemented using peer storage.

Example
package main

import (
	"context"
	"fmt"
	"os"
	"os/signal"

	pebbledb "github.com/cockroachdb/pebble"
	"github.com/go-faster/errors"

	"github.com/gotd/td/telegram/message"
	"github.com/gotd/td/telegram/message/peer"
	"github.com/gotd/td/tg"

	"github.com/gotd/td/telegram"

	"github.com/gotd/contrib/pebble"
	"github.com/gotd/contrib/storage"
)

func resolverCache(ctx context.Context) error {
	db, err := pebbledb.Open("pebble.db", &pebbledb.Options{})
	if err != nil {
		return errors.Wrap(err, "create pebble storage")
	}

	client, err := telegram.ClientFromEnvironment(telegram.Options{})
	if err != nil {
		return errors.Wrap(err, "create client")
	}

	return client.Run(ctx, func(ctx context.Context) error {
		raw := tg.NewClient(client)
		resolver := storage.NewResolverCache(peer.Plain(raw), pebble.NewPeerStorage(db))
		s := message.NewSender(raw).WithResolver(resolver)

		_, err := s.Resolve("durov").Text(ctx, "Hi!")
		return err
	})
}

func main() {
	ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
	defer cancel()

	if err := resolverCache(ctx); err != nil {
		_, _ = fmt.Fprintf(os.Stderr, "%+v\n", err)
		os.Exit(1)
	}
}

func NewResolverCache

func NewResolverCache(next peer.Resolver, storage PeerStorage) ResolverCache

NewResolverCache creates new ResolverCache.

func (ResolverCache) ResolveDomain

func (r ResolverCache) ResolveDomain(ctx context.Context, domain string) (tg.InputPeerClass, error)

ResolveDomain implements peer.Resolver

func (ResolverCache) ResolvePhone

func (r ResolverCache) ResolvePhone(ctx context.Context, phone string) (tg.InputPeerClass, error)

ResolvePhone implements peer.Resolver

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL