Flutter ZipFile.extractToDirectory doesn't overwrite existing files on iOS - ios

I am using flutter_archive 4.0.1 (just updated to 4.1.1) and attempting to unzip a file into an existing directory.
My scenario is that I am backing up this folder, sending to a web server, then at some point, I will want to restore into the same folder. This folder will have many files that are the same filenames as in the zip. I need to overwrite the local files with the ones in the zip.
This works perfect on Android. iOS has always had problems when it comes to working with Zip files.
The extractToDirectory does not have an overwrite switch, so I attempted to use the onExtracting, to check if the file already exists locally, delete the local one, then allow the zip one to take its place.
The problem I am experiencing is that to check if it exists, and to delete, I have to use a Future, but as they are async, I cannot get them to synchronise.
Here is what I have tried.
if (Platform.isIOS) {
await ZipFile.extractToDirectory(
zipFile: zipFile,
destinationDir: destinationDir,
onExtracting: (zipEntry, progress) {
exists(zipEntry.name).then((value) {
if (value) {
deleteFile(zipEntry.name).then((value) {
return ZipFileOperation.includeItem;
});
} else {
return ZipFileOperation.includeItem;
}
});
return ZipFileOperation.includeItem;
}
);
}
Both exists and deleteFile are local Futures, that uses the File functionality.
What I have tried, is that the zipEntry.name will be the same as the file I need to overwrite, so this aspect should work fine. It is now just trying to make things work in order.
The Android version is the same, apart from it does not have the onExtracting functionality.

