PHAssetChangeRequest Cocoa error -1 - c#

I need to save downloaded video to gallery on iPhone, but getting error:
The operation couldnt be completed. (Cocoa error -1/)
Tried also to do this through webClient.DownloadDataAsync(), getting same error. Here is my listing:
public async Task<string> DownloadFile(string fileUri)
{
var tcs = new TaskCompletionSource<string>();
string fileName = fileUri.Split('/').Last();
var documentsDirectory = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
string videoFileName = System.IO.Path.Combine(documentsDirectory, fileName);
var webClient = new WebClient();
webClient.DownloadFileCompleted += (s, e) =>
{
var authStatus = await PHPhotoLibrary.RequestAuthorizationAsync();
if(authStatus == PHAuthorizationStatus.Authorized){
var fetchOptions = new PHFetchOptions();
var collections = PHAssetCollection.FetchAssetCollections(PHAssetCollectionType.Album, PHAssetCollectionSubtype.Any, fetchOptions);
collection = collections.firstObject as PHAssetCollection;
PHPhotoLibrary.SharedPhotoLibrary.PerformChanges(() => {
var assetCreationRequest = PHAssetChangeRequest.FromVideo(NSUrl.FromFileName(videoFileName));
var assetPlaceholder = assetCreationRequest.PlaceholderForCreatedAsset;
var albumChangeRequest = PHAssetCollectionChangeRequest.ChangeRequest(collection);
albumChangeRequest.AddAssets(new PHObject[] { assetPlaceholder });
}, delegate (bool status, NSError error) {
if (status)
{
Console.Write("Video added");
tcs.SetResult("success");
}
});
}
try
{
webClient.DownloadFileAsync(new Uri(fileUri), videoFileName);
}
catch (Exception e)
{
tcs.SetException(e);
}
return await tcs.Task;
}
Any help would be appreciated. Thanks.

(Cocoa error -1/)
Are you sure that you actually have valid data/mp4 from your download?
Are you using SSL (https), otherwise have you applied for an ATS exception in your info.plist?
Check the phone/simulator console output for errors concerning ATS
Note: I typically use NSUrlSession directly to avoid the HttpClient wrapper...
Example using NSUrlSession task:
var videoURL = NSUrl.FromString(urlString);
var videoPath = NSFileManager.DefaultManager.GetUrls(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomain.User)[0];
NSUrlSession.SharedSession.CreateDownloadTask(videoURL, (location, response, createTaskError) =>
{
if (location != null && createTaskError == null)
{
var destinationURL = videoPath.Append(response?.SuggestedFilename ?? videoURL.LastPathComponent, false);
// If file exists, it is already downloaded, but for debugging, delete it...
if (NSFileManager.DefaultManager.FileExists(destinationURL.Path)) NSFileManager.DefaultManager.Remove(destinationURL, out var deleteError);
NSFileManager.DefaultManager.Move(location, destinationURL, out var moveError);
if (moveError == null)
{
PHPhotoLibrary.RequestAuthorization((status) =>
{
if (status.HasFlag(PHAuthorizationStatus.Authorized))
{
PHPhotoLibrary.SharedPhotoLibrary.PerformChanges(() =>
{
PHAssetChangeRequest.FromVideo(destinationURL);
}, (complete, requestError) =>
{
if (!complete && requestError != null)
Console.WriteLine(requestError);
});
}
});
}
else
Console.WriteLine(moveError);
}
else
Console.WriteLine(createTaskError);
}).Resume();
Note: To confirm your code, try using a known valid secure URL source:
https://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4 via “Video For Everybody” Test Page

Related

Async promise not awaited for before return statement

Had this problem yesterday, changed some code, and it's happening again... Why don't both mails send? The promise is not awaited for. Sometimes 1, sometimes 2 mails send. Bool "messagesSent" also returns false sometimes, when it should return true.
The function:
private async Task<bool> SendMails(string email, string name, string pdfPath, string imgPath)
{
var client = new SendGridClient(_config["SendGrid:Key"]);
bool messagesSent = false;
try
{
var messageClient = new SendGridMessage
{
From = new EmailAddress(_config["SendGrid:Recipient"]),
Subject = "Subject1",
HtmlContent = _textManager.Get("email-b")
};
var MessageServer = new SendGridMessage
{
From = new EmailAddress(_config["SendGrid:Recipient"]),
Subject = "Subject2",
HtmlContent = _textManager.Get("email-s")
};
messageClient.AddTo(email, name);
MessageServer.AddTo(email, name);
string[] fileListClient = new string[] { pdfPath };
string[] FileListServer = new string[] { pdfPath, imgPath };
foreach (var file in fileListClient)
{
var fileInfo = new FileInfo(file);
if (fileInfo.Exists)
await messageClient.AddAttachmentAsync(fileInfo.Name, fileInfo.OpenRead());
}
foreach (var file in FileListServer)
{
var fileInfo = new FileInfo(file);
if (fileInfo.Exists)
await MessageServer.AddAttachmentAsync(fileInfo.Name, fileInfo.OpenRead());
}
var responseClient = await client.SendEmailAsync(messageClient);
var responseServer = await client.SendEmailAsync(MessageServer);
if (responseClient.StatusCode.ToString() == "202" && responseServer.StatusCode.ToString() == "202")
{
messagesSent = true;
}
}
catch (Exception e)
{
Console.WriteLine(e);
}
return messagesSent;
}
Called by:
bool sentMails = await SendMails(formCollection["email"], formCollection["name"], pdfPath, imgPath);
if (!sentMails)
{
errorMessage = "Error sending mails.";
succes = false;
}
EDIT:
Request was valid, the call was being blocked by ISP/MailServer. This had nothing to do with faulty async.
Ok, the whole point of an asynchroneous call, is not to "wait" or delay performance. The method will just 'run'. Unless there is an exception raised in that method, there is nothing that would prevent the method from not get executed successfully.
Try adding a break-point and see why the messagesSent returns false sometimes, I am suspecting this line:
if (responseClient.StatusCode.ToString() == "202" && responseServer.StatusCode.ToString() == "202")
Is there any chance that the StatusCode might be returning a "200" as well?

How do I detect canceled asynchronous post

I use the following code to upload a file and form data asynchronous. What do I need to change in order to detect if the transfer has been cancelled or interrupted and then take proper action?
[HttpPost]
public async Task<object> UploadFile()
{
if (!Request.Content.IsMimeMultipartContent("form-data"))
{
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.UnsupportedMediaType));
}
var streamProvider = new MultipartFormDataStreamProvider(HttpContext.Current.Server.MapPath("~/App_Data/Temp/"));
try
{
var filesReadToProvider = await Request.Content.ReadAsMultipartAsync(streamProvider);
var paramName = "formInput";
var formStreamToRead = filesReadToProvider.Contents.First(x => x.Headers.ContentDisposition.Name == $"\"{formInput}\"");
var formInput = await formStreamToRead.ReadAsStringAsync();
foreach (var fileData in streamProvider.FileData)
{
var fileName = "";
if (string.IsNullOrEmpty(fileData.Headers.ContentDisposition.FileName))
{
fileName = Guid.NewGuid().ToString();
}
fileName = fileData.Headers.ContentDisposition.FileName;
if (fileName.StartsWith("\"") && fileName.EndsWith("\""))
{
fileName = fileName.Trim('"');
}
if (fileName.Contains(#"/") || fileName.Contains(#"\"))
{
fileName = Path.GetFileName(fileName);
}
File.Move(fileData.LocalFileName, Path.Combine(HttpContext.Current.Server.MapPath("~/App_Data/"), Path.GetDirectoryName(fileName) + Guid.NewGuid() + Path.GetExtension(fileName)));
}
return Request.CreateResponse(HttpStatusCode.OK);
}
catch (Exception e)
{
return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, e);
}
}

