Skip to content
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
3 changes: 3 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ test/model/schemas/*.json -diff
# Kotlin files generated by, e.g., Pigeon:
*.g.kt -diff

# Swift files generated by, e.g., Pigeon:
*.g.swift -diff

# On the other hand, keep diffs for pubspec.lock. It contains
# information independent of any non-generated file in the tree.
# And thankfully it's much less verbose than, say, a yarn.lock.
Expand Down
16 changes: 15 additions & 1 deletion ios/Runner.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
B340EB382F5B092B007AD309 /* IosNative.g.swift in Sources */ = {isa = PBXBuildFile; fileRef = B340EB372F5B092B007AD309 /* IosNative.g.swift */; };
B34E9F092D776BEB0009AED2 /* Notifications.g.swift in Sources */ = {isa = PBXBuildFile; fileRef = B34E9F082D776BEB0009AED2 /* Notifications.g.swift */; };
F311C174AF9C005CE4AADD72 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3EAE3F3F518B95B7BFEB4FE7 /* Pods_Runner.framework */; };
/* End PBXBuildFile section */
Expand Down Expand Up @@ -63,12 +64,23 @@
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
B340EB372F5B092B007AD309 /* IosNative.g.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IosNative.g.swift; sourceTree = "<group>"; };
B34E9F082D776BEB0009AED2 /* Notifications.g.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Notifications.g.swift; sourceTree = "<group>"; };
B3AF53A72CA20BD10039801D /* Zulip.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Zulip.xcconfig; path = Flutter/Zulip.xcconfig; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFileSystemSynchronizedRootGroup section */
37D847A22F4CE7C20020EB99 /* RunnerUITests */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = RunnerUITests; sourceTree = "<group>"; };
37D847A22F4CE7C20020EB99 /* RunnerUITests */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
);
explicitFileTypes = {
};
explicitFolders = (
);
path = RunnerUITests;
sourceTree = "<group>";
};
Comment on lines -71 to +83
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The substantive change here is just to add an empty exceptions entry, right? It looks like this section was added in 509d88f (Patrol setup); I suspect it's not related to the intended change for your commit.

Would you take a few minutes to try and reproduce this change on its own, and either discard it or make a separate commit for it depending on what you learn? (E.g. probably we want to keep this change if it happens naturally when you clear an Xcode cache and rebuild, because in that case it probably belonged in 509d88f and was just left out accidentally.)

