Upload Multiple Files With Ktor - kotlin

I am Using ktor with kmm to upload a list of images to server
But there is no available guide in the docs to make me upload list of files
i am converting my files to byte array and uploading them
I tried to upload it this way
mainClient.post {
setBody(
MultiPartFormDataContent(
formData {
append("attachments[]", listOf(toBytes(kmmFile),toBytes(kmmFile)) )
}
)
)
}
but got connection refused

You can iterate through all byte arrays in a collection and call the append method for each of them. Here is an example:
val images: List<ByteArray> // assigned externally
val response = client.post("https://httpbin.org/post") {
setBody(MultiPartFormDataContent(
formData {
for (bytes in images) {
append("attachments[]", bytes)
}
}
))
}

I use below code for uploading single file and run forEach when call this method(for each n append doesn't work for me). I think your serve must be supported upload multiple file at same time.
override suspend fun upload(
uploadFiles: Map<String, File>,
texts: Map<String, String>
): ResultWrapper<ResponseData<List<UploadFileDto>>> {
return {
httpClient.submitForm {
url(BASE_URL + "api/v1/static/upload-file")
method = HttpMethod.Post
setBody(MultiPartFormDataContent(
formData {
headers {
append(
"document",
uploadFiles.entries.first().value.readBytes(),
Headers.build {
append(
HttpHeaders.ContentDisposition,
"filename=${uploadFiles.entries.first().value.name}"
)
})
}
}
))
}.body()
}

Related

Set programmatically jsonValidation for dynamic mapping

I am creating a new vscode extension, and I need to extend the standard usage of the jsonValidation system already present in vscode.
Note : I am talking about the system defined in package.json :
"contributes" : {
"languages": [
{
"id" : "yml",
"filenamePatterns": ["module.service"]
},
{
"id" : "json",
"filenamePatterns": ["module.*"]
}
],
"jsonValidation": [
{
"fileMatch": "module.test",
"url": "./resources/test.schema"
}
]
}
Now, I need to create a dynamic mapping, where the json fields filematch/url are defined from some internal rules (like version and other internal stuff). The standard usage is static : one fileMatch -> one schema.
I want for example to read the version from the json file to validate, and set the schema after that :
{
"version" : "1.1"
}
validation schema must be test-schema.1.1 instead of test-schema.1.0
note : The question is only about the modification of the configuration provided by package.json from the extensions.ts
Thanks for the support
** EDIT since the previous solution was not working in all cases
There is one solution to modify the package.json at the activating of the function.
export function activate(context: vscode.ExtensionContext) {
const myPlugin = vscode.extensions.getExtension("your.plugin.id");
if (!myPlugin)
{
throw new Error("Composer plugin is not found...")
}
// Get the current workspace path to found the schema later.
const folderPath = vscode.workspace.workspaceFolders;
if (!folderPath)
{
return;
}
const baseUri : vscode.Uri = folderPath[0].uri;
let packageJSON = myPlugin.packageJSON;
if (packageJSON && packageJSON.contributes && packageJSON.contributes.jsonValidation)
{
let jsonValidation = packageJSON.contributes.jsonValidation;
const schemaUri : vscode.Uri = vscode.Uri.joinPath(baseUri, "/schema/value-0.3.0.json-schema");
const schema = new JsonSchemaMatch("value.ospp", schemaUri)
jsonValidation.push(schema);
}
}
And the json schema class
class JsonSchemaMatch
{
fileMatch: string;
url : string;
constructor(fileMatch : string, url: vscode.Uri)
{
this.fileMatch = fileMatch;
this.url = url.path;
}
}
Another important information is the loading of the element of contributes is not reread after modification, for example
class Language
{
id: string;
filenamePatterns : string[];
constructor(id : string, filenamePatterns: string[])
{
this.id = id;
this.filenamePatterns = filenamePatterns;
}
}
if (packageJSON && packageJSON.contributes && packageJSON.contributes.languages)
{
let languages : Language[] = packageJSON.contributes.languages;
for (let language of languages) {
if (language.id == "json") {
language.filenamePatterns.push("test.my-json-type")
}
}
}
This change has no effect, since the loading of file association is already done (I have not dig for the reason, but I think this is the case)
In this case, creating a settings.json in the workspace directory can do the job:
settings.json
{
"files.associations": {
"target.snmp": "json",
"stack.cfg": "json"
}
}
Be aware that the settings.json can be created by the user with legitimate reason, so don't override it, just fill it.

How to send Http Form with parameters by ktor-client

I've found nearly everywhere in ktor-client documentation and examples they use empty formData to show how the client works
formParameters: Parameters = Parameters.Empty
So what's the kotlin/ktor way to fill it with parameters?
Ktor uses this approach to fill the parameters:
client.submitForm<HttpResponse>(
url = "https://foo.com/login",
formParameters = Parameters.build {
append("_username", username)
append("_password", password)
})
Alternatively, you can also simply pass the form data using formData, e.g.:
client.post<HttpResponse>("https://example.com/login") {
formData {
parameter("username", username)
parameter("password", password)
}
}
I've found at least three ways to post www-urlencoded form:
return httpClient.submitForm("url") {
parameter("key", "value")
}
return httpClient.post("url") {
FormDataContent(Parameters.build {
parameter("key", "value")
})
}
return httpClient.post("url") {
formData {
parameter("key", "value")
}
}
append() method is marked as internal and not working with ktor 1.6.4
client.get{
url("https://api.yelp.com/v3/businesses/search")
contentType(ContentType.Application.Json)
headers {
append(HttpHeaders.Authorization, "Bearer $API_KEY")
}
formData {
parameter("location","NYC") //use this wey
}
}

CKEditor 5 Image Upload Issues

I'm using ckeditor5 into my project. I have to support image upload so I have search and followed this stackoverflow article.
I have created an uploadAdapter which is:
class UploadAdapter {
constructor( loader, url, t ) {
this.loader = loader;
this.url = url;
this.t = t;
}
upload() {
return new Promise( ( resolve, reject ) => {
this._initRequest();
this._initListeners( resolve, reject );
this._sendRequest();
} );
}
abort() {
if ( this.xhr ) {
this.xhr.abort();
}
}
_initRequest() {
const xhr = this.xhr = new XMLHttpRequest();
xhr.open( 'POST', this.url, true );
xhr.responseType = 'json';
}
_initListeners( resolve, reject ) {
const xhr = this.xhr;
const loader = this.loader;
const t = this.t;
const genericError = t( 'Cannot upload file:' ) + ` ${ loader.file.name }.`;
xhr.addEventListener( 'error', () => reject( genericError ) );
xhr.addEventListener( 'abort', () => reject() );
xhr.addEventListener( 'load', () => {
const response = xhr.response;
if ( !response || !response.uploaded ) {
return reject( response && response.error && response.error.message ? response.error.message : genericError );
}
resolve( {
default: response.url
} );
} );
if ( xhr.upload ) {
xhr.upload.addEventListener( 'progress', evt => {
if ( evt.lengthComputable ) {
loader.uploadTotal = evt.total;
loader.uploaded = evt.loaded;
}
} );
}
}
_sendRequest() {
const data = new FormData();
data.append( 'upload', this.loader.file );
this.xhr.send( data );
}
}
import Plugin from '#ckeditor/ckeditor5-core/src/plugin';
import FileRepository from '#ckeditor/ckeditor5-upload/src/filerepository';
export default class GappUploadAdapter extends Plugin {
static get requires() {
return [ FileRepository ];
}
static get pluginName() {
return 'GappUploadAdapter';
}
init() {
const url = this.editor.config.get( 'gapp.uploadUrl' );
if ( !url ) {
return;
}
this.editor.plugins.get( FileRepository ).createUploadAdapter = loader => new UploadAdapter( loader, url, this.editor.t );
}
}
Now this is explained. I have 2 issues.
Once uploaded ( my upload on server is working fine and returning a valid url in format {default: url}, why is my image content inserted as data-uri and not in url as for easy image demo here. I want my image to be url like.
I would like to listen for a kind of success upload image ( with image id retrieved from upload server call ) to insert some content in my page. How to proceed ?
Thanks for help.
PS: I'm building ckeditor with command 'npm run build' from git repo cloned from https://github.com/ckeditor/ckeditor5-build-classic
EDIT:
Thanks to accepted response, I saw that I was wrong in returned data. I was not returning any URL in my uploader front end which was causing editor image to stay in img-data way. Once valid URL was returned, it was parsed automatically and my editor image was containing a valid url.
If the data-uri is still used after successful upload I would assume that server response was not processed correctly and the received url could not be retrieved. I have tested adapter code you provided and it works fine (with CKFinder on server side). I would check how the upload server response looks and if it can be correctly parsed.
When using CKFinder you will see:
and a parsed JSON response:
You could check if response is processed correctly in your adapter in:
xhr.addEventListener( 'load', () => {
const response = xhr.response;
...
}
Listening to successful image upload may be tricky as there is no event directly related to it. Depending on what exactly you are trying to achieve you may try to extend you custom loader so when successful response is received (and resolve() called) you may execute some code. However, in this state the image element is still not updated (in model, view and DOM) with new URL and UploadAdapter lacks a direct access to editor instance so it may be hard to do anything complex.
Better way may be to listen to model changes, the similar way it is done in ImageUploadEditing plugin (see code here) checking the image uploadStatus attribute change:
editor.model.document.on( 'change', () => {
const changes = doc.differ.getChanges();
for ( const entry of changes ) {
const uploaded = entry.type === 'attribute' && entry.attributeNewValue === 'complete' && entry.attributeOldValue === 'uploading';
console.log( entry );
}
} );
If it changes from uploading to complete it means the images was successfully uploaded:
You may also take a look at another answer, which shows how to hook into FileRepository API to track entire upload process - https://github.com/ckeditor/ckeditor5-image/issues/243#issuecomment-442393578.

SWXMLHash: how to build struct for very deep XML?

I am now working on a complex XML parsing.
Here is the link: https://www.reddit.com/hot/.rss
I use Alamofire to fetch data:
protocol APIUsable {
}
struct API: APIUsable {
static func fetchRedditFeedListData() -> Observable<[Entry]>{
let URL = "https://www.reddit.com/hot/.rss"
return Observable.create { observer in
Alamofire.request(URL).response { response in
guard let data = response.data else {
return
}
do {
let xml = SWXMLHash.parse(data)
let entries: [Entry] = try xml["feed"]["entry"].value()
observer.onNext(entries)
} catch let error as NSError {
print(error.localizedDescription)
}
observer.onCompleted()
}
return Disposables.create()
}
}
}
The following is the struct I build for parsing. And It works well.
struct Entry: XMLIndexerDeserializable {
let title: String
let updated: String
let category: String
let content: String
static func deserialize(_ node: XMLIndexer) throws -> Entry {
return try Entry(
title: node["title"].value(),
updated: node["updated"].value(),
category: node["category"].value(ofAttribute: "term"),
content: node["content"].value()
)
}
}
But I also want to get the image string belong to content, img, src
I have found the solution by myself.
As we can see that there is a HTML in a XML.
So, I use SWXMLHash again to parse content
let xml = SWXMLHash.parse(/*put the content String here*/)
let imageString = xml ["table"]["tr"]["td"][0]["a"]["img"].element?.attribute(by: "src")?.text
then we can get the value of src as string for future use

PDF Blob is not showing content, Angular 2

I have problem very similar to this PDF Blob - Pop up window not showing content, but I am using Angular 2. The response on question was to set responseType to arrayBuffer, but it not works in Angular 2, the error is the reponseType does not exist in type RequestOptionsArgs. I also tried to extend it by BrowserXhr, but still not work (https://github.com/angular/http/issues/83).
My code is:
createPDF(customerServiceId: string) {
console.log("Sending GET on " + this.getPDFUrl + "/" + customerServiceId);
this._http.get(this.getPDFUrl + '/' + customerServiceId).subscribe(
(data) => {
this.handleResponse(data);
});
}
And the handleResponse method:
handleResponse(data: any) {
console.log("[Receipt service] GET PDF byte array " + JSON.stringify(data));
var file = new Blob([data._body], { type: 'application/pdf' });
var fileURL = URL.createObjectURL(file);
window.open(fileURL);
}
I also tried to saveAs method from FileSaver.js, but it is the same problem, pdf opens, but the content is not displayed. Thanks
I had a lot of problems with downloading and showing content of PDF, I probably wasted a day or two to fix it, so I'll post working example of how to successfully download PDF or open it in new tab:
myService.ts
downloadPDF(): any {
return this._http.get(url, { responseType: ResponseContentType.Blob }).map(
(res) => {
return new Blob([res.blob()], { type: 'application/pdf' })
}
}
myComponent.ts
this.myService.downloadPDF().subscribe(
(res) => {
saveAs(res, "myPDF.pdf"); //if you want to save it - you need file-saver for this : https://www.npmjs.com/package/file-saver
var fileURL = URL.createObjectURL(res);
window.open(fileURL); / if you want to open it in new tab
}
);
NOTE
It is also worth mentioning that if you are extending Http class to add headers to all your requests or something like that, it can also create problems for downloading PDF because you will override RequestOptions, which is where we add responseType: ResponseContentType.Blob and this will get you The request body isn't either a blob or an array buffer error.
ANGULAR 5
I had the same problem which I lost few days on that.
Here my answer may help others, which helped to render pdf.
For me even though if i mention as responseType : 'arraybuffer', it was unable to take it.
For that you need to mention as responseType : 'arraybuffer' as 'json'.(Reference)
Working code
downloadPDF(): any {
return this._http.get(url, { responseType: 'blob' as 'json' }).subscribe((res) => {
var file = new Blob([res], { type: 'application/pdf' });
var fileURL = URL.createObjectURL(file);
window.open(fileURL);
}
}
Referred from the below link
https://github.com/angular/angular/issues/18586
Amit,
You can rename the filename by adding a variable to the end of the string
so saveAs(res, "myPDF.pdf");
Becomes
saveAs(res, "myPDF_"+someVariable+".pdf");
where someVariable might be a counter or my personal favorite a date time string.
This worked for me
var req = this.getPreviewPDFRequest(fd);
this.postData(environment.previewPDFRFR, req).then(res => {
res.blob().then(blob => {
console.clear();
console.log(req);
console.log(JSON.stringify(req));
const fileURL = URL.createObjectURL(blob);
window.open(fileURL, '', 'height=650,width=840');
})
});
Server side (Java/Jetty) : REST service that returns a File Response
The File Response itself will automatically be parsed into a pdf blob file by Jetty (because of the annotation #Produces("application/pdf") ), in other to be send to and read by the web client
#GET
#Path("/download-pdf/{id}")
#Produces("application/pdf")
public Response downloadPDF(#ApiParam(value = "Id of the report record")
#PathParam("id") Long id) {
ResponseBuilder response = null;
try {
PDFReportService service = new PDFReportService();
File reportFile = service.getPDFReportFile(id);
response = Response.ok((Object) reportFile);
response.header("Content-Disposition","attachment; filename="+reportFile.getName());
return response.build();
} catch (DomainException e) {
response = Response.serverError().entity("server.error");
}
return response.build();
}
Client side code (angular 2) : grab the blob and print it in a new browser tab
The key is to insure that you read the request reponse as a blob (as the server returned a blob; in my case)
Now, I tried so hard but I finally figured out that Angular 2 has not implemented any function to handle blob responses (neither res['_body'], nor res.blob() worked for me)
So I found no other workaround than using JQuery ajax to perform that file blob request, like following:
public downloadPDFFile() {
let fileURL = serverURL+"/download-pdf/"+id;
let userToken: string = your_token;
showWaitingLoader();
$.ajax({
url: fileURL,
cache: false,
headers: {
"Content-Type": "application/json",
"Authorization": "Basic " + userToken
},
xhrFields: {
responseType: 'blob' //Most important : configure the response type as a blob
},
success: function(blobFile) {
const url = window.URL.createObjectURL(blobFile);
window.open(url);
stopWaitingLoader();
},
error: function(e){
console.log("DOWNLOAD ERROR :", e);
}
});
}