StorageProgress Report function is not called by GetFileAsync

I'm trying to download resources file for my game from Firebase Storage, and to show downloading progress. But I came to strange behavior of GetFileAsync function. The Report function of StorageProgress is not called periodically during file downloading how it is described in reference. Report function is called during saving to disk, after the file is placed into memory(downloaded). What I'm doing wrong?
string resourcesURL = "URL_TO_RESOURCE_FILE"; // https://firebasestorage.googleapis.com/......
public void UpdateProgress(float val)
{
labelProgress.text = Mathf.RoundToInt(val * 100f) + "%";
circleProgress.fillAmount = val;
}
void OnDownloadClick()
{
SetDownloadingView();
string saveToPath = GetParentFolder() + "/resources.bin";
StorageReference fileRef = FirebaseBackend.Instance.GetStorage().GetReferenceFromUrl(resourcesURL);
StorageProgress<Firebase.Storage.DownloadState> progressHandler = new StorageProgress<Firebase.Storage.DownloadState>(state =>
{
Debug.Log(String.Format ("Progress: {0} of {1} bytes transferred.", state.BytesTransferred, state.TotalByteCount));
UpdateProgress(state.BytesTransferred / state.TotalByteCount);
});
Task dwnTask = fileRef.GetFileAsync
(
saveToPath,
progressHandler,
CancellationToken.None
);
dwnTask.ContinueWith(resultTask =>
{
if (!resultTask.IsFaulted && !resultTask.IsCanceled)
{
ResetView();
Debug.Log("Download finished!");
}
else
{
ResetView();
Debug.Log("Download fail!");
}
});
}
Unity version is 5.5.0f3
Firebase Unity SDK version is 3.0.1
You may not be doing anything wrong at all.
According to this post, this is a Firebase bug that is to be addressed in the next Firebase update.
Try with an IEnumerator, this is my code with the storage progress working:
protected IEnumerator downloadObject(string target_file, string target_path, int item_index)
{
if (target_file != "")
{
FirebaseStorage storage = FirebaseStorage.DefaultInstance;
string local_url = target_path.Replace("\\", "/").Replace("//", "/");
StorageReference storage_ref =
storage.GetReferenceFromUrl(FB_Conf.gsURL + target_file);
Task task = null;
try
{
task = storage_ref.GetFileAsync(local_url,
new Firebase.Storage.StorageProgress<DownloadState>((DownloadState state) =>
{
// called periodically during the download
long tbyte = state.TotalByteCount;
file_progress = (float)state.BytesTransferred / (float)tbyte;
}), CancellationToken.None);
}
catch (Exception exc)
{
Debug.Log("Get file async error: " + exc.Message);
}
if (task != null)
{
yield return new WaitUntil(() => task.IsCompleted);
task.ContinueWith((resultTask) =>
{
if ((resultTask.IsFaulted) || (resultTask.IsCanceled))
{
error_count++;
string msg = (resultTask.IsCanceled) ? "Download error." : "";
if ((resultTask.Exception != null) &&
(resultTask.Exception.InnerExceptions.Count > 0) &&
(resultTask.Exception.InnerExceptions[0] is Firebase.Storage.StorageException))
msg = ((Firebase.Storage.StorageException)resultTask.Exception.InnerExceptions[0]).HttpResultCode.ToString();
else if (resultTask.Exception != null)
msg = resultTask.Exception.Message;
}
});
}
else
{
error_count++;
}
}
}

