Skip to content

Commit

Permalink
iCloud cloud sync driver (#16794)
Browse files Browse the repository at this point in the history
  • Loading branch information
warmenhoven authored Jul 19, 2024
1 parent 39a2cec commit 6379938
Show file tree
Hide file tree
Showing 19 changed files with 365 additions and 31 deletions.
17 changes: 17 additions & 0 deletions configuration.c
Original file line number Diff line number Diff line change
Expand Up @@ -1349,6 +1349,18 @@ const char *config_get_default_led(void)
return "null";
}

/**
* config_get_default_cloudsync:
*
* Gets default cloud sync driver.
*
* Returns: Default cloud sync driver.
**/
const char *config_get_default_cloudsync(void)
{
return "null";
}

/**
* config_get_default_location:
*
Expand Down Expand Up @@ -2706,6 +2718,7 @@ void config_set_defaults(void *data)
const char *def_bluetooth = config_get_default_bluetooth();
const char *def_wifi = config_get_default_wifi();
const char *def_led = config_get_default_led();
const char *def_cloudsync = config_get_default_cloudsync();
const char *def_location = config_get_default_location();
const char *def_record = config_get_default_record();
const char *def_midi = config_get_default_midi();
Expand Down Expand Up @@ -2788,6 +2801,10 @@ void config_set_defaults(void *data)
configuration_set_string(settings,
settings->arrays.led_driver,
def_led);
if (def_cloudsync)
configuration_set_string(settings,
settings->arrays.cloud_sync_driver,
def_cloudsync);
if (def_location)
configuration_set_string(settings,
settings->arrays.location_driver,
Expand Down
4 changes: 4 additions & 0 deletions griffin/griffin_objc.m
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,7 @@
#if defined(HAVE_NETWORKING) && defined(HAVE_NETPLAYDISCOVERY) && defined(HAVE_NETPLAYDISCOVERY_NSNET)
#import "../network/netplay/netplay_nsnetservice.m"
#endif

#if defined(HAVE_CLOUDSYNC) && defined(HAVE_ICLOUD)
#include "../network/cloud_sync/icloud.m"
#endif
25 changes: 15 additions & 10 deletions menu/menu_displaylist.c
Original file line number Diff line number Diff line change
Expand Up @@ -10704,20 +10704,25 @@ unsigned menu_displaylist_build_list(
break;
case DISPLAYLIST_CLOUD_SYNC_SETTINGS_LIST:
{
menu_displaylist_build_info_t build_list[] = {
{MENU_ENUM_LABEL_CLOUD_SYNC_ENABLE, PARSE_ONLY_BOOL },
{MENU_ENUM_LABEL_CLOUD_SYNC_DESTRUCTIVE, PARSE_ONLY_BOOL },
{MENU_ENUM_LABEL_CLOUD_SYNC_SYNC_SAVES, PARSE_ONLY_BOOL },
{MENU_ENUM_LABEL_CLOUD_SYNC_SYNC_CONFIGS, PARSE_ONLY_BOOL },
{MENU_ENUM_LABEL_CLOUD_SYNC_DRIVER, PARSE_ONLY_STRING_OPTIONS },
{MENU_ENUM_LABEL_CLOUD_SYNC_URL, PARSE_ONLY_STRING },
{MENU_ENUM_LABEL_CLOUD_SYNC_USERNAME, PARSE_ONLY_STRING },
{MENU_ENUM_LABEL_CLOUD_SYNC_PASSWORD, PARSE_ONLY_STRING },
menu_displaylist_build_info_selective_t build_list[] = {
{MENU_ENUM_LABEL_CLOUD_SYNC_ENABLE, PARSE_ONLY_BOOL, true},
{MENU_ENUM_LABEL_CLOUD_SYNC_DESTRUCTIVE, PARSE_ONLY_BOOL, true},
{MENU_ENUM_LABEL_CLOUD_SYNC_SYNC_SAVES, PARSE_ONLY_BOOL, true},
{MENU_ENUM_LABEL_CLOUD_SYNC_SYNC_CONFIGS, PARSE_ONLY_BOOL, true},
{MENU_ENUM_LABEL_CLOUD_SYNC_DRIVER, PARSE_ONLY_STRING_OPTIONS, true},
{MENU_ENUM_LABEL_CLOUD_SYNC_URL, PARSE_ONLY_STRING, false},
{MENU_ENUM_LABEL_CLOUD_SYNC_USERNAME, PARSE_ONLY_STRING, false},
{MENU_ENUM_LABEL_CLOUD_SYNC_PASSWORD, PARSE_ONLY_STRING, false},
};

if (string_is_equal(settings->arrays.cloud_sync_driver, "webdav"))
for (i = 0; i < ARRAY_SIZE(build_list); i++)
build_list[i].checked = true;

for (i = 0; i < ARRAY_SIZE(build_list); i++)
{
if (MENU_DISPLAYLIST_PARSE_SETTINGS_ENUM(list,
if (build_list[i].checked &&
MENU_DISPLAYLIST_PARSE_SETTINGS_ENUM(list,
build_list[i].enum_idx, build_list[i].parse_type,
false) == 0)
count++;
Expand Down
10 changes: 10 additions & 0 deletions menu/menu_setting.c
Original file line number Diff line number Diff line change
Expand Up @@ -9059,6 +9059,16 @@ static void general_write_handler(rarch_setting_t *setting)
video_driver_is_threaded());
}
break;
#if HAVE_CLOUDSYNC
case MENU_ENUM_LABEL_CLOUD_SYNC_DRIVER:
{
struct menu_state *menu_st = menu_state_get_ptr();
menu_st->flags |= MENU_ST_FLAG_PREVENT_POPULATE
| MENU_ST_FLAG_ENTRIES_NEED_REFRESH;
task_push_cloud_sync_update_driver();
}
break;
#endif
default:
/* Special cases */

