remote_to_disk_file.go 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. package pkg
  2. import (
  3. "archive/zip"
  4. "context"
  5. "encoding/json"
  6. "errors"
  7. "fmt"
  8. "github.com/ente-io/cli/pkg/mapper"
  9. "github.com/ente-io/cli/pkg/model"
  10. "github.com/ente-io/cli/pkg/model/export"
  11. "github.com/ente-io/cli/utils"
  12. "log"
  13. "os"
  14. "path/filepath"
  15. "strings"
  16. "time"
  17. )
  18. func (c *ClICtrl) syncFiles(ctx context.Context, account model.Account) error {
  19. log.Printf("Starting file download")
  20. exportRoot := account.ExportDir
  21. _, albumIDToMetaMap, err := readFolderMetadata(exportRoot)
  22. if err != nil {
  23. return err
  24. }
  25. entries, err := c.getRemoteAlbumEntries(ctx)
  26. if err != nil {
  27. return err
  28. }
  29. log.Println("total entries", len(entries))
  30. model.SortAlbumFileEntry(entries)
  31. defer utils.TimeTrack(time.Now(), "process_files")
  32. var albumDiskInfo *albumDiskInfo
  33. for i, albumFileEntry := range entries {
  34. if albumFileEntry.SyncedLocally {
  35. continue
  36. }
  37. albumInfo, ok := albumIDToMetaMap[albumFileEntry.AlbumID]
  38. if !ok {
  39. log.Printf("Album %d not found in local metadata", albumFileEntry.AlbumID)
  40. continue
  41. }
  42. if albumInfo.IsDeleted {
  43. putErr := c.DeleteAlbumEntry(ctx, albumFileEntry)
  44. if putErr != nil {
  45. return putErr
  46. }
  47. continue
  48. }
  49. if albumDiskInfo == nil || albumDiskInfo.AlbumMeta.ID != albumInfo.ID {
  50. albumDiskInfo, err = readFilesMetadata(exportRoot, albumInfo)
  51. if err != nil {
  52. return err
  53. }
  54. }
  55. fileBytes, err := c.GetValue(ctx, model.RemoteFiles, []byte(fmt.Sprintf("%d", albumFileEntry.FileID)))
  56. if err != nil {
  57. return err
  58. }
  59. if fileBytes != nil {
  60. var existingEntry *model.RemoteFile
  61. err = json.Unmarshal(fileBytes, &existingEntry)
  62. if err != nil {
  63. return err
  64. }
  65. log.Printf("[%d/%d] Sync %s for album %s", i, len(entries), existingEntry.GetTitle(), albumInfo.AlbumName)
  66. err = c.downloadEntry(ctx, albumDiskInfo, *existingEntry, albumFileEntry)
  67. if err != nil {
  68. if errors.Is(err, model.ErrDecryption) {
  69. continue
  70. } else if existingEntry.IsLivePhoto() && errors.Is(err, zip.ErrFormat) {
  71. log.Printf(fmt.Sprintf("err processing live photo %s (%d), %s", existingEntry.GetTitle(), existingEntry.ID, err.Error()))
  72. continue
  73. } else if existingEntry.IsLivePhoto() && errors.Is(err, model.ErrLiveZip) {
  74. continue
  75. } else {
  76. return err
  77. }
  78. }
  79. } else {
  80. // file metadata is missing in the localDB
  81. if albumFileEntry.IsDeleted {
  82. delErr := c.DeleteAlbumEntry(ctx, albumFileEntry)
  83. if delErr != nil {
  84. log.Fatalf("Error deleting album entry %d (deleted: %v) %v", albumFileEntry.FileID, albumFileEntry.IsDeleted, delErr)
  85. }
  86. } else {
  87. log.Fatalf("Failed to find entry in db for file %d (deleted: %v)", albumFileEntry.FileID, albumFileEntry.IsDeleted)
  88. }
  89. }
  90. }
  91. return nil
  92. }
  93. func (c *ClICtrl) downloadEntry(ctx context.Context,
  94. diskInfo *albumDiskInfo,
  95. file model.RemoteFile,
  96. albumEntry *model.AlbumFileEntry,
  97. ) error {
  98. if !diskInfo.AlbumMeta.IsDeleted && albumEntry.IsDeleted {
  99. albumEntry.IsDeleted = true
  100. diskFileMeta := diskInfo.GetDiskFileMetadata(file)
  101. if diskFileMeta != nil {
  102. removeErr := removeDiskFile(diskFileMeta, diskInfo)
  103. if removeErr != nil {
  104. return removeErr
  105. }
  106. }
  107. delErr := c.DeleteAlbumEntry(ctx, albumEntry)
  108. if delErr != nil {
  109. return delErr
  110. }
  111. return nil
  112. }
  113. diskFileMeta := diskInfo.GetDiskFileMetadata(file)
  114. if diskFileMeta != nil {
  115. removeErr := removeDiskFile(diskFileMeta, diskInfo)
  116. if removeErr != nil {
  117. return removeErr
  118. }
  119. }
  120. if !diskInfo.IsFilePresent(file) {
  121. decrypt, err := c.downloadAndDecrypt(ctx, file, c.KeyHolder.DeviceKey)
  122. if err != nil {
  123. return err
  124. }
  125. fileDiskMetadata := mapper.MapRemoteFileToDiskMetadata(file)
  126. // Get the extension
  127. extension := filepath.Ext(fileDiskMetadata.Title)
  128. baseFileName := strings.TrimSuffix(filepath.Clean(filepath.Base(fileDiskMetadata.Title)), extension)
  129. diskMetaFileName := diskInfo.GenerateUniqueMetaFileName(baseFileName, extension)
  130. if file.IsLivePhoto() {
  131. imagePath, videoPath, err := UnpackLive(*decrypt)
  132. if err != nil {
  133. return err
  134. }
  135. if imagePath == "" && videoPath == "" {
  136. log.Printf("imagePath %s, videoPath %s", imagePath, videoPath)
  137. return model.ErrLiveZip
  138. }
  139. if imagePath != "" {
  140. imageExtn := filepath.Ext(imagePath)
  141. imageFileName := diskInfo.GenerateUniqueFileName(baseFileName, imageExtn)
  142. imageFilePath := filepath.Join(diskInfo.ExportRoot, diskInfo.AlbumMeta.FolderName, imageFileName)
  143. moveErr := Move(imagePath, imageFilePath)
  144. if moveErr != nil {
  145. return moveErr
  146. }
  147. fileDiskMetadata.AddFileName(imageFileName)
  148. }
  149. if videoPath == "" {
  150. videoExtn := filepath.Ext(videoPath)
  151. videoFileName := diskInfo.GenerateUniqueFileName(baseFileName, videoExtn)
  152. videoFilePath := filepath.Join(diskInfo.ExportRoot, diskInfo.AlbumMeta.FolderName, videoFileName)
  153. // move the decrypt file to filePath
  154. moveErr := Move(videoPath, videoFilePath)
  155. if moveErr != nil {
  156. return moveErr
  157. }
  158. fileDiskMetadata.AddFileName(videoFileName)
  159. }
  160. } else {
  161. fileName := diskInfo.GenerateUniqueFileName(baseFileName, extension)
  162. filePath := filepath.Join(diskInfo.ExportRoot, diskInfo.AlbumMeta.FolderName, fileName)
  163. // move the decrypt file to filePath
  164. err = Move(*decrypt, filePath)
  165. if err != nil {
  166. return err
  167. }
  168. fileDiskMetadata.AddFileName(fileName)
  169. }
  170. fileDiskMetadata.MetaFileName = diskMetaFileName
  171. err = diskInfo.AddEntry(fileDiskMetadata)
  172. if err != nil {
  173. return err
  174. }
  175. err = writeJSONToFile(filepath.Join(diskInfo.ExportRoot, diskInfo.AlbumMeta.FolderName, ".meta", diskMetaFileName), fileDiskMetadata)
  176. if err != nil {
  177. return err
  178. }
  179. albumEntry.SyncedLocally = true
  180. putErr := c.UpsertAlbumEntry(ctx, albumEntry)
  181. if putErr != nil {
  182. return putErr
  183. }
  184. }
  185. return nil
  186. }
  187. func removeDiskFile(diskFileMeta *export.DiskFileMetadata, diskInfo *albumDiskInfo) error {
  188. // remove the file from disk
  189. log.Printf("Removing file %s from disk", diskFileMeta.MetaFileName)
  190. err := os.Remove(filepath.Join(diskInfo.ExportRoot, diskInfo.AlbumMeta.FolderName, ".meta", diskFileMeta.MetaFileName))
  191. if err != nil && !os.IsNotExist(err) {
  192. return err
  193. }
  194. for _, fileName := range diskFileMeta.Info.FileNames {
  195. err = os.Remove(filepath.Join(diskInfo.ExportRoot, diskInfo.AlbumMeta.FolderName, fileName))
  196. if err != nil && !os.IsNotExist(err) {
  197. return err
  198. }
  199. }
  200. return diskInfo.RemoveEntry(diskFileMeta)
  201. }
  202. // readFolderMetadata reads the metadata of the files in the given path
  203. // For disk export, a particular albums files are stored in a folder named after the album.
  204. // Inside the folder, the files are stored at top level and its metadata is stored in a .meta folder
  205. func readFilesMetadata(home string, albumMeta *export.AlbumMetadata) (*albumDiskInfo, error) {
  206. albumMetadataFolder := filepath.Join(home, albumMeta.FolderName, albumMetaFolder)
  207. albumPath := filepath.Join(home, albumMeta.FolderName)
  208. // verify the both the album folder and the .meta folder exist
  209. if _, err := os.Stat(albumMetadataFolder); err != nil {
  210. return nil, err
  211. }
  212. if _, err := os.Stat(albumPath); err != nil {
  213. return nil, err
  214. }
  215. result := make(map[string]*export.DiskFileMetadata)
  216. //fileNameToFileName := make(map[string]*export.DiskFileMetadata)
  217. fileIdToMetadata := make(map[int64]*export.DiskFileMetadata)
  218. claimedFileName := make(map[string]bool)
  219. // Read the top-level directories in the given path
  220. albumFileEntries, err := os.ReadDir(albumPath)
  221. if err != nil {
  222. return nil, err
  223. }
  224. for _, entry := range albumFileEntries {
  225. if !entry.IsDir() {
  226. claimedFileName[strings.ToLower(entry.Name())] = true
  227. }
  228. }
  229. metaEntries, err := os.ReadDir(albumMetadataFolder)
  230. if err != nil {
  231. return nil, err
  232. }
  233. for _, entry := range metaEntries {
  234. if !entry.IsDir() {
  235. fileName := entry.Name()
  236. if fileName == albumMetaFile {
  237. continue
  238. }
  239. if !strings.HasSuffix(fileName, ".json") {
  240. log.Printf("Skipping file %s as it is not a JSON file", fileName)
  241. continue
  242. }
  243. fileMetadataPath := filepath.Join(albumMetadataFolder, fileName)
  244. // Initialize as nil, will remain nil if JSON file is not found or not readable
  245. result[strings.ToLower(fileName)] = nil
  246. // Read the JSON file if it exists
  247. var metaData export.DiskFileMetadata
  248. metaDataBytes, err := os.ReadFile(fileMetadataPath)
  249. if err != nil {
  250. continue // Skip this entry if reading fails
  251. }
  252. if err := json.Unmarshal(metaDataBytes, &metaData); err == nil {
  253. metaData.MetaFileName = fileName
  254. result[strings.ToLower(fileName)] = &metaData
  255. fileIdToMetadata[metaData.Info.ID] = &metaData
  256. }
  257. }
  258. }
  259. return &albumDiskInfo{
  260. ExportRoot: home,
  261. AlbumMeta: albumMeta,
  262. FileNames: &claimedFileName,
  263. MetaFileNameToDiskFileMap: &result,
  264. FileIdToDiskFileMap: &fileIdToMetadata,
  265. }, nil
  266. }