From c2ed126ab80359796fabf63b9462386c7e1d8221 Mon Sep 17 00:00:00 2001 From: Derrick Hammer Date: Thu, 4 Jan 2024 08:20:37 -0500 Subject: [PATCH] feat: wip directory metadata --- metadata/directory.go | 92 ++++++ metadata/directory_details.go | 75 +++++ metadata/directory_reference.go | 87 +++++ metadata/directory_test.go | 514 +++++++++++++++++++++++++++++ metadata/extra.go | 86 +++++ metadata/file_reference.go | 131 ++++++++ metadata/file_version.go | 114 +++++++ metadata/file_version_thumbnail.go | 97 ++++++ metadata/meta.go | 14 + metadata/misc.go | 55 +++ metadata/testdata/directory.bin | Bin 0 -> 1707 bytes metadata/testdata/directory.json | 193 +++++++++++ 12 files changed, 1458 insertions(+) create mode 100644 metadata/directory.go create mode 100644 metadata/directory_details.go create mode 100644 metadata/directory_reference.go create mode 100644 metadata/directory_test.go create mode 100644 metadata/extra.go create mode 100644 metadata/file_reference.go create mode 100644 metadata/file_version.go create mode 100644 metadata/file_version_thumbnail.go create mode 100644 metadata/meta.go create mode 100644 metadata/misc.go create mode 100644 metadata/testdata/directory.bin create mode 100644 metadata/testdata/directory.json diff --git a/metadata/directory.go b/metadata/directory.go new file mode 100644 index 0000000..9b251a1 --- /dev/null +++ b/metadata/directory.go @@ -0,0 +1,92 @@ +package metadata + +import ( + "errors" + "git.lumeweb.com/LumeWeb/libs5-go/serialize" + "git.lumeweb.com/LumeWeb/libs5-go/types" + "github.com/vmihailenco/msgpack/v5" +) + +type DirectoryMetadata struct { + Details DirectoryMetadataDetails `json:"details"` + Directories map[string]DirectoryReference `json:"directories"` + Files map[string]FileReference `json:"files"` + ExtraMetadata ExtraMetadata `json:"extraMetadata"` + BaseMetadata +} + +var _ SerializableMetadata = (*DirectoryMetadata)(nil) + +func NewDirectoryMetadata(details DirectoryMetadataDetails, directories map[string]DirectoryReference, files map[string]FileReference, extraMetadata ExtraMetadata) *DirectoryMetadata { + dirMetadata := &DirectoryMetadata{ + Details: details, + Directories: directories, + Files: files, + ExtraMetadata: extraMetadata, + } + + dirMetadata.Type = "directory" + return dirMetadata +} +func (dm *DirectoryMetadata) EncodeMsgpack(enc *msgpack.Encoder) error { + err := serialize.InitMarshaller(types.MetadataTypeDirectory, enc) + if err != nil { + return err + } + + items := make([]interface{}, 4) + + items[0] = dm.Details + items[1] = dm.Directories + items[2] = dm.Files + items[3] = dm.ExtraMetadata.Data + + return enc.Encode(items) +} + +func (dm *DirectoryMetadata) DecodeMsgpack(dec *msgpack.Decoder) error { + err := serialize.InitUnmarshaller(types.MetadataTypeDirectory, dec) + if err != nil { + return err + } + val, err := dec.DecodeArrayLen() + + if err != nil { + return err + } + + if val != 4 { + return errors.New(" Corrupted metadata") + } + + for i := 0; i < val; i++ { + switch i { + case 0: + err = dec.Decode(&dm.Details) + if err != nil { + return err + } + case 1: + err = dec.Decode(&dm.Directories) + if err != nil { + return err + } + + case 2: + err = dec.Decode(&dm.Files) + if err != nil { + return err + } + case 3: + intMap, err := decodeIntMap(dec) + if err != nil { + return err + } + dm.ExtraMetadata.Data = intMap + } + } + + dm.Type = "directory" + + return nil +} diff --git a/metadata/directory_details.go b/metadata/directory_details.go new file mode 100644 index 0000000..f76b8a7 --- /dev/null +++ b/metadata/directory_details.go @@ -0,0 +1,75 @@ +package metadata + +import "github.com/vmihailenco/msgpack/v5" + +type DirectoryMetadataDetails struct { + Data map[int]interface{} +} + +func NewDirectoryMetadataDetails(data map[int]interface{}) *DirectoryMetadataDetails { + return &DirectoryMetadataDetails{ + Data: data, + } +} + +func (dmd *DirectoryMetadataDetails) IsShared() bool { + _, exists := dmd.Data[3] + return exists +} + +func (dmd *DirectoryMetadataDetails) IsSharedReadOnly() bool { + value, exists := dmd.Data[3].([]interface{}) + if !exists { + return false + } + return len(value) > 1 && value[1] == true +} + +func (dmd *DirectoryMetadataDetails) IsSharedReadWrite() bool { + value, exists := dmd.Data[3].([]interface{}) + if !exists { + return false + } + return len(value) > 2 && value[2] == true +} + +func (dmd *DirectoryMetadataDetails) SetShared(value bool, write bool) { + if dmd.Data == nil { + dmd.Data = make(map[int]interface{}) + } + sharedValue, exists := dmd.Data[3].([]interface{}) + if !exists { + sharedValue = make([]interface{}, 3) + dmd.Data[3] = sharedValue + } + if write { + sharedValue[2] = value + } else { + sharedValue[1] = value + } +} +func (dmd *DirectoryMetadataDetails) DecodeMsgpack(dec *msgpack.Decoder) error { + mapLen, err := dec.DecodeMapLen() + + if err != nil { + return err + } + + for i := 0; i < mapLen; i++ { + key, err := dec.DecodeInt8() + if err != nil { + return err + } + value, err := dec.DecodeInterface() + if err != nil { + return err + } + dmd.Data[int(key)] = value + } + + return nil +} + +func (dmd DirectoryMetadataDetails) EncodeMsgpack(enc *msgpack.Encoder) error { + return enc.Encode(dmd.Data) +} diff --git a/metadata/directory_reference.go b/metadata/directory_reference.go new file mode 100644 index 0000000..ebb5503 --- /dev/null +++ b/metadata/directory_reference.go @@ -0,0 +1,87 @@ +package metadata + +import "github.com/vmihailenco/msgpack/v5" + +var _ SerializableMetadata = (*DirectoryReference)(nil) + +type DirectoryReference struct { + Created uint64 `json:"created"` + Name string `json:"name"` + EncryptedWriteKey Base64UrlBinary `json:"encryptedWriteKey,string"` + PublicKey Base64UrlBinary `json:"publicKey,string"` + EncryptionKey Base64UrlBinary `json:"encryptionKey,string"` + Ext map[string]interface{} `json:"ext"` + URI string `json:"uri"` + Key string `json:"key"` + Size int64 `json:"size"` +} + +func NewDirectoryReference(created uint64, name string, encryptedWriteKey, publicKey, encryptionKey []byte, ext map[string]interface{}) *DirectoryReference { + return &DirectoryReference{ + Created: created, + Name: name, + EncryptedWriteKey: encryptedWriteKey, + PublicKey: publicKey, + EncryptionKey: encryptionKey, + Ext: ext, + URI: "", + Key: "", + Size: 0, + } +} + +func (dr *DirectoryReference) EncodeMsgpack(enc *msgpack.Encoder) error { + data := map[int]interface{}{ + 1: dr.Name, + 2: dr.Created, + 3: dr.PublicKey, + 4: dr.EncryptedWriteKey, + } + + if dr.EncryptionKey != nil { + data[5] = dr.EncryptionKey + } + + if dr.Ext != nil { + data[6] = dr.Ext + } + + return enc.Encode(data) +} + +func (dr *DirectoryReference) DecodeMsgpack(dec *msgpack.Decoder) error { + var ( + err error + l int + ) + if l, err = dec.DecodeMapLen(); err != nil { + return err + } + + for i := 0; i < l; i++ { + key, err := dec.DecodeInt8() + if err != nil { + return err + } + value, err := dec.DecodeInterface() + if err != nil { + return err + } + switch key { + case int8(1): + dr.Name = value.(string) + case int8(2): + dr.Created = value.(uint64) + case int8(3): + dr.PublicKey = value.([]byte) + case int8(4): + dr.EncryptedWriteKey = value.([]byte) + case int8(5): + dr.EncryptionKey = value.([]byte) + case int8(6): + dr.Ext = value.(map[string]interface{}) + } + } + + return nil +} diff --git a/metadata/directory_test.go b/metadata/directory_test.go new file mode 100644 index 0000000..ecd8a41 --- /dev/null +++ b/metadata/directory_test.go @@ -0,0 +1,514 @@ +package metadata + +import ( + "encoding/json" + "fmt" + "git.lumeweb.com/LumeWeb/libs5-go/encoding" + cmp "github.com/LumeWeb/go-cmp" + "github.com/vmihailenco/msgpack/v5" + "os" + "path/filepath" + "reflect" + "testing" +) + +func readFile(filename string) []byte { + filePath := filepath.Join("testdata", filename) + data, err := os.ReadFile(filePath) + if err != nil { + panic(err) + } + return data +} + +func getDirectoryMeta() *DirectoryMetadata { + data := readFile("directory.json") + + var dir DirectoryMetadata + + err := json.Unmarshal(data, &dir) + if err != nil { + panic(err) + } + + return &dir +} + +func TestDirectoryMetadata_DecodeMsgpack(t *testing.T) { + type fields struct { + Details DirectoryMetadataDetails + Directories map[string]DirectoryReference + Files map[string]FileReference + ExtraMetadata ExtraMetadata + } + tests := []struct { + name string + wantErr bool + }{ + { + name: "Decode", + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + jsonDm := getDirectoryMeta() + dm := &DirectoryMetadata{} + + if err := msgpack.Unmarshal(readFile("directory.bin"), dm); (err != nil) != tt.wantErr { + t.Errorf("DecodeMsgpack() error = %v, wantErr %v", err, tt.wantErr) + } + + fmt.Println(cmp.Diff(jsonDm, dm)) + if !cmp.Equal(jsonDm, dm) { + t.Errorf("DecodeMsgpack() error = %v, wantErr %v", "msgpack does not match json", tt.wantErr) + } + }) + } +} + +func TestDirectoryMetadata_EncodeMsgpack(t *testing.T) { + type fields struct { + Details DirectoryMetadataDetails + Directories map[string]DirectoryReference + Files map[string]FileReference + ExtraMetadata ExtraMetadata + } + type args struct { + enc *msgpack.Encoder + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + dm := &DirectoryMetadata{ + Details: tt.fields.Details, + Directories: tt.fields.Directories, + Files: tt.fields.Files, + ExtraMetadata: tt.fields.ExtraMetadata, + } + if err := dm.EncodeMsgpack(tt.args.enc); (err != nil) != tt.wantErr { + t.Errorf("EncodeMsgpack() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestDirectoryReference_DecodeMsgpack(t *testing.T) { + type fields struct { + Created uint64 + Name string + EncryptedWriteKey []byte + PublicKey []byte + EncryptionKey []byte + Ext map[string]interface{} + URI string + Key string + Size int64 + } + type args struct { + dec *msgpack.Decoder + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + dr := &DirectoryReference{ + Created: tt.fields.Created, + Name: tt.fields.Name, + EncryptedWriteKey: tt.fields.EncryptedWriteKey, + PublicKey: tt.fields.PublicKey, + EncryptionKey: tt.fields.EncryptionKey, + Ext: tt.fields.Ext, + URI: tt.fields.URI, + Key: tt.fields.Key, + Size: tt.fields.Size, + } + if err := dr.DecodeMsgpack(tt.args.dec); (err != nil) != tt.wantErr { + t.Errorf("DecodeMsgpack() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestDirectoryReference_EncodeMsgpack(t *testing.T) { + type fields struct { + Created uint64 + Name string + EncryptedWriteKey []byte + PublicKey []byte + EncryptionKey []byte + Ext map[string]interface{} + URI string + Key string + Size int64 + } + type args struct { + enc *msgpack.Encoder + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + dr := &DirectoryReference{ + Created: tt.fields.Created, + Name: tt.fields.Name, + EncryptedWriteKey: tt.fields.EncryptedWriteKey, + PublicKey: tt.fields.PublicKey, + EncryptionKey: tt.fields.EncryptionKey, + Ext: tt.fields.Ext, + URI: tt.fields.URI, + Key: tt.fields.Key, + Size: tt.fields.Size, + } + if err := dr.EncodeMsgpack(tt.args.enc); (err != nil) != tt.wantErr { + t.Errorf("EncodeMsgpack() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestFileReference_DecodeMsgpack(t *testing.T) { + type fields struct { + Name string + Created int + Version int + File *FileVersion + Ext map[string]interface{} + History map[int]*FileVersion + MimeType string + URI string + Key string + } + type args struct { + dec *msgpack.Decoder + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fr := &FileReference{ + Name: tt.fields.Name, + Created: tt.fields.Created, + Version: tt.fields.Version, + File: tt.fields.File, + Ext: tt.fields.Ext, + History: tt.fields.History, + MimeType: tt.fields.MimeType, + URI: tt.fields.URI, + Key: tt.fields.Key, + } + if err := fr.DecodeMsgpack(tt.args.dec); (err != nil) != tt.wantErr { + t.Errorf("DecodeMsgpack() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestFileReference_EncodeMsgpack(t *testing.T) { + type fields struct { + Name string + Created int + Version int + File *FileVersion + Ext map[string]interface{} + History map[int]*FileVersion + MimeType string + URI string + Key string + } + type args struct { + enc *msgpack.Encoder + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fr := &FileReference{ + Name: tt.fields.Name, + Created: tt.fields.Created, + Version: tt.fields.Version, + File: tt.fields.File, + Ext: tt.fields.Ext, + History: tt.fields.History, + MimeType: tt.fields.MimeType, + URI: tt.fields.URI, + Key: tt.fields.Key, + } + if err := fr.EncodeMsgpack(tt.args.enc); (err != nil) != tt.wantErr { + t.Errorf("EncodeMsgpack() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestFileReference_Modified(t *testing.T) { + type fields struct { + Name string + Created int + Version int + File *FileVersion + Ext map[string]interface{} + History map[int]*FileVersion + MimeType string + URI string + Key string + } + tests := []struct { + name string + fields fields + want int + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fr := &FileReference{ + Name: tt.fields.Name, + Created: tt.fields.Created, + Version: tt.fields.Version, + File: tt.fields.File, + Ext: tt.fields.Ext, + History: tt.fields.History, + MimeType: tt.fields.MimeType, + URI: tt.fields.URI, + Key: tt.fields.Key, + } + if got := fr.Modified(); got != tt.want { + t.Errorf("Modified() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestFileVersionThumbnail_DecodeMsgpack(t *testing.T) { + type fields struct { + ImageType string + AspectRatio float64 + CID *encoding.EncryptedCID + Thumbhash []byte + } + type args struct { + dec *msgpack.Decoder + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fvt := &FileVersionThumbnail{ + ImageType: tt.fields.ImageType, + AspectRatio: tt.fields.AspectRatio, + CID: tt.fields.CID, + Thumbhash: tt.fields.Thumbhash, + } + if err := fvt.DecodeMsgpack(tt.args.dec); (err != nil) != tt.wantErr { + t.Errorf("DecodeMsgpack() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestFileVersionThumbnail_Encode(t *testing.T) { + type fields struct { + ImageType string + AspectRatio float64 + CID *encoding.EncryptedCID + Thumbhash []byte + } + tests := []struct { + name string + fields fields + want map[int]interface{} + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fvt := &FileVersionThumbnail{ + ImageType: tt.fields.ImageType, + AspectRatio: tt.fields.AspectRatio, + CID: tt.fields.CID, + Thumbhash: tt.fields.Thumbhash, + } + if got := fvt.Encode(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Encode() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestFileVersionThumbnail_EncodeMsgpack(t *testing.T) { + type fields struct { + ImageType string + AspectRatio float64 + CID *encoding.EncryptedCID + Thumbhash []byte + } + type args struct { + enc *msgpack.Encoder + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fvt := &FileVersionThumbnail{ + ImageType: tt.fields.ImageType, + AspectRatio: tt.fields.AspectRatio, + CID: tt.fields.CID, + Thumbhash: tt.fields.Thumbhash, + } + if err := fvt.EncodeMsgpack(tt.args.enc); (err != nil) != tt.wantErr { + t.Errorf("EncodeMsgpack() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestFileVersion_CID(t *testing.T) { + type fields struct { + Ts int + EncryptedCID *encoding.EncryptedCID + PlaintextCID *encoding.CID + Thumbnail *FileVersionThumbnail + Hashes []*encoding.Multihash + Ext map[string]interface{} + } + tests := []struct { + name string + fields fields + want *encoding.CID + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fv := &FileVersion{ + Ts: tt.fields.Ts, + EncryptedCID: tt.fields.EncryptedCID, + PlaintextCID: tt.fields.PlaintextCID, + Thumbnail: tt.fields.Thumbnail, + Hashes: tt.fields.Hashes, + Ext: tt.fields.Ext, + } + if got := fv.CID(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("CID() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestFileVersion_DecodeMsgpack(t *testing.T) { + type fields struct { + Ts int + EncryptedCID *encoding.EncryptedCID + PlaintextCID *encoding.CID + Thumbnail *FileVersionThumbnail + Hashes []*encoding.Multihash + Ext map[string]interface{} + } + type args struct { + dec *msgpack.Decoder + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fv := &FileVersion{ + Ts: tt.fields.Ts, + EncryptedCID: tt.fields.EncryptedCID, + PlaintextCID: tt.fields.PlaintextCID, + Thumbnail: tt.fields.Thumbnail, + Hashes: tt.fields.Hashes, + Ext: tt.fields.Ext, + } + if err := fv.DecodeMsgpack(tt.args.dec); (err != nil) != tt.wantErr { + t.Errorf("DecodeMsgpack() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestFileVersion_EncodeMsgpack(t *testing.T) { + type fields struct { + Ts int + EncryptedCID *encoding.EncryptedCID + PlaintextCID *encoding.CID + Thumbnail *FileVersionThumbnail + Hashes []*encoding.Multihash + Ext map[string]interface{} + } + type args struct { + enc *msgpack.Encoder + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fv := &FileVersion{ + Ts: tt.fields.Ts, + EncryptedCID: tt.fields.EncryptedCID, + PlaintextCID: tt.fields.PlaintextCID, + Thumbnail: tt.fields.Thumbnail, + Hashes: tt.fields.Hashes, + Ext: tt.fields.Ext, + } + if err := fv.EncodeMsgpack(tt.args.enc); (err != nil) != tt.wantErr { + t.Errorf("EncodeMsgpack() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/metadata/extra.go b/metadata/extra.go new file mode 100644 index 0000000..8389a29 --- /dev/null +++ b/metadata/extra.go @@ -0,0 +1,86 @@ +package metadata + +import ( + "git.lumeweb.com/LumeWeb/libs5-go/encoding" + "git.lumeweb.com/LumeWeb/libs5-go/types" + "github.com/vmihailenco/msgpack/v5" +) + +type ExtraMetadata struct { + Data map[int]interface{} +} + +func NewExtraMetadata(data map[int]interface{}) *ExtraMetadata { + return &ExtraMetadata{ + Data: data, + } +} + +func (em *ExtraMetadata) ToJSON() map[string]interface{} { + jsonObject := make(map[string]interface{}) + names := map[types.MetadataExtension]string{ + types.MetadataExtensionLicenses: "licenses", + types.MetadataExtensionDonationKeys: "donationKeys", + types.MetadataExtensionWikidataClaims: "wikidataClaims", + types.MetadataExtensionLanguages: "languages", + types.MetadataExtensionSourceUris: "sourceUris", + types.MetadataExtensionPreviousVersions: "previousVersions", + types.MetadataExtensionTimestamp: "timestamp", + types.MetadataExtensionOriginalTimestamp: "originalTimestamp", + types.MetadataExtensionTags: "tags", + types.MetadataExtensionCategories: "categories", + types.MetadataExtensionBasicMediaMetadata: "basicMediaMetadata", + types.MetadataExtensionViewTypes: "viewTypes", + types.MetadataExtensionBridge: "bridge", + types.MetadataExtensionRoutingHints: "routingHints", + } + + for key, value := range em.Data { + name, ok := names[types.MetadataExtension(key)] + if ok { + if types.MetadataExtension(key) == types.MetadataExtensionUpdateCID { + cid, err := encoding.CIDFromBytes(value.([]byte)) + var cidString string + if err == nil { + cidString, err = cid.ToString() + } + + if err == nil { + jsonObject["updateCID"] = cidString + } else { + jsonObject["updateCID"] = "" + } + + } else { + jsonObject[name] = value + } + } + } + + return jsonObject +} +func (em *ExtraMetadata) DecodeMsgpack(dec *msgpack.Decoder) error { + mapLen, err := dec.DecodeMapLen() + + if err != nil { + return err + } + + for i := 0; i < mapLen; i++ { + key, err := dec.DecodeInt8() + if err != nil { + return err + } + value, err := dec.DecodeInterface() + if err != nil { + return err + } + em.Data[int(key)] = value + } + + return nil +} + +func (em ExtraMetadata) EncodeMsgpack(enc *msgpack.Encoder) error { + return enc.Encode(em.Data) +} diff --git a/metadata/file_reference.go b/metadata/file_reference.go new file mode 100644 index 0000000..8b16c09 --- /dev/null +++ b/metadata/file_reference.go @@ -0,0 +1,131 @@ +package metadata + +import "github.com/vmihailenco/msgpack/v5" + +var _ SerializableMetadata = (*FileReference)(nil) + +type FileReference struct { + Name string `json:"name"` + Created int `json:"created"` + Version int `json:"version"` + File *FileVersion `json:"file"` + Ext map[string]interface{} `json:"ext"` + History map[int]*FileVersion `json:"history"` + MimeType string `json:"mimeType"` + URI string `json:"uri"` + Key string `json:"key"` +} + +func NewFileReference(name string, created, version int, file *FileVersion, ext map[string]interface{}, history map[int]*FileVersion, mimeType string) *FileReference { + return &FileReference{ + Name: name, + Created: created, + Version: version, + File: file, + Ext: ext, + History: history, + MimeType: mimeType, + URI: "", + Key: "", + } +} + +func (fr *FileReference) Modified() int { + return fr.File.Ts +} + +func (fr *FileReference) EncodeMsgpack(enc *msgpack.Encoder) error { + data := map[int]interface{}{ + 1: fr.Name, + 2: fr.Created, + 4: fr.File, + 5: fr.Version, + } + + if fr.MimeType != "" { + data[6] = fr.MimeType + } + + if fr.Ext != nil { + data[7] = fr.Ext + } + + if fr.History != nil { + historyData := make(map[int]interface{}) + for key, value := range fr.History { + historyData[key] = value + } + data[8] = historyData + } + return nil +} +func (fr *FileReference) DecodeMsgpack(dec *msgpack.Decoder) error { + mapLen, err := dec.DecodeMapLen() + + if err != nil { + return err + } + + for i := 0; i < mapLen; i++ { + key, err := dec.DecodeInt8() + if err != nil { + return err + } + + switch key { + case int8(1): + err := dec.Decode(&fr.Name) + if err != nil { + return err + } + case int8(2): + err := dec.Decode(&fr.Created) + if err != nil { + return err + } + case int8(4): + err := dec.Decode(&fr.File) + if err != nil { + return err + } + case int8(5): + val, err := dec.DecodeInt() + if err != nil { + return err + } + + fr.Version = val + case int8(6): + err := dec.Decode(&fr.MimeType) + if err != nil { + return err + } + case int8(7): + err := dec.Decode(&fr.Ext) + if err != nil { + return err + } + case int8(8): + historyDataLen, err := dec.DecodeMapLen() + if err != nil { + return err + } + fr.History = make(map[int]*FileVersion, historyDataLen) + for range fr.History { + k, err := dec.DecodeInt() + if err != nil { + return err + } + + var fileVersion FileVersion + err = dec.Decode(&fileVersion) + if err != nil { + return err + } + + fr.History[k] = &fileVersion + } + } + } + return nil +} diff --git a/metadata/file_version.go b/metadata/file_version.go new file mode 100644 index 0000000..3180daa --- /dev/null +++ b/metadata/file_version.go @@ -0,0 +1,114 @@ +package metadata + +import ( + "git.lumeweb.com/LumeWeb/libs5-go/encoding" + "github.com/vmihailenco/msgpack/v5" +) + +type FileVersion struct { + Ts int `json:"ts"` + EncryptedCID *encoding.EncryptedCID `json:"encryptedCID,string"` + PlaintextCID *encoding.CID `json:"plaintextCID,string"` + Thumbnail *FileVersionThumbnail `json:"thumbnail"` + Hashes []*encoding.Multihash `json:"hashes"` + Ext map[string]interface{} `json:"ext"` +} + +func NewFileVersion(ts int, encryptedCID *encoding.EncryptedCID, plaintextCID *encoding.CID, thumbnail *FileVersionThumbnail, hashes []*encoding.Multihash, ext map[string]interface{}) *FileVersion { + return &FileVersion{ + Ts: ts, + EncryptedCID: encryptedCID, + PlaintextCID: plaintextCID, + Thumbnail: thumbnail, + Hashes: hashes, + Ext: ext, + } +} + +func (fv *FileVersion) EncodeMsgpack(enc *msgpack.Encoder) error { + data := map[int]interface{}{ + 8: fv.Ts, + } + + if fv.EncryptedCID != nil { + data[1] = fv.EncryptedCID.ToBytes() + } + + if fv.PlaintextCID != nil { + data[2] = fv.PlaintextCID.ToBytes() + } + + if len(fv.Hashes) > 0 { + hashesData := make([][]byte, len(fv.Hashes)) + for i, hash := range fv.Hashes { + hashesData[i] = hash.FullBytes + } + data[9] = hashesData + } + + if fv.Thumbnail != nil { + data[10] = fv.Thumbnail.Encode() + } + + return enc.Encode(data) +} + +func (fv *FileVersion) DecodeMsgpack(dec *msgpack.Decoder) error { + mapLen, err := dec.DecodeMapLen() + + if err != nil { + return err + } + + for i := 0; i < mapLen; i++ { + key, err := dec.DecodeInt8() + if err != nil { + return err + } + switch key { + + case int8(1): + err := dec.Decode(&fv.EncryptedCID) + if err != nil { + return err + } + + case int8(2): + err := dec.Decode(&fv.PlaintextCID) + if err != nil { + return err + } + case int8(8): + err := dec.Decode(&fv.Ts) + if err != nil { + return err + } + case int8(9): + hashesData, err := dec.DecodeSlice() + if err != nil { + return err + } + + fv.Hashes = make([]*encoding.Multihash, len(hashesData)) + for i, hashData := range hashesData { + hashBytes := hashData.([]byte) + fv.Hashes[i] = encoding.NewMultihash(hashBytes) + } + + case int8(10): + err := dec.Decode(&fv.Thumbnail) + if err != nil { + return err + } + } + } + + return nil +} + +func (fv *FileVersion) CID() *encoding.CID { + if fv.PlaintextCID != nil { + return fv.PlaintextCID + } + return &fv.EncryptedCID.OriginalCID +} diff --git a/metadata/file_version_thumbnail.go b/metadata/file_version_thumbnail.go new file mode 100644 index 0000000..33cc6ba --- /dev/null +++ b/metadata/file_version_thumbnail.go @@ -0,0 +1,97 @@ +package metadata + +import ( + "git.lumeweb.com/LumeWeb/libs5-go/encoding" + "github.com/vmihailenco/msgpack/v5" +) + +type FileVersionThumbnail struct { + ImageType string + AspectRatio float64 + CID *encoding.EncryptedCID + Thumbhash []byte +} + +func NewFileVersionThumbnail(imageType string, aspectRatio float64, cid *encoding.EncryptedCID, thumbhash []byte) *FileVersionThumbnail { + return &FileVersionThumbnail{ + ImageType: imageType, + AspectRatio: aspectRatio, + CID: cid, + Thumbhash: thumbhash, + } +} + +func (fvt *FileVersionThumbnail) EncodeMsgpack(enc *msgpack.Encoder) error { + data := map[int]interface{}{ + 2: fvt.AspectRatio, + 3: fvt.CID.ToBytes(), + } + + if fvt.ImageType != "" { + data[1] = fvt.ImageType + } + + if fvt.Thumbhash != nil { + data[4] = fvt.Thumbhash + } + + return enc.Encode(data) +} +func (fvt *FileVersionThumbnail) DecodeMsgpack(dec *msgpack.Decoder) error { + mapLen, err := dec.DecodeMapLen() + + if err != nil { + return err + } + + for i := 0; i < mapLen; i++ { + key, err := dec.DecodeInt8() + if err != nil { + return err + } + switch key { + case int8(1): + err := dec.Decode(&fvt.ImageType) + if err != nil { + return err + } + case int8(2): + err := dec.Decode(&fvt.AspectRatio) + if err != nil { + return err + } + case int8(3): + val, err := dec.DecodeBytes() + if err != nil { + return err + } + fvt.CID, err = encoding.EncryptedCIDFromBytes(val) + if err != nil { + return err + } + + case int8(4): + err := dec.Decode(&fvt.Thumbhash) + if err != nil { + return err + } + } + } + return nil +} +func (fvt *FileVersionThumbnail) Encode() map[int]interface{} { + data := map[int]interface{}{ + 2: fvt.AspectRatio, + 3: fvt.CID.ToBytes(), + } + + if fvt.ImageType != "" { + data[1] = fvt.ImageType + } + + if fvt.Thumbhash != nil { + data[4] = fvt.Thumbhash + } + + return data +} diff --git a/metadata/meta.go b/metadata/meta.go new file mode 100644 index 0000000..1b059dd --- /dev/null +++ b/metadata/meta.go @@ -0,0 +1,14 @@ +package metadata + +import "github.com/vmihailenco/msgpack/v5" + +type Metadata interface { +} +type SerializableMetadata interface { + msgpack.CustomEncoder + msgpack.CustomDecoder +} + +type BaseMetadata struct { + Type string `json:"type"` +} diff --git a/metadata/misc.go b/metadata/misc.go new file mode 100644 index 0000000..a471752 --- /dev/null +++ b/metadata/misc.go @@ -0,0 +1,55 @@ +package metadata + +import ( + "encoding/base64" + "github.com/vmihailenco/msgpack/v5" +) + +type Base64UrlBinary []byte + +func (b *Base64UrlBinary) UnmarshalJSON(data []byte) error { + strData := string(data) + if len(strData) >= 2 && strData[0] == '"' && strData[len(strData)-1] == '"' { + strData = strData[1 : len(strData)-1] + } + + if strData == "null" { + return nil + } + + decodedData, err := base64.RawURLEncoding.DecodeString(strData) + if err != nil { + return err + } + + *b = (decodedData) + return nil +} +func (b Base64UrlBinary) MarshalJSON() ([]byte, error) { + return []byte(base64.RawURLEncoding.EncodeToString(b)), nil + +} + +func decodeIntMap(dec *msgpack.Decoder) (map[int]interface{}, error) { + mapLen, err := dec.DecodeMapLen() + + if err != nil { + return nil, err + } + + data := make(map[int]interface{}, mapLen) + + for i := 0; i < mapLen; i++ { + key, err := dec.DecodeInt() + if err != nil { + return nil, err + } + value, err := dec.DecodeInterface() + if err != nil { + return nil, err + } + data[key] = value + } + + return data, nil +} diff --git a/metadata/testdata/directory.bin b/metadata/testdata/directory.bin new file mode 100644 index 0000000000000000000000000000000000000000..826363de26221e2412490f4147c3f5776a67d854 GIT binary patch literal 1707 zcma!NnbOd>B(W$tqlFPnF`Z{%VC*^i`nxdm5yiLRr4!h{=(VY7EV2EeFF7w(eYwV| z8|UnbG@y{d(g z2t`nv-+b0$Y2tvm0?1}MqNXOF5Kw;jaW8MN;k3KW;@%?n_8D@!eGANx4poWvoh$h2 z$D@bQ+^h^YRS7youQ)Tkm61q=PzSx0V23*h$YwgCtR}zkLS3HK%Xbg0_O6~ETx^pk zrDOQiaaPlLqyED$-`QR0>1AbL+nHEUkdv95Sdy8ar(cj>pbNA+FR`SwD0K_MozP&) zEY5Fb#3~Gnj}Kqqj(Gjy3pi9l)3340eYRUP`E@M+Hgl&5J0x$sU)o=KUiZ~Q#@fq* z8yO5t&4KRNjqHvJU7%H#W@ZMunYoGSsXMT_1QJL%rC`A#b_4FBw_-QI!BXik>&Bnk zFS=DaI#c>fvp=d^%~D~Q%gJh$_QFN~RLTSV!LlFW7$XBC6Fmb%Ac7?%a0n4nLEFH( zsZ79u(9}hYGMWYAO?5(!EXgk_O3f<)W&>iC!W^q5g@|!2DW)STYVs=VH?>YGyjnCb zK2EuKRl9sW=kM9ybna@uZkI2adG^bf$UDFkxC1o>CS{gD-EdO};aEh`ff@PmQiLS+ zP-jz6pcs(iJZS8btOpjlR4-;$B^ec$<`(OfQ~)Czi377x3sKO%)dCi@!04P(c9Zp} z7vrqcHn~ElGxM#p!>`8me7c=eP`TDKnn7QzhY1*+t4dNUO7shI5;ODG6=x(GnVKQ2 zK@)&mrH+(x)WKGTCat{aSO14w*(2nu4eOK{Z+q&!53fAC=DXc=&cJ0`Ui2`cTGapm DJdKO9 literal 0 HcmV?d00001 diff --git a/metadata/testdata/directory.json b/metadata/testdata/directory.json new file mode 100644 index 0000000..15faafc --- /dev/null +++ b/metadata/testdata/directory.json @@ -0,0 +1,193 @@ +{ + "type": "directory", + "details": {}, + "directories": { + "arch": { + "name": "arch", + "created": 1704127624979, + "publicKey": "7Vd1kAf0LoYmKKQ9-C8Znl0npyjK2M4-ciiNWShOTpbD", + "encryptedWriteKey": "", + "encryptionKey": null, + "ext": null + } + }, + "files": { + "archlinux-bootstrap-x86_64.tar.gz": { + "name": "archlinux-bootstrap-x86_64.tar.gz", + "created": 1704127689514, + "modified": 1704127689514, + "version": 0, + "mimeType": null, + "file": { + "ts": 1704127689514, + "encryptedCID": null, + "cid": "z6e5xDpewiueHz6n9HJz21uGmTDvvctawujzYSeu9773k8s9RkG8a", + "hashes": null, + "thumbnail": null + }, + "ext": null, + "history": null + }, + "archlinux-bootstrap-x86_64.tar.gz.sig": { + "name": "archlinux-bootstrap-x86_64.tar.gz.sig", + "created": 1704127698951, + "modified": 1704127698951, + "version": 0, + "mimeType": "application/pgp-signature", + "file": { + "ts": 1704127698951, + "encryptedCID": null, + "cid": "z4odYNo3uQhHhypXze7ThLAKrnSGsJ3cRf1bYsvnt65Mqi9gp", + "hashes": null, + "thumbnail": null + }, + "ext": null, + "history": null + }, + "archlinux-x86_64.iso": { + "name": "archlinux-x86_64.iso", + "created": 1704127623412, + "modified": 1704127623412, + "version": 0, + "mimeType": "application/x-iso9660-image", + "file": { + "ts": 1704127623412, + "encryptedCID": null, + "cid": "z6e5sRB3BeynWLyrzRPLWFetUS7PNDZWZHURnFAyikcRyGTnxjMwC", + "hashes": null, + "thumbnail": null + }, + "ext": null, + "history": null + }, + "archlinux-x86_64.iso.sig": { + "name": "archlinux-x86_64.iso.sig", + "created": 1704127698648, + "modified": 1704127698648, + "version": 0, + "mimeType": "application/pgp-signature", + "file": { + "ts": 1704127698648, + "encryptedCID": null, + "cid": "z4odLKvhW8QPH4dLNqVzU1Zq6nj1MBdU6HYpJmThdzag4RJma", + "hashes": null, + "thumbnail": null + }, + "ext": null, + "history": null + }, + "archlinux-2024.01.01-x86_64.iso": { + "name": "archlinux-2024.01.01-x86_64.iso", + "created": 1704127623412, + "modified": 1704127623412, + "version": 0, + "mimeType": "application/x-iso9660-image", + "file": { + "ts": 1704127623412, + "encryptedCID": null, + "cid": "z6e5sRB3BeynWLyrzRPLWFetUS7PNDZWZHURnFAyikcRyGTnxjMwC", + "hashes": null, + "thumbnail": null + }, + "ext": null, + "history": null + }, + "archlinux-2024.01.01-x86_64.iso.sig": { + "name": "archlinux-2024.01.01-x86_64.iso.sig", + "created": 1704127698648, + "modified": 1704127698648, + "version": 0, + "mimeType": "application/pgp-signature", + "file": { + "ts": 1704127698648, + "encryptedCID": null, + "cid": "z4odLKvhW8QPH4dLNqVzU1Zq6nj1MBdU6HYpJmThdzag4RJma", + "hashes": null, + "thumbnail": null + }, + "ext": null, + "history": null + }, + "archlinux-2024.01.01-x86_64.iso.torrent": { + "name": "archlinux-2024.01.01-x86_64.iso.torrent", + "created": 1704127703578, + "modified": 1704127703578, + "version": 0, + "mimeType": "application/x-bittorrent", + "file": { + "ts": 1704127703578, + "encryptedCID": null, + "cid": "zHnmSZc3qDZMGfhNaZ6iVjkbxeVzMonKtAnTmBtRSwXy7RLYQK", + "hashes": null, + "thumbnail": null + }, + "ext": null, + "history": null + }, + "archlinux-bootstrap-2024.01.01-x86_64.tar.gz": { + "name": "archlinux-bootstrap-2024.01.01-x86_64.tar.gz", + "created": 1704127689514, + "modified": 1704127689514, + "version": 0, + "mimeType": null, + "file": { + "ts": 1704127689514, + "encryptedCID": null, + "cid": "z6e5xDpewiueHz6n9HJz21uGmTDvvctawujzYSeu9773k8s9RkG8a", + "hashes": null, + "thumbnail": null + }, + "ext": null, + "history": null + }, + "archlinux-bootstrap-2024.01.01-x86_64.tar.gz.sig": { + "name": "archlinux-bootstrap-2024.01.01-x86_64.tar.gz.sig", + "created": 1704127698951, + "modified": 1704127698951, + "version": 0, + "mimeType": "application/pgp-signature", + "file": { + "ts": 1704127698951, + "encryptedCID": null, + "cid": "z4odYNo3uQhHhypXze7ThLAKrnSGsJ3cRf1bYsvnt65Mqi9gp", + "hashes": null, + "thumbnail": null + }, + "ext": null, + "history": null + }, + "b2sums.txt": { + "name": "b2sums.txt", + "created": 1704127703594, + "modified": 1704127703594, + "version": 0, + "mimeType": "text/plain", + "file": { + "ts": 1704127703594, + "encryptedCID": null, + "cid": "zHnoyywarEZHWrSMZjL55nXeMAPsYi1mhXQs7ZmWqkpSg5Auz1", + "hashes": null, + "thumbnail": null + }, + "ext": null, + "history": null + }, + "sha256sums.txt": { + "name": "sha256sums.txt", + "created": 1704127703591, + "modified": 1704127703591, + "version": 0, + "mimeType": "text/plain", + "file": { + "ts": 1704127703591, + "encryptedCID": null, + "cid": "zHnnZGvqwnoN2AE342zhrxGWnuJpv79esruhY259TkXYyqmPB6", + "hashes": null, + "thumbnail": null + }, + "ext": null, + "history": null + } + }, + "extraMetadata": {} +}