expo-updates 0.10.12 → 0.10.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -10,6 +10,12 @@
10
10
 
11
11
  ### 💡 Others
12
12
 
13
+ ## 0.10.13 — 2021-11-05
14
+
15
+ ### 🐛 Bug fixes
16
+
17
+ - Fix issue with assets that are duplicated in the local SQLite db being reaped when they are still in use. ([#15049](https://github.com/expo/expo/pull/15049) by [@esamelson](https://github.com/esamelson))
18
+
13
19
  ## 0.10.12 — 2021-11-04
14
20
 
15
21
  ### 🐛 Bug fixes
@@ -3,7 +3,7 @@ apply plugin: 'kotlin-android'
3
3
  apply plugin: 'maven'
4
4
 
5
5
  group = 'host.exp.exponent'
6
- version = '0.10.12'
6
+ version = '0.10.13'
7
7
 
8
8
  apply from: "../scripts/create-manifest-android.gradle"
9
9
 
@@ -59,7 +59,7 @@ android {
59
59
  minSdkVersion safeExtGet("minSdkVersion", 21)
60
60
  targetSdkVersion safeExtGet("targetSdkVersion", 30)
61
61
  versionCode 31
62
- versionName '0.10.12'
62
+ versionName '0.10.13'
63
63
  consumerProguardFiles("proguard-rules.pro")
64
64
  testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
65
65
  // uncomment below to export the database schema when making changes
@@ -41,6 +41,12 @@ public abstract class AssetDao {
41
41
  " WHERE updates.keep);")
42
42
  public abstract void _unmarkUsedAssetsFromDeletion();
43
43
 
44
+ @Query("UPDATE assets SET marked_for_deletion = 0 WHERE relative_path IN (" +
45
+ " SELECT relative_path" +
46
+ " FROM assets" +
47
+ " WHERE marked_for_deletion = 0);")
48
+ public abstract void _unmarkDuplicateUsedAssetsFromDeletion();
49
+
44
50
  @Query("SELECT * FROM assets WHERE marked_for_deletion = 1;")
45
51
  public abstract List<AssetEntity> _loadAssetsMarkedForDeletion();
46
52
 
@@ -121,6 +127,8 @@ public abstract class AssetDao {
121
127
  // this is safe since this is a transaction and will be rolled back upon failure
122
128
  _markAllAssetsForDeletion();
123
129
  _unmarkUsedAssetsFromDeletion();
130
+ // check for duplicate rows representing a single file on disk
131
+ _unmarkDuplicateUsedAssetsFromDeletion();
124
132
 
125
133
  List<AssetEntity> deletedAssets = _loadAssetsMarkedForDeletion();
126
134
  _deleteAssetsMarkedForDeletion();
@@ -275,6 +275,17 @@ static NSString * const EXUpdatesDatabaseServerDefinedHeadersKey = @"serverDefin
275
275
  return nil;
276
276
  }
277
277
 
278
+ // check for duplicate rows representing a single file on disk
279
+ NSString * const update3Sql = @"UPDATE assets SET marked_for_deletion = 0 WHERE relative_path IN (\
280
+ SELECT relative_path\
281
+ FROM assets\
282
+ WHERE marked_for_deletion = 0\
283
+ );";
284
+ if ([self _executeSql:update3Sql withArgs:nil error:error] == nil) {
285
+ sqlite3_exec(_db, "ROLLBACK;", NULL, NULL, NULL);
286
+ return nil;
287
+ }
288
+
278
289
  NSString * const selectSql = @"SELECT * FROM assets WHERE marked_for_deletion = 1;";
279
290
  NSArray<NSDictionary *> *rows = [self _executeSql:selectSql withArgs:nil error:error];
280
291
  if (!rows) {
@@ -2,6 +2,8 @@
2
2
 
3
3
  #import <XCTest/XCTest.h>
4
4
 
5
+ #import <EXManifests/EXManifestsNewManifest.h>
6
+ #import <EXUpdates/EXUpdatesAsset.h>
5
7
  #import <EXUpdates/EXUpdatesConfig.h>
6
8
  #import <EXUpdates/EXUpdatesDatabase+Tests.h>
7
9
  #import <EXUpdates/EXUpdatesNewUpdate.h>
@@ -172,4 +174,85 @@
172
174
  XCTAssertEqualObjects(expected, actual);
173
175
  }
174
176
 
177
+ - (void)testDeleteUnusedAssets_DuplicateFilenames
178
+ {
179
+ EXManifestsNewManifest *manifest1 = [[EXManifestsNewManifest alloc] initWithRawManifestJSON:@{
180
+ @"runtimeVersion": @"1",
181
+ @"id": @"0eef8214-4833-4089-9dff-b4138a14f196",
182
+ @"createdAt": @"2020-11-11T00:17:54.797Z",
183
+ @"launchAsset": @{@"url": @"https://url.to/bundle1.js", @"contentType": @"application/javascript"}
184
+ }];
185
+ EXManifestsNewManifest *manifest2 = [[EXManifestsNewManifest alloc] initWithRawManifestJSON:@{
186
+ @"runtimeVersion": @"1",
187
+ @"id": @"0eef8214-4833-4089-9dff-b4138a14f197",
188
+ @"createdAt": @"2020-11-11T00:17:55.797Z",
189
+ @"launchAsset": @{@"url": @"https://url.to/bundle2.js", @"contentType": @"application/javascript"}
190
+ }];
191
+
192
+ EXUpdatesAsset *asset1 = [self createMockAssetWithKey:@"key1"];
193
+ EXUpdatesAsset *asset2 = [self createMockAssetWithKey:@"key2"];
194
+ EXUpdatesAsset *asset3 = [self createMockAssetWithKey:@"key3"];
195
+
196
+ // simulate two assets with different keys that share a file on disk
197
+ // this can happen if we, for example, change the format of asset keys that we serve
198
+ asset2.filename = @"same-filename.png";
199
+ asset3.filename = @"same-filename.png";
200
+
201
+ EXUpdatesUpdate *update1 = [EXUpdatesNewUpdate updateWithNewManifest:manifest1 response:nil config:_config database:_db];
202
+ EXUpdatesUpdate *update2 = [EXUpdatesNewUpdate updateWithNewManifest:manifest2 response:nil config:_config database:_db];
203
+
204
+ dispatch_sync(_db.databaseQueue, ^{
205
+ NSError *update1Error;
206
+ [_db addUpdate:update1 error:&update1Error];
207
+ NSError *update2Error;
208
+ [_db addUpdate:update2 error:&update2Error];
209
+ NSError *updateAsset1Error;
210
+ [_db addNewAssets:@[asset1, asset2] toUpdateWithId:update1.updateId error:&updateAsset1Error];
211
+ NSError *updateAsset2Error;
212
+ [_db addNewAssets:@[asset3] toUpdateWithId:update2.updateId error:&updateAsset2Error];
213
+ if (update1Error || update2Error || updateAsset1Error || updateAsset2Error) {
214
+ XCTFail(@"%@ %@ %@ %@", update1Error.localizedDescription, update2Error.localizedDescription, updateAsset1Error.localizedDescription, updateAsset2Error.localizedDescription);
215
+ return;
216
+ }
217
+
218
+ // simulate update1 being reaped, update2 being kept
219
+ NSError *deleteUpdateError;
220
+ [_db deleteUpdates:@[update1] error:&deleteUpdateError];
221
+ if (deleteUpdateError) {
222
+ XCTFail(@"%@", deleteUpdateError.localizedDescription);
223
+ return;
224
+ }
225
+
226
+ NSArray<EXUpdatesAsset *> *assets = [_db allAssetsWithError:nil];
227
+ XCTAssertEqual(3, assets.count); // two bundles and asset1 and asset2
228
+
229
+ NSError *deleteAssetsError;
230
+ NSArray<EXUpdatesAsset *> *deletedAssets = [_db deleteUnusedAssetsWithError:&deleteAssetsError];
231
+ if (deleteAssetsError) {
232
+ XCTFail(@"%@", deleteAssetsError.localizedDescription);
233
+ return;
234
+ }
235
+
236
+ // asset1 should have been deleted, but asset2 should have been kept
237
+ // since it shared a filename with asset3, which is still in use
238
+ XCTAssertEqual(1, deletedAssets.count);
239
+ for (EXUpdatesAsset *deletedAsset in deletedAssets) {
240
+ XCTAssertEqualObjects(@"key1", deletedAsset.key);
241
+ }
242
+
243
+ XCTAssertNil([_db assetWithKey:@"key1" error:nil]);
244
+ XCTAssertNotNil([_db assetWithKey:@"key2" error:nil]);
245
+ XCTAssertNotNil([_db assetWithKey:@"key3" error:nil]);
246
+ });
247
+ }
248
+
249
+ - (EXUpdatesAsset *)createMockAssetWithKey:(NSString *)key
250
+ {
251
+ EXUpdatesAsset *asset = [[EXUpdatesAsset alloc] initWithKey:key type:@"png"];
252
+ asset.downloadTime = [NSDate date];
253
+ asset.contentHash = key;
254
+ asset.filename = [NSString stringWithFormat:@"%@.png", key];
255
+ return asset;
256
+ }
257
+
175
258
  @end
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-updates",
3
- "version": "0.10.12",
3
+ "version": "0.10.13",
4
4
  "description": "Fetches and manages remotely-hosted assets and updates to your app's JS bundle.",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -50,5 +50,5 @@
50
50
  "fs-extra": "^9.1.0",
51
51
  "memfs": "^3.2.0"
52
52
  },
53
- "gitHead": "41c62d399d0eb8588b8d55919697fdb6760c3eac"
53
+ "gitHead": "4fa1b1ea7e2365343a87f4c102210f4b986a23c8"
54
54
  }