package record import ( "errors" "github.com/safing/portmaster/base/database/accessor" "github.com/safing/portmaster/base/log" "github.com/safing/structures/container" "github.com/safing/structures/dsd" ) // TODO(ppacher): // we can reduce the record.Record interface a lot by moving // most of those functions that require the Record as it's first // parameter to static package functions // (i.e. Marshal, MarshalRecord, GetAccessor, ...). // We should also consider given Base a GetBase() *Base method // that returns itself. This way we can remove almost all Base // only methods from the record.Record interface. That is, we can // remove all those CreateMeta, UpdateMeta, ... stuff from the // interface definition (not the actual functions!). This would make // the record.Record interface slim and only provide methods that // most users actually need. All those database/storage related methods // can still be accessed by using GetBase().XXX() instead. We can also // expose the dbName and dbKey and meta properties directly which would // make a nice JSON blob when marshalled. // Base provides a quick way to comply with the Model interface. type Base struct { dbName string dbKey string meta *Meta } // SetKey sets the key on the database record. The key may only be set once and // future calls to SetKey will be ignored. If you want to copy/move the record // to another database key, you will need to create a copy and assign a new key. // A key must be set before the record is used in any database operation. func (b *Base) SetKey(key string) { if !b.KeyIsSet() { b.dbName, b.dbKey = ParseKey(key) } else { log.Errorf("database: key is already set: tried to replace %q with %q", b.Key(), key) } } // ResetKey resets the database name and key. // Use with caution! func (b *Base) ResetKey() { b.dbName = "" b.dbKey = "" } // Key returns the key of the database record. // As the key must be set before any usage and can only be set once, this // function may be used without locking the record. func (b *Base) Key() string { return b.dbName + ":" + b.dbKey } // KeyIsSet returns true if the database key is set. // As the key must be set before any usage and can only be set once, this // function may be used without locking the record. func (b *Base) KeyIsSet() bool { return b.dbName != "" } // DatabaseName returns the name of the database. // As the key must be set before any usage and can only be set once, this // function may be used without locking the record. func (b *Base) DatabaseName() string { return b.dbName } // DatabaseKey returns the database key of the database record. // As the key must be set before any usage and can only be set once, this // function may be used without locking the record. func (b *Base) DatabaseKey() string { return b.dbKey } // Meta returns the metadata object for this record. func (b *Base) Meta() *Meta { return b.meta } // CreateMeta sets a default metadata object for this record. func (b *Base) CreateMeta() { b.meta = &Meta{} } // UpdateMeta creates the metadata if it does not exist and updates it. func (b *Base) UpdateMeta() { if b.meta == nil { b.CreateMeta() } b.meta.Update() } // SetMeta sets the metadata on the database record, it should only be called after loading the record. Use MoveTo to save the record with another key. func (b *Base) SetMeta(meta *Meta) { b.meta = meta } // Marshal marshals the object, without the database key or metadata. It returns nil if the record is deleted. func (b *Base) Marshal(self Record, format uint8) ([]byte, error) { if b.Meta() == nil { return nil, errors.New("missing meta") } if b.Meta().Deleted > 0 { return nil, nil } dumped, err := dsd.Dump(self, format) if err != nil { return nil, err } return dumped, nil } // MarshalRecord packs the object, including metadata, into a byte array for saving in a database. func (b *Base) MarshalRecord(self Record) ([]byte, error) { if b.Meta() == nil { return nil, errors.New("missing meta") } // version c := container.New([]byte{1}) // meta encoding metaSection, err := dsd.Dump(b.meta, dsd.GenCode) if err != nil { return nil, err } c.AppendAsBlock(metaSection) // data dataSection, err := b.Marshal(self, dsd.JSON) if err != nil { return nil, err } c.Append(dataSection) return c.CompileData(), nil } // IsWrapped returns whether the record is a Wrapper. func (b *Base) IsWrapped() bool { return false } // GetAccessor returns an accessor for this record, if available. func (b *Base) GetAccessor(self Record) accessor.Accessor { return accessor.NewStructAccessor(self) }