Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Followup to #6922 refactoring after_realm_open into exclude_from_icloud_backup #6927

Merged
merged 4 commits into from
Nov 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* None

### Enhancements
* Added `excludeFromIcloudBackup` option to the `Realm` constructor to exclude the realm files from iCloud backup. ([#4139](https://github.com/realm/realm-js/issues/4139))
* Added `excludeFromIcloudBackup` option to the `Realm` constructor to exclude the realm files from iCloud backup. ([#4139](https://github.com/realm/realm-js/issues/4139) and [#6927](https://github.com/realm/realm-js/pull/6927))
```typescript
const realm = new Realm({
schema: [
Expand Down
39 changes: 25 additions & 14 deletions contrib/guide-testing-exclude-icloud-backup.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,29 @@
# Guide: Testing Exclude iCloud Backup

## Prerequisites

- macOS
- iOS Simulator

## Testing

Ensure you have booted a simulator and execute the integration tests on iOS:

```sh
MOCHA_REMOTE_GREP='icloud' npm run test:ios --workspace @realm/react-native-test-app-tests
```

In the command above, we're explicitly grepping for the icloud backup tests.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
In the command above, we're explicitly grepping for the icloud backup tests.
In the command above, we're explicitly grepping for the iCloud backup tests.


To verify if a file has been successfully excluded from iCloud backup, you need to check the file's attributes.
We provide an easy script to do so:

```sh
./contrib/scripts/check-exclude-icloud-backup.sh
```

## Testing in your own app

Before starting the testing process, you need to configure your Realm database to either include or exclude files from iCloud backup. This is done by setting the `excludeFromIcloudBackup` property in your Realm configuration. Here is an example of how to set this property:

```javascript
Expand All @@ -16,20 +40,7 @@ const realm = new Realm(realmConfig);

Make sure to replace the schema and path with your actual Realm schema and desired file path. Once you have configured this property, you can proceed with the following steps to test if the exclusion from iCloud backup is working correctly.

## Prerequisites

- macOS
- iOS Simulator

## Testing

To verify if a file has been successfully excluded from iCloud backup, you need to check the file's attributes. We provide an easy script to do so. Ensure you have booted a simulator with an app using Realm. From the root of the project, run:

```sh
contrib/scripts/check-exclude-icloud-backup.sh <com.your.app.bundle>
```

If the script doesn't work, you can also check it manually. First, get the path of the Realm files from the simulator's Documents folder by running:
First, get the path of the Realm files from the simulator's Documents folder by running:

```sh
open `xcrun simctl get_app_container booted com.your.app.bundleId data`/Documents
Expand Down
16 changes: 5 additions & 11 deletions contrib/scripts/check-exclude-icloud-backup.sh
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
#!/bin/bash

# Check if appbundle parameter is provided
if [ -z "$1" ]; then
echo "Usage: $0 <com.app.bundle.id>"
exit 1
fi

appbundle=$1
TEST_APP_BUNDLE_ID=com.microsoft.ReactTestApp

# Check if a simulator is booted
booted_simulators=$(xcrun simctl list | grep "Booted")
Expand All @@ -25,15 +19,15 @@ if [ "$booted_count" -gt 1 ]; then
fi

# Extract the name of the booted simulator
booted_simulator=$(echo "$booted_simulator" | xargs)
booted_simulator=$(echo "$booted_simulators" | xargs)
echo -e "Running script on simulator: $booted_simulator\n"

# Get the app container path
app_container_path=$(xcrun simctl get_app_container booted "$appbundle" data 2>/dev/null)
app_container_path=$(xcrun simctl get_app_container booted "$TEST_APP_BUNDLE_ID" data 2>/dev/null)

# Check if the command was successful
if [ $? -ne 0 ] || [ -z "$app_container_path" ]; then
echo "Failed to get app container path for $appbundle"
echo "Failed to get app container path for $TEST_APP_BUNDLE_ID"
exit 1
fi

Expand All @@ -47,7 +41,7 @@ if [ ! -d "$documents_path" ]; then
fi

# Run xattr on all files in the directory
for file in "$documents_path"/*; do
for file in "$documents_path"/icloud-backup-tests/*.realm; do
if [ -e "$file" ]; then
filename=$(basename "$file")
attrs=$(xattr "$file" 2>/dev/null)
Expand Down
1 change: 1 addition & 0 deletions integration-tests/tests/src/tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ import "./tests/counter";
import "./tests/dictionary";
import "./tests/dynamic-schema-updates";
import "./tests/enums";
import "./tests/exclude-from-icloud-backup";
import "./tests/iterators";
import "./tests/linking-objects";
import "./tests/list";
Expand Down
53 changes: 53 additions & 0 deletions integration-tests/tests/src/tests/exclude-from-icloud-backup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2024 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////

import Realm from "realm";

const BASE_PATH = "icloud-backup-tests";

describe("icloud backup", () => {
// This is in a separate suite to avoid
// See contrib/guide-testing-exclude-icloud-backup.md to verify the result of the test
it("excludes", () => {
const realm = new Realm({
path: `${BASE_PATH}/excluded.realm`,
schema: [],
excludeFromIcloudBackup: true,
});
realm.close();
});

it("removes exclusion", () => {
const path = `${BASE_PATH}/reincluded.realm`;
{
const realm = new Realm({
path,
schema: [],
excludeFromIcloudBackup: true,
});
realm.close();
}
{
const realm = new Realm({
path,
excludeFromIcloudBackup: false,
});
realm.close();
}
});
});
2 changes: 1 addition & 1 deletion packages/realm/bindgen/js_opt_in_spec.yml
Original file line number Diff line number Diff line change
Expand Up @@ -237,8 +237,8 @@ classes:
- remove_realm_files_from_directory
- remove_file
- remove_directory
- exclude_from_icloud_backup
- get_cpu_arch
- after_realm_open

WeakSyncSession:
methods:
Expand Down
2 changes: 1 addition & 1 deletion packages/realm/bindgen/js_spec.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ classes:
remove_realm_files_from_directory: '(directory: const std::string&)'
remove_file: '(path: const std::string&)'
remove_directory: '(path: const std::string&)'
exclude_from_icloud_backup: '(path: const std::string&, value: bool)'
get_cpu_arch: () -> std::string
after_realm_open: '(realm: SharedRealm, exclude_from_icloud_backup: bool)'
# print: (const char* fmt, ...) # can't expose varargs directly. Could expose a fixed overload.

WeakSyncSession:
Expand Down
7 changes: 5 additions & 2 deletions packages/realm/binding/android/src/main/cpp/platform.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,11 @@ void JsPlatformHelpers::remove_file(const std::string& path)
fs::remove(path);
}

void JsPlatformHelpers::exclude_from_icloud_backup(const std::string&, bool)
{
// no-op
}

void JsPlatformHelpers::print(const char* fmt, ...)
{
va_list vl;
Expand All @@ -131,8 +136,6 @@ void JsPlatformHelpers::print(const char* fmt, ...)
va_end(vl);
}

void JsPlatformHelpers::after_realm_open(SharedRealm, bool) {}

std::string JsPlatformHelpers::get_cpu_arch()
{
#if defined(__arm__)
Expand Down
47 changes: 17 additions & 30 deletions packages/realm/binding/apple/platform.mm
Original file line number Diff line number Diff line change
Expand Up @@ -35,32 +35,6 @@
return error.localizedDescription;
}

static void RLMCheckSkipBackupAttributeToItemAtPath(std::string_view path, bool exclude_from_icloud_backup) {
NSNumber *current;

[[NSURL fileURLWithPath:@(path.data())]
getResourceValue:&current
forKey:NSURLIsExcludedFromBackupKey
error:nil];

if (current.boolValue != exclude_from_icloud_backup) {
[[NSURL fileURLWithPath:@(path.data())]
setResourceValue:@(exclude_from_icloud_backup)
forKey:NSURLIsExcludedFromBackupKey
error:nil];

}
}

static void RLMCheckSkipBackupAttributeToRealmFilesAtPath(std::string path, bool exclude_from_icloud_backup) {
const std::vector<std::string> extensions = {"", ".lock", ".note",
".management"};

for (const auto& ext : extensions) {
RLMCheckSkipBackupAttributeToItemAtPath(path + ext, exclude_from_icloud_backup);
}
}

static std::string s_default_realm_directory;

namespace realm {
Expand Down Expand Up @@ -184,15 +158,28 @@ static void RLMCheckSkipBackupAttributeToRealmFilesAtPath(std::string path, bool
}
}

void JsPlatformHelpers::after_realm_open(const SharedRealm realm, bool exclude_from_icloud_backup) {
RLMCheckSkipBackupAttributeToRealmFilesAtPath(realm->config().path, exclude_from_icloud_backup);
}

void JsPlatformHelpers::remove_directory(const std::string &path)
{
remove_file(path); // works for directories too
}

void JsPlatformHelpers::exclude_from_icloud_backup(const std::string& path, bool value) {
NSNumber *current;

[[NSURL fileURLWithPath:@(path.data())]
getResourceValue:&current
forKey:NSURLIsExcludedFromBackupKey
error:nil];

if (current.boolValue != value) {
[[NSURL fileURLWithPath:@(path.data())]
setResourceValue:@(value)
forKey:NSURLIsExcludedFromBackupKey
error:nil];

}
}

void JsPlatformHelpers::print(const char *fmt, ...) {
va_list vl;
va_start(vl, fmt);
Expand Down
10 changes: 5 additions & 5 deletions packages/realm/binding/node/platform.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,11 @@ void JsPlatformHelpers::remove_file(const std::string& path)
}
}

void JsPlatformHelpers::exclude_from_icloud_backup(const std::string&, bool)
{
// no-op
}

void JsPlatformHelpers::print(const char* fmt, ...)
{
va_list vl;
Expand All @@ -219,11 +224,6 @@ void JsPlatformHelpers::print(const char* fmt, ...)
va_end(vl);
}

void JsPlatformHelpers::after_realm_open(SharedRealm, bool)
{
// no-op
}

// this should never be called
std::string JsPlatformHelpers::get_cpu_arch()
{
Expand Down
5 changes: 2 additions & 3 deletions packages/realm/binding/platform.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
#pragma once

#include <string>
#include <realm/object-store/shared_realm.hpp>

namespace realm {
//
Expand Down Expand Up @@ -56,7 +55,7 @@ class JsPlatformHelpers {
// print something
static void print(const char* fmt, ...);

// runs after the realm has been opened
static void after_realm_open(const SharedRealm realm, const bool exclude_from_icloud_backup = false);
// excludes the path from icloud backup on iOS and no-op on other platforms
static void exclude_from_icloud_backup(const std::string& path, bool value);
};
} // namespace realm
10 changes: 9 additions & 1 deletion packages/realm/src/Realm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -579,7 +579,15 @@
this.schemaExtras = schemaExtras || {};
}

binding.JsPlatformHelpers.afterRealmOpen(this.internal, config.excludeFromIcloudBackup ?? false);
// Optionally: Exclude or include Realm files from iCloud backup
const { excludeFromIcloudBackup } = config;
if (typeof excludeFromIcloudBackup === "boolean") {
const realmPath = this.internal.config.path;
for (const fileNameSuffix of ["", ".lock", ".note", ".management"]) {
const filePath = realmPath + fileNameSuffix;
binding.JsPlatformHelpers.excludeFromIcloudBackup(filePath, excludeFromIcloudBackup);
}
}

Object.defineProperty(this, "classes", {
enumerable: false,
Expand Down Expand Up @@ -834,7 +842,7 @@

/**
* Deletes a Realm model, including all of its objects.
* If called outside a migration function, {@link schema} and {@link schemaVersion} are updated.

Check warning on line 845 in packages/realm/src/Realm.ts

View workflow job for this annotation

GitHub Actions / Lint

The type 'schemaVersion' is undefined
* @param name - The model name.
*/
deleteModel(name: string): void {
Expand Down Expand Up @@ -996,7 +1004,7 @@
* Remove the listener {@link callback} for the specified event {@link eventName}.
* @param eventName - The event name.
* @param callback - Function that was previously added as a listener for this event through the {@link addListener} method.
* @throws an {@link Error} If an invalid event {@link eventName} is supplied, if Realm is closed or if {@link callback} is not a function.

Check warning on line 1007 in packages/realm/src/Realm.ts

View workflow job for this annotation

GitHub Actions / Lint

The type 'addListener' is undefined
*/
removeListener(eventName: RealmEventName, callback: RealmListenerCallback): void {
assert.open(this);
Expand Down Expand Up @@ -1040,7 +1048,7 @@
}

/**
* Synchronously call the provided {@link callback} inside a write transaction. If an exception happens inside a transaction,

Check warning on line 1051 in packages/realm/src/Realm.ts

View workflow job for this annotation

GitHub Actions / Lint

The type 'beginTransaction' is undefined

Check warning on line 1051 in packages/realm/src/Realm.ts

View workflow job for this annotation

GitHub Actions / Lint

The type 'commitTransaction' is undefined

Check warning on line 1051 in packages/realm/src/Realm.ts

View workflow job for this annotation

GitHub Actions / Lint

The type 'cancelTransaction' is undefined

Check warning on line 1051 in packages/realm/src/Realm.ts

View workflow job for this annotation

GitHub Actions / Lint

The type 'commitTransaction' is undefined

Check warning on line 1051 in packages/realm/src/Realm.ts

View workflow job for this annotation

GitHub Actions / Lint

The type 'write' is undefined

Check warning on line 1051 in packages/realm/src/Realm.ts

View workflow job for this annotation

GitHub Actions / Lint

The type 'write' is undefined

Check warning on line 1051 in packages/realm/src/Realm.ts

View workflow job for this annotation

GitHub Actions / Lint

The type 'write' is undefined
* you’ll lose the changes in that transaction, but the Realm itself won’t be affected (or corrupted).
* More precisely, {@link beginTransaction} and {@link commitTransaction} will be called
* automatically. If any exception is thrown during the transaction {@link cancelTransaction} will
Expand Down
Loading