mirror of
https://github.com/libretro/RetroArch
synced 2025-01-30 21:32:45 +00:00
197 lines
8.0 KiB
Objective-C
197 lines
8.0 KiB
Objective-C
/* RetroArch - A frontend for libretro.
|
|
*
|
|
* RetroArch is free software: you can redistribute it and/or modify it under the terms
|
|
* of the GNU General Public License as published by the Free Software Found-
|
|
* ation, either version 3 of the License, or (at your option) any later version.
|
|
*
|
|
* RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
|
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
|
* PURPOSE. See the GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along with RetroArch.
|
|
* If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#import <CloudKit/CloudKit.h>
|
|
|
|
#include "../cloud_sync_driver.h"
|
|
#include "../../verbosity.h"
|
|
|
|
#define IC_RECORD_TYPE @"cloudsync"
|
|
|
|
static bool icloud_sync_begin(cloud_sync_complete_handler_t cb, void *user_data)
|
|
{
|
|
[CKContainer.defaultContainer accountStatusWithCompletionHandler:^(CKAccountStatus accountStatus, NSError * _Nullable error) {
|
|
BOOL success = (error == nil) && (accountStatus == CKAccountStatusAvailable);
|
|
cb(user_data, NULL, success, NULL);
|
|
}];
|
|
return true;
|
|
}
|
|
|
|
static bool icloud_sync_end(cloud_sync_complete_handler_t cb, void *user_data)
|
|
{
|
|
cb(user_data, NULL, true, NULL);
|
|
return true;
|
|
}
|
|
|
|
static CKRecord *icloud_remove_duplicates(NSArray<CKRecord *> *results)
|
|
{
|
|
if (!results || ![results count])
|
|
return nil;
|
|
if ([results count] == 1)
|
|
return results[0];
|
|
|
|
CKRecord *newest = nil;
|
|
for (CKRecord *rec in results)
|
|
{
|
|
CKRecord *toDelete = rec;
|
|
if (newest == nil || [newest.modificationDate compare:rec.modificationDate] == NSOrderedAscending)
|
|
{
|
|
toDelete = newest;
|
|
newest = rec;
|
|
}
|
|
if (toDelete)
|
|
{
|
|
[CKContainer.defaultContainer.privateCloudDatabase deleteRecordWithID:toDelete.recordID
|
|
completionHandler:^(CKRecordID * _Nullable recordID, NSError * _Nullable error) {
|
|
RARCH_DBG("[iCloud] delete callback for duplicate of %s %s\n", toDelete[@"path"], error == nil ? "succeeded" : "failed");
|
|
}];
|
|
}
|
|
}
|
|
return newest;
|
|
}
|
|
|
|
static void icloud_query_path(const char *path, void(^cb)(CKRecord * results, NSError * error))
|
|
{
|
|
NSPredicate *pred = [NSComparisonPredicate
|
|
predicateWithLeftExpression:[NSExpression expressionForKeyPath:@"path"]
|
|
rightExpression:[NSExpression expressionForConstantValue:[NSString stringWithUTF8String:path]]
|
|
modifier:NSDirectPredicateModifier
|
|
type:NSEqualToPredicateOperatorType
|
|
options:0];
|
|
CKQuery *query = [[CKQuery alloc] initWithRecordType:IC_RECORD_TYPE predicate:pred];
|
|
[CKContainer.defaultContainer.privateCloudDatabase performQuery:query
|
|
inZoneWithID:nil
|
|
completionHandler:^(NSArray<CKRecord *> * _Nullable results, NSError * _Nullable error) {
|
|
if (error || ![results count])
|
|
{
|
|
RARCH_DBG("[iCloud] could not find %s (%s)\n", path, error == nil ? "successfully" : "failure");
|
|
if (error)
|
|
RARCH_DBG("[iCloud] error: %s\n", [[error debugDescription] UTF8String]);
|
|
cb(nil, nil);
|
|
}
|
|
else
|
|
{
|
|
RARCH_DBG("[iCloud] found %d results looking for %s\n", [results count], path);
|
|
cb(icloud_remove_duplicates(results), nil);
|
|
}
|
|
}];
|
|
}
|
|
|
|
static bool icloud_read(const char *p, const char *f, cloud_sync_complete_handler_t cb, void *user_data)
|
|
{
|
|
char *path = strdup(p);
|
|
char *file = strdup(f);
|
|
icloud_query_path(path, ^(CKRecord *result, NSError *error) {
|
|
if (result)
|
|
{
|
|
[CKContainer.defaultContainer.privateCloudDatabase fetchRecordWithID:result.recordID
|
|
completionHandler:^(CKRecord * _Nullable fetchedRecord, NSError * _Nullable error) {
|
|
if (error)
|
|
{
|
|
RARCH_DBG("[iCloud] failed to fetch record for %s\n", path);
|
|
cb(user_data, path, false, NULL);
|
|
}
|
|
else
|
|
{
|
|
CKAsset *asset = fetchedRecord[@"data"];
|
|
NSData *data = [NSFileManager.defaultManager contentsAtPath:asset.fileURL.path];
|
|
RARCH_DBG("[iCloud] successfully fetched %s, size %d\n", path, [data length]);
|
|
RFILE *rfile = filestream_open(file,
|
|
RETRO_VFS_FILE_ACCESS_READ_WRITE,
|
|
RETRO_VFS_FILE_ACCESS_HINT_NONE);
|
|
if (rfile)
|
|
{
|
|
filestream_truncate(rfile, 0);
|
|
filestream_write(rfile, [data bytes], [data length]);
|
|
filestream_seek(rfile, 0, SEEK_SET);
|
|
}
|
|
cb(user_data, path, true, rfile);
|
|
}
|
|
free(path);
|
|
free(file);
|
|
}];
|
|
}
|
|
else
|
|
{
|
|
cb(user_data, path, error == nil, NULL);
|
|
free(path);
|
|
free(file);
|
|
}
|
|
});
|
|
return true;
|
|
}
|
|
|
|
static bool icloud_update(const char *p, RFILE *rfile, cloud_sync_complete_handler_t cb, void *user_data)
|
|
{
|
|
char *path = strdup(p);
|
|
icloud_query_path(path, ^(CKRecord *record, NSError *error) {
|
|
bool update = true;
|
|
if (error || !record)
|
|
{
|
|
record = [[CKRecord alloc] initWithRecordType:IC_RECORD_TYPE];
|
|
record[@"path"] = [NSString stringWithUTF8String:path];
|
|
update = false;
|
|
}
|
|
NSString *fileStr = [NSString stringWithUTF8String:filestream_get_path(rfile)];
|
|
NSURL *fileURL = [NSURL fileURLWithPath:fileStr];
|
|
record[@"data"] = [[CKAsset alloc] initWithFileURL:fileURL];
|
|
[CKContainer.defaultContainer.privateCloudDatabase saveRecord:record completionHandler:^(CKRecord * _Nullable newrecord, NSError * _Nullable error) {
|
|
RARCH_DBG("[iCloud] %s %s %s\n", error == nil ? "succeeded" : "failed", update ? "updating" : "creating", path);
|
|
if (error)
|
|
RARCH_DBG("[iCloud] error: %s\n", [[error debugDescription] UTF8String]);
|
|
cb(user_data, path, error == nil, rfile);
|
|
free(path);
|
|
}];
|
|
});
|
|
return true;
|
|
}
|
|
|
|
static bool icloud_delete(const char *p, cloud_sync_complete_handler_t cb, void *user_data)
|
|
{
|
|
char *path = strdup(p);
|
|
NSPredicate *pred = [NSComparisonPredicate
|
|
predicateWithLeftExpression:[NSExpression expressionForKeyPath:@"path"]
|
|
rightExpression:[NSExpression expressionForConstantValue:[NSString stringWithUTF8String:path]]
|
|
modifier:NSDirectPredicateModifier
|
|
type:NSEqualToPredicateOperatorType
|
|
options:0];
|
|
CKQuery *query = [[CKQuery alloc] initWithRecordType:IC_RECORD_TYPE predicate:pred];
|
|
[CKContainer.defaultContainer.privateCloudDatabase performQuery:query
|
|
inZoneWithID:nil
|
|
completionHandler:^(NSArray<CKRecord *> * _Nullable results, NSError * _Nullable error) {
|
|
RARCH_DBG("[iCloud] deleting %d records for %s\n", [results count], path);
|
|
for (CKRecord *record in results)
|
|
{
|
|
[CKContainer.defaultContainer.privateCloudDatabase deleteRecordWithID:record.recordID
|
|
completionHandler:^(CKRecordID * _Nullable recordID, NSError * _Nullable error) {
|
|
RARCH_DBG("[iCloud] delete callback for %s %s\n", path, error == nil ? "succeeded" : "failed");
|
|
if (error)
|
|
RARCH_DBG("[iCloud] error: %s\n", [[error debugDescription] UTF8String]);
|
|
}];
|
|
}
|
|
cb(user_data, path, error == nil, NULL);
|
|
free(path);
|
|
}];
|
|
return true;
|
|
}
|
|
|
|
cloud_sync_driver_t cloud_sync_icloud = {
|
|
icloud_sync_begin,
|
|
icloud_sync_end,
|
|
icloud_read,
|
|
icloud_update,
|
|
icloud_delete,
|
|
"icloud" /* ident */
|
|
};
|