GDrive api: Whenever the file name contains special utf-8 chars -> "Content size exceeds specified contentLength. [...]" [duplicate] - flutter

This question already has answers here:
Flutter: HttpClient post contentLength -- exception
(3 answers)
Closed 2 years ago.
I'm writing a flutter application, in which I want to sync files to Google Drive. However I came across the problem, that whenever I want to upload a file for which I specify the name with a utf-8 char. like e.g. in "Käsekuchen", I get the following exception:
"ClientException (Content size exceeds specified contentLength. 1410067 bytes written while expected 1410066. [--314159265358979323846--])":
When I change the name to something without the special character, it works fine. I guess the solution is quite simple but I couldn't find it.
import 'package:googleapis/drive/v3.dart' as ga;
var client = GoogleHttpClient(await googleSignInAccount.authHeaders);
var drive = ga.DriveApi(client);
ga.File fileToUpload = ga.File();
fileToUpload.parents = ["appDataFolder"];
fileToUpload.name = "Käsekuchen.jpg";
var response = await drive.files.create(
fileToUpload,
uploadMedia:
ga.Media(uploadFile.openRead(), uploadFile.lengthSync()),
);
So do I have to specify a certain encoding somewhere or what am I doing wrong?

Answer:
The special character is endoded as two bytes. You need to account for this in the content length.
Fix:
Change:
var response = await drive.files.create(
fileToUpload,
uploadMedia:
ga.Media(uploadFile.openRead(), uploadFile.lengthSync()),
);
to:
var response = await drive.files.create(
fileToUpload,
uploadMedia:
ga.Media(uploadFile.openRead(), uploadFile.lengthSync() + 1),
);

Related

Multipart Request in Flutter with MultipartFile.fromBytes

I am trying to send a file to an API with Flutter and the http library.
From a file selector, I get the bytes of the file (a pdf) and create a MultipartRequest as follows:
var request = http.MultipartRequest('PUT', Uri.parse('https://xxx/files'))
..fields['grade'] = 'xxx'
..fields['candidate'] = 'xxx'
..files.add(http.MultipartFile.fromBytes(
'document', file.bytes!, contentType: MediaType('application', 'pdf')));
var response = await request.send();
The request failed, it seems that no binary content is added to this request.
In debug mode I can see that the _stream attribute of the file is null (I am sure that file.bytes contains the file) :
content of the debugging
What did I miss?
I found the problem. Without the optional FileName parameter of the fromBytes method the request is not correctly created. You just need to specify a file name for it to work.

Upload Blob Url to Firebase Storage | Flutter

So I already read about the topic but I simply didn't understand the solutions on stack.
I came up with this code:
Im saving a url looking like this:
final String myDataUrl = file.url;
print(myDataUrl);
blob:http://localhost:51947/2952a3b1-db6a-4882-a42a-8e1bf0a0ad73
& then Im trying to add it into Firebase Storage with the putString operator, that I guessed that suited me best while reading the Documentation. I thought that I have a Url and therefore should be able to upload it like this:
FirebaseStorage.instance
.ref()
.child("bla")
.putString(myDataUrl, format: PutStringFormat.dataUrl);
But it doesn't work, it says that:
Error: Invalid argument (uri): Scheme must be 'data': Instance of '_Uri'
So Im guessing that it somehow can't format my url to one that is accepted.
What can I do different to upload a blob successfully to firebase Storage?
-----------------Answer----------------------
Answer in the comment of the answer.
You have to convert your Blob to a Uint8List & upload it like:
Future<Uint8List> fileConverter() async {
final reader = html.FileReader();
reader.readAsArrayBuffer(file!);
await reader.onLoad.first;
return reader.result as Uint8List;
}
and then put it into your Storage:
Future uploadFile(String uid) async {
if (file == null) return;
final path = "nachweise/$uid";
Uint8List fileConverted = await fileConverter();
try {
FirebaseStorage.instance
.ref()
.child(path)
.putData(fileConverted)
.then((bla) => print("sucess"));
} on FirebaseException catch (e) {
return null;
}
}
The Firebase Storage SDKs can upload local data as either a File, an array of bytes, or a base-64 encoded string. The only URLs it accepts are so-called data URLs, which start with data:// and contain the complete data of the object. They cannot upload data directly from URLs that you more commonly see, such as http:// or https://.
You'll need to first download the data from that URL to the local device, and then upload it from there.

Base 64 convert to Image and get the error Invalid character (at character 6)

I am still struggiling with this annoying error. I have base64 string which I want to convert to Image. Here is the simpliest piece of code which is doing exactly what I want (at least, I saw it in different answers and code samples on the SO). I am getting the error:
Invalid character (at character 6)
my code is:
final String encodedStr = 'https://securelink.com/cameratypes/picture/13/true';
Uint8List bytes = base64.decode(encodedStr);
and i want to disply image:
Image.memory(bytes)
Finally, I found the solution, I don't know if it is important and will be useful to anyone who is struggling like me, but I am going to help. So, it would be easy and quick because I have already converted my image to nedeed formart (my image is base64 format), i made a dumb mistake when I was trying to convert it in String again, because it is already a String and I need Uint8List format. Side note: if your api devs said it should take a cookie or any kind of auth, it should be so.
code:
Future<String> _createFileFromString() async {
final response = await http.get(
Uri.parse(
'your link here',
),
headers: {
'cookie':
'your cookie here'
});
final Uint8List bytes = response.bodyBytes;
String dir = (await getApplicationDocumentsDirectory()).path;
String fullPath = '$dir/abc.png';
print("local file full path ${fullPath}");
File file = File(fullPath);
await file.writeAsBytes(List.from(bytes));
print(file.path);
final result = await ImageGallerySaver.saveImage(bytes);
print(result);
return file.path;
}
This code saves your image in straight to the app gallery and do not display on the screen anything
If your URI that contains data after comma as it is defined by RFC-2397. Dart's Uri class is based on RFC-3986, so you can't use it. Split the string by comma and take the last part of it:
String uri = 'data:image/gif;base64,...';
Uint8List _bytes = base64.decode(uri.split(',').last);
REFERENCE: https://stackoverflow.com/a/59015116/12382178

