iCloud cloud sync driver (#16794)

This commit is contained in:
Eric Warmenhoven 2024-07-19 01:02:52 -04:00 committed by GitHub
parent 39a2cecdc5
commit 63799385fc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 365 additions and 31 deletions

View File

@ -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:
*
@ -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();
@ -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,

View File

@ -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

View File

@ -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++;

View File

@ -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 */

184
network/cloud_sync/icloud.m Normal file
View File

@ -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 */
};

View File

@ -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
};

View File

@ -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[];

View File

@ -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

View File

@ -9,3 +9,5 @@
#include "Metal.xcconfig"
DEVELOPMENT_TEAM=UK699V5ZS8
OTHER_CFLAGS = $(inherited) -DHAVE_ICLOUD
CODE_SIGN_ENTITLEMENTS = RetroArchCI.entitlements

View File

@ -1653,7 +1653,6 @@
</array>
</dict>
</dict>
<dict>
<key>UTTypeConformsTo</key>
<array>

View File

@ -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>

View File

@ -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>

View File

@ -65,6 +65,7 @@
0720995929B1258C001642BB /* IOKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84DD5EB41A89E737007336C1 /* IOKit.framework */; };
072976DD296284F600D6E00C /* OpenGL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 072976DC296284F600D6E00C /* OpenGL.framework */; };
0746953A2997393000CCB7BD /* GameController.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 074695362995C03900CCB7BD /* GameController.framework */; };
075650252C488918004C5E7E /* CloudKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 075650242C488918004C5E7E /* CloudKit.framework */; };
076E640C2BF30A7A00681536 /* CoreHaptics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0795A8C6299A095300D5035D /* CoreHaptics.framework */; };
076E640D2BF30A9200681536 /* GameController.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 074695362995C03900CCB7BD /* GameController.framework */; };
076E640E2BF30AA200681536 /* OpenAL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 070A883F2A4E7A1B003161C0 /* OpenAL.framework */; };
@ -563,6 +564,8 @@
0720996029B1258C001642BB /* RetroArch.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = RetroArch.app; sourceTree = BUILT_PRODUCTS_DIR; };
072976DC296284F600D6E00C /* OpenGL.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenGL.framework; path = System/Library/Frameworks/OpenGL.framework; sourceTree = SDKROOT; };
074695362995C03900CCB7BD /* GameController.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = GameController.framework; path = System/Library/Frameworks/GameController.framework; sourceTree = SDKROOT; };
075650242C488918004C5E7E /* CloudKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CloudKit.framework; path = System/Library/Frameworks/CloudKit.framework; sourceTree = SDKROOT; };
075650262C48B417004C5E7E /* RetroArchCI.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = RetroArchCI.entitlements; sourceTree = "<group>"; };
0776EF3829A005D600AF0237 /* Steam.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Steam.xcconfig; sourceTree = "<group>"; };
0790F6782BF282B400AA58C9 /* Media.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Media.xcassets; path = OSX/Media.xcassets; sourceTree = "<group>"; };
0795A8C6299A095300D5035D /* CoreHaptics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreHaptics.framework; path = System/Library/Frameworks/CoreHaptics.framework; sourceTree = SDKROOT; };
@ -691,6 +694,7 @@
0795A8C7299A095300D5035D /* CoreHaptics.framework in Frameworks */,
05A8E23820A63CB40084ABDA /* Metal.framework in Frameworks */,
05269A6220ABF20500C29F1E /* MetalKit.framework in Frameworks */,
075650252C488918004C5E7E /* CloudKit.framework in Frameworks */,
5061C8A41AE47E510080AE14 /* libz.dylib in Frameworks */,
070A88402A4E7A1B003161C0 /* OpenAL.framework in Frameworks */,
07EF0FF92BEB117000EDCA9B /* MoltenVK.xcframework in Frameworks */,
@ -1466,6 +1470,7 @@
isa = PBXGroup;
children = (
07F8037C2BEFE4BD000FD557 /* RetroArchAppStore.entitlements */,
075650262C48B417004C5E7E /* RetroArchCI.entitlements */,
686201AB24B823A800EBDD95 /* RetroArch.entitlements */,
05D7753120A55D2700646447 /* BaseConfig.xcconfig */,
05422E5C2140CFC500F09961 /* Metal.xcconfig */,
@ -1501,6 +1506,7 @@
29B97323FDCFA39411CA2CEA /* Frameworks */ = {
isa = PBXGroup;
children = (
075650242C488918004C5E7E /* CloudKit.framework */,
07EF0FF42BEB114000EDCA9B /* MoltenVK.xcframework */,
070A883F2A4E7A1B003161C0 /* OpenAL.framework */,
0795A8C6299A095300D5035D /* CoreHaptics.framework */,
@ -2022,8 +2028,6 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES;
CODE_SIGN_ENTITLEMENTS = RetroArchAppStore.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 44;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.entertainment";
PRODUCT_BUNDLE_IDENTIFIER = com.libretro.dist.RetroArch;
@ -2037,8 +2041,6 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES;
CODE_SIGN_ENTITLEMENTS = RetroArchAppStore.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 44;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.entertainment";
PRODUCT_BUNDLE_IDENTIFIER = com.libretro.dist.RetroArch;
@ -2088,8 +2090,7 @@
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_ENTITLEMENTS = RetroArch.entitlements;
CODE_SIGN_IDENTITY = "Developer ID Application";
CODE_SIGN_STYLE = Manual;
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
COPY_PHASE_STRIP = NO;
DEVELOPMENT_TEAM = UK699V5ZS8;
@ -2136,7 +2137,7 @@
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
PRODUCT_BUNDLE_IDENTIFIER = libretro.RetroArch;
PRODUCT_BUNDLE_IDENTIFIER = com.libretro.dist.RetroArch;
PRODUCT_NAME = RetroArch;
RUN_CLANG_STATIC_ANALYZER = YES;
SDKROOT = macosx;
@ -2169,8 +2170,7 @@
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_ENTITLEMENTS = RetroArch.entitlements;
CODE_SIGN_IDENTITY = "Developer ID Application";
CODE_SIGN_STYLE = Manual;
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = UK699V5ZS8;
@ -2218,7 +2218,7 @@
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
MTL_IGNORE_WARNINGS = YES;
PRODUCT_BUNDLE_IDENTIFIER = libretro.RetroArch;
PRODUCT_BUNDLE_IDENTIFIER = com.libretro.dist.RetroArch;
PRODUCT_NAME = RetroArch;
SDKROOT = macosx;
STRIP_STYLE = debugging;

View File

@ -203,6 +203,7 @@
0712A7772B807AE400C9765F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
0718BC5F2ABBA807001F2CBE /* Network.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Network.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS17.0.sdk/System/Library/Frameworks/Network.framework; sourceTree = DEVELOPER_DIR; };
073DB2892B8706490001BA32 /* RetroArchTV.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = RetroArchTV.entitlements; sourceTree = "<group>"; };
076A9EAF2C438E1D00504BDF /* RetroArchiOS.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; name = RetroArchiOS.entitlements; path = iOS/RetroArchiOS.entitlements; sourceTree = SOURCE_ROOT; };
076CA50C2B695C2C00840EA5 /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS17.2.sdk/usr/lib/libz.tbd; sourceTree = DEVELOPER_DIR; };
077AB2C82BFB0E28002BBE2F /* AppStore.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = AppStore.xcconfig; path = iOS/AppStore.xcconfig; sourceTree = "<group>"; };
0789FC2E2A07845300D042B7 /* AltKit */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = AltKit; path = Frameworks/AltKit; sourceTree = "<group>"; };
@ -582,6 +583,7 @@
9222F20A2315DD3D0097C0FD /* retroarch_logo.png */,
83EB675F19EEAF050096F441 /* iOS/modules */,
69D31DE31A547EC800EF4C92 /* iOS/Resources/Icons.xcassets */,
076A9EAF2C438E1D00504BDF /* RetroArchiOS.entitlements */,
);
name = iOS;
path = Resources;
@ -1785,6 +1787,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = Default;
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES;
CODE_SIGN_ENTITLEMENTS = "$(IOS_CODE_SIGN_ENTITLEMENTS)";
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/iOS/Frameworks",
@ -1833,6 +1836,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = Default;
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES;
CODE_SIGN_ENTITLEMENTS = "$(IOS_CODE_SIGN_ENTITLEMENTS)";
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/iOS/Frameworks",

View File

@ -14,11 +14,13 @@ MARKETING_VERSION = 1.18.3
CURRENT_PROJECT_VERSION = 12
OTHER_CFLAGS = $(inherited) -DHAVE_APPLE_STORE
OTHER_CFLAGS = $(inherited) -DHAVE_ICLOUD
OTHER_CFLAGS = $(inherited) -DkRetroArchAppGroup=@\"group.com.libretro.dist.RetroArchGroup\"
IOS_BUNDLE_IDENTIFIER = com.libretro.dist.RetroArch
TVOS_BUNDLE_IDENTIFIER = com.libretro.dist.RetroArch
IOS_CODE_SIGN_ENTITLEMENTS = iOS/RetroArchiOS.entitlements
TVOS_CODE_SIGN_ENTITLEMENTS = tvOS/RetroArchTV.entitlements
IPHONEOS_DEPLOYMENT_TARGET = 14.2

View File

@ -0,0 +1,16 @@
<?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>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>
</dict>
</plist>

View File

@ -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>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.application-groups</key>
<array>
<string>group.com.libretro.dist.RetroArchGroup</string>

View File

@ -18,6 +18,7 @@
#include <lists/dir_list.h>
#include <lists/file_list.h>
#include <lrc_hash.h>
#include <rthreads/rthreads.h>
#include <streams/file_stream.h>
#include <string/stdstring.h>
#include <time/rtime.h>
@ -68,6 +69,8 @@ typedef struct
bool conflicts;
} task_cloud_sync_state_t;
static slock_t *tcs_running_lock = NULL;
static void task_cloud_sync_begin_handler(void *user_data, const char *path, bool success, RFILE *file)
{
retro_task_t *task = (retro_task_t *)user_data;
@ -79,7 +82,6 @@ static void task_cloud_sync_begin_handler(void *user_data, const char *path, boo
if (!(sync_state = (task_cloud_sync_state_t *)task->state))
return;
sync_state->waiting = false;
if (success)
{
RARCH_LOG(CSPFX "begin succeeded\n");
@ -91,6 +93,9 @@ static void task_cloud_sync_begin_handler(void *user_data, const char *path, boo
task_set_title(task, strdup("Cloud Sync failed"));
task_set_finished(task, true);
}
slock_lock(tcs_running_lock);
sync_state->waiting = false;
slock_unlock(tcs_running_lock);
}
static bool tcs_object_member_handler(void *ctx, const char *s, size_t len)
@ -184,12 +189,14 @@ static void task_cloud_sync_manifest_handler(void *user_data, const char *path,
if (!sync_state)
return;
sync_state->waiting = false;
if (!success)
{
RARCH_WARN(CSPFX "server manifest fetch failed\n");
sync_state->failures = true;
sync_state->phase = CLOUD_SYNC_PHASE_END;
slock_lock(tcs_running_lock);
sync_state->waiting = false;
slock_unlock(tcs_running_lock);
return;
}
@ -201,6 +208,9 @@ static void task_cloud_sync_manifest_handler(void *user_data, const char *path,
filestream_close(file);
}
sync_state->phase = CLOUD_SYNC_PHASE_READ_LOCAL_MANIFEST;
slock_lock(tcs_running_lock);
sync_state->waiting = false;
slock_unlock(tcs_running_lock);
}
static void task_cloud_sync_fetch_server_manifest(task_cloud_sync_state_t *sync_state)
@ -479,8 +489,6 @@ static void task_cloud_sync_fetch_cb(void *user_data, const char *path, bool suc
if (!sync_state)
return;
sync_state->waiting = false;
if (success && file)
{
hash = task_cloud_sync_md5_rfile(file);
@ -496,8 +504,11 @@ static void task_cloud_sync_fetch_cb(void *user_data, const char *path, bool suc
else
RARCH_WARN(CSPFX "failed to write file from server: %s\n", path);
sync_state->failures = true;
return;
}
slock_lock(tcs_running_lock);
sync_state->waiting = false;
slock_unlock(tcs_running_lock);
}
static void task_cloud_sync_fetch_server_file(task_cloud_sync_state_t *sync_state)
@ -584,8 +595,6 @@ static void task_cloud_sync_upload_cb(void *user_data, const char *path, bool su
if (!sync_state)
return;
sync_state->waiting = false;
if (success)
{
/* need to update server manifest as well */
@ -609,6 +618,10 @@ static void task_cloud_sync_upload_cb(void *user_data, const char *path, bool su
RARCH_WARN(CSPFX "uploading %s failed\n", path);
sync_state->failures = true;
}
slock_lock(tcs_running_lock);
sync_state->waiting = false;
slock_unlock(tcs_running_lock);
}
static void task_cloud_sync_upload_current_file(task_cloud_sync_state_t *sync_state)
@ -722,8 +735,6 @@ static void task_cloud_sync_delete_cb(void *user_data, const char *path, bool su
if (!sync_state)
return;
sync_state->waiting = false;
if (!success)
{
/* if the delete fails, resurrect the hash from the last sync */
@ -735,6 +746,9 @@ static void task_cloud_sync_delete_cb(void *user_data, const char *path, bool su
}
RARCH_WARN(CSPFX "deleting %s failed\n", path);
sync_state->failures = true;
slock_lock(tcs_running_lock);
sync_state->waiting = false;
slock_unlock(tcs_running_lock);
return;
}
@ -745,6 +759,9 @@ static void task_cloud_sync_delete_cb(void *user_data, const char *path, bool su
task_cloud_sync_add_to_updated_manifest(sync_state, path, NULL, true);
task_cloud_sync_add_to_updated_manifest(sync_state, path, NULL, false);
sync_state->need_manifest_uploaded = true;
slock_lock(tcs_running_lock);
sync_state->waiting = false;
slock_unlock(tcs_running_lock);
}
static void task_cloud_sync_delete_server_file(task_cloud_sync_state_t *sync_state)
@ -910,8 +927,10 @@ static void task_cloud_sync_update_manifest_cb(void *user_data, const char *path
return;
RARCH_LOG(CSPFX "uploading updated manifest succeeded\n");
sync_state->waiting = false;
sync_state->phase = CLOUD_SYNC_PHASE_END;
slock_lock(tcs_running_lock);
sync_state->waiting = false;
slock_unlock(tcs_running_lock);
}
static RFILE *task_cloud_sync_write_updated_manifest(file_list_t *manifest, char *path)
@ -1034,11 +1053,14 @@ static void task_cloud_sync_task_handler(retro_task_t *task)
if (!(sync_state = (task_cloud_sync_state_t *)task->state))
goto task_finished;
slock_lock(tcs_running_lock);
if (sync_state->waiting)
{
task->when = cpu_features_get_time_usec() + 500 * 1000; /* 500ms */
slock_unlock(tcs_running_lock);
return;
}
slock_unlock(tcs_running_lock);
switch (sync_state->phase)
{
@ -1126,6 +1148,9 @@ void task_push_cloud_sync(void)
if (!settings->bools.cloud_sync_enable)
return;
if (!tcs_running_lock)
tcs_running_lock = slock_new();
find_data.func = task_cloud_sync_task_finder;
if (task_queue_find(&find_data))
{
@ -1154,3 +1179,18 @@ void task_push_cloud_sync(void)
task_queue_push(task);
}
void task_push_cloud_sync_update_driver(void)
{
char manifest_path[PATH_MAX_LENGTH];
settings_t *settings = config_get_ptr();
cloud_sync_find_driver(settings, "cloud sync driver", verbosity_is_enabled());
/* The sync does a three-way diff: current local <- last sync -> current server.
* When the server changes it becomes a four way diff, which can lead to odd
* conflicts or data loss. The easiest way to resolve it is to reset the last sync
*/
task_cloud_sync_manifest_filename(manifest_path, sizeof(manifest_path), false);
filestream_delete(manifest_path);
}

View File

@ -308,6 +308,7 @@ void menu_explore_wait_for_init_task(void);
extern const char* const input_builtin_autoconfs[];
/* cloud sync tasks */
void task_push_cloud_sync_update_driver(void);
void task_push_cloud_sync(void);
RETRO_END_DECLS