Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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: 2 additions & 1 deletion Crashlytics/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Unreleased
- [added] Added a new API checkAndUpdateUnsentReportsWithCompletion for updating the crash report from the previous run of the app if, for example, the developer wants to implement a feedback dialog to ask end-users for more information. Unsent Crashlytics Reports have familiar methods like setting custom keys and logs.
- [added] Added a new API checkAndUpdateUnsentReportsWithCompletion for updating the crash report from the previous run of the app if, for example, the developer wants to implement a feedback dialog to ask end-users for more information. Unsent Crashlytics Reports have familiar methods like setting custom keys and logs (#7503).
- [changed] Added a limit to the number of unsent reports on disk to prevent disk filling up when automatic data collection is off. Developers can ensure this limit is never reached by calling send/deleteUnsentReports every run (#7619).

# v7.6.0
- [fixed] Fixed an issue where some developers experienced a race condition involving binary image operations (#7459).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ NS_ASSUME_NONNULL_BEGIN
* new report for this run of the app has been created. Any
* reports in ExistingReportManager will be uploaded or deleted
* and we don't want to do that for the current run of the app.
*
* If there are over MAX_UNSENT_REPORTS valid reports, this will delete them.
*
* This methods is slow and should be called only once.
*/
- (void)collectExistingReports;

Expand Down
82 changes: 58 additions & 24 deletions Crashlytics/Crashlytics/Controllers/FIRCLSExistingReportManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,29 @@
#import "Crashlytics/Crashlytics/Controllers/FIRCLSManagerData.h"
#import "Crashlytics/Crashlytics/Controllers/FIRCLSReportUploader.h"
#import "Crashlytics/Crashlytics/DataCollection/FIRCLSDataCollectionToken.h"
#import "Crashlytics/Crashlytics/Helpers/FIRCLSLogger.h"
#import "Crashlytics/Crashlytics/Models/FIRCLSFileManager.h"
#import "Crashlytics/Crashlytics/Models/FIRCLSInternalReport.h"
#import "Crashlytics/Crashlytics/Private/FIRCrashlyticsReport_Private.h"
#import "Crashlytics/Crashlytics/Public/FirebaseCrashlytics/FIRCrashlyticsReport.h"

// This value should stay in sync with the Android SDK
const NSUInteger MAX_UNSENT_REPORTS = 4;

@interface FIRCLSExistingReportManager ()

@property(nonatomic, strong) FIRCLSFileManager *fileManager;
@property(nonatomic, strong) NSOperationQueue *operationQueue;
@property(nonatomic, strong) FIRCLSReportUploader *reportUploader;
@property(nonatomic, strong) NSOperationQueue *operationQueue;

// This list of active reports excludes the brand new active report that will be created this run of
// the app.
@property(nonatomic, strong) NSArray *existingUnemptyActiveReportPaths;
@property(nonatomic, strong) NSArray *processingReportPaths;
@property(nonatomic, strong) NSArray *preparedReportPaths;

@property(nonatomic, strong) FIRCLSInternalReport *newestInternalReport;

@end

@implementation FIRCLSExistingReportManager
Expand All @@ -52,15 +58,15 @@ - (instancetype)initWithManagerData:(FIRCLSManagerData *)managerData
return self;
}

NSInteger compareOlder(FIRCLSInternalReport *reportA,
NSInteger compareNewer(FIRCLSInternalReport *reportA,
FIRCLSInternalReport *reportB,
void *context) {
return [reportA.dateCreated compare:reportB.dateCreated];
return -1 * [reportA.dateCreated compare:reportB.dateCreated];
}

- (void)collectExistingReports {
self.existingUnemptyActiveReportPaths =
[self getUnemptyExistingActiveReportsAndDeleteEmpty:self.fileManager.activePathContents];
[self getUnsentActiveReportsAndDeleteEmptyOrOld:self.fileManager.activePathContents];
self.processingReportPaths = self.fileManager.processingPathContents;
self.preparedReportPaths = self.fileManager.preparedPathContents;
}
Expand All @@ -70,43 +76,71 @@ - (FIRCrashlyticsReport *)newestUnsentReport {
return nil;
}

NSMutableArray<NSString *> *allReportPaths =
[NSMutableArray arrayWithArray:self.existingUnemptyActiveReportPaths];
return [[FIRCrashlyticsReport alloc] initWithInternalReport:self.newestInternalReport];
}

- (NSUInteger)unsentReportsCount {
// There are nuances about why we only count active reports.
// See the header comment for more information.
return self.existingUnemptyActiveReportPaths.count;
}

/*
* This has the side effect of deleting any reports over the max, starting with oldest reports.
*/
- (NSArray<NSString *> *)getUnsentActiveReportsAndDeleteEmptyOrOld:(NSArray *)reportPaths {
NSMutableArray<FIRCLSInternalReport *> *validReports = [NSMutableArray array];
for (NSString *path in allReportPaths) {
for (NSString *path in reportPaths) {
FIRCLSInternalReport *_Nullable report = [FIRCLSInternalReport reportWithPath:path];
if (!report) {
continue;
}

// Delete reports without any crashes or non-fatals
if (![report hasAnyEvents]) {
[self.operationQueue addOperationWithBlock:^{
[self.fileManager removeItemAtPath:path];
}];
continue;
}

[validReports addObject:report];
}

[validReports sortUsingFunction:compareOlder context:nil];
if (validReports.count == 0) {
return @[];
}

FIRCLSInternalReport *_Nullable internalReport = [validReports lastObject];
return [[FIRCrashlyticsReport alloc] initWithInternalReport:internalReport];
}
// Sort with the newest at the end
[validReports sortUsingFunction:compareNewer context:nil];

- (NSUInteger)unsentReportsCount {
// There are nuances about why we only count active reports.
// See the header comment for more information.
return self.existingUnemptyActiveReportPaths.count;
}
// Set our report for updating in checkAndUpdateUnsentReports
self.newestInternalReport = [validReports firstObject];

- (NSArray *)getUnemptyExistingActiveReportsAndDeleteEmpty:(NSArray *)reportPaths {
NSMutableArray *unemptyReports = [NSMutableArray array];
for (NSString *path in reportPaths) {
FIRCLSInternalReport *report = [FIRCLSInternalReport reportWithPath:path];
if ([report hasAnyEvents]) {
[unemptyReports addObject:path];
} else {
// Delete any reports above the limit, starting with the oldest
// which should be at the start of the array.
if (validReports.count > MAX_UNSENT_REPORTS) {
NSUInteger deletingCount = validReports.count - MAX_UNSENT_REPORTS;
FIRCLSInfoLog(@"Deleting %lu unsent reports over the limit of %lu to prevent disk space from "
@"filling up. To prevent this make sure to call send/deleteUnsentReports.",
deletingCount, MAX_UNSENT_REPORTS);
}

// Not that validReports is sorted, delete any reports at indices > MAX_UNSENT_REPORTS, and
// collect the rest of the reports to return.
NSMutableArray<NSString *> *validReportPaths = [NSMutableArray array];
for (int i = 0; i < validReports.count; i++) {
if (i >= MAX_UNSENT_REPORTS) {
[self.operationQueue addOperationWithBlock:^{
NSString *path = [[validReports objectAtIndex:i] path];
[self.fileManager removeItemAtPath:path];
}];
} else {
[validReportPaths addObject:[[validReports objectAtIndex:i] path]];
}
}
return unemptyReports;

return validReportPaths;
}

- (void)sendUnsentReportsWithToken:(FIRCLSDataCollectionToken *)dataCollectionToken
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright 2021 Google LLC
//
// 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.

#ifndef FIRCLSExistingReportManager_Private_h
#define FIRCLSExistingReportManager_Private_h

#import "Crashlytics/Crashlytics/Controllers/FIRCLSExistingReportManager.h"

/**
* Visible for testing
*/
@interface FIRCLSExistingReportManager (Private)

@property(nonatomic, strong) NSOperationQueue *operationQueue;

@property(nonatomic, strong) NSArray *existingUnemptyActiveReportPaths;
@property(nonatomic, strong) NSArray *processingReportPaths;
@property(nonatomic, strong) NSArray *preparedReportPaths;

@end

#endif /* FIRCLSExistingReportManager_Private_h */
Loading