Upload CSV file Flutter Web

I am using the file_picker plugin to pick a CSV file in Flutter Web. Although I am able to pick the file it is converting the file into bytes (Uint8List). Is there any way I can get the original CSV file or if I can convert these bytes to CSV maybe I can get the path of the file?
Code:
void pickCSV() async {
FilePickerResult? result = await FilePicker.platform.pickFiles(type: FileType.custom, allowedExtensions: ['csv']);
if (result != null) {
var fileBytes = result.files.first.bytes;
csfFileName.value = result.files.first.name;
} else {
// User canceled the picker
}
}
I know it's a bit late but you have a couple of choices and maybe it helps others out aswell.
Both of the choices requires server-side processing, so you will need to read on how to do that.
Get the content of the CSV file send it to the server and make a new file on the server with that content. You can use String.fromCharCodes to read the content, in the web, after you select the file.
Convert the Uint8List into a base64 string, using base64Encode function, send it to the server, process it there.
Alternatively, if you use Firebase Storage you can use putData like so:
final metaData = SettableMetadata(contentType: mimeType);
final task = await _storage.ref().child(cloudPath).putData(fileData, metaData);
/// Get the URL
await task.ref.getDownloadURL()
Storing the mimeType ensures proper handling of the file when used after download
cloudPath means the location in FirebaseStorage, such as: storageDirectory/filename.extension
fileData is the Uint8List you provided

Backing up my local database on google drive always creates a different file id

I am creating a to-do list app with flutter, and I want my users to be able to back-up their tasks on google drive.
This is the code I'm using:
// Create the file we want to upload.
ga.File fileToUpload = ga.File();
var file = await _localFile;
fileToUpload.parents = ["appDataFolder"];
fileToUpload.name = path.basename(file.absolute.path);
// Create a new back-up file on google drive.
var response = await drive.files.create(
fileToUpload,
uploadMedia: ga.Media(file.openRead(), file.lengthSync()),
);
// Get the file id.
fileId = response.id;
The problem is that every time I get a different file id and I need to retrieve the file
from google drive with the same file id all the time and not with a different id every time.
I've tried using the update method instead of the create method:
ga.File fileToUpload = ga.File();
var file = await _localFile;
fileToUpload.parents = ["appDataFolder"];
fileToUpload.name = path.basename(file.absolute.path);
drive.files.update(fileToUpload, fileId);
But I get Unhandled Exception: DetailedApiRequestError(status: 403, message: The parents field is not directly writable in update requests. Use the addParents and removeParents parameters instead.)
I also tried to set the file id before using the create method:
fileToUpload.id = fileId;
await drive.files.create(
fileToUpload,
uploadMedia: ga.Media(file.openRead(), file.lengthSync()),
);
But then I get Unhandled Exception: DetailedApiRequestError(status: 400, message: The provided file ID is not usable.)
Or that a file with that id is already exists.
So I've tried to delete the file from google drive and then create it again with the same id:
fileToUpload.id = fileId;
drive.files.get(fileId).then((value) {
if (value != null) {
drive.files.delete(fileId).then((value) {
drive.files.create(
fileToUpload,
uploadMedia: ga.Media(file.openRead(), file.lengthSync()),
);
});
} else {
drive.files.create(
fileToUpload,
uploadMedia: ga.Media(file.openRead(), file.lengthSync()),
);
}
});
But then I also get Unhandled Exception: DetailedApiRequestError(status: 400, message: The provided file ID is not usable.)
Even though I'm using the same file id given by google drive for the original file.
Any solution?
What you need to first check if the file is present in the drive by the name. Since there is not direct API to fetch a file with a name from google drive, you need to make use of the List api and get the files first before checking their name
For that you can use the following query
{
q: `'appDataFolder' in parents and trashed = false`
}
Once you get the response you can check if your file is present by name. If its get its id and trigger an update call for the file.
Note: you do not pass parents key to upload but addParents
For media upload you would use the following url
PATCH https://www.googleapis.com/upload/drive/v3/files/fileId
If you do not find the file, you go by the method of creating a new one
// Create the file we want to upload.
ga.File fileToUpload = ga.File();
var file = await _localFile;
fileToUpload.parents = ["appDataFolder"];
fileToUpload.name = path.basename(file.absolute.path);
// Create a new back-up file on google drive.
var response = await drive.files.create(
fileToUpload,
uploadMedia: ga.Media(file.openRead(), file.lengthSync()),
);
If you want to set the id of the file you have to use a generated id from google. That's why you're getting the provided file ID is not usable. Theres's a class you can use called generateIds which you can use to create id's that can be used with create requests as you've done above. The Google Drive API developer website has a tool where you can make requests to the api. It's called "Try it now"(like postman) For example, create the list of ids(just press execute) here. Pick one of the ids and add it to the request body (in the request body box on the left side , press the plus sign to get the id key and add the generatedId) here . You should get a 200 response with the id you sent with the request. It will also return a specific error message that you can handle if the id already exists (code 409)
you don't have to specify the
fileToUpload.parents = ["appDataFolder"];
while updating a file in google drive