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
192 lines
4.7 KiB
Go
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))
|
|
}
|