I try to write code for iOS to upload files via Ktor using Stream but I don't know how to connect iOS stream to Android streams or Kotlin cannels.
I use the below code for Android.
private suspend fun uploadFiles(
uriList: List<Uri>,
contentResolver: ContentResolver,
entity: String,
objectId: String
): Map<String, Boolean>? {
val inputStreamMap = mutableMapOf<String, InputStream>()
uriList.forEach {
val inputStream = contentResolver.openInputStream(it) ?: return#forEach
val fileName = contentResolver.query(
it, null, null, null, null
)?.run {
val displayNameIndex = getColumnIndex(OpenableColumns.DISPLAY_NAME)
moveToFirst()
val displayName = getString(displayNameIndex)
close()
displayName
} ?: return#forEach
inputStreamMap[fileName] = inputStream
}
val channels = inputStreamMap.mapValues {
object : OutgoingContent.WriteChannelContent() {
override suspend fun writeTo(channel: ByteWriteChannel) {
it.value.copyTo(channel.toOutputStream(), 1024)
}
override val contentType = ContentType.Application.OctetStream
override val contentLength: Long = it.value.available().toLong()
}
}
val result = useCases.uploadFiles(channels, entity, objectId)
inputStreamMap.values.forEach { it.close() }
return result
}
Related
Here is my code
fun Uri.getOriginalFileName(context: Context): String? {
return context.contentResolver.query(this, null, null, null, null)?.use {
val nameColumnIndex = it.getColumnIndex(OpenableColumns.DISPLAY_NAME)
it.moveToFirst()
it.getString(nameColumnIndex)
}
}
#Composable
fun AppNotice() {
val intent = Intent(Intent.ACTION_GET_CONTENT)
intent.type = "*/*"; ///
intent.addCategory(Intent.CATEGORY_OPENABLE);
val launcher = rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) {
activityResult -> if (activityResult.resultCode == RESULT_OK)
{
Log.d("appDebug", "oky")
var uri = intent.getData()
val name: String? = uri?.getOriginalFileName(Context)
Log.d("seeFile", "$name")
}
else {
Log.d("appDebug", "Denied")
}
Button(onClick={launcher.launch(intent)})
{Text("Button")}
}
Context is a class then how to know what method will i need in this line/context name: String? = uri?.getOriginalFileName(Context)
I went to adroid.dev page but found a huge page and cant figure out what i need since saw only few uri permission related stuffs.
I got crash when try to encode to string a mutable map
internal class AsyncStorageHolder(
var manifest: MutableMap<String, String> = mutableMapOf(),
var hasCheatedStorageDirectory: Boolean = false,
var haveSetup: Boolean = false
)
private val asyncStorageHolder = IsolateState { AsyncStorageHolder() }
private fun writeManifest() {
val json = Json {
encodeDefaults = true
ignoreUnknownKeys = true
}
asyncStorageHolder.access {
val error: CPointer<ObjCObjectVar<NSError?>>? = null
val serialized = json.encodeToString(it.manifest) as NSString
serialized.writeToFile(
createStorageDirectoryPath(getManifestFilePath()),
true,
NSUTF8StringEncoding,
error
)
}
}
Log crash
DefaultAuthStateRepository.setState - setState error: kotlin.native.concurrent.FreezingException: freezing of Continuation # 147 has failed, first blocker is vn.momo.core.modules.storage.async.AsyncStorageModuleImpl.AsyncStorageHolder#2bc5be8
Im trying to get the Messages from a Youtube Livestream, works, but i dont get new Messages. The NextPageToken is included.
Sometimes i get new messages, but it takes arround 5-10min.
Youtube Chat Sending works also fine.
Any Idea?
This is from the Docs: https://developers.google.com/youtube/v3/live/docs/liveChatMessages/list
private async Task GetMessagesAsync(string liveChatId, string nextPageToken, long? pollingIntervalMillis)
{
liveChatId = "EiEKGFVDVUQ3WGNXTk92SlpvaHFMM3dZTi1uZxIFL2xpdmU";
if (!updatingChat)
{
if (!string.IsNullOrEmpty(liveChatId))
{
newMessages = true;
var chatMessages = youTubeService.LiveChatMessages.List(liveChatId, "id,snippet,authorDetails");
var chatResponse = await chatMessages.ExecuteAsync();
PageInfo pageInfo = chatResponse.PageInfo;
newMessages = false;
if (pageInfo.TotalResults.HasValue)
{
if (!prevCount.Equals(pageInfo.TotalResults.Value))
{
prevCount = pageInfo.TotalResults.Value;
newMessages = true;
}
}
if (newMessages)
{
Messages = new List<YouTubeMessage>();
foreach (var chatMessage in chatResponse.Items)
{
string messageId = chatMessage.Id;
string displayName = chatMessage.AuthorDetails.DisplayName;
string displayMessage = chatMessage.Snippet.DisplayMessage;
string NextPagetoken = chatResponse.NextPageToken;
YouTubeMessage message = new YouTubeMessage(messageId, displayName, displayMessage);
if (!Messages.Contains(message))
{
Messages.Add(message);
string output = "[" + displayName + "]: " + displayMessage;
Console.WriteLine(time + output);
}
}
}
await GetMessagesAsync(liveChatId, chatResponse.NextPageToken, chatResponse.PollingIntervalMillis);
}
}
updatingChat = false;
await Task.Delay(100);
}
public async Task YouTubeChatSend(string message)
{
try
{
LiveChatMessage liveMessage = new LiveChatMessage();
liveMessage.Snippet = new LiveChatMessageSnippet()
{
LiveChatId = "EiEKGFVDVUQ3WGNXTk92SlpvaHFMM3dZTi1uZxIFL2xpdmU",
Type = "textMessageEvent",
TextMessageDetails = new LiveChatTextMessageDetails() { MessageText = message }
};
var insert = this.youTubeService.LiveChatMessages.Insert(liveMessage, "snippet");
var response = await insert.ExecuteAsync();
if (response != null)
{
}
}
catch
{
Console.WriteLine("Failed to chat send");
}
}
From the API page, I gather there's no function for what I'm trying to do. I want to read text from a file storing it as a list of strings, manipulate the text, and save the file. The first part is easy using the function:
abstract List<String> readAsLinesSync([Encoding encoding = Encoding.UTF_8])
However, there is no function that let's me write the contents of the list directly to the file e.g.
abstract void writeAsLinesSync(List<String> contents, [Encoding encoding = Encoding.UTF_8, FileMode mode = FileMode.WRITE])
Instead, I've been using:
abstract void writeAsStringSync(String contents, [Encoding encoding = Encoding.UTF_8, FileMode mode = FileMode.WRITE])
by reducing the list to a single string. I'm sure I could also use a for loop and feed to a stream line by line. I was wondering two things:
Is there a way to just hand the file a list of strings for writing?
Why is there a readAsLinesSync but no writeAsLinesSync? Is this an oversight or a design decision?
Thanks
I just made my own export class that handles writes to a file or for sending the data to a websocket.
Usage:
exportToWeb(mapOrList, 'local', 8080);
exportToFile(mapOrList, 'local/data/data.txt');
Class:
//Save data to a file.
void exportToFile(var data, String filename) =>
new _Export(data).toFile(filename);
//Send data to a websocket.
void exportToWeb(var data, String host, int port) =>
new _Export(data).toWeb(host, port);
class _Export {
HashMap mapData;
List listData;
bool isMap = false;
bool isComplex = false;
_Export(var data) {
// Check is input is List of Map data structure.
if (data.runtimeType == HashMap) {
isMap = true;
mapData = data;
} else if (data.runtimeType == List) {
listData = data;
if (data.every((element) => element is Complex)) {
isComplex = true;
}
} else {
throw new ArgumentError("input data is not valid.");
}
}
// Save to a file using an IOSink. Handles Map, List and List<Complex>.
void toFile(String filename) {
List<String> tokens = filename.split(new RegExp(r'\.(?=[^.]+$)'));
if (tokens.length == 1) tokens.add('txt');
if (isMap) {
mapData.forEach((k, v) {
File fileHandle = new File('${tokens[0]}_k$k.${tokens[1]}');
IOSink dataFile = fileHandle.openWrite();
for (var i = 0; i < mapData[k].length; i++) {
dataFile.write('${mapData[k][i].real}\t'
'${mapData[k][i].imag}\n');
}
dataFile.close();
});
} else {
File fileHandle = new File('${tokens[0]}_data.${tokens[1]}');
IOSink dataFile = fileHandle.openWrite();
if (isComplex) {
for (var i = 0; i < listData.length; i++) {
listData[i] = listData[i].cround2;
dataFile.write("${listData[i].real}\t${listData[i].imag}\n");
}
} else {
for (var i = 0; i < listData.length; i++) {
dataFile.write('${listData[i]}\n');
}
}
dataFile.close();
}
}
// Set up a websocket to send data to a client.
void toWeb(String host, int port) {
//connect with ws://localhost:8080/ws
//for echo - http://www.websocket.org/echo.html
if (host == 'local') host = '127.0.0.1';
HttpServer.bind(host, port).then((server) {
server.transform(new WebSocketTransformer()).listen((WebSocket webSocket) {
webSocket.listen((message) {
var msg = json.parse(message);
print("Received the following message: \n"
"${msg["request"]}\n${msg["date"]}");
if (isMap) {
webSocket.send(json.stringify(mapData));
} else {
if (isComplex) {
List real = new List(listData.length);
List imag = new List(listData.length);
for (var i = 0; i < listData.length; i++) {
listData[i] = listData[i].cround2;
real[i] = listData[i].real;
imag[i] = listData[i].imag;
}
webSocket.send(json.stringify({"real": real, "imag": imag}));
} else {
webSocket.send(json.stringify({"real": listData, "imag": null}));
}
}
},
onDone: () {
print('Connection closed by client: Status - ${webSocket.closeCode}'
' : Reason - ${webSocket.closeReason}');
server.close();
});
});
});
}
}
I asked Mads Agers about this. He works on the io module. He said that he decided not to add writeAsLines because he didn't find it useful. For one it is trivial to write the for loop and the other thing is that you have to parameterize it which the kind of line separator that you want to use. He said he can add it if there is a strong feeling that it would be valuable. He didn't immediately see a lot of value in it.
In my firefox extension I'm creating a xul:browser element. I want to have an observer that intercepts any url changes within the embedded browser and opens the url in a new browser tab (in the main browser). I'd also like new windows spawned by the xul:browser window to open in a tab instead of a new browser window.
I've created an observer which works, but I don't yet know how to apply that observer only to the xul:browser element.
function myFunction(){
var container = jQuery("#container")[0];
var new_browser_element = document.createElement('browser');
container.appendChild(new_browser_element);
var observerService = Components.classes["#mozilla.org/observer-service;1"].getService(Components.interfaces.nsIObserverService);
observerService.addObserver(myObserver, "http-on-modify-request", false);
}
var myObserver = {
observe: function(aSubject, aTopic, aData){
if (aTopic != 'http-on-modify-request'){
aSubject.QueryInterface(Components.interfaces.nsIHttpChannel);
// alert(aSubject.URI.spec);
// Now open url in new tab
}
},
QueryInterface: function(iid){
if (!iid.equals(Components.interfaces.nsISupports) &&
!iid.equals(Components.interfaces.nsIObserver))
throw Components.results.NS_ERROR_NO_INTERFACE;
return this;
}
};
You could try:
var myObserver = {
observe: function(aSubject, aTopic, aData){
if (aTopic == 'http-on-modify-request')
{
aSubject.QueryInterface(Components.interfaces.nsIHttpChannel);
var url = aSubject.URI.spec;
var postData ;
if (aSubject.requestMethod.toLowerCase() == "post")
{
var postText = this.readPostTextFromRequest(request);
if (postText)
{
var dataString = parseQuery(postText);
postData = postDataFromString(dataString);
}
}
var oHttp = aSubject.QueryInterface(Components.interfaces.nsIHttpChannel);
var interfaceRequestor = oHttp.notificationCallbacks.QueryInterface(Components.interfaces.nsIInterfaceRequestor);
var DOMWindow = interfaceRequestor.getInterface(Components.interfaces.nsIDOMWindow);
//check if it is one of your mini browser windows
if (jQuery(DOMWindow).hasClass('mini_browser'))
{
openInTab(url, postData);
var request = aSubject.QueryInterface(Components.interfaces.nsIRequest);
request.cancel(Components.results.NS_BINDING_ABORTED);
}
}
},
QueryInterface: function(iid){
if (!iid.equals(Components.interfaces.nsISupports) &&
!iid.equals(Components.interfaces.nsIObserver))
throw Components.results.NS_ERROR_NO_INTERFACE;
return this;
},
readPostTextFromRequest : function(request) {
var is = request.QueryInterface(Components.interfaces.nsIUploadChannel).uploadStream;
if (is)
{
var ss = is.QueryInterface(Components.interfaces.nsISeekableStream);
var prevOffset;
if (ss)
{
prevOffset = ss.tell();
ss.seek(Components.interfaces.nsISeekableStream.NS_SEEK_SET, 0);
}
// Read data from the stream..
var charset = "UTF-8";
var text = this.readFromStream(is, charset, true);
// Seek locks the file so, seek to the beginning only if necko hasn't read it yet,
// since necko doesn't seek to 0 before reading (at lest not till 459384 is fixed).
if (ss && prevOffset == 0)
ss.seek(Components.interfaces.nsISeekableStream.NS_SEEK_SET, 0);
return text;
}
else {
dump("Failed to Query Interface for upload stream.\n");
}
}
return null;
},
readFromStream : function(stream, charset, noClose)
{
var sis = Components.classes["#mozilla.org/binaryinputstream;1"]
.getService(Components.interfaces.nsIBinaryInputStream);
sis.setInputStream(stream);
var segments = [];
for (var count = stream.available(); count; count = stream.available())
segments.push(sis.readBytes(count));
if (!noClose)
sis.close();
var text = segments.join("");
return text;
}
};
function openInTab(url, postData)
{
var wm = Components.classes["#mozilla.org/appshell/window-mediator;1"]
.getService(Components.interfaces.nsIWindowMediator);
var recentWindow = wm.getMostRecentWindow("navigator:browser");
if (recentWindow)
{
// Use an existing browser window, open tab and "select" it
recentWindow.gBrowser.selectedTab = recentWindow.gBrowser.addTab(url, null, null, postData);
}
}
function parseQuery() {
var qry = this;
var rex = /[?&]?([^=]+)(?:=([^&#]*))?/g;
var qmatch, key;
var paramValues = {};
// parse querystring storing key/values in the ParamValues associative array
while (qmatch = rex.exec(qry)) {
key = decodeURIComponent(qmatch[1]);// get decoded key
val = decodeURIComponent(qmatch[2]);// get decoded value
paramValues[key] = val;
}
return paramValues;
}
function postDataFromString(dataString)
{
// POST method requests must wrap the encoded text in a MIME
// stream
var stringStream = Components.classes["#mozilla.org/io/string-input-stream;1"]
.createInstance(Components.interfaces.nsIStringInputStream);
if ("data" in stringStream) // Gecko 1.9 or newer
stringStream.data = dataString;
else // 1.8 or older
stringStream.setData(dataString, dataString.length);
var postData = Components.classes["#mozilla.org/network/mime-input-stream;1"].
createInstance(Components.interfaces.nsIMIMEInputStream);
postData.addHeader("Content-Type", "application/x-www-form-urlencoded");
postData.addContentLength = true;
postData.setData(stringStream);
return postData;
}
I'll update this to fill in the blanks in a bit.
edit: see http://forums.mozillazine.org/viewtopic.php?p=2772951#p2772951 for how to get the source window of a request.
Request cancellation code from http://zenit.senecac.on.ca/wiki/index.php/Support_For_OpenID.
see http://mxr.mozilla.org/mozilla-central/source/netwerk/base/public/nsIRequest.idl for details on nsIRequest.
See http://forums.mozillazine.org/viewtopic.php?p=2404533#p2404533 and https://developer.mozilla.org/en/XUL/Method/addTab for the definition of addTab.
parseQuery comes from http://blog.strictly-software.com/2008/10/using-javascript-to-parse-querystring.html.
See https://developer.mozilla.org/en/Code_snippets/Post_data_to_window#Preprocessing_POST_data for how to process post data in a form suitable for addTab.
ReadPostFromText and ReadTextFromStream both come from firebug (though slightly modified)