ente/pkg/remote_to_disk_file.go

264 lines
8 KiB
Go
Raw Normal View History

2023-10-16 11:22:45 +00:00
package pkg
import (
"context"
"encoding/json"
"errors"
2023-10-16 11:22:45 +00:00
"fmt"
2023-10-21 09:26:13 +00:00
"github.com/ente-io/cli/pkg/mapper"
"github.com/ente-io/cli/pkg/model"
"github.com/ente-io/cli/pkg/model/export"
"github.com/ente-io/cli/utils"
2023-10-16 11:22:45 +00:00
"log"
"os"
"path/filepath"
"strings"
2023-10-16 11:26:34 +00:00
"time"
2023-10-16 11:22:45 +00:00
)
2023-10-16 17:49:35 +00:00
func (c *ClICtrl) syncFiles(ctx context.Context, account model.Account) error {
2023-11-01 04:38:26 +00:00
log.Printf("Starting file download")
2023-10-16 17:49:35 +00:00
exportRoot := account.ExportDir
2023-10-16 11:22:45 +00:00
_, albumIDToMetaMap, err := readFolderMetadata(exportRoot)
if err != nil {
return err
}
entries, err := c.getRemoteAlbumEntries(ctx)
if err != nil {
return err
}
log.Println("total entries", len(entries))
model.SortAlbumFileEntry(entries)
2023-10-16 11:26:34 +00:00
defer utils.TimeTrack(time.Now(), "process_files")
2023-10-16 11:32:16 +00:00
var albumDiskInfo *albumDiskInfo
2023-10-16 11:22:45 +00:00
for i, entry := range entries {
if entry.SyncedLocally {
continue
}
albumInfo, ok := albumIDToMetaMap[entry.AlbumID]
if !ok {
log.Printf("Album %d not found in local metadata", entry.AlbumID)
continue
}
2023-10-16 11:32:16 +00:00
2023-10-16 11:22:45 +00:00
if albumInfo.IsDeleted {
2023-10-16 13:47:36 +00:00
putErr := c.DeleteAlbumEntry(ctx, entry)
2023-10-16 11:22:45 +00:00
if putErr != nil {
return putErr
}
continue
}
2023-10-16 13:47:36 +00:00
2023-10-16 11:32:16 +00:00
if albumDiskInfo == nil || albumDiskInfo.AlbumMeta.ID != albumInfo.ID {
albumDiskInfo, err = readFilesMetadata(exportRoot, albumInfo)
if err != nil {
return err
}
2023-10-16 11:22:45 +00:00
}
fileBytes, err := c.GetValue(ctx, model.RemoteFiles, []byte(fmt.Sprintf("%d", entry.FileID)))
if err != nil {
return err
}
if fileBytes != nil {
var existingEntry *model.RemoteFile
err = json.Unmarshal(fileBytes, &existingEntry)
if err != nil {
return err
}
2023-10-16 16:27:31 +00:00
log.Printf("[%d/%d] Sync %s for album %s", i, len(entries), existingEntry.GetTitle(), albumInfo.AlbumName)
err = c.downloadEntry(ctx, albumDiskInfo, *existingEntry, entry)
if err != nil {
if errors.Is(err, model.ErrDecryption) {
continue
} else {
return err
}
2023-10-16 11:22:45 +00:00
}
} else {
log.Fatalf("remoteFile %d not found in remoteFiles", entry.FileID)
}
}
return nil
}
func (c *ClICtrl) downloadEntry(ctx context.Context,
diskInfo *albumDiskInfo,
file model.RemoteFile,
2023-10-16 13:47:36 +00:00
albumEntry *model.AlbumFileEntry,
) error {
2023-10-16 11:22:45 +00:00
if !diskInfo.AlbumMeta.IsDeleted && albumEntry.IsDeleted {
albumEntry.IsDeleted = true
diskFileMeta := diskInfo.GetDiskFileMetadata(file)
if diskFileMeta != nil {
removeErr := removeDiskFile(diskFileMeta, diskInfo)
if removeErr != nil {
return removeErr
2023-10-16 11:22:45 +00:00
}
}
2023-10-16 13:47:36 +00:00
delErr := c.DeleteAlbumEntry(ctx, albumEntry)
if delErr != nil {
return delErr
2023-10-16 11:22:45 +00:00
}
2023-10-16 13:47:36 +00:00
return nil
2023-10-16 11:22:45 +00:00
}
diskFileMeta := diskInfo.GetDiskFileMetadata(file)
if diskFileMeta != nil {
removeErr := removeDiskFile(diskFileMeta, diskInfo)
if removeErr != nil {
return removeErr
}
}
2023-10-16 11:22:45 +00:00
if !diskInfo.IsFilePresent(file) {
2023-10-16 12:54:00 +00:00
decrypt, err := c.downloadAndDecrypt(ctx, file, c.KeyHolder.DeviceKey)
if err != nil {
return err
}
2023-10-16 11:22:45 +00:00
fileDiskMetadata := mapper.MapRemoteFileToDiskMetadata(file)
// Get the extension
extension := filepath.Ext(fileDiskMetadata.Title)
2023-10-24 12:35:37 +00:00
baseFileName := strings.TrimSuffix(filepath.Clean(filepath.Base(fileDiskMetadata.Title)), extension)
potentialDiskFileName := fmt.Sprintf("%s%s.json", baseFileName, extension)
2023-10-16 11:22:45 +00:00
count := 1
for diskInfo.IsMetaFileNamePresent(potentialDiskFileName) {
// separate the file name and extension
2023-10-16 15:52:48 +00:00
baseFileName = fmt.Sprintf("%s_%d", baseFileName, count)
potentialDiskFileName = fmt.Sprintf("%s%s.json", baseFileName, extension)
2023-10-16 11:22:45 +00:00
count++
if !diskInfo.IsMetaFileNamePresent(potentialDiskFileName) {
break
}
}
2023-10-16 15:52:48 +00:00
if file.IsLivePhoto() {
imagePath, videoPath, err := UnpackLive(*decrypt)
if err != nil {
return err
}
imageExtn := filepath.Ext(imagePath)
videoExtn := filepath.Ext(videoPath)
imageFileName := diskInfo.GenerateUniqueFileName(baseFileName, imageExtn)
videoFileName := diskInfo.GenerateUniqueFileName(baseFileName, videoExtn)
2023-10-16 15:52:48 +00:00
imageFilePath := filepath.Join(diskInfo.ExportRoot, diskInfo.AlbumMeta.FolderName, imageFileName)
videoFilePath := filepath.Join(diskInfo.ExportRoot, diskInfo.AlbumMeta.FolderName, videoFileName)
// move the decrypt file to filePath
2023-10-18 08:25:29 +00:00
err = Move(imagePath, imageFilePath)
2023-10-16 15:52:48 +00:00
if err != nil {
return err
}
2023-10-18 08:25:29 +00:00
err = Move(videoPath, videoFilePath)
2023-10-16 15:52:48 +00:00
if err != nil {
return err
}
fileDiskMetadata.AddFileName(imageFileName)
fileDiskMetadata.AddFileName(videoFileName)
} else {
fileName := diskInfo.GenerateUniqueFileName(baseFileName, extension)
2023-10-16 15:52:48 +00:00
filePath := filepath.Join(diskInfo.ExportRoot, diskInfo.AlbumMeta.FolderName, fileName)
// move the decrypt file to filePath
2023-10-18 08:25:29 +00:00
err = Move(*decrypt, filePath)
2023-10-16 15:52:48 +00:00
if err != nil {
return err
}
fileDiskMetadata.AddFileName(fileName)
}
2023-10-16 15:52:48 +00:00
fileDiskMetadata.MetaFileName = potentialDiskFileName
err = diskInfo.AddEntry(fileDiskMetadata)
if err != nil {
return err
}
err = writeJSONToFile(filepath.Join(diskInfo.ExportRoot, diskInfo.AlbumMeta.FolderName, ".meta", potentialDiskFileName), fileDiskMetadata)
2023-10-16 11:22:45 +00:00
if err != nil {
return err
}
2023-10-16 13:47:36 +00:00
albumEntry.SyncedLocally = true
putErr := c.UpsertAlbumEntry(ctx, albumEntry)
if putErr != nil {
return putErr
}
2023-10-16 11:22:45 +00:00
}
return nil
}
func removeDiskFile(diskFileMeta *export.DiskFileMetadata, diskInfo *albumDiskInfo) error {
// remove the file from disk
log.Printf("Removing file %s from disk", diskFileMeta.MetaFileName)
err := os.Remove(filepath.Join(diskInfo.ExportRoot, diskInfo.AlbumMeta.FolderName, ".meta", diskFileMeta.MetaFileName))
if err != nil && !os.IsNotExist(err) {
return err
}
for _, fileName := range diskFileMeta.Info.FileNames {
err = os.Remove(filepath.Join(diskInfo.ExportRoot, diskInfo.AlbumMeta.FolderName, fileName))
if err != nil && !os.IsNotExist(err) {
return err
}
}
return diskInfo.RemoveEntry(diskFileMeta)
}
2023-10-16 11:22:45 +00:00
// readFolderMetadata reads the metadata of the files in the given path
// For disk export, a particular albums files are stored in a folder named after the album.
// Inside the folder, the files are stored at top level and its metadata is stored in a .meta folder
func readFilesMetadata(home string, albumMeta *export.AlbumMetadata) (*albumDiskInfo, error) {
albumMetadataFolder := filepath.Join(home, albumMeta.FolderName, albumMetaFolder)
albumPath := filepath.Join(home, albumMeta.FolderName)
// verify the both the album folder and the .meta folder exist
if _, err := os.Stat(albumMetadataFolder); err != nil {
return nil, err
}
if _, err := os.Stat(albumPath); err != nil {
return nil, err
}
result := make(map[string]*export.DiskFileMetadata)
//fileNameToFileName := make(map[string]*export.DiskFileMetadata)
fileIdToMetadata := make(map[int64]*export.DiskFileMetadata)
claimedFileName := make(map[string]bool)
// Read the top-level directories in the given path
albumFileEntries, err := os.ReadDir(albumPath)
if err != nil {
return nil, err
}
for _, entry := range albumFileEntries {
if !entry.IsDir() {
claimedFileName[strings.ToLower(entry.Name())] = true
2023-10-16 11:22:45 +00:00
}
}
metaEntries, err := os.ReadDir(albumMetadataFolder)
if err != nil {
return nil, err
}
for _, entry := range metaEntries {
if !entry.IsDir() {
fileName := entry.Name()
if fileName == albumMetaFile {
continue
}
if !strings.HasSuffix(fileName, ".json") {
log.Printf("Skipping file %s as it is not a JSON file", fileName)
continue
}
fileMetadataPath := filepath.Join(albumMetadataFolder, fileName)
// Initialize as nil, will remain nil if JSON file is not found or not readable
result[strings.ToLower(fileName)] = nil
2023-10-16 11:22:45 +00:00
// Read the JSON file if it exists
var metaData export.DiskFileMetadata
metaDataBytes, err := os.ReadFile(fileMetadataPath)
if err != nil {
continue // Skip this entry if reading fails
}
if err := json.Unmarshal(metaDataBytes, &metaData); err == nil {
metaData.MetaFileName = fileName
result[strings.ToLower(fileName)] = &metaData
2023-10-16 11:22:45 +00:00
fileIdToMetadata[metaData.Info.ID] = &metaData
}
}
}
return &albumDiskInfo{
ExportRoot: home,
AlbumMeta: albumMeta,
FileNames: &claimedFileName,
MetaFileNameToDiskFileMap: &result,
FileIdToDiskFileMap: &fileIdToMetadata,
}, nil
}