corso/src/internal/m365/graph/metadata_collection.go
Abin Simon be59928f98
Fix cases where we had a trailing comma (#4208)
Not sure if we wanna merge it as it might generate way too many conflicts, but this should help us add a linter in CI. If we are good, I'll add something that can do lints for this in a follow up PR.

Super hacky, but this fix was created using `while true ; do tree-grepper -q go '(argument_list "," @nope .)' | tail -n1| awk -F: "{print \$1,\"+\"\$2\" -c ':norm \$xJZZ'\"}" | xargs vim ; done`.

---

#### Does this PR need a docs update or release note?

- [ ]  Yes, it's included
- [ ] 🕐 Yes, but in a later PR
- [ ]  No

#### Type of change

<!--- Please check the type of change your PR introduces: --->
- [ ] 🌻 Feature
- [ ] 🐛 Bugfix
- [ ] 🗺️ Documentation
- [ ] 🤖 Supportability/Tests
- [ ] 💻 CI/Deployment
- [ ] 🧹 Tech Debt/Cleanup

#### Issue(s)

<!-- Can reference multiple issues. Use one of the following "magic words" - "closes, fixes" to auto-close the Github issue. -->
* https://github.com/alcionai/corso/issues/3654

#### Test Plan

<!-- How will this be tested prior to merging.-->
- [ ] 💪 Manual
- [ ]  Unit test
- [ ] 💚 E2E
2023-09-08 17:10:29 +00:00

192 lines
4.7 KiB
Go

package graph
import (
"bytes"
"context"
"encoding/json"
"io"
"github.com/alcionai/clues"
"github.com/alcionai/corso/src/internal/data"
"github.com/alcionai/corso/src/internal/m365/support"
"github.com/alcionai/corso/src/pkg/fault"
"github.com/alcionai/corso/src/pkg/path"
)
var (
_ data.BackupCollection = &MetadataCollection{}
_ data.Item = &MetadataItem{}
)
// MetadataCollection in a simple collection that assumes all items to be
// returned are already resident in-memory and known when the collection is
// created. This collection has no logic for lazily fetching item data.
type MetadataCollection struct {
fullPath path.Path
items []MetadataItem
statusUpdater support.StatusUpdater
}
// MetadataCollectionEntry describes a file that should get added to a metadata
// collection. The Data value will be encoded into json as part of a
// transformation into a MetadataItem.
type MetadataCollectionEntry struct {
fileName string
data any
}
func NewMetadataEntry(fileName string, mData any) MetadataCollectionEntry {
return MetadataCollectionEntry{fileName, mData}
}
func (mce MetadataCollectionEntry) toMetadataItem() (MetadataItem, error) {
if len(mce.fileName) == 0 {
return MetadataItem{}, clues.New("missing metadata filename")
}
if mce.data == nil {
return MetadataItem{}, clues.New("missing metadata")
}
buf := &bytes.Buffer{}
encoder := json.NewEncoder(buf)
if err := encoder.Encode(mce.data); err != nil {
return MetadataItem{}, clues.Wrap(err, "serializing metadata")
}
return NewMetadataItem(mce.fileName, buf.Bytes()), nil
}
// MakeMetadataCollection creates a metadata collection that has a file
// containing all the provided metadata as a single json object. Returns
// nil if the map does not have any entries.
func MakeMetadataCollection(
pathPrefix path.Path,
metadata []MetadataCollectionEntry,
statusUpdater support.StatusUpdater,
) (data.BackupCollection, error) {
if len(metadata) == 0 {
return nil, nil
}
items := make([]MetadataItem, 0, len(metadata))
for _, md := range metadata {
item, err := md.toMetadataItem()
if err != nil {
return nil, err
}
items = append(items, item)
}
coll := NewMetadataCollection(pathPrefix, items, statusUpdater)
return coll, nil
}
func NewMetadataCollection(
p path.Path,
items []MetadataItem,
statusUpdater support.StatusUpdater,
) *MetadataCollection {
return &MetadataCollection{
fullPath: p,
items: items,
statusUpdater: statusUpdater,
}
}
func (md MetadataCollection) FullPath() path.Path {
return md.fullPath
}
// TODO(ashmrtn): Fill in with previous path once the Controller compares old
// and new folder hierarchies.
func (md MetadataCollection) PreviousPath() path.Path {
return nil
}
// TODO(ashmrtn): Fill in once the Controller compares old and new folder
// hierarchies.
func (md MetadataCollection) State() data.CollectionState {
return data.NewState
}
func (md MetadataCollection) DoNotMergeItems() bool {
return false
}
func (md MetadataCollection) Items(
ctx context.Context,
_ *fault.Bus, // not used, just here for interface compliance
) <-chan data.Item {
res := make(chan data.Item)
go func() {
totalBytes := int64(0)
defer func() {
// Need to report after the collection is created because otherwise
// statusUpdater may not have accounted for the fact that this collection
// will be running.
status := support.CreateStatus(
ctx,
support.Backup,
1,
support.CollectionMetrics{
Objects: len(md.items),
Successes: len(md.items),
Bytes: totalBytes,
},
md.fullPath.Folder(false))
md.statusUpdater(status)
}()
defer close(res)
for _, item := range md.items {
totalBytes += int64(len(item.data))
res <- item
}
}()
return res
}
// MetadataItem is an in-memory data.Item implementation. MetadataItem does
// not implement additional interfaces like data.ItemInfo, so it should only
// be used for items with a small amount of content that don't need to be added
// to backup details.
//
// Currently the expected use-case for this struct are storing metadata for a
// backup like delta tokens or a mapping of container IDs to container paths.
type MetadataItem struct {
// uuid is an ID that can be used to refer to the item.
uuid string
// data is a buffer of data that the item refers to.
data []byte
}
func NewMetadataItem(uuid string, itemData []byte) MetadataItem {
return MetadataItem{
uuid: uuid,
data: itemData,
}
}
func (mi MetadataItem) ID() string {
return mi.uuid
}
// TODO(ashmrtn): Fill in once we know how to handle this.
func (mi MetadataItem) Deleted() bool {
return false
}
func (mi MetadataItem) ToReader() io.ReadCloser {
return io.NopCloser(bytes.NewReader(mi.data))
}