Flutter: How to read file from assets, asynchronously, without blocking the UI - asynchronous

In flutter, rootBundle.load() gives me a ByteData object.
What exactly is a ByteData object in dart? Can It be used to read files asynchronously?
I don't really understand the motive behind this.
Why not just give me a good ol' File object, or better yet the full path of the asset?
In my case, I want to read bytes from an asset file asynchronously, byte by byte and write to a new file. (to build an XOR decryption thingy that doesn't hang up the UI)
This is the best I could do, and it miserably hangs up the UI.
loadEncryptedPdf(fileName, secretKey, cacheDir) async {
final lenSecretKey = secretKey.length;
final encryptedByteData = await rootBundle.load('assets/$fileName');
final outputFilePath = cacheDir + '/' + fileName;
final outputFile = File(outputFilePath);
if (!await outputFile.exists()) {
Stream decrypter() async* {
// read bits from encryptedByteData, and stream the xor inverted bits
for (var index = 0; index < encryptedByteData.lengthInBytes; index++)
yield encryptedByteData.getUint8(index) ^
secretKey.codeUnitAt(index % lenSecretKey);
print('done!');
}
print('decrypting $fileName using $secretKey ..');
await outputFile.openWrite(encoding: AsciiCodec()).addStream(decrypter());
print('finished');
}
return outputFilePath;
}

In Dart a ByteData is similar to a Java ByteBuffer. It wraps a byte array, providing getter and setter functions for 1, 2 and 4 byte integers (both endians).
Since you want to manipulate bytes it's easiest to just work on the underlying byte array (a Dart Uint8List). RootBundle.load() will have already read the whole asset into memory, so change it in memory and write it out.
Future<String> loadEncryptedPdf(
String fileName, String secretKey, String cacheDir) async {
final lenSecretKey = secretKey.length;
final encryptedByteData = await rootBundle.load('assets/$fileName');
String path = cacheDir + '/' + fileName;
final outputFile = File(path);
if (!await outputFile.exists()) {
print('decrypting $fileName using $secretKey ..');
Uint8List bytes = encryptedByteData.buffer.asUint8List();
for (int i = 0; i < bytes.length; i++) {
bytes[i] ^= secretKey.codeUnitAt(i % lenSecretKey);
}
await outputFile.writeAsBytes(bytes);
print('finished');
}
return path;
}

If you are doing work that is expensive and you don't want to block the UI, use the compute method from package:flutter/foundation.dart. This will run the provided function in a separate isolate and return the results to you asynchronously. loadEncryptedPdf must be a top level or static function to use it here though, and you are limited to passing one argument (but you can put them in a Map).
import 'package:flutter/foundation.dart';
Future<String> loadEncryptedPdf(Map<String, String> arguments) async {
// this runs on another isolate
...
}
final String result = await compute(loadEncryptedPdf, {'fileName': /*.../*});

So, while the answers posted by #Jonah Williams and #Richard Heap don't suffice on their own, I tried a combination of both, and it works good for me.
Here is a complete solution -
uses path_provider package to get the cache directory
import 'package:flutter/foundation.dart';
import 'package:path_provider/path_provider.dart';
// Holds a Future to a temporary cache directory
final cacheDirFuture =
(() async => (await (await getTemporaryDirectory()).createTemp()).path)();
_xorDecryptIsolate(args) {
Uint8List pdfBytes = args[0].buffer.asUint8List();
String secretKey = args[1];
File outputFile = args[2];
int lenSecretKey = secretKey.length;
for (int i = 0; i < pdfBytes.length; i++)
pdfBytes[i] ^= secretKey.codeUnitAt(i % lenSecretKey);
outputFile.writeAsBytesSync(pdfBytes, flush: true);
}
/// decrypt a file from assets using XOR,
/// and return the path to a cached-temporary decrypted file.
xorDecryptFromAssets(String assetFileName, String secretKey) async {
final pdfBytesData = await rootBundle.load('assets/$assetFileName');
final outputFilePath = (await pdfCacheDirFuture) + '/' + assetFileName;
final outputFile = File(outputFilePath);
if ((await outputFile.stat()).size > 0) {
print('decrypting $assetFileName using $secretKey ..');
await compute(_xorDecryptIsolate, [pdfBytesData, secretKey, outputFile]);
print('done!');
}
return outputFilePath;
}

Related

How to set a field with multi-value into a Post request?

This command works fine on Linux terminal:
curl -X POST "https://my-api.plantnet.org/v2/identify/all?api-key=11111111111111111111" -H "accept: application/json" -F "organs=flower" -F "organs=leaf" -F "images=#images/image_1.jpeg" -F "images=#images/image_2.jpeg"
As you may have seen there are two multi-value fields,organs and images, one is for String objects and the another is for File objects.
I've made this code:
static Future<T> postFilesAndGetJson<T>(String url, {List<MapEntry<String, String>> paths, List<MapEntry<String, String>> fields}) async {
var request = http.MultipartRequest('POST', Uri.parse(url));
if (paths != null && paths.isNotEmpty) {
paths.forEach((path) {
var file = File.fromUri(Uri.parse(path.value));
var multipartFile = http.MultipartFile.fromBytes(
path.key, file.readAsBytesSync(), filename: p.basename(file.path)
);
request.files.add(multipartFile);
});
}
if (fields != null && fields.isNotEmpty) {
request.fields.addEntries(fields);
}
return http.Response
.fromStream(await request.send())
.then((response) {
if (response.statusCode == 200) {
return jsonDecode(response.body) as T;
}
print('Status Code : ${response.statusCode}...');
return null;
});
}
And it works fine while field names are different, so for this case it doesn't work because I get status code 400 (Bad Request).
request.fields property is Map<String, String> so I cannot (apparently) set a List<String> as value. Similar case is for request.files.
How to work with multi-value fields?
The files are actually OK having duplicate field names. The 400 error you get is probably because you send two images but only one organs. So looks like the only thing you need to fix is sending multiple fields of the same name.
Having no better ideas, you may copy the original MultipartRequest and create your own class like MultipartListRequest. Then change fields from a map to a list (changed lines are commented):
import 'dart:convert';
import 'dart:math';
import 'package:http/http.dart'; // CHANGED
import 'package:http/src/utils.dart'; // CHANGED
import 'package:http/src/boundary_characters.dart'; // CHANGED
final _newlineRegExp = RegExp(r'\r\n|\r|\n');
class MultipartListRequest extends BaseRequest { // CHANGED
/// The total length of the multipart boundaries used when building the
/// request body.
///
/// According to http://tools.ietf.org/html/rfc1341.html, this can't be longer
/// than 70.
static const int _boundaryLength = 70;
static final Random _random = Random();
/// The form fields to send for this request.
final fields = <MapEntry<String, String>>[]; // CHANGED
/// The list of files to upload for this request.
final files = <MultipartFile>[];
MultipartListRequest(String method, Uri url) : super(method, url);
/// The total length of the request body, in bytes.
///
/// This is calculated from [fields] and [files] and cannot be set manually.
#override
int get contentLength {
var length = 0;
fields.forEach((field) { // CHANGED
final name = field.key; // CHANGED
final value = field.value; // CHANGED
length += '--'.length +
_boundaryLength +
'\r\n'.length +
utf8.encode(_headerForField(name, value)).length +
utf8.encode(value).length +
'\r\n'.length;
});
for (var file in files) {
length += '--'.length +
_boundaryLength +
'\r\n'.length +
utf8.encode(_headerForFile(file)).length +
file.length +
'\r\n'.length;
}
return length + '--'.length + _boundaryLength + '--\r\n'.length;
}
#override
set contentLength(int? value) {
throw UnsupportedError('Cannot set the contentLength property of '
'multipart requests.');
}
/// Freezes all mutable fields and returns a single-subscription [ByteStream]
/// that will emit the request body.
#override
ByteStream finalize() {
// TODO: freeze fields and files
final boundary = _boundaryString();
headers['content-type'] = 'multipart/form-data; boundary=$boundary';
super.finalize();
return ByteStream(_finalize(boundary));
}
Stream<List<int>> _finalize(String boundary) async* {
const line = [13, 10]; // \r\n
final separator = utf8.encode('--$boundary\r\n');
final close = utf8.encode('--$boundary--\r\n');
for (var field in fields) { // CHANGED
yield separator;
yield utf8.encode(_headerForField(field.key, field.value));
yield utf8.encode(field.value);
yield line;
}
for (final file in files) {
yield separator;
yield utf8.encode(_headerForFile(file));
yield* file.finalize();
yield line;
}
yield close;
}
/// Returns the header string for a field.
///
/// The return value is guaranteed to contain only ASCII characters.
String _headerForField(String name, String value) {
var header =
'content-disposition: form-data; name="${_browserEncode(name)}"';
if (!isPlainAscii(value)) {
header = '$header\r\n'
'content-type: text/plain; charset=utf-8\r\n'
'content-transfer-encoding: binary';
}
return '$header\r\n\r\n';
}
/// Returns the header string for a file.
///
/// The return value is guaranteed to contain only ASCII characters.
String _headerForFile(MultipartFile file) {
var header = 'content-type: ${file.contentType}\r\n'
'content-disposition: form-data; name="${_browserEncode(file.field)}"';
if (file.filename != null) {
header = '$header; filename="${_browserEncode(file.filename!)}"';
}
return '$header\r\n\r\n';
}
/// Encode [value] in the same way browsers do.
String _browserEncode(String value) =>
// http://tools.ietf.org/html/rfc2388 mandates some complex encodings for
// field names and file names, but in practice user agents seem not to
// follow this at all. Instead, they URL-encode `\r`, `\n`, and `\r\n` as
// `\r\n`; URL-encode `"`; and do nothing else (even for `%` or non-ASCII
// characters). We follow their behavior.
value.replaceAll(_newlineRegExp, '%0D%0A').replaceAll('"', '%22');
/// Returns a randomly-generated multipart boundary string
String _boundaryString() {
var prefix = 'dart-http-boundary-';
var list = List<int>.generate(
_boundaryLength - prefix.length,
(index) =>
boundaryCharacters[_random.nextInt(boundaryCharacters.length)],
growable: false);
return '$prefix${String.fromCharCodes(list)}';
}
}
(Would be better to subclass, but many valuable things are private there.)
Then in your code set the fields using addAll instead of addEntries:
request.fields.addAll(fields);
I see that you have already submitted an issue to Dart http package. This is good.

Is there a way to process multiple xml feeds asynchoronously?

I am using Atom10FeedFormatter class for processing atom xml feeds calling OData Rest API endpoint.
It works fine, but the api gives the result slow, if there are more than 200 entries in the feed.
That is what I use:
Atom10FeedFormatter formatter = new Atom10FeedFormatter();
XNamespace d = "http://schemas.microsoft.com/ado/2007/08/dataservices";
string odataurl= "http://{mysite}/_api/ProjectData/Projects";
using (XmlReader reader = XmlReader.Create(odataurl))
{
formatter.ReadFrom(reader);
}
foreach (SyndicationItem item in formatter.Feed.Items)
{
//processing the result
}
I want to speed up this process at least a little faster by splitting the original request to query the results skipping some entries and limiting entry size.
The main idea is count the number of feeds using $count, divide the feed results into blocks of 20, use the $skip and $top in the endpoint url, iterate through the results, and finally summarize them.
int countoffeeds = 500; // for the sake of simplicity, of course, i get it from the odataurl using $count
int numberofblocks = (countoffeeds/20) + 1;
for(int i = 0; i++; i<numberofblocks){
int skip = i*20;
int top = 20;
string odataurl = "http://{mysite}/_api/ProjectData/Projects"+"?&$skip="+skip+"&top=20";
Atom10FeedFormatter formatter = new Atom10FeedFormatter();
using (XmlReader reader = XmlReader.Create(odataurl))
{
formatter.ReadFrom(reader); // And this the part where I am stuck. It returns a void so I
//cannot use Task<void> and process the result later with await
}
...
Normally I would use async calls to the api (this case numberofblocks = 26 calls in parallel), but I do not know how would I do that. formatter.ReadFrom returns void, thus I can not use it with Task.
How can I solve this, and how can I read multiple xml feeds at the same time?
Normally I would use async calls to the api (this case numberofblocks = 26 calls in parallel), but I do not know how would I do that. formatter.ReadFrom returns void, thus I can not use it with Task.
Atom10FeedFormatter is a very dated type at this point, and it doesn't support asynchrony. Nor is it likely to be updated to support asynchrony.
How can I solve this, and how can I read multiple xml feeds at the same time?
Since you're stuck in the synchronous world, you do have the option of using "fake asynchrony". This just means you would do the synchronous blocking work on a thread pool thread, and treat each of those operations as though they were asynchronous. I.e.:
var tasks = new List<Task<Atom10FeedFormatter>>();
for(int i = 0; i++; i<numberofblocks) {
int skip = i*20;
int top = 20;
tasks.Add(Task.Run(() =>
{
string odataurl = "http://{mysite}/_api/ProjectData/Projects"+"?&$skip="+skip+"&top=20";
Atom10FeedFormatter formatter = new Atom10FeedFormatter();
using (XmlReader reader = XmlReader.Create(odataurl))
{
formatter.ReadFrom(reader);
return formatter;
}
}));
}
var formatters = await Task.WhenAll(tasks);

TesseractApi Xamarin Infinite Freeze

I am using Tessaract's Xamarin Forms Nuget(https://github.com/halkar/Tesseract.Xamarin), and am trying to scan a picture taken by the Android Device. This is the code that I am using:
private async System.Threading.Tasks.Task<string> OCRAsync(byte[] bytes)
{
TesseractApi api;
api = new TesseractApi(this, AssetsDeployment.OncePerInitialization);
await api.Init("bul");
await api.SetImage(bytes);
var detectedText = api.Results(PageIteratorLevel.Block);
result = string.Empty;
if (detectedText != null)
{
foreach (var annotation in detectedText)
{
result = FindWordInDictionary(annotation.Text);
}
}
return result;
}
The method is called from a synchronized method like this:
var task = OCRAsync(data);
result = task.Result;
Whenever the compiler gets to "await api.Init("bul");" the app freezes indefinitely. Do you know what may cause this problem? Thank you.
The problem was that I needed to give a file location in the .init function:
await api.Init(pathToDataFile, "bul");

Flutter & Firebase: Compression before upload image

I want to send photo selected by user in my app to Firebase Storage. I have a simple class with property _imageFile which is set like this:
File _imageFile;
_getImage() async {
var fileName = await ImagePicker.pickImage();
setState(() {
_imageFile = fileName;
});
}
after that I send photo like with this code:
final String rand1 = "${new Random().nextInt(10000)}";
final String rand2 = "${new Random().nextInt(10000)}";
final String rand3 = "${new Random().nextInt(10000)}";
final StorageReference ref = FirebaseStorage.instance.ref().child('${rand1}_${rand2}_${rand3}.jpg');
final StorageUploadTask uploadTask = ref.put(_imageFile);
final Uri downloadUrl = (await uploadTask.future).downloadUrl;
print(downloadUrl);
The problem is that the photos are often very large. Is there any method in Flutter/Dart to compress and resize photo before upload? I am ok with loss of quality.
June 05, 2020 - Update
The image_picker plugin now supports an imageQuality paramater. You can do something like
ImagePicker imagePicker = ImagePicker();
PickedFile compressedImage = await imagePicker.getImage(
source: ImageSource.camera,
imageQuality: 85,
);
Old Answer
or if you want to compress an image without using ImagePicker
I ran into this and was able to accomplish compression / resizing with the Dart image package along with path provider. You can look at dart image api and examples for other ways and more help.
Here's what I did:
import 'package:image/image.dart' as Im;
import 'package:path_provider/path_provider.dart';
import 'dart:math' as Math;
void compressImage() async {
File imageFile = await ImagePicker.pickImage();
final tempDir = await getTemporaryDirectory();
final path = tempDir.path;
int rand = new Math.Random().nextInt(10000);
Im.Image image = Im.decodeImage(imageFile.readAsBytesSync());
Im.Image smallerImage = Im.copyResize(image, 500); // choose the size here, it will maintain aspect ratio
var compressedImage = new File('$path/img_$rand.jpg')..writeAsBytesSync(Im.encodeJpg(image, quality: 85));
}
Then I uploaded compressedImage to firebase storage. You can adjust the quality that the jpg is saved with using the quality property, in my case I chose 85 (out of 100).
Hope this helps! Let me know if you have any questions.
The image_picker plugin is currently very simple. It would be straightforward to add an option for specifying the desired size/quality of the picked image. If you do this, please send us a pull request!
Use image_picker plugin and call pick image function as
Future<File> imageFile = ImagePicker.pickImage(source: ImageSource.gallery , maxHeight: 200 , maxWidth: 200 );
change maxHeight and maxWidth to whatever size of image you need.
There are many solutions :
Use image_picker package:
You can use the built-in imageQuality property of ImagePicker to compress the image. This property takes a value between 0 and 100 and represents a percentage of the quality of the original image.
First, add image_picker as a dependency in your pubspec.yaml file.
Usage
File _image;
Future getImage() async {
var image = await ImagePicker.pickImage(
source: ImageSource.gallery,
imageQuality: 25,
);
setState(() {
_image = image;
});
}
The advantage of this approach is that it is embedded in the
image_picker package and is therefore incredibly easy to use.
You can also adjust the quality from the Image widget.
Use filterQuality to set the FilterQuality of the image.
Example :
Image.asset('assets/kab1.png', filterQuality: FilterQuality.high,),
These properties are present in AssetImage , NetworkImage , FileImage and MemoryImage.
You can also simply resize the image (Resizing an image can compress it). To do so, try the ResizeImage widget
Another solution is to use flutter_image_compress package
Besides mentioning this native library:
https://pub.dartlang.org/packages/flutter_image_compress
This is a fully dart based compressor with isolates, which might make the compression parallel to UI thread in multi core CPUs.
You might want to use compute function which makes using isolates simpler:
https://docs.flutter.io/flutter/foundation/compute.html
https://flutter.io/cookbook/networking/background-parsing/
import 'package:image/image.dart' as ImageLib;
import 'package:path_provider/path_provider.dart';
Future<void> getCompressedImage(SendPort sendPort) async {
ReceivePort receivePort = ReceivePort();
sendPort.send(receivePort.sendPort);
List msg = (await receivePort.first) as List;
String srcPath = msg[0];
String name = msg[1];
String destDirPath = msg[2];
SendPort replyPort = msg[3];
ImageLib.Image image =
ImageLib.decodeImage(await new File(srcPath).readAsBytes());
if (image.width > 500 || image.height > 500) {
image = ImageLib.copyResize(image, 500);
}
File destFile = new File(destDirPath + '/' + name);
await destFile.writeAsBytes(ImageLib.encodeJpg(image, quality: 60));
replyPort.send(destFile.path);
}
Future<File> compressImage(File f) async {
ReceivePort receivePort = ReceivePort();
await Isolate.spawn(getCompressedImage, receivePort.sendPort);
SendPort sendPort = await receivePort.first;
ReceivePort receivePort2 = ReceivePort();
sendPort.send([
f.path,
f.uri.pathSegments.last,
(await getTemporaryDirectory()).path,
receivePort2.sendPort,
]);
var msg = await receivePort2.first;
return new File(msg);
}
if (false ==
await SimplePermissions.checkPermission(
Permission.ReadExternalStorage)) {
await SimplePermissions.requestPermission(
Permission.ReadExternalStorage);
}
File img = await ImagePicker.pickImage(
source: ImageSource.gallery);
if (null != img) {
img = await compressImage(img);
}
The following code is what I use to take an image with the camera and then compress it:
import 'dart:async' show Future;
import 'dart:io' show File;
import 'package:flutter/foundation.dart' show compute;
import 'package:flutter/material.dart' show BuildContext;
import 'package:image/image.dart' as Im;
import 'dart:math' as Math;
import 'package:image_picker/image_picker.dart';
import 'package:path_provider/path_provider.dart' show getTemporaryDirectory;
Future<File> takeCompressedPicture(BuildContext context) async {
var _imageFile = await ImagePicker.pickImage(source: ImageSource.camera);
if (_imageFile == null) {
return null;
}
// You can have a loading dialog here but don't forget to pop before return file;
final tempDir = await getTemporaryDirectory();
final rand = Math.Random().nextInt(10000);
_CompressObject compressObject =
_CompressObject(_imageFile, tempDir.path, rand);
String filePath = await _compressImage(compressObject);
print('new path: ' + filePath);
File file = File(filePath);
// Pop loading
return file;
}
Future<String> _compressImage(_CompressObject object) async {
return compute(_decodeImage, object);
}
String _decodeImage(_CompressObject object) {
Im.Image image = Im.decodeImage(object.imageFile.readAsBytesSync());
Im.Image smallerImage = Im.copyResize(
image, 1024); // choose the size here, it will maintain aspect ratio
var decodedImageFile = File(object.path + '/img_${object.rand}.jpg');
decodedImageFile.writeAsBytesSync(Im.encodeJpg(smallerImage, quality: 85));
return decodedImageFile.path;
}
class _CompressObject {
File imageFile;
String path;
int rand;
_CompressObject(this.imageFile, this.path, this.rand);
}
You can call this very easy with this:
import 'path/to/compress_image.dart' as CompressImage;
// ...
File file = await CompressImage.takeCompressedPicture(context);
While i am using
package:image/image.dart
facing below issues
showing blank page while converting image ( may be taking so much process while compressing image)
After compression image was stretched and not looking good
Then used below plugin, same working fine without any issue, even faster and what i expected
https://github.com/btastic/flutter_native_image.git
steps and method available in above link.
You can use a plugin called flutter_image_compress
// Active image file
File _imageFile;
// Select an image via gallery or camera
Future<void> _pickImage(ImageSource source) async {
File selected = await ImagePicker.pickImage(source: source);
// Compress plugin
File compressedImage = await FlutterImageCompress.compressAndGetFile(
selected.path,
selected.path,
quality: 50,
);
setState(() {
_imageFile = compressedImage;
print('compressedimagesize: ${_imageFile.lengthSync()}');
});
}
Voila! Compressed file image
Since you are using Firebase, one option would be using the Extension - Resize Image. It gives you the option to keep or delete the original image and it is very easy to install and use.
You can do as follows,
//Compressing Image
File compressedImg = await FlutterNativeImage.compressImage(
_image.path,
quality: 70,
);
//Compressing Image
Well
1. If you are in mobile you could use flutter_image_compress: ^1.0.0 will do the work
Eg.
Pass youe Uint8List and quality, you will get compressed image in no time.
Future<Uint8List> testComporessList(Uint8List uint8List) async {
var result = await FlutterImageCompress.compressWithList(
uint8List,
quality: 50,
);
return result;
}
2. But If you are in flutter web then you won't get any other option than image picker etc etc.
I end up using javascript. You can find answer here.
Flutter Web: How Do You Compress an Image/File?
Update 2020 'pickImage' is deprecated and shouldn't be used. Use imagePicker.getImage() method instead**
ImagePicker picker = ImagePicker();
PickedFile compressedImage = await imagePicker.getImage(
source: ImageSource.camera,
imageQuality: 80,
);
Doc for ImageQuality :
Returns a PickedFile object wrapping the image that was picked. The
returned PickedFile is intended to be used within a single APP
session. Do not save the file path and use it across sessions. The
source argument controls where the image comes from. This can be
either ImageSource.camera or ImageSource.gallery. Where iOS supports
HEIC images, Android 8 and below doesn't. Android 9 and above only
support HEIC images if used in addition to a size modification, of
which the usage is explained below. If specified, the image will be at
most maxWidth wide and maxHeight tall. Otherwise the image will be
returned at it's original width and height. The imageQuality argument
modifies the quality of the image, ranging from 0-100 where 100 is the
original/max quality. If imageQuality is null, the image with the
original quality will be returned. Compression is only supported for
certain image types such as JPEG and on Android PNG and WebP, too. If
compression is not supported for the image that is picked, a warning
message will be logged. Use preferredCameraDevice to specify the
camera to use when the source is ImageSource.camera. The
preferredCameraDevice is ignored when source is ImageSource.gallery.
It is also ignored if the chosen camera is not supported on the
device. Defaults to CameraDevice.rear. Note that Android has no
documented parameter for an intent to specify if the front or rear
camera should be opened, this function is not guaranteed to work on an
Android device. In Android, the MainActivity can be destroyed for
various reasons. If that happens, the result will be lost in this
call. You can then call getLostData when your app relaunches to
retrieve the lost data.

MethodAccessException when updating a record in sqlite db

I encounter this exception when I try to updating a record with following statement.
UPDATE GroupTable SET groupId=100 WHERE groupId=101
I tested the statement under SQLite Manager of Firefox plug-in, and it works.
The error message is as following image. It crashed at the os_win_c.cs, the method named getTempname().
Well, I modified the original codes and fixed this bug.
The Path.GetTempPath() doesn't work because the sandbox enviroment. It has no access right.
I fixed by following codes. And it works now.
static int getTempname(int nBuf, StringBuilder zBuf)
{
const string zChars = "abcdefghijklmnopqrstuvwxyz0123456789";
StringBuilder zRandom = new StringBuilder(20);
i64 iRandom = 0;
for (int i = 0; i < 20; i++)
{
sqlite3_randomness(1, ref iRandom);
zRandom.Append((char)zChars[(int)(iRandom % (zChars.Length - 1))]);
}
//! Modified by Toro, 2011,05,10
string tmpDir = "tmpDir";
IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForApplication();
store.CreateDirectory(tmpDir);
//zBuf.Append(Path.GetTempPath() + SQLITE_TEMP_FILE_PREFIX + zRandom.ToString());
zBuf.Append(tmpDir + "/" + SQLITE_TEMP_FILE_PREFIX + zRandom.ToString());
return SQLITE_OK;
}
The above patch will result in an extra folder tmpDir in the isolatedstorage, and the temp files won't be deleted automatically, so it needs to be delete by self. I tried to delete those files in tmpDir in the method of winClose inside os_win_c.cs, and I found it will result in crash when I do VACUUM. Finally, I delete those tmp files when I closed the database. The following is a Dispose method in SQLiteConnection class.
public void Dispose()
{
if (_open)
{
// Original codes for close sqlite database
Sqlite3.sqlite3_close(_db);
_db = null;
_open = false;
// Clear tmp files in tmpDir, added by Toro 2011,05,13
IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForApplication();
string tmpDir = "tmpDir";
if (store.DirectoryExists(tmpDir) == false) return;
string searchPath = System.IO.Path.Combine(tmpDir, "*.*");
foreach (string file in store.GetFileNames(searchPath)) {
store.DeleteFile(System.IO.Path.Combine(tmpDir, file));
}
}
}

Resources