/* End PBXFileSystemSynchronizedRootGroup section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -140,6 +152,7 @@
97C146F01CF9000F007C117D /* Runner */ = {
isa = PBXGroup;
children = (
B340EB372F5B092B007AD309 /* IosNative.g.swift */,
3752899A2AF472D400475D9C /* Runner.entitlements */,
97C146FA1CF9000F007C117D /* Main.storyboard */,
97C146FD1CF9000F007C117D /* Assets.xcassets */,
Expand Down Expand Up @@ -469,6 +482,7 @@
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
B34E9F092D776BEB0009AED2 /* Notifications.g.swift in Sources */,
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
B340EB382F5B092B007AD309 /* IosNative.g.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
12 changes: 12 additions & 0 deletions ios/Runner/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import Flutter

let controller = window?.rootViewController as! FlutterViewController

IosNativeHostApiSetup.setUp(binaryMessenger: controller.binaryMessenger, api: IosNativeHostApiImpl())

// Retrieve the remote notification payload from launch options;
// this will be null if the launch wasn't triggered by a notification.
let notificationPayload = launchOptions?[.remoteNotification] as? [AnyHashable : Any]
Expand Down Expand Up @@ -69,3 +71,13 @@ class NotificationTapEventListener: NotificationTapEventsStreamHandler {
eventSink?.success(IosNotificationTapEvent(payload: payload))
}
}

private class IosNativeHostApiImpl: IosNativeHostApi {
func setExcludedFromBackup(filePath: String) throws {
var resourceValues = URLResourceValues()
resourceValues.isExcludedFromBackup = true

var url = URL(fileURLWithPath: filePath, isDirectory: false)
try url.setResourceValues(resourceValues)
}
}
107 changes: 107 additions & 0 deletions ios/Runner/IosNative.g.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// Autogenerated from Pigeon (v26.1.7), do not edit directly.
// See also: https://pub.dev/packages/pigeon

import Foundation

#if os(iOS)
import Flutter
#elseif os(macOS)
import FlutterMacOS
#else
#error("Unsupported platform.")
#endif

private func wrapResult(_ result: Any?) -> [Any?] {
return [result]
}

private func wrapError(_ error: Any) -> [Any?] {
if let pigeonError = error as? PigeonError {
return [
pigeonError.code,
pigeonError.message,
pigeonError.details,
]
}
if let flutterError = error as? FlutterError {
return [
flutterError.code,
flutterError.message,
flutterError.details,
]
}
return [
"\(error)",
"\(type(of: error))",
"Stacktrace: \(Thread.callStackSymbols)",
]
}

private func isNullish(_ value: Any?) -> Bool {
return value is NSNull || value == nil
}

private func nilOrValue<T>(_ value: Any?) -> T? {
if value is NSNull { return nil }
return value as! T?
}


private class IosNativePigeonCodecReader: FlutterStandardReader {
}

private class IosNativePigeonCodecWriter: FlutterStandardWriter {
}

private class IosNativePigeonCodecReaderWriter: FlutterStandardReaderWriter {
override func reader(with data: Data) -> FlutterStandardReader {
return IosNativePigeonCodecReader(data: data)
}

override func writer(with data: NSMutableData) -> FlutterStandardWriter {
return IosNativePigeonCodecWriter(data: data)
}
}

class IosNativePigeonCodec: FlutterStandardMessageCodec, @unchecked Sendable {
static let shared = IosNativePigeonCodec(readerWriter: IosNativePigeonCodecReaderWriter())
}

/// Generated protocol from Pigeon that represents a handler of messages from Flutter.
protocol IosNativeHostApi {
/// Sets UrlResourceValues.isExcludedFromBackup for the given file path.
///
/// See doc:
/// https://developer.apple.com/documentation/foundation/urlresourcevalues/isexcludedfrombackup
/// https://developer.apple.com/documentation/foundation/optimizing-your-app-s-data-for-icloud-backup
func setExcludedFromBackup(filePath: String) throws
}

/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`.
class IosNativeHostApiSetup {
static var codec: FlutterStandardMessageCodec { IosNativePigeonCodec.shared }
/// Sets up an instance of `IosNativeHostApi` to handle messages through the `binaryMessenger`.
static func setUp(binaryMessenger: FlutterBinaryMessenger, api: IosNativeHostApi?, messageChannelSuffix: String = "") {
let channelSuffix = messageChannelSuffix.count > 0 ? ".\(messageChannelSuffix)" : ""
/// Sets UrlResourceValues.isExcludedFromBackup for the given file path.
///
/// See doc:
/// https://developer.apple.com/documentation/foundation/urlresourcevalues/isexcludedfrombackup
/// https://developer.apple.com/documentation/foundation/optimizing-your-app-s-data-for-icloud-backup
let setExcludedFromBackupChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.zulip.IosNativeHostApi.setExcludedFromBackup\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
setExcludedFromBackupChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let filePathArg = args[0] as! String
do {
try api.setExcludedFromBackup(filePath: filePathArg)
reply(wrapResult(nil))
} catch {
reply(wrapError(error))
}
}
} else {
setExcludedFromBackupChannel.setMessageHandler(nil)
}
}
}
79 changes: 79 additions & 0 deletions lib/host/ios_native.g.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// Autogenerated from Pigeon (v26.1.7), do not edit directly.
// See also: https://pub.dev/packages/pigeon
// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, omit_obvious_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers

import 'dart:async';
import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List;

import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer;
import 'package:flutter/services.dart';

PlatformException _createConnectionError(String channelName) {
return PlatformException(
code: 'channel-error',
message: 'Unable to establish connection on channel: "$channelName".',
);
}


class _PigeonCodec extends StandardMessageCodec {
const _PigeonCodec();
@override
void writeValue(WriteBuffer buffer, Object? value) {
if (value is int) {
buffer.putUint8(4);
buffer.putInt64(value);
} else {
super.writeValue(buffer, value);
}
}

@override
Object? readValueOfType(int type, ReadBuffer buffer) {
switch (type) {
default:
return super.readValueOfType(type, buffer);
}
}
}

class IosNativeHostApi {
/// Constructor for [IosNativeHostApi]. The [binaryMessenger] named argument is
/// available for dependency injection. If it is left null, the default
/// BinaryMessenger will be used which routes to the host platform.
IosNativeHostApi({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''})
: pigeonVar_binaryMessenger = binaryMessenger,
pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : '';
final BinaryMessenger? pigeonVar_binaryMessenger;

static const MessageCodec<Object?> pigeonChannelCodec = _PigeonCodec();

final String pigeonVar_messageChannelSuffix;

/// Sets UrlResourceValues.isExcludedFromBackup for the given file path.
///
/// See doc:
/// https://developer.apple.com/documentation/foundation/urlresourcevalues/isexcludedfrombackup
/// https://developer.apple.com/documentation/foundation/optimizing-your-app-s-data-for-icloud-backup
Future<void> setExcludedFromBackup(String filePath) async {
final pigeonVar_channelName = 'dev.flutter.pigeon.zulip.IosNativeHostApi.setExcludedFromBackup$pigeonVar_messageChannelSuffix';
final pigeonVar_channel = BasicMessageChannel<Object?>(
pigeonVar_channelName,
pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger,
);
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[filePath]);
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
if (pigeonVar_replyList == null) {
throw _createConnectionError(pigeonVar_channelName);
} else if (pigeonVar_replyList.length > 1) {
throw PlatformException(
code: pigeonVar_replyList[0]! as String,
message: pigeonVar_replyList[1] as String?,
details: pigeonVar_replyList[2],
);
} else {
return;
}
}
}
32 changes: 31 additions & 1 deletion lib/model/store.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import '../api/model/model.dart';
import '../api/route/events.dart';
import '../api/backoff.dart';
import '../api/route/realm.dart';
import '../host/ios_native.g.dart';
import '../log.dart';
import 'actions.dart';
import 'autocomplete.dart';
Expand Down Expand Up @@ -1098,7 +1099,8 @@ class LiveGlobalStore extends GlobalStore {
// we'd invest in this area more. For example we'd try doing these
// in parallel, or deferring some to be concurrent with loading server data.
final stopwatch = Stopwatch()..start();
final db = AppDatabase(NativeDatabase.createInBackground(await _dbFile()));
final file = await _dbFile();
final db = AppDatabase(NativeDatabase.createInBackground(file));
final t1 = stopwatch.elapsed;
final globalSettings = await db.getGlobalSettings();
final t2 = stopwatch.elapsed;
Expand All @@ -1116,6 +1118,12 @@ class LiveGlobalStore extends GlobalStore {
"${format(t4 - t3)} int-settings, ${format(t5 - t4)} accounts");
}

// Disable OS backups for the database file, see:
// https://github.com/zulip/zulip-flutter/issues/2158
// This comes after the queries above, because it must come after
// the database file has been created on disk.
unawaited(_maybeDisableOsBackup(file)); // TODO(log) on error

return LiveGlobalStore._(
backend: LiveGlobalStoreBackend._(db: db),
globalSettings: globalSettings,
Expand Down Expand Up @@ -1143,6 +1151,28 @@ class LiveGlobalStore extends GlobalStore {
return File(p.join(dir.path, 'zulip.db'));
}

/// Excludes the provided database file from OS backups.
///
/// It is no-op on platforms other than iOS.
static Future<void> _maybeDisableOsBackup(File databaseFile) async {
switch (defaultTargetPlatform) {
case TargetPlatform.iOS:
await IosNativeHostApi().setExcludedFromBackup(databaseFile.path);

case TargetPlatform.android:
// On Android, backups are disabled for all files.
// See: https://github.com/zulip/zulip-flutter/pull/2160.
break;

case TargetPlatform.fuchsia:
case TargetPlatform.linux:
case TargetPlatform.macOS:
case TargetPlatform.windows:
// Does nothing on these platforms.
break;
}
}

final LiveGlobalStoreBackend _backend;

// The methods that use this should probably all move to [GlobalStoreBackend]
Expand Down
22 changes: 22 additions & 0 deletions pigeon/ios_native.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import 'package:pigeon/pigeon.dart';

// To rebuild this pigeon's output after editing this file,
// run `tools/check pigeon --fix`.
@ConfigurePigeon(PigeonOptions(
dartOut: 'lib/host/ios_native.g.dart',
swiftOut: 'ios/Runner/IosNative.g.swift',
swiftOptions: SwiftOptions(includeErrorClass: false),
))

@HostApi()
abstract class IosNativeHostApi {
/// Sets UrlResourceValues.isExcludedFromBackup for the given file path.
///
/// The file at this path must already exist,
/// and be a regular file (not a directory).
///
/// See doc:
/// https://developer.apple.com/documentation/foundation/urlresourcevalues/isexcludedfrombackup
/// https://developer.apple.com/documentation/foundation/optimizing-your-app-s-data-for-icloud-backup
void setExcludedFromBackup(String filePath);
}
Loading