How to modify http response in Firefox extension - firefox

Hey i have been able to write an nsIStreamListener listener to listen on responses and get the response text following tutorials at nsitraceablechannel-intercept-http-traffic .But i am unable to modify the response sent to browser.Actually if i return the reponse and sent back to chain it reflects in firebug but not in browser.
What i am guessing is we will have to replace default listener rather than listening in the chain.I cant get any docs anywhere which explains how to do this.
Could anyone give me some insight into this.This is mainly for education purposes.
Thanks in advance
Edit : As of now i have arrived at a little solutions i am able to do this
var old;
function TracingListener() {}
TracingListener.prototype = {
originalListener: null,
receivedData: null, //will be an array for incoming data.
//For the listener this is step 1.
onStartRequest: function (request, context) {
this.receivedData = []; //initialize the array
//Pass on the onStartRequest call to the next listener in the chain -- VERY IMPORTANT
//old.onStartRequest(request, context);
},
//This is step 2. This gets called every time additional data is available
onDataAvailable: function (request, context, inputStream, offset, count) {
var binaryInputStream = CCIN("#mozilla.org/binaryinputstream;1",
"nsIBinaryInputStream");
binaryInputStream.setInputStream(inputStream);
var storageStream = CCIN("#mozilla.org/storagestream;1",
"nsIStorageStream");
//8192 is the segment size in bytes, count is the maximum size of the stream in bytes
storageStream.init(8192, count, null);
var binaryOutputStream = CCIN("#mozilla.org/binaryoutputstream;1",
"nsIBinaryOutputStream");
binaryOutputStream.setOutputStream(storageStream.getOutputStream(0));
// Copy received data as they come.
var data = binaryInputStream.readBytes(count);
this.receivedData.push(data);
binaryOutputStream.writeBytes(data, count);
//Pass it on down the chain
//old.onDataAvailable(request, context,storageStream.newInputStream(0), offset, count);
},
onStopRequest: function (request, context, statusCode) {
try {
//QueryInterface into HttpChannel to access originalURI and requestMethod properties
request.QueryInterface(Ci.nsIHttpChannel);
//Combine the response into a single string
var responseSource = this.receivedData.join('');
//edit data as needed
responseSource = "test";
console.log(responseSource);
} catch (e) {
//standard function to dump a formatted version of the error to console
dumpError(e);
}
var stream = Cc["#mozilla.org/io/string-input-stream;1"]
.createInstance(Ci.nsIStringInputStream);
stream.setData(responseSource, -1);
//Pass it to the original listener
//old.originalListener=null;
old.onStartRequest(channel, context);
old.onDataAvailable(channel, context, stream, 0, stream.available());
old.onStopRequest(channel, context, statusCode);
},
QueryInterface: function (aIID) {
if (aIID.equals(Ci.nsIStreamListener) ||
aIID.equals(Ci.nsISupports)) {
return this;
}
throw components.results.NS_NOINTERFACE;
},
readPostTextFromRequest: function (request, context) {
try {
var is = request.QueryInterface(Ci.nsIUploadChannel).uploadStream;
if (is) {
var ss = is.QueryInterface(Ci.nsISeekableStream);
var prevOffset;
if (ss) {
prevOffset = ss.tell();
ss.seek(Ci.nsISeekableStream.NS_SEEK_SET, 0);
}
// Read data from the stream..
var charset = "UTF-8";
var text = this.readFromStream(is, charset, true);
if (ss && prevOffset == 0)
ss.seek(Ci.nsISeekableStream.NS_SEEK_SET, 0);
return text;
} else {
dump("Failed to Query Interface for upload stream.\n");
}
} catch (exc) {
dumpError(exc);
}
return null;
},
readFromStream: function (stream, charset, noClose) {
var sis = CCSV("#mozilla.org/binaryinputstream;1",
"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;
}
}
httpRequestObserver = {
observe: function (request, aTopic, aData) {
if (typeof Cc == "undefined") {
var Cc = components.classes;
}
if (typeof Ci == "undefined") {
var Ci = components.interfaces;
}
if (aTopic == "http-on-examine-response") {
request.QueryInterface(Ci.nsIHttpChannel);
console.log(request.statusCode);
var newListener = new TracingListener();
request.QueryInterface(Ci.nsITraceableChannel);
channel = request;
//newListener.originalListener
//add new listener as default and save old one
old = request.setNewListener(newListener);
old.originalListener = null;
var threadManager = Cc["#mozilla.org/thread-manager;1"]
.getService(Ci.nsIThreadManager);
threadManager.currentThread.dispatch(newListener, Ci.nsIEventTarget.DISPATCH_NORMAL);
}
},
QueryInterface: function (aIID) {
if (typeof Cc == "undefined") {
var Cc = components.classes;
}
if (typeof Ci == "undefined") {
var Ci = components.interfaces;
}
if (aIID.equals(Ci.nsIObserver) ||
aIID.equals(Ci.nsISupports)) {
return this;
}
throw components.results.NS_NOINTERFACE;
},
};
var observerService = Cc["#mozilla.org/observer-service;1"]
.getService(Ci.nsIObserverService);
observerService.addObserver(httpRequestObserver,
"http-on-examine-response", false);

This example works for me on Firefox 34 (current nightly): https://github.com/Noitidart/demo-nsITraceableChannel
I downloaded the xpi, edited bootstrap.js to modify the stream:
132 // Copy received data as they come.
133 var data = binaryInputStream.readBytes(count);
134 data = data.replace(/GitHub/g, "TEST");
135 this.receivedData.push(data);
installed the XPI then reloaded the github page. It read "TEST" in the footer.
The version of code you posted doesn't actually pass the results back to the old listener, so that's the first thing that ought to be changed.
It also may have interacted with Firebug or another extension badly. It's a good idea to try reproducing the problem in a clean profile (with only your extension installed).

Related

Streaming audio from FFMPEG to browser via WebSocket and WebAudioApi

My project has 2 parts:
a web interface that the user accesses
and a standalone app installed on the computer, that acts as a websocket server.
From the web UI, the user has to hear his computer's microphone.
At this moment, I have a working solution that listens to microphone and sends the raw PCM audio chunks back to web-UI which is able to play them. But some serious lag gets added in time, despite it all runs on the same computer, so there's no internet latency/etc. That is why I am testing FFMPEG now.
So, here's the FFMPEG command for streaming microphone data:
ffmpeg.exe -re -f dshow -i audio="Microphone (HD Pro Webcam C920)" -ar 44100 -ac 1 -f f32le pipe:1
Data gets sent successfully via websocket, but playing it using WebAudioApi is not working, i mean i don't hear anything.
Can anyone point me to what am I doing wrong?
Here's the web javascript:
let ipOfAudioServer = 'localhost';
let wsClient = null;
var audioCtx = null;
var subcounter = 0;
var audiobuffer = [];
var source = null;
// must match the values in the audio-server. Thought despite audio-server could send 2channels.. we resume to only one, to save bandwidth
var sampleRate = 44100;
var channels = 1;
var microphone = 'Microphone (HD Pro Webcam C920)';
// this method reads current position from the audiobuffer and plays the audio
// the method will re-call itself, in order to play the next item in queue
this.play = function(soundName) {
var ffs = audiobuffer[subcounter];
if (ffs) {
var frameCount = ffs.byteLength;
console.log(frameCount, audiobuffer.length);
var myAudioBuffer = audioCtx.createBuffer(channels, frameCount, sampleRate);
myAudioBuffer.getChannelData(0).set(ffs)
if (myAudioBuffer != null)
{
subcounter += 1;
source = audioCtx.createBufferSource();
source.buffer = myAudioBuffer;
source.connect(audioCtx.destination);
source.onended = () => { console.log("finished, continuing to seek buffer!"); play(soundName); }
source.start();
}
}
// just in case the counter got to be bigger than the actual amount of items in the list, set it back to last one
if (subcounter > audiobuffer.length)
subcounter = audiobuffer.length;
};
// the method to initialize WS client
this.initWebsocketClient = function ()
{
if (wsClient == null)
{
wsClient = new WebSocket(`ws://${ipOfAudioServer}:23233`, "protocol");
wsClient.binaryType = "arraybuffer";
wsClient.onmessage = function (event)
{
if (typeof event.data === 'object') {
console.log(event.data, event.data.size);
// clear memory in case buffer is already too big
if (subcounter > 50) {
console.log('cleared memory');
audiobuffer = [];
subcounter = 0;
}
audiobuffer.push(event.data);
if (audiobuffer.length == 1) {
play('sinewave');
}
}
else {
if (event.data == 'stopMicrophone=ok') {
wsClient.close();
wsClient = null;
audiobuffer = [];
subcounter = 0;
}
console.log(event.data);
}
}
}
};
// method used in send() which will actually send the message only after connection has been established successfully.
this.waitForConnection = function (callback, interval) {
if (wsClient.readyState === 1) {
callback();
} else {
var that = this;
// optional: implement backoff for interval here
setTimeout(function () {
that.waitForConnection(callback, interval);
}, interval);
}
};
// using this method to send WS messages to the audio-server
this.send = function (message, callback)
{
this.initWebsocketClient();
this.waitForConnection(function () {
wsClient.send(message);
if (typeof callback !== 'undefined') {
callback();
}
}, 1000);
};
// called by clicking the start button
function startCapture() {
if (audioCtx == null)
audioCtx = new (window.AudioContext || window.webkitAudioContext)();
audiobuffer = [];
subcounter = 0;
this.send(`startMicrophone?device=${microphone}`);
}
// called by clicking the stop button
function stopCapture() {
this.send('stopMicrophone');
}

Reduce script run time in Google Apps Script?

I created a script that tracks attendance for distance learning. After a while it times out so I think I am having issues with too many calls to the Google Classroom API, however I don't see a way that I can change it to take those calls out of a loop.
The script takes all the Google Classroom classes that my apps script account is a co-teacher on and using timed triggers creates a daily attendance assignment with one question that says 'here'. Students are then supposed to answer the question and then another trigger at night runs the function to 'grade' each assignment and populate my spreadsheet so school secretaries can view it in the morning and record the previous days attendance.
The part that seems to have the bottleneck is my getStudentResponses() function. I tried to reduce time by filtering out students that didn't submit the assignment, but it wasn't enough. Does anyone see any way that I can make this faster? I was reading up on using the Cache Service, but I couldn't figure out how to get that to work. Any help would be appreciated.
var ss = SpreadsheetApp.getActive();
var date = new Date();
/*
creates a button to programmatically create all necessary timed triggers for easy deployment
*/
function onOpen() {
var ui = SpreadsheetApp.getUi();
ui.createMenu('Attendance')
.addItem('Create Triggers', 'createTriggers')
.addToUi();
}
/*
auto accepts any co-teacher invites
*/
function acceptInvite() {
try{
var optionalArgs = {
userId: "me"
};
var invites = Classroom.Invitations.list(optionalArgs);
for(var i = 0; i < invites.invitations.length; i++) {
Classroom.Invitations.accept(invites.invitations[i].id);
}
}
catch(e){}
}
/*
populates a spreadsheet with all the classes that the script Google account is a co-teacher of
the sheet has two columns one with the course name and two with the course id
*/
function listCourses() {
var optionalArgs = {courseStates: "ACTIVE"};
var response = Classroom.Courses.list(optionalArgs);
var courses = response.courses;
var classSheet;
try{
classSheet = ss.insertSheet("Classes", 0);
ss.insertSheet("Assignments", 1);
}
catch(e) {
classSheet = ss.getSheetByName("Classes");
}
classSheet.clear();
if (courses && courses.length > 0) {
for (i = 0; i < courses.length; i++) {
var course = courses[i];
classSheet.appendRow([course.name, course.id]);
}
}
}
/*
reads the sheet to get all the classes and creates a new array with all the class IDs
*/
function getCourses() {
var classSheet = ss.getSheetByName("Classes");
var classList = new Array();
var range = classSheet.getDataRange();
var values = range.getValues();
for(var i in values) {
var row = values[i];
var courseId = row[1]+"";
classList.push(Classroom.Courses.get(courseId));
}
createTopics(classList);
}
/*
called immediatly after getCourses, creates topics in each class that will contain the daily attendance assignment
*/
function createTopics(classList) {
for(i = 0; i < classList.length; i++) {
var topic;
var resource = {name: "Daily Online Attendance"};
try {
topic = Classroom.Courses.Topics.create(resource, classList[i].id);
createAssignment(topic,classList[i]);
}
catch(e) {
if(e == "GoogleJsonResponseException: API call to classroom.courses.topics.create failed with error: Requested entity already exists") {
var topics = Classroom.Courses.Topics.list(classList[i].id);
for(j = 0; j < topics.topic.length; j++) {
if(topics.topic[j].name == "Daily Online Attendance") {
createAssignment(topics.topic[j], classList[i]);
}
}
}
}
}
}
/*
creates an assignment in each class, under each topic
each assignment only has one choice that says "here" and is going to be 'graded' each night to track attendance
*/
function createAssignment(topic,course) {
var resource = {
title: "Attendance for "+(date.getMonth()+1)+"/"+date.getDate()+"/2020",
description: "Please fill this assignment out each day for attendance",
topicId: topic.topicId,
state: "PUBLISHED",
workType: "MULTIPLE_CHOICE_QUESTION",
multipleChoiceQuestion: {
"choices": [
"Here"
]
}
};
try {
var assignment = Classroom.Courses.CourseWork.create(resource, course.id);
var sheet = ss.getSheetByName("Assignments");
sheet.appendRow([course.id,assignment.id]);
}
catch(e){}
}
/*
creates a new sheet for each day and logs each assignement
*/
function getStudentResponses() {
var assignmentSheet = ss.getSheetByName("Assignments");
var sheet2;
var response;
assignmentSheet.sort(1, true);
try{
sheet2 = ss.insertSheet("Attendance for "+(date.getMonth()+1)+"/"+date.getDate()+"/2020",(ss.getSheets().length-(ss.getSheets().length-2)));
sheet2.appendRow(["Student Last Name","Student First Name","Grade","Class Name","Assignment Answer"]);
}
catch(e) {
sheet2 = ss.getSheetByName("Attendance for "+(date.getMonth()+1)+"/"+date.getDate()+"/2020");
}
sheet2.setFrozenRows(1);
var range = assignmentSheet.getDataRange();
var values = range.getValues();
for(var i in values) {
var row = values[i];
var courseId = row[0]+"";
var courseWorkId = row[1]+"";
try {
response = Classroom.Courses.CourseWork.StudentSubmissions.list(courseId, courseWorkId);
for(var j in response.studentSubmissions) {
if(response.studentSubmissions[j].state == "TURNED_IN") {
try {
var grade;
var email = Classroom.UserProfiles.get(response.studentSubmissions[j].userId).emailAddress;
sheet2.appendRow([Classroom.UserProfiles.get(response.studentSubmissions[j].userId).name.familyName,Classroom.UserProfiles.get(response.studentSubmissions[j].userId).name.givenName,grade,Classroom.Courses.get(courseId).name,response.studentSubmissions[j].multipleChoiceSubmission.answer]);
}
catch (e) {}
}
}
}
catch(e) {}
}
}
/*
deletes all assignemnts that were created
*/
function deleteAssignments() {
var assignmentSheet = ss.getSheetByName("Assignments");
assignmentSheet.sort(1, true);
var range = assignmentSheet.getDataRange();
var values = range.getValues();
for(var i in values) {
var row = values[i];
var courseId = row[0]+"";
var courseWorkId = row[1]+"";
try {
Classroom.Courses.CourseWork.remove(courseId, courseWorkId);
}
catch(e) {}
assignmentSheet.clear();
}
}
function createTriggers() {
ScriptApp.newTrigger('getCourses')
.timeBased()
.everyDays(1)
.atHour(6)
.create();
ScriptApp.newTrigger('getStudentResponses')
.timeBased()
.everyDays(1)
.atHour(22)
.create();
ScriptApp.newTrigger('deleteAssignments')
.timeBased()
.everyDays(1)
.atHour(23)
.create();
ScriptApp.newTrigger('listCourses')
.timeBased()
.everyDays(1)
.atHour(21)
.create();
ScriptApp.newTrigger('acceptInvite')
.timeBased()
.everyDays(1)
.atHour(20)
.create();
}
appendRow is slow, you should avoid to used it inside a for loop. Instead build an array, then pass the values using a single setValues call.
Resources
Best Practices | Apps Script
Related
Google Script Performance Slow Down
Increase my script performance Google Sheets Script
Very slow execution of for...in loop

Last promise not fullfilled on first call

Here is some code, in a cloud function, on Parse-Server using promises.
It is supposed to be called once and do a certain job of removing some contents layed out in a tree stucture.
The problem is that I have to call the same code twice to get the job fully done.
If someone can take a look and say what is wrong, that will be very helpful.
Here is what happens, when the code is called for the first time. All the work is done, except the last removal.
That means the code from the line:
console.log("We finally remove the unit.");
and after is not executed.
When the code is run the second time, the last part is properly executed.
I may have done something incorrect with the promises, but I can't see what.
Parse.Cloud.define
("removeTheThing", function(request, response) {
var thingQuery;
thingQuery = .....;
.....
thingQuery.find().then
(function(resUnit) {
var secondLevelQuery;
secondLevelQuery = .....;
.....
secondLevelQuery.find().then
(function(resSentence) {
var thirdLevelQuery;
thirdLevelQuery = .....;
.....
thirdLevelQuery.find().then
(function(resTranslat) {
var fourthLevelQuery;
fourthLevelQuery = .....;
.....
fourthLevelQuery.find().then
(function(resExplain) {
var destroyPromises = [];
for (i = 0; i < resExplain.length; i++) {
destroyPromises.push(resExplain[i].destroy({}));
}
return Parse.Promise.when(destroyPromises);
}).then
(function() {
var destroyPromises = [];
for (iT = 0; iT < resTranslat.length; iT++) {
destroyPromises.push(resTranslat[iT].destroy({}));
}
return Parse.Promise.when(destroyPromises);
}).then
(function() {
var destroyPromises = [];
const s3 = new aws.S3();
for (iS = 0; iS < resSentence.length; iS++) {
// Let us remove the voice recording:
destroyPromises.push(s3.deleteObject({
Bucket: "londonspeak",
Key: resSentence[iS].get("audio")
}).promise());
destroyPromises.push(resSentence[iS].destroy({}));
}
return Parse.Promise.when(destroyPromises);
}).then
(function() {
console.log("We finally remove the unit.");
//return resUnit[0].destroy({});
//return Parse.Promise.when(resUnit[0].destroy({}));//Promise()
var destroyPromise = [];
destroyPromise.push(resUnit[0].destroy({}));
return Parse.Promise.when(destroyPromise);
}).then(response.success);
});
});
});
});

Can't advertise on bluetooth

I want to create a Gatt Server in my Xamarin.Forms app so that other devices can scan for it via bluetooth. I am using this plugin:
https://github.com/aritchie/bluetoothle
This is my code to create a Gatt Server and advertise data:
server = CrossBleAdapter.Current.CreateGattServer();
var service = server.AddService(serviceGuid, true);
var characteristic = service.AddCharacteristic(
characteristicGuid,
CharacteristicProperties.Read |
CharacteristicProperties.Write | CharacteristicProperties.WriteNoResponse,
GattPermissions.Read | GattPermissions.Write
);
var notifyCharacteristic = service.AddCharacteristic
(
notifyCharacteristicGuid,
CharacteristicProperties.Indicate | CharacteristicProperties.Notify,
GattPermissions.Read | GattPermissions.Write
);
IDisposable notifyBroadcast = null;
notifyCharacteristic.WhenDeviceSubscriptionChanged().Subscribe(e =>
{
var #event = e.IsSubscribed ? "Subscribed" : "Unsubcribed";
if (notifyBroadcast == null)
{
notifyBroadcast = Observable
.Interval(TimeSpan.FromSeconds(1))
.Where(x => notifyCharacteristic.SubscribedDevices.Count > 0)
.Subscribe(_ =>
{
Debug.WriteLine("Sending Broadcast");
var dt = DateTime.Now.ToString("g");
var bytes = Encoding.UTF8.GetBytes("SendingBroadcast");
notifyCharacteristic.Broadcast(bytes);
});
}
});
characteristic.WhenReadReceived().Subscribe(x =>
{
var write = "HELLO";
// you must set a reply value
x.Value = Encoding.UTF8.GetBytes(write);
x.Status = GattStatus.Success; // you can optionally set a status, but it defaults to Success
});
characteristic.WhenWriteReceived().Subscribe(x =>
{
var write = Encoding.UTF8.GetString(x.Value, 0, x.Value.Length);
Debug.WriteLine("in WhenWriteReceived() value: " + write);
// do something value
});
await server.Start(new AdvertisementData
{
LocalName = "DariusServer",
ServiceUuids = new List<Guid>() { serverServiceGuid }
});
I am using this app to scan for my advertisement data:
https://play.google.com/store/apps/details?id=no.nordicsemi.android.mcp
I can't discover my app with it. I don't know what I'm doing wrong? I am testing with a real device, SM-T350 tablet
I spent countless hours to get this plugin to work with no luck. But this native code works for anyone else who has the same problem:
private async Task AndroidBluetooth()
{
try
{
await Task.Delay(5000); // just to make sure bluetooth is ready to go, this probably isn't needed, but good for peace of mind during testing
BluetoothLeAdvertiser advertiser = BluetoothAdapter.DefaultAdapter.BluetoothLeAdvertiser;
var advertiseBuilder = new AdvertiseSettings.Builder();
var parameters = advertiseBuilder.SetConnectable(true)
.SetAdvertiseMode(AdvertiseMode.Balanced)
//.SetTimeout(10000)
.SetTxPowerLevel(AdvertiseTx.PowerHigh)
.Build();
AdvertiseData data = (new AdvertiseData.Builder()).AddServiceUuid(new ParcelUuid(Java.Util.UUID.FromString("your UUID here"))).Build();
MyAdvertiseCallback callback = new MyAdvertiseCallback();
advertiser.StartAdvertising(parameters, data, callback);
}
catch(Exception e)
{
}
}
public class MyAdvertiseCallback : AdvertiseCallback
{
public override void OnStartFailure([GeneratedEnum] AdvertiseFailure errorCode)
{
// put a break point here, in case something goes wrong, you can see why
base.OnStartFailure(errorCode);
}
public override void OnStartSuccess(AdvertiseSettings settingsInEffect)
{
base.OnStartSuccess(settingsInEffect);
}
}
}
Just to note, it wouldn't work if if I included the device name, because the bluetooth transmission would be too large in that case with a service UUID (max 31 bytes I believe).