Expand Down
184 changes: 184 additions & 0 deletions network/cloud_sync/icloud.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
/* 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 *path, const char *file, cloud_sync_complete_handler_t cb, void *user_data)
{
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);
}
}];
}
else
cb(user_data, path, error == nil, NULL);
});
return true;
}

static bool icloud_update(const char *path, RFILE *rfile, cloud_sync_complete_handler_t cb, void *user_data)
{
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);
}];
});
return true;
}

static bool icloud_delete(const char *path, cloud_sync_complete_handler_t cb, void *user_data)
{
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);
}];
return true;
}

cloud_sync_driver_t cloud_sync_icloud = {
icloud_sync_begin,
icloud_sync_end,
icloud_read,
icloud_update,
icloud_delete,
"icloud" /* ident */
};
3 changes: 3 additions & 0 deletions network/cloud_sync_driver.c
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ static cloud_sync_driver_t cloud_sync_null = {

const cloud_sync_driver_t *cloud_sync_drivers[] = {
&cloud_sync_webdav,
#ifdef HAVE_ICLOUD
&cloud_sync_icloud,
#endif
&cloud_sync_null,
NULL
};
Expand Down
3 changes: 3 additions & 0 deletions network/cloud_sync_driver.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ typedef struct
cloud_sync_driver_state_t *cloud_sync_state_get_ptr(void);

extern cloud_sync_driver_t cloud_sync_webdav;
#ifdef HAVE_ICLOUD
extern cloud_sync_driver_t cloud_sync_icloud;
#endif

extern const cloud_sync_driver_t *cloud_sync_drivers[];

Expand Down
2 changes: 2 additions & 0 deletions pkg/apple/AppStore.xcconfig
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@
INFOPLIST_FILE = $(SRCROOT)/OSX/Info_AppStore.plist
DEVELOPMENT_TEAM=UK699V5ZS8
OTHER_CFLAGS = $(inherited) -DHAVE_APPLE_STORE
OTHER_CFLAGS = $(inherited) -DHAVE_ICLOUD
CODE_SIGN_ENTITLEMENTS = RetroArchAppStore.entitlements
2 changes: 2 additions & 0 deletions pkg/apple/GitLabCI.xcconfig
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,5 @@

#include "Metal.xcconfig"
DEVELOPMENT_TEAM=UK699V5ZS8
OTHER_CFLAGS = $(inherited) -DHAVE_ICLOUD
CODE_SIGN_ENTITLEMENTS = RetroArchCI.entitlements
1 change: 0 additions & 1 deletion pkg/apple/OSX/Info_Metal.plist
Original file line number Diff line number Diff line change
Expand Up @@ -1653,7 +1653,6 @@
</array>
</dict>
</dict>

<dict>
<key>UTTypeConformsTo</key>
<array>
Expand Down
10 changes: 10 additions & 0 deletions pkg/apple/RetroArchAppStore.entitlements
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.aps-environment</key>
<string>production</string>
<key>com.apple.developer.icloud-container-identifiers</key>
<array>
<string>iCloud.com.libretro.dist.RetroArch</string>
</array>
<key>com.apple.developer.icloud-services</key>
<array>
<string>CloudKit</string>
</array>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
Expand Down
22 changes: 22 additions & 0 deletions pkg/apple/RetroArchCI.entitlements
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.aps-environment</key>
<string>production</string>
<key>com.apple.developer.icloud-container-identifiers</key>
<array>
<string>iCloud.com.libretro.dist.RetroArch</string>
</array>
<key>com.apple.developer.icloud-services</key>
<array>
<string>CloudKit</string>
</array>
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
<true/>
<key>com.apple.security.cs.disable-executable-page-protection</key>
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
</dict>
</plist>
Loading

0 comments on commit 6379938

Please sign in to comment.