github.com/cozy/cozy-stack@v0.0.0-20240327093429-939e4a21320e/model/move/cursor.go (about) 1 package move 2 3 import ( 4 "fmt" 5 "sort" 6 "strings" 7 8 "github.com/cozy/cozy-stack/model/instance" 9 "github.com/cozy/cozy-stack/model/vfs" 10 "github.com/cozy/cozy-stack/pkg/consts" 11 "github.com/cozy/cozy-stack/pkg/couchdb" 12 ) 13 14 // Cursor can be used to know which files must be included in a part. 15 type Cursor struct { 16 Number int 17 Doctype string 18 ID string 19 } 20 21 // String returns a string representation of the cursor. 22 func (c Cursor) String() string { 23 return fmt.Sprintf("%s/%s", c.Doctype, c.ID) 24 } 25 26 // ParseCursor checks that the given cursor is part of our pre-defined list of 27 // cursors. 28 func ParseCursor(exportDoc *ExportDoc, cursorStr string) (Cursor, error) { 29 if cursorStr == "" { 30 return Cursor{0, consts.Files, ""}, nil 31 } 32 for i, c := range exportDoc.PartsCursors { 33 if c == cursorStr { 34 return parseCursor(i+1, cursorStr) 35 } 36 } 37 return Cursor{}, ErrExportInvalidCursor 38 } 39 40 func parseCursor(number int, cursorStr string) (Cursor, error) { 41 parts := strings.SplitN(cursorStr, "/", 2) 42 if len(parts) != 2 { 43 return Cursor{}, ErrExportInvalidCursor 44 } 45 return Cursor{number, parts[0], parts[1]}, nil 46 } 47 48 func splitFiles(partsSize, remaining int64, sizesByID map[string]int64, doctype string) ([]string, int64) { 49 ids := make([]string, 0, len(sizesByID)) 50 for id := range sizesByID { 51 ids = append(ids, id) 52 } 53 sort.Strings(ids) 54 55 cursors := make([]string, 0) 56 for _, id := range ids { 57 size := sizesByID[id] 58 if size > remaining && remaining != partsSize { 59 remaining = partsSize 60 cursor := Cursor{0, doctype, id}.String() 61 cursors = append(cursors, cursor) 62 } 63 remaining -= size 64 } 65 66 return cursors, remaining 67 } 68 69 func listFilesFromCursor(inst *instance.Instance, exportDoc *ExportDoc, start Cursor) ([]*vfs.FileDoc, error) { 70 if start.Doctype != consts.Files { 71 return []*vfs.FileDoc{}, nil 72 } 73 74 var end Cursor 75 if start.Number < len(exportDoc.PartsCursors) { 76 c, err := parseCursor(start.Number+1, exportDoc.PartsCursors[start.Number]) 77 if err != nil { 78 return nil, err 79 } 80 end = c 81 } 82 if end.Doctype != consts.Files { 83 end = Cursor{len(exportDoc.PartsCursors), consts.Files, couchdb.MaxString} 84 } 85 86 var files []*vfs.FileDoc 87 req := couchdb.AllDocsRequest{ 88 StartKeyDocID: start.ID, 89 EndKeyDocID: end.ID, 90 Limit: 1000, 91 } 92 for { 93 var results []*vfs.FileDoc 94 if err := couchdb.GetAllDocs(inst, consts.Files, &req, &results); err != nil { 95 return nil, err 96 } 97 if len(results) == 0 { 98 break 99 } 100 for _, res := range results { 101 if res.DocID == end.ID { 102 return files, nil 103 } 104 if res.Type == consts.FileType { // Exclude the directories 105 files = append(files, res) 106 } 107 } 108 req.StartKeyDocID = results[len(results)-1].DocID 109 req.Skip = 1 // Do not fetch again the last file from this page 110 } 111 112 return files, nil 113 } 114 115 func listVersionsFromCursor(inst *instance.Instance, exportDoc *ExportDoc, start Cursor) ([]*vfs.Version, error) { 116 var end Cursor 117 if start.Number < len(exportDoc.PartsCursors) { 118 c, err := parseCursor(start.Number+1, exportDoc.PartsCursors[start.Number]) 119 if err != nil { 120 return nil, err 121 } 122 end = c 123 } 124 if end.Doctype == consts.Files { 125 return []*vfs.Version{}, nil 126 } else if end.Doctype == "" { 127 end = Cursor{len(exportDoc.PartsCursors), consts.FilesVersions, couchdb.MaxString} 128 } 129 130 if start.Doctype != consts.FilesVersions { 131 start = Cursor{start.Number, consts.FilesVersions, ""} 132 } 133 134 var versions []*vfs.Version 135 req := couchdb.AllDocsRequest{ 136 StartKeyDocID: start.ID, 137 EndKeyDocID: end.ID, 138 Limit: 1000, 139 } 140 for { 141 var results []*vfs.Version 142 if err := couchdb.GetAllDocs(inst, consts.FilesVersions, &req, &results); err != nil { 143 return nil, err 144 } 145 if len(results) == 0 { 146 break 147 } 148 for _, res := range results { 149 if res.DocID == end.ID { 150 return versions, nil 151 } 152 versions = append(versions, res) 153 } 154 req.StartKeyDocID = results[len(results)-1].DocID 155 req.Skip = 1 // Do not fetch again the last file from this page 156 } 157 158 return versions, nil 159 }