How to batch queue records and execute them in a different thread and wait till it;s over?

I am using push sharp version PushSharp 4.0.4.
I am using it in a windows application.
I have three main methods
1- BroadCastToAll
2- BrodcatsToIOS
3- BrodcatsToAndriod
I have a button calld send. On the click event of the button. I am calling the
BroadCastToAll function.
private void btnSend_Click(object sender, EventArgs e)
{
var url = "www.mohammad-jouhari.com"
var promotion = new Promotion ();
BroadCastToAll(promotion, url);
}
Here is the BrodcastToAll Function
public void BroadCastToAll(Promotion promotion, string url)
{
var deviceCatalogs = GetDeviceCatalog();
BroadCastToIOS(promotion, url, deviceCatalogs.Where(d => d.OS == "IOS").ToList());
BroadCastToAndriod(promotion, url, deviceCatalogs.Where(d => d.OS == "Android").ToList());
}
Here is the BrodcastToIOS Function
public void BroadCastToIOS(Promotion promotion, string url, List<DeviceCatalog> deviceCatalogs)
{
if (deviceCatalogs.Count == 0)
return;
lock (_lock)// Added this lock because there is a potential chance that PushSharp callback execute during registering devices
{
QueueAllAppleDevicesForNotification(promotion, url, deviceCatalogs, logsMessage);
}
}
Here is the BrodcastToAndriod Function
public void BroadCastToAndriod(Promotion promotion, string url, List<DeviceCatalog> deviceCatalogs)
{
if (deviceCatalogs.Count == 0)
return;
lock (_lock)// Added this lock because there is a potential chance that PushSharp callback execute during registering devices
{
QueueAllGcmDevicesForNotification(promotion, url, deviceCatalogs, logsMessage);
}
}
Here is the QueueAllAppleDevicesForNotification function
private void QueueAllAppleDevicesForNotification(Promotion promotion, string url, List<DeviceCatalog> deviceCatalogs)
{
var apnsServerEnviroment = UseProductionCertificate ? ApnsConfiguration.ApnsServerEnvironment.Production : ApnsConfiguration.ApnsServerEnvironment.Sandbox;
var fileService = new FileService();
var filePath = Application.StartupPath+ "/Certifcates/" + (UseProductionCertificate ? "prod.p12" : "dev.p12");
var buffer = fileService.GetFileBytes(filePath);
var config = new ApnsConfiguration(apnsServerEnviroment, buffer, APPLE_CERTIFICATE_PWD);
apnsServiceBroker = new ApnsServiceBroker(config);
apnsServiceBroker.OnNotificationFailed += (notification, aggregateEx) => {
aggregateEx.Handle (ex => {
// Log the Resposne
});
};
apnsServiceBroker.OnNotificationSucceeded += (notification) => {
// Log The Response
};
apnsServiceBroker.Start();
foreach (var deviceToken in deviceCatalogs) {
var title = GetTitle(promotion, deviceToken);
//title += DateTime.UtcNow.TimeOfDay.ToString();
var NotificationPayLoadObject = new NotificationPayLoadObjectApple();
NotificationPayLoadObject.aps.alert = title;
NotificationPayLoadObject.aps.badge = 0;
NotificationPayLoadObject.aps.sound = "default";
NotificationPayLoadObject.url = url;
var payLoad = JObject.Parse(JsonConvert.SerializeObject(NotificationPayLoadObject));
apnsServiceBroker.QueueNotification(new ApnsNotification
{
Tag = this,
DeviceToken = deviceToken.UniqueID,
Payload = payLoad
});
}
var fbs = new FeedbackService(config);
fbs.FeedbackReceived += (string deviceToken, DateTime timestamp) =>
{
// This Token is no longer avaialble in APNS
new DeviceCatalogService().DeleteExpiredIosDevice(deviceToken);
};
fbs.Check();
apnsServiceBroker.Stop();
}
And here is the QueueAllGcmDevicesForNotification
private void QueueAllGcmDevicesForNotification(Promotion promotion, string url, List<DeviceCatalog> deviceCatalogs, )
{
var config = new GcmConfiguration(ANDROID_SENDER_ID, ANDROID_SENDER_AUTH_TOKEN, ANDROID_APPLICATION_ID_PACKAGE_NAME);
gcmServiceBroker = new GcmServiceBroker(config);
gcmServiceBroker.OnNotificationFailed += (notification, aggregateEx) => {
aggregateEx.Handle (ex => {
// Log Response
return true;
});
};
gcmServiceBroker.OnNotificationSucceeded += (notification) => {
// Log Response
};
var title = GetTitle(shopexPromotion);
gcmServiceBroker.Start ();
foreach (var regId in deviceCatalogs) {
var NotificationPayLoadObject = new NotificationPayLoadObjectAndriod(url, title, "7", promotion.ImageUrl);
var payLoad = JObject.Parse(JsonConvert.SerializeObject(NotificationPayLoadObject));
gcmServiceBroker.QueueNotification(new GcmNotification
{
RegistrationIds = new List<string> {
regId.UniqueID
},
Data = payLoad
});
}
gcmServiceBroker.Stop();
}
Now When I click the send button. The event will start executing.
The BrodcastToAll function will be called. I am calling BrodcastToIOS devices first and then BrodcatsToAndriod.
Is there any way in which I can call BrodcastToIOS and wait until all the devices have been Queued and notification has been pushed by the library and the call back events fired fully then start executing the BrodcastToAndriod Fucntion ?
What lines of code I need to add ?
also Is there any way to batch the number of devices to be Queued ?
For example.
Let us say I have 1000 Devices
500 IOS
500 Andriod
Can I queue 100, 100,100,100,100 for IOS and when it's done
I queue 100,100,100,100,100 for Andriod.
Any Help is appreciated.
Thanks.
The call to broker.Stop () by default will block until all the notifications from the queue have been processed.