Not sure if you have found the answer or even if there is a good answer. I ran into this issue myself, and it seems the alternative is delete the target dir before unzipping. There seems no override option for unzip. Here is some snip bits about deletion (as also suggested by the package's unit test code):
final _appDataDir = Directory.systemTemp; //from dart.io
final destinationDir = Directory("${_appDataDir.path}/unzip");
if (destinationDir.existsSync()) {
print("Deleting existing unzip directory: ${destinationDir.path}");
destinationDir.deleteSync(recursive: true);
}
Hope this solution helps others who may have similar issues.

Related

cordova-plugin-file: files not accessible after app update (iOS)

I am experiencing a funny problem: I am developing an ionic app, using cordova-plugin-file to store images. The resulting paths (URIs in form file:///...) are stored in a SQLite DB along with more information. When I install the app and use it, all works perfect. But if I update (or reinstall) the app, the old images are not found anymore, while newly added images work perfect.
I first suspected that the image files were being deleted on update, but I checked the content of the directory and the files are still there. (FYI: I am using file.dataDirectory/scans/ to place my files).
Then I thought that maybe I could go around the problem loading into img src a base64 blob loaded with cordova.file.readAsUrl(), but cordova.file does not find the file as well (I insist, the files are there, I checked with XCode).
So I checked further with cordova.file and it only finds files that are added after last app install, but the older files are still present in the directory.
Here my code:
private getImgSrcFromDocument(doc: Document): any {
const uri = doc.fileName;
const src = this.webView.convertFileSrc(uri);
const sanitized = this.sanitizer.bypassSecurityTrustUrl(src);
console.log({uri, src, sanitized});
return sanitized;
}
<img class="document_thumbnail" [src]="getImgSrcFromDocument(doc)">
I have already checked this, but is not my case.
By the way, the same code works perfect on Android.
Any idea what could be the problem?
Thanks in advance!
I found the reason why and the solution (very obvious when you know the problem):
Reason
On every new install, iOS renames the data directory for the app. The directory path has this form:
file:///var/mobile/Containers/Data/Application/ABC0000-1234-99DD-00FA-E835FEA/Library/NoCloud/
The hash in the middle is renewed on every install, so the stored full paths in DB are not valid anymore.
Solution
If you still can do it (no deploy yet, no real users), store only the relative path and complete it every time with this.file.dataDirectory (or wherever you wanted to store your files).
If you already have real users and want your update to 'find the files', just ignore the first part of the stored path and build it like before:
const ValidUri = this.file.dataDirectory + // The injected cordova-plugin-file
'relativeSubDirectories/' + // If you store your files in some subdirectory
this.document.storedFullPath.substr( // Take from fullPath only the filename
this.document.storedFullPath.lastIndexOf('/') + 1
);
Where storedFullPath is the string file:///var/mobile/....
After that, you still have to do the webView conversion and the sanitizing, like in the question above.
Hope this helps someone.

Copy a DriveItem but overwrite

I want to copy and overwrite the existing file but cannot do it without getting the error nameAlreadyExists .
As a workaround I seem to have to download it into a stream and then reupload it.
The code is as per the API
await graphClient.Sites.Root.Drives[documentLibraryId].Items[fileId]
.Copy(newFileName, parentReference)
.Request()
.PostAsync();
As a workaround I may have to download the file into a strea and then upload it like as per the following which seems to work
await RetryWithExponentialBackoff.RunAsync(async () =>
{
uploadSession = await graphClient.Drives[driveId]
.Root
.ItemWithPath(filePath)
.CreateUploadSession()
.Request()
.PostAsync();
});
Usually you just need to set the microsoft.graph.conflictBehavior to replace. This works fine with functions such as moving. However, the Api ignores the specified conflict behavior with the copy function. More information on the bug here: link to github issue. It does not seem to be a priority for Microsoft, since the bug was reported over a year ago and nothing happened since. So you will most likely need a workaround.
Another idea for a workaround:
Copy the DriveItem to the target document library or folder, but use for example a combination of the current date and the drive item id instead of the desired file name newFileName to make sure, that you not get a conflict with any existing file.
Change the copied DriveItem's name to the desired file name newFileName, e. g.:
PATCH https://graph.microsoft.com/v1.0/sites/siteId/drives/driveId/items/itemId
{
"name": "newFileName",
"#microsoft.graph.conflictBehavior":"replace"
}
In my tests, this replaced the existing file with the name newFileName with the copied file.
With this workaround, you do not need to download and reupload the file. Thus, the performance should be better. However, the user may wonder about weird file names.

What is the purpose of buildResources folder in electron-builder building process?

I'm reading through electron and electron-builder docs, but I still do not quite understand what is the purpose of the buildResources folder?
Here's what a configuration doc for electron-builder says:
buildResources = build String - The path to build resources.
Kind of self-explanatory... But how or when they are involved in the build process, especially having that:
...build resources is not packed into the app. If you need to use some
files, e.g. as tray icon, please include required files explicitly
Can we simply put those icon files in an arbitrary folder and then copy over into the app/ manually (since we need to include buildResources manually anyway)?
TL;DR:
As far as I can tell from a quick glance at the source code, the buildResources folder is used to hold additional scripts, plugins, etc. that can be used by the package building software. Electron-builder doesn't generate the packages itself, it uses tools like NSIS.
Explanation:
I've had the same question and unfortunately find an answer for this isn't very straight-forward. The docs entry is pretty useless. I found out that someone asked about it in the GitHub issues but never got an answer.
I decided to dig in the code a bit myself to find out what it does. In NsisTargets.ts, you can see that the buildResources folder can contain custom includes and plugins for NSIS.
// NsisTargets.ts
taskManager.add(async () => {
const userPluginDir = path.join(packager.info.buildResourcesDir, pluginArch)
const stat = await statOrNull(userPluginDir)
if (stat != null && stat.isDirectory()) {
scriptGenerator.addPluginDir(pluginArch, userPluginDir)
}
})
// [...]
taskManager.add(async () => {
const customInclude = await packager.getResource(this.options.include, "installer.nsh")
if (customInclude != null) {
scriptGenerator.addIncludeDir(packager.info.buildResourcesDir)
scriptGenerator.include(customInclude)
}
})
and in pkg.ts it's used to load additional scripts to the pkg builder:
// pkg.ts
if (options.scripts != null) {
args.push("--scripts", path.resolve(this.packager.info.buildResourcesDir, options.scripts))
}
It appears as though buildResources can contain assets/scripts specifically used for the build process. That also explains why the contents of buildResources aren't included in the resulting app.asar file.
So, I'm going to say straight away that the documentation for this option is just awful.
Files included in buildResources will appear in the asar file which you can find documentation about on electron's website.
The option files will include files such as pictures which are not accessible in the asar file.
I.E.
given I have a folder called assets in my build folder I want to include with my app.
"files": [
"./build/**/*"
],
"directories": {
"buildResources": "assets"
}
This will put all folders inside build into the asar file, which you can then unpack by including,
"asarUnpack": "**/assets/*"
This will put the folder assets into the build folder in the app directory.

How to make web_ui compile css files automatically

I'm using web_ui and whenever I change a CSS file in web/css/ it will not be compiled unless I change the web/index.html file. I guess that's because only the file 'web/index.html' is listed as entry point in build.dart.
But adding the stylesheet to the entry points list didn't work.
Is there a way to autocompile CSS files every time they are changed without having to edit the .html files?
Keep in mind that you can edit any .dart or .html file and the compiler will run; it doesn't have to be the entry point file.
Autocompilation of CSS files on change can be achieved by passing the compiler the full flag:
build(['--machine', '--full'], ['web/index.html']);
The machine flag tells the compiler to print messages to the Dart Editor console. For a full list of flags see Build.dart and the Dart Editor Build System.
This method means that every time a file is changed your entire project will be rebuilt instead of the usual incremental approach. If you have a large project this may take a while. Here is a more comprehensive build file that takes advantage of incremental compilation and only rebuilds the whole project if a css file was changed:
List<String> args = new Options().arguments;
bool fullRebuild = false;
for (String arg in args) {
if (arg.startsWith('--changed=') && arg.endsWith('.css')) {
fullRebuild = true;
}
}
if(fullRebuild) {
build(['--machine', '--full'], ['web/index.html']);
} else {
build(args, ['web/index.html']);
}

how to set the path to where aapt add command adds the file

I'm using aapt tool to remove some files from different folders of my apk. This works fine.
But when I want to add files to the apk, the aapt tool add command doesn't let me specify the path to where I want the file to be added, therefore I can add files only to the root folder of the apk.
This is strange because I don't think that developers would never want to add files to a subfolder of the apk (res folder for example). Is this possible with aapt or any other method? Cause removing files from any folder works fine, and adding file works only for the root folder of the apk. Can't use it for any other folder.
Thanks
The aapt tool retains the directory structure specified in the add command, if you want to add something to an existing folder in an apk you simply must have a similar folder on your system and must specify each file to add fully listing the directory. Example
$ aapt list test.apk
res/drawable-hdpi/pic1.png
res/drawable-hdpi/pic2.png
AndroidManifest.xml
$ aapt remove test.apk res/drawable-hdpi/pic1.png
$ aapt add test.apk res/drawable-hdpi/pic1.png
The pic1.png that will is added resides in a folder in the current working directory of the terminal res/drawable-hdpi/ , hope this answered your question
There is actually a bug in aapt that will make this randomly impossible. The way it is supposed to work is as the other answer claims: paths are kept, unless you pass -k. Let's see how this is implemented:
The flag that controls whether the path is ignored is mJunkPath:
bool mJunkPath;
This variable is in a class called Bundle, and is controlled by two accessors:
bool getJunkPath(void) const { return mJunkPath; }
void setJunkPath(bool val) { mJunkPath = val; }
If the user specified -k at the command line, it is set to true:
case 'k':
bundle.setJunkPath(true);
break;
And, when the data is being added to the file, it is checked:
if (bundle->getJunkPath()) {
String8 storageName = String8(fileName).getPathLeaf();
printf(" '%s' as '%s'...\n", fileName, storageName.string());
result = zip->add(fileName, storageName.string(),
bundle->getCompressionMethod(), NULL);
} else {
printf(" '%s'...\n", fileName);
result = zip->add(fileName, bundle->getCompressionMethod(), NULL);
}
Unfortunately, the one instance of Bundle used by the application is allocated in main on the stack, and there is no initialization of mJunkPath in the constructor, so the value of the variable is random; without a way to explicitly set it to false, on my system I (seemingly deterministically) am unable to add files at specified paths.
However, you can also just use zip, as an APK is simply a Zip file, and the zip tool works fine.
(For the record, I have not submitted the trivial fix for this as a patch to Android yet, if someone else wants to the world would likely be a better place. My experience with the Android code submission process was having to put up with an incredibly complex submission mechanism that in the end took six months for someone to get back to me, in some cases with minor modifications that could have just been made on their end were their submission process not so horribly complex. Given that there is a really easy workaround to this problem, I do not consider it important enough to bother with all of that again.)

Resources