Xamarin-CrossDownloadManager - waiting for download file

I use Xamarin-CrossDownloadManager (https://github.com/SimonSimCity/Xamarin-CrossDownloadManager) and I need waiting for download a file. I have this code:
private static async Task<bool> FileDownloadedTest(string LinkToFile, string PathFile)
{
var downloadManager = CrossDownloadManager.Current;
CrossDownloadManager.Current.PathNameForDownloadedFile = new System.Func<IDownloadFile, string>(file => {
return PathFile;
});
{
await DeleteFile(PathFile);
var file = downloadManager.CreateDownloadFile(LinkToFile);
await Task.Run(() => downloadManager.Start(file, true)); //why this not wait???
}
bool FileExist = await IsFileExist(PathFile);
return FileExist;
}
Why it not wait for finish download action? How to do it?
On library site they wrote, that I can watch the IDownloadManager.Queue to get information when the file is downloaded. But, I don't know how to use this in my method... Can you help me?
PS: Sorry for my english, I'm still learning it ;)
With that library, there is no callback or event published for when a file is finished downloading, but you can do a simple check and wait some more loop.
await Task.Run(async () =>
{
var downloadManager = CrossDownloadManager.Current;
var file = downloadManager.CreateDownloadFile(someFileBasedUrl);
downloadManager.Start(file);
bool isDownloading = true;
while (isDownloading)
{
await Task.Delay(10 * 1000);
isDownloading = IsDownloading(file);
}
});
The IsDownloading method:
bool IsDownloading(IDownloadFile file)
{
if (file == null) return false;
switch (file.Status)
{
case DownloadFileStatus.INITIALIZED:
case DownloadFileStatus.PAUSED:
case DownloadFileStatus.PENDING:
case DownloadFileStatus.RUNNING:
return true;
case DownloadFileStatus.COMPLETED:
case DownloadFileStatus.CANCELED:
case DownloadFileStatus.FAILED:
return false;
default:
throw new ArgumentOutOfRangeException();
}
}
Re: https://github.com/SimonSimCity/Xamarin-CrossDownloadManager/blob/develop/Sample/DownloadExample/Downloader.cs#L46
I don't know why IDownloadFile.Status = COMPLETED not working, but i found solve problem:
await Task.Run(() =>
{
var downloadManager = CrossDownloadManager.Current;
var file = downloadManager.CreateDownloadFile(LinkToFile);
downloadManager.Start(file);
while (file.Status == DownloadFileStatus.INITIALIZED)
{
while (file.TotalBytesExpected > file.TotalBytesWritten)
{
Debug.WriteLine(file.Status);
}
}
});
Somebody know why DownloadFileStatus.INITIALIZED working, but DownloadFileStatus.COMPLETED not?
SushiHangover's answer above worked great, and I combined it with ACR User Dialog's package (https://github.com/aritchie/userdialogs) to show a nice loading progress screen to the user while waiting for the download to complete. This works nicely on Android (I couldn't test iOS yet).
...
var downloadManager = CrossDownloadManager.Current;
var fileToDownload = downloadManager.CreateDownloadFile(args.Url);
downloadManager.Start(fileToDownload, true);
args.Cancel = true;
UserDialogs.Instance.Progress("Downloading").Show();
bool isDownloading = true;
while (isDownloading)
{
await Task.Delay(100);
if (fileToDownload.TotalBytesExpected > 0)
{
UserDialogs.Instance.Progress().PercentComplete = (int)(fileToDownload.TotalBytesWritten / fileToDownload.TotalBytesExpected * 100);
Console.WriteLine(("DOWNLOAD PROGRESS: " + fileToDownload.TotalBytesWritten / fileToDownload.TotalBytesExpected * 100).ToString() + "%");
}
isDownloading = IsDownloading(fileToDownload);
}
UserDialogs.Instance.Progress().Hide();
//Below code opens the download location after download has completed.
Intent intent = new Intent(DownloadManager.ActionViewDownloads);
intent.AddFlags(ActivityFlags.NewTask);
Android.App.Application.Context.StartActivity(intent);
return;
}
}
}
bool IsDownloading(IDownloadFile file)
{
if (file == null) return false;
switch (file.Status)
{
case DownloadFileStatus.INITIALIZED:
case DownloadFileStatus.PAUSED:
case DownloadFileStatus.PENDING:
case DownloadFileStatus.RUNNING:
return true;
case DownloadFileStatus.COMPLETED:
case DownloadFileStatus.CANCELED:
case DownloadFileStatus.FAILED:
return false;
default:
throw new ArgumentOutOfRangeException();
}
}

Resources