main.dart 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. import 'dart:async';
  2. import 'dart:io';
  3. import 'package:background_fetch/background_fetch.dart';
  4. import 'package:firebase_messaging/firebase_messaging.dart';
  5. import 'package:flutter/foundation.dart';
  6. import 'package:flutter/material.dart';
  7. import 'package:in_app_purchase/in_app_purchase.dart';
  8. import 'package:logging/logging.dart';
  9. import 'package:path_provider/path_provider.dart';
  10. import 'package:photos/app.dart';
  11. import 'package:photos/core/configuration.dart';
  12. import 'package:photos/core/constants.dart';
  13. import 'package:photos/core/error-reporting/super_logging.dart';
  14. import 'package:photos/core/network.dart';
  15. import 'package:photos/db/upload_locks_db.dart';
  16. import 'package:photos/services/app_lifecycle_service.dart';
  17. import 'package:photos/services/billing_service.dart';
  18. import 'package:photos/services/collections_service.dart';
  19. import 'package:photos/services/feature_flag_service.dart';
  20. import 'package:photos/services/local_sync_service.dart';
  21. import 'package:photos/services/memories_service.dart';
  22. import 'package:photos/services/notification_service.dart';
  23. import 'package:photos/services/push_service.dart';
  24. import 'package:photos/services/remote_sync_service.dart';
  25. import 'package:photos/services/sync_service.dart';
  26. import 'package:photos/services/trash_sync_service.dart';
  27. import 'package:photos/services/update_service.dart';
  28. import 'package:photos/ui/app_lock.dart';
  29. import 'package:photos/ui/lock_screen.dart';
  30. import 'package:photos/utils/crypto_util.dart';
  31. import 'package:photos/utils/file_uploader.dart';
  32. import 'package:photos/utils/local_settings.dart';
  33. import 'package:shared_preferences/shared_preferences.dart';
  34. final _logger = Logger("main");
  35. bool _isProcessRunning = false;
  36. const kLastBGTaskHeartBeatTime = "bg_task_hb_time";
  37. const kLastFGTaskHeartBeatTime = "fg_task_hb_time";
  38. const kHeartBeatFrequency = Duration(seconds: 1);
  39. const kFGSyncFrequency = Duration(minutes: 5);
  40. const kBGTaskTimeout = Duration(seconds: 25);
  41. const kBGPushTimeout = Duration(seconds: 28);
  42. const kFGTaskDeathTimeoutInMicroseconds = 5000000;
  43. void main() async {
  44. WidgetsFlutterBinding.ensureInitialized();
  45. await _runInForeground();
  46. BackgroundFetch.registerHeadlessTask(_headlessTaskHandler);
  47. }
  48. Future<void> _runInForeground() async {
  49. return await _runWithLogs(() async {
  50. _logger.info("Starting app in foreground");
  51. await _init(false, via: 'mainMethod');
  52. _scheduleFGSync('appStart in FG');
  53. runApp(AppLock(
  54. builder: (args) => EnteApp(_runBackgroundTask, _killBGTask),
  55. lockScreen: LockScreen(),
  56. enabled: Configuration.instance.shouldShowLockScreen(),
  57. themeData: themeData,
  58. ));
  59. });
  60. }
  61. Future<void> _runBackgroundTask(String taskId) async {
  62. if (Platform.isIOS && _isProcessRunning) {
  63. _logger.info("Background task triggered when process was already running");
  64. await _sync('bgTaskActiveProcess');
  65. BackgroundFetch.finish(taskId);
  66. } else {
  67. _runWithLogs(() async {
  68. _logger.info("run background task");
  69. _runInBackground(taskId);
  70. }, prefix: "[bg]");
  71. }
  72. }
  73. Future<void> _runInBackground(String taskId) async {
  74. await Future.delayed(Duration(seconds: 3));
  75. if (await _isRunningInForeground()) {
  76. _logger.info("FG task running, skipping BG taskID: $taskId");
  77. BackgroundFetch.finish(taskId);
  78. return;
  79. } else {
  80. _logger.info("FG task is not running");
  81. }
  82. _logger.info("[BackgroundFetch] Event received: $taskId");
  83. _scheduleBGTaskKill(taskId);
  84. if (Platform.isIOS) {
  85. _scheduleSuicide(kBGTaskTimeout, taskId); // To prevent OS from punishing us
  86. }
  87. await _init(true, via: 'runViaBackgroundTask');
  88. UpdateService.instance.showUpdateNotification();
  89. await _sync('bgSync');
  90. BackgroundFetch.finish(taskId);
  91. }
  92. void _headlessTaskHandler(HeadlessTask task) {
  93. if (task.timeout) {
  94. BackgroundFetch.finish(task.taskId);
  95. } else {
  96. _runInBackground(task.taskId);
  97. }
  98. }
  99. Future<void> _init(bool isBackground, {String via = ''}) async {
  100. _isProcessRunning = true;
  101. _logger.info("Initializing... inBG =$isBackground via: $via");
  102. await _logFGHeartBeatInfo();
  103. _scheduleHeartBeat(isBackground);
  104. if (isBackground) {
  105. AppLifecycleService.instance.onAppInBackground('init via: $via');
  106. } else {
  107. AppLifecycleService.instance.onAppInForeground('init via: $via');
  108. }
  109. InAppPurchaseConnection.enablePendingPurchases();
  110. CryptoUtil.init();
  111. await NotificationService.instance.init();
  112. await Network.instance.init();
  113. await Configuration.instance.init();
  114. await UpdateService.instance.init();
  115. await BillingService.instance.init();
  116. await CollectionsService.instance.init();
  117. await FileUploader.instance.init(isBackground);
  118. await LocalSyncService.instance.init();
  119. await TrashSyncService.instance.init();
  120. await RemoteSyncService.instance.init();
  121. await SyncService.instance.init();
  122. await MemoriesService.instance.init();
  123. await LocalSettings.instance.init();
  124. if (Platform.isIOS) {
  125. PushService.instance.init().then((_) {
  126. FirebaseMessaging.onBackgroundMessage(
  127. _firebaseMessagingBackgroundHandler);
  128. });
  129. }
  130. FeatureFlagService.instance.init();
  131. _logger.info("Initialization done");
  132. }
  133. Future<void> _sync(String caller) async {
  134. if (!AppLifecycleService.instance.isForeground) {
  135. _logger.info("Syncing in background caller $caller");
  136. } else {
  137. _logger.info("Syncing in foreground caller $caller");
  138. }
  139. try {
  140. await SyncService.instance.sync();
  141. } catch (e, s) {
  142. _logger.severe("Sync error", e, s);
  143. }
  144. }
  145. Future _runWithLogs(Function() function, {String prefix = ""}) async {
  146. await SuperLogging.main(LogConfig(
  147. body: function,
  148. logDirPath: (await getTemporaryDirectory()).path + "/logs",
  149. maxLogFiles: 5,
  150. sentryDsn: kDebugMode ? kSentryDebugDSN : kSentryDSN,
  151. enableInDebugMode: true,
  152. prefix: prefix,
  153. ));
  154. }
  155. Future<void> _scheduleHeartBeat(bool isBackground) async {
  156. final prefs = await SharedPreferences.getInstance();
  157. await prefs.setInt(
  158. isBackground ? kLastBGTaskHeartBeatTime : kLastFGTaskHeartBeatTime,
  159. DateTime.now().microsecondsSinceEpoch);
  160. Future.delayed(kHeartBeatFrequency, () async {
  161. _scheduleHeartBeat(isBackground);
  162. });
  163. }
  164. Future<void> _scheduleFGSync(String caller) async {
  165. await _sync(caller);
  166. Future.delayed(kFGSyncFrequency, () async {
  167. _scheduleFGSync('fgSyncCron');
  168. });
  169. }
  170. void _scheduleBGTaskKill(String taskId) async {
  171. if (await _isRunningInForeground()) {
  172. _logger.info("Found app in FG, committing seppuku. $taskId");
  173. await _killBGTask(taskId);
  174. return;
  175. }
  176. Future.delayed(kHeartBeatFrequency, () async {
  177. _scheduleBGTaskKill(taskId);
  178. });
  179. }
  180. Future<bool> _isRunningInForeground() async {
  181. final prefs = await SharedPreferences.getInstance();
  182. await prefs.reload();
  183. final currentTime = DateTime.now().microsecondsSinceEpoch;
  184. return (prefs.getInt(kLastFGTaskHeartBeatTime) ?? 0) >
  185. (currentTime - kFGTaskDeathTimeoutInMicroseconds);
  186. }
  187. Future<void> _killBGTask([String taskId]) async {
  188. await UploadLocksDB.instance.releaseLocksAcquiredByOwnerBefore(
  189. ProcessType.background.toString(), DateTime.now().microsecondsSinceEpoch);
  190. final prefs = await SharedPreferences.getInstance();
  191. prefs.remove(kLastBGTaskHeartBeatTime);
  192. if (taskId != null) {
  193. BackgroundFetch.finish(taskId);
  194. }
  195. }
  196. Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
  197. bool isRunningInFG = await _isRunningInForeground(); // hb
  198. bool isInForeground = AppLifecycleService.instance.isForeground;
  199. if (_isProcessRunning) {
  200. _logger.info(
  201. "Background push received when app is alive and runningInFS: $isRunningInFG inForeground: $isInForeground");
  202. if (PushService.shouldSync(message)) {
  203. await _sync('firebaseBgSyncActiveProcess');
  204. }
  205. } else {
  206. // App is dead
  207. _runWithLogs(() async {
  208. _logger.info("Background push received");
  209. if (Platform.isIOS) {
  210. _scheduleSuicide(kBGPushTimeout); // To prevent OS from punishing us
  211. }
  212. await _init(true, via: 'firebasePush');
  213. if (PushService.shouldSync(message)) {
  214. await _sync('firebaseBgSyncNoActiveProcess');
  215. }
  216. }, prefix: "[fbg]");
  217. }
  218. }
  219. Future<void> _logFGHeartBeatInfo() async {
  220. bool isRunningInFG = await _isRunningInForeground();
  221. final prefs = await SharedPreferences.getInstance();
  222. await prefs.reload();
  223. var lastFGTaskHeartBeatTime = prefs.getInt(kLastFGTaskHeartBeatTime) ?? 0;
  224. String lastRun = lastFGTaskHeartBeatTime == 0
  225. ? 'never'
  226. : DateTime.fromMicrosecondsSinceEpoch(lastFGTaskHeartBeatTime).toString();
  227. _logger.info('isAlreaduunningFG: $isRunningInFG, last Beat: $lastRun');
  228. }
  229. void _scheduleSuicide(Duration duration, [String taskID]) {
  230. var taskIDVal = taskID ?? 'no taskID';
  231. _logger.warning("Schedule seppuku taskID: $taskIDVal");
  232. Future.delayed(duration, () {
  233. _logger.warning("TLE, committing seppuku for taskID: $taskIDVal");
  234. _killBGTask(taskID);
  235. });
  236. }