How to wait for multiple async http request

I'd like to ask about how to wait for multiple async http requests.
My code is like this :
public void Convert(XDocument input, out XDocument output)
{
var ns = input.Root.Name.Namespace;
foreach (var element in input.Root.Descendants(ns + "a"))
{
Uri uri = new Uri((string)element.Attribute("href"));
var wc = new WebClient();
wc.OpenReadCompleted += ((sender, e) =>
{
element.Attribute("href").Value = e.Result.ToString();
}
);
wc.OpenReadAsync(uri);
}
//I'd like to wait here until above async requests are all completed.
output = input;
}
Dose anyone know a solution for this?
There is an article by Scott Hanselman in which he describes how to do non blocking requests. Scrolling to the end of it, there is a public Task<bool> ValidateUrlAsync(string url) method.
You could modify it like this (could be more robust about response reading)
public Task<string> GetAsync(string url)
{
var tcs = new TaskCompletionSource<string>();
var request = (HttpWebRequest)WebRequest.Create(url);
try
{
request.BeginGetResponse(iar =>
{
HttpWebResponse response = null;
try
{
response = (HttpWebResponse)request.EndGetResponse(iar);
using(var reader = new StreamReader(response.GetResponseStream()))
{
tcs.SetResult(reader.ReadToEnd());
}
}
catch(Exception exc) { tcs.SetException(exc); }
finally { if (response != null) response.Close(); }
}, null);
}
catch(Exception exc) { tcs.SetException(exc); }
return tsc.Task;
}
So with this in hand, you could then use it like this
var urls=new[]{"url1","url2"};
var tasks = urls.Select(GetAsync).ToArray();
var completed = Task.Factory.ContinueWhenAll(tasks,
completedTasks =>{
foreach(var result in completedTasks.Select(t=>t.Result))
{
Console.WriteLine(result);
}
});
completed.Wait();
//anything that follows gets executed after all urls have finished downloading
Hope this puts you in the right direction.
PS. this is probably as clear as it can get without using async/await
Consider using continuation passing style. If you can restructure your Convert method like this,
public void ConvertAndContinueWith(XDocument input, Action<XDocument> continueWith)
{
var ns = input.Root.Name.Namespace;
var elements = input.Root.Descendants(ns + "a");
int incompleteCount = input.Root.Descendants(ns + "a").Count;
foreach (var element in elements)
{
Uri uri = new Uri((string)element.Attribute("href"));
var wc = new WebClient();
wc.OpenReadCompleted += ((sender, e) =>
{
element.Attribute("href").Value = e.Result.ToString();
if (interlocked.Decrement(ref incompleteCount) == 0)
// This is the final callback, so we can continue executing.
continueWith(input);
}
);
wc.OpenReadAsync(uri);
}
}
You then run that code like this:
XDocument doc = something;
ConvertAndContinueWith(doc, (finishedDocument) => {
// send the completed document to the web client, or whatever you need to do
});

Categories