-
Notifications
You must be signed in to change notification settings - Fork 564
Add resources at end of APK #4659
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
base: main
Are you sure you want to change the base?
Changes from all commits
0cdd106
f53a5c7
5fc6087
968f4eb
abd6e73
bbadcf2
6ddd5e3
47e2583
37adaa0
86d468a
24cc3a0
63e83c6
5946d0c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -122,46 +122,23 @@ void ExecuteWithAbi (string [] supportedAbis, string apkInputPath, string apkOut | |
| } | ||
|
|
||
| ArchiveFileList files = new ArchiveFileList (); | ||
| bool refresh = true; | ||
| if (apkInputPath != null && File.Exists (apkInputPath) && !File.Exists (apkOutputPath)) { | ||
| Log.LogDebugMessage ($"Copying {apkInputPath} to {apkInputPath}"); | ||
| File.Copy (apkInputPath, apkOutputPath, overwrite: true); | ||
| refresh = false; | ||
| } | ||
|
|
||
| using (var notice = Assembly.GetExecutingAssembly ().GetManifestResourceStream ("NOTICE.txt")) | ||
| using (var apk = new ZipArchiveEx (apkOutputPath, File.Exists (apkOutputPath) ? FileMode.Open : FileMode.Create )) { | ||
| if (refresh) { | ||
| for (long i = 0; i < apk.Archive.EntryCount; i++) { | ||
| ZipEntry e = apk.Archive.ReadEntry ((ulong) i); | ||
| Log.LogDebugMessage ($"Registering item {e.FullName}"); | ||
| existingEntries.Add (e.FullName); | ||
| } | ||
| bool apkInputPathExists = apkInputPath != null && File.Exists (apkInputPath); | ||
|
|
||
| DateTime lastWriteOutput = DateTime.MinValue; | ||
| DateTime lastWriteInput = DateTime.MinValue; | ||
| if (apkInputPathExists) { | ||
| lastWriteOutput = File.Exists (apkOutputPath) ? File.GetLastWriteTimeUtc (apkOutputPath) : DateTime.MinValue; | ||
| lastWriteInput = File.GetLastWriteTimeUtc (apkInputPath); | ||
| } | ||
| if (apkInputPath != null && File.Exists (apkInputPath) && refresh) { | ||
| var lastWriteOutput = File.Exists (apkOutputPath) ? File.GetLastWriteTimeUtc (apkOutputPath) : DateTime.MinValue; | ||
| var lastWriteInput = File.GetLastWriteTimeUtc (apkInputPath); | ||
| using (var packaged = new ZipArchiveEx (apkInputPath, FileMode.Open)) { | ||
| foreach (var entry in packaged.Archive) { | ||
| Log.LogDebugMessage ($"Deregistering item {entry.FullName}"); | ||
| existingEntries.Remove (entry.FullName); | ||
| if (lastWriteInput <= lastWriteOutput) | ||
| continue; | ||
| if (apk.Archive.ContainsEntry (entry.FullName)) { | ||
| ZipEntry e = apk.Archive.ReadEntry (entry.FullName); | ||
| // check the CRC values as the ModifiedDate is always 01/01/1980 in the aapt generated file. | ||
| if (entry.CRC == e.CRC) { | ||
| Log.LogDebugMessage ($"Skipping {entry.FullName} from {apkInputPath} as its up to date."); | ||
| continue; | ||
| } | ||
| } | ||
| var ms = new MemoryStream (); | ||
| entry.Extract (ms); | ||
| Log.LogDebugMessage ($"Refreshing {entry.FullName} from {apkInputPath}"); | ||
| apk.Archive.AddStream (ms, entry.FullName, compressionMethod: entry.CompressionMethod); | ||
| } | ||
| } | ||
|
|
||
| for (long i = 0; i < apk.Archive.EntryCount; i++) { | ||
| ZipEntry e = apk.Archive.ReadEntry ((ulong) i); | ||
| Log.LogDebugMessage ($"Registering item {e.FullName}"); | ||
| existingEntries.Add (e.FullName); | ||
| } | ||
| apk.FixupWindowsPathSeparators ((a, b) => Log.LogDebugMessage ($"Fixing up malformed entry `{a}` -> `{b}`")); | ||
| string noticeName = RootPath + "NOTICE"; | ||
| existingEntries.Remove (noticeName); | ||
| if (!apk.Archive.ContainsEntry (noticeName)) | ||
|
|
@@ -248,6 +225,16 @@ void ExecuteWithAbi (string [] supportedAbis, string apkInputPath, string apkOut | |
| count = 0; | ||
| } | ||
| } | ||
|
|
||
| // For now, we keep track of deleted entry indices & skip them ourselves when iterating zip contents in FixupWindowsPathSeparators. | ||
| // For this to work, no one shoud call Flush on the zip before calling FixupWindowsPathSeparators, since Flush changes all the indices | ||
| // (and gets rid of the deleted stuff). Later when we move to a newer LibZipSharp, with the fix to skip deleted entries itself, | ||
| // we can remove this workaround. See FixupWindowsPathSeparators for more info. | ||
| HashSet<ulong> deletedEntries = new HashSet<ulong> (); | ||
| if (apkInputPathExists) | ||
| UpdateEntriesFromInputApk (apkInputPath, apk, lastWriteOutput, lastWriteInput, deletedEntries); | ||
| apk.FixupWindowsPathSeparators (deletedEntries, (a, b) => Log.LogDebugMessage ($"Fixing up malformed entry `{a}` -> `{b}`")); | ||
|
|
||
| // Clean up Removed files. | ||
| foreach (var entry in existingEntries) { | ||
| Log.LogDebugMessage ($"Removing {entry} as it is not longer required."); | ||
|
|
@@ -258,6 +245,49 @@ void ExecuteWithAbi (string [] supportedAbis, string apkInputPath, string apkOut | |
| } | ||
| } | ||
|
|
||
| private void UpdateEntriesFromInputApk (string apkInputPath, ZipArchiveEx apk, DateTime lastWriteOutput, DateTime lastWriteInput, HashSet<ulong> deletedEntries) | ||
| { | ||
| using (var packaged = new ZipArchiveEx (apkInputPath, FileMode.Open)) { | ||
| foreach (var entry in packaged.Archive) { | ||
| Log.LogDebugMessage ($"Deregistering item {entry.FullName}"); | ||
| existingEntries.Remove (entry.FullName); | ||
| if (lastWriteInput <= lastWriteOutput) | ||
| continue; | ||
|
|
||
| long entryIndexInOutput; | ||
| if (apk.Archive.ContainsEntry (entry.FullName, out entryIndexInOutput)) { | ||
| ZipEntry e = apk.Archive.ReadEntry (entry.FullName); | ||
| // check the CRC values as the ModifiedDate is always 01/01/1980 in the aapt generated file. | ||
| if (entry.CRC == e.CRC) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @grendello: what's the "likelihood" of CRC collisions? This is "only" a 32-bit value. Should we be relying on it for change "identicality" checks?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For ZIP purposes it's not likely that we'd have collisions. Adler-32 (used to calculate the CRC) is weak for short messages, however, so there is a possibility of collision on small files. How big a likelihood? Depends on the data set... I wouldn't use it alone to see if the data has changed or not, but combined with the file name check I think it's good enough. |
||
| Log.LogDebugMessage ($"Skipping {entry.FullName} from {apkInputPath} as its up to date."); | ||
| continue; | ||
| } | ||
| } else { | ||
| entryIndexInOutput = -1; | ||
| } | ||
|
|
||
| var ms = new MemoryStream (); | ||
| entry.Extract (ms); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this advisable or a good idea? @grendello: is there a "good" libZipSharp method which can copy data between My fear is that Maybe the "proper" thing to do is check
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. e.g.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. which is only publicly exposed via
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It was never needed, so there was no reason to make it public (or, rather, wrapped in a method to call it) |
||
|
|
||
| if (entryIndexInOutput != -1) { | ||
| Log.LogDebugMessage ($"Refreshing {entry.FullName} from {apkInputPath}"); | ||
|
|
||
| // Force the modified resource to move to the end of the file, by deleting it first so that AddStream adds it | ||
| // back with a new index. Keeping modified resources toward the end optimizes the delta install for the typical | ||
| // dev scenario where the user is editing a few resources but most of the APK contents (e.g. the native libs) | ||
| // don't change and we want to keep their byte offset in the APK fixed. Delta install need not update APK | ||
| // contents that don't change and don't move. | ||
| apk.Archive.DeleteEntry ((ulong) entryIndexInOutput); | ||
| deletedEntries.Add ((ulong) entryIndexInOutput); | ||
| } else { | ||
| Log.LogDebugMessage ($"Adding {entry.FullName} from {apkInputPath}"); | ||
| } | ||
|
|
||
| apk.Archive.AddStream (ms, entry.FullName, compressionMethod: entry.CompressionMethod); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| public override bool RunTask () | ||
| { | ||
| Aot.TryGetSequencePointsMode (AndroidSequencePointsMode, out sequencePointsMode); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this a
ulonginstead of along, when.EntryCountislong?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@grendello - do you know if there's a rationale for that? maybe EntryCount can be -1 in some case?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As far as I remember, the rationale for
EntryCountbeinglongwas some sort of compatibility with BCL'sZipArchive(which doesn't haveEntryCountbut has a collection which has a signed integerCountproperty). At the same time, I always avoid using signed integers whenever a size or an index is concerned - on the notion that we have no negative indices and we have no negative sizes (or at least - they make no sense). Thus using an unsigned integer limits the size of the error domain (index can be too big, but can't be too small - one less thing that can go wrong)