Extract an archive with progress bar? - c#

How i can use an progress bar in this case?
void Client_DownloadFileCompleted(object sender, AsyncCompletedEventArgs e)
{
//System.Windows.MessageBox.Show("Update Complete!", "Message", MessageBoxButton.OK, MessageBoxImage.Information);
Uri uri = new Uri(url);
string filename = System.IO.Path.GetFileName(uri.AbsolutePath);
ZipFile.ExtractToDirectory(filePathDir + "/" + filename, filePathDir);
}
EDIT:
#Alessandro D'Andria , But in this case?:
WebClient wc = new WebClient();
Stream zipReadingStream = wc.OpenRead(url);
ZipArchive zip = new ZipArchive(zipReadingStream);
ZipFileExtensions.ExtractToDirectory(zip, filePathDir);

You can see the source of ExtractToDirectory on GitHub, the only thing you need to do is pass in a Progress<ZipProgress> and call it inside the foreach loop.
//This is a new class that represents a progress object.
public class ZipProgress
{
public ZipProgress(int total, int processed, string currentItem)
{
Total = total;
Processed = processed;
CurrentItem = currentItem;
}
public int Total { get; }
public int Processed { get; }
public string CurrentItem { get; }
}
public static class MyZipFileExtensions
{
public static void ExtractToDirectory(this ZipArchive source, string destinationDirectoryName, IProgress<ZipProgress> progress)
{
ExtractToDirectory(source, destinationDirectoryName, progress, overwrite: false);
}
public static void ExtractToDirectory(this ZipArchive source, string destinationDirectoryName, IProgress<ZipProgress> progress, bool overwrite)
{
if (source == null)
throw new ArgumentNullException(nameof(source));
if (destinationDirectoryName == null)
throw new ArgumentNullException(nameof(destinationDirectoryName));
// Rely on Directory.CreateDirectory for validation of destinationDirectoryName.
// Note that this will give us a good DirectoryInfo even if destinationDirectoryName exists:
DirectoryInfo di = Directory.CreateDirectory(destinationDirectoryName);
string destinationDirectoryFullPath = di.FullName;
int count = 0;
foreach (ZipArchiveEntry entry in source.Entries)
{
count++;
string fileDestinationPath = Path.GetFullPath(Path.Combine(destinationDirectoryFullPath, entry.FullName));
if (!fileDestinationPath.StartsWith(destinationDirectoryFullPath, StringComparison.OrdinalIgnoreCase))
throw new IOException("File is extracting to outside of the folder specified.");
var zipProgress = new ZipProgress(source.Entries.Count, count, entry.FullName);
progress.Report(zipProgress);
if (Path.GetFileName(fileDestinationPath).Length == 0)
{
// If it is a directory:
if (entry.Length != 0)
throw new IOException("Directory entry with data.");
Directory.CreateDirectory(fileDestinationPath);
}
else
{
// If it is a file:
// Create containing directory:
Directory.CreateDirectory(Path.GetDirectoryName(fileDestinationPath));
entry.ExtractToFile(fileDestinationPath, overwrite: overwrite);
}
}
}
}
This is used like
public class YourClass
{
public Progress<ZipProgress> _progress;
public YourClass()
{
// Create the progress object in the constructor, it will call it's ReportProgress using the sync context it was constructed on.
// If your program is a UI program that means you want to new it up on the UI thread.
_progress = new Progress<ZipProgress>();
_progress.ProgressChanged += Report
}
private void Report(object sender, ZipProgress zipProgress)
{
//Use zipProgress here to update the UI on the progress.
}
//I assume you have a `Task.Run(() => Download(url, filePathDir);` calling this so it is on a background thread.
public void Download(string url, string filePathDir)
{
WebClient wc = new WebClient();
Stream zipReadingStream = wc.OpenRead(url);
ZipArchive zip = new ZipArchive(zipReadingStream);
zip.ExtractToDirectory(filePathDir, _progress);
}
//...

Maybe something like this can work for you:
using (var archive = new ZipArchive(zipReadingStream))
{
var totalProgress = archive.Entries.Count;
foreach (var entry in archive.Entries)
{
entry.ExtractToFile(destinationFileName); // specify the output path of thi entry
// update progess there
}
}
It's simple a workaround to keep track of the progress.

Related

Why is it not possible to delete files quickly (<60s) across threads in aspnet?

I get error
System.IO.IOException: 'The process cannot access the file 'xxx' because it is being used by another process.'
when I try to delete a temp file in a background worker service in aspnet core.
I am eventually allowed to delete the file after about a minute (52s, 73s).
If I change garbage collection to workstation mode, I may instead delete after ~1s (but still, a delay).
I have tried a combination of FileOptions to no avail, including FileOptions.WriteThrough.
When the controller writes the file, I use
FlushAsync(), Close(), Dispose() and 'using' (I know it's overkill.)
I also tried using just File.WriteAllBytesAsync, with same result.
In the background reader, I as well use Close() and Dispose().
(hint: background reader will not allow me to use DeleteOnClose,
which would have been ideal.)
As I search stackoverflow for similar 'used by another process' issues,
all those I have found eventually resolve to
'argh it turns out I/he still had an extra open instance/reference
he forgot about',
but I have not been able to figure out that I am doing that.
Another hint:
In the writing controller, I am able to delete the file immediately
after writing it, I presume because I am still on the same thread?
Is there some secret knowledge I should read somewhere,
about being able to delete recently open files, across threads?
UPDATE: Here relevant(?) code snippets:
// (AspNet Controller)
[RequestSizeLimit(9999999999)]
[DisableFormValueModelBinding]
[RequestFormLimits(MultipartBodyLengthLimit = MaxFileSize)]
[HttpPost("{sessionId}")]
public async Task<IActionResult> UploadRevisionChunk(Guid sessionId) {
log.LogWarning($"UploadRevisionChunk: {sessionId}");
string uploadFolder = UploadFolder.sessionFolderPath(sessionId);
if (!Directory.Exists(uploadFolder)) { throw new Exception($"chunk-upload failed"); }
var cr = parseContentRange(Request);
if (cr == null) { return this.BadRequest("no content range header specified"); }
string chunkName = $"{cr.From}-{cr.To}";
string saveChunkPath = Path.Combine(uploadFolder,chunkName);
await streamToChunkFile_WAB(saveChunkPath); // write-all-bytes.
//await streamToChunkFile_MAN(saveChunkPath); // Manual.
long crTo = cr.To ?? 0;
long crFrom = cr.From ?? 0;
long expected = (crTo - crFrom) + 1;
var fi = new FileInfo(saveChunkPath);
var dto = new ChunkResponse { wrote = fi.Length, expected = expected, where = "?" };
string msg = $"at {crFrom}, wrote {dto.wrote} bytes (expected {dto.expected}) to {dto.where}";
log.LogWarning(msg);
return Ok(dto);
}
private async Task streamToChunkFile_WAB(string saveChunkPath) {
using (MemoryStream ms = new MemoryStream()) {
Request.Body.CopyTo(ms);
byte[] allBytes = ms.ToArray();
await System.IO.File.WriteAllBytesAsync(saveChunkPath, allBytes);
}
}
// stream reader in the backgroundService:
public class MyMultiStream : Stream {
string[] filePaths;
FileStream curStream = null;
IEnumerator<string> i;
ILogger log;
QueueItem qItem;
public MyMultiStream(string[] filePaths_, Stream[] streams_, ILogger log_, QueueItem qItem_) {
qItem = qItem_;
log = log_;
filePaths = filePaths_;
log.LogWarning($"filepaths has #items: {filePaths.Length}");
IEnumerable<string> enumerable = filePaths;
i = enumerable.GetEnumerator();
i.MoveNext();// necessary to prime the iterator.
}
public override bool CanRead { get { return true; } }
public override bool CanWrite { get { return false; } }
public override bool CanSeek { get { return false; } }
public override long Length { get { throw new Exception("dont get length"); } }
public override long Position {
get { throw new Exception("dont get Position"); }
set { throw new Exception("dont set Position"); }
}
public override void SetLength(long value) { throw new Exception("dont set length"); }
public override long Seek(long offset, SeekOrigin origin) { throw new Exception("dont seek"); }
public override void Write(byte[] buffer, int offset, int count) { throw new Exception("dont write"); }
public override void Flush() { throw new Exception("dont flush"); }
public static int openStreamCounter = 0;
public static int closedStreamCounter = 0;
string curFileName = "?";
private FileStream getNextStream() {
string nextFileName = i.Current;
if (nextFileName == null) { throw new Exception("getNextStream should not be called past file list"); }
//tryDelete(nextFileName,log);
FileStream nextStream = new FileStream(
path:nextFileName,
mode: FileMode.Open,
access: FileAccess.Read,
share: FileShare.ReadWrite| FileShare.Delete,
bufferSize:4096, // apparently default.
options: 0
| FileOptions.Asynchronous
| FileOptions.SequentialScan
// | FileOptions.DeleteOnClose // (1) this ought to be possible, (2) we should fix this approach (3) if we can fix this, our issue is solved, and our code much simpler.
); // None); // ReadWrite); // None); // ReadWrite); //| FileShare.Read);
log.LogWarning($"TELLUS making new stream [{nextFileName}] opened:[{++openStreamCounter}] closed:[{closedStreamCounter}]");
curFileName = nextFileName;
++qItem.chunkCount;
return nextStream;
}
public override int Read(byte[] buffer, int offset, int count) {
int bytesRead = 0;
while (true) {
bytesRead = 0;
if (curStream == null) { curStream = getNextStream(); }
try {
bytesRead = curStream.Read(buffer, offset, count);
log.LogWarning($"..bytesRead:{bytesRead} [{Path.GetFileName(curFileName)}]"); // (only show a short name.)
} catch (Exception e) {
log.LogError($"failed reading [{curFileName}] [{e.Message}]",e);
}
if (bytesRead > 0) { break; }
curStream.Close();
curStream.Dispose();
curStream = null;
log.LogWarning($"TELLUS closing stream [{curFileName}] opened:[{openStreamCounter}] closed:[{++closedStreamCounter}]");
//tryDelete(curFileName); Presumably we can't delete so soon.
bool moreFileNames = i.MoveNext();
log.LogWarning($"moreFileNames?{moreFileNames}");
if (!moreFileNames) {
break;
}
}
return bytesRead;
}
..
// Background worker operating multistream:
public class BackgroundChunkWorker: BackgroundService {
ILogger L;
ChunkUploadQueue q;
public readonly IServiceScopeFactory scopeFactory;
public BackgroundChunkWorker(ILogger<int> log_, ChunkUploadQueue q_, IServiceScopeFactory scopeFactory_) {
q = q_; L = log_;
scopeFactory = scopeFactory_;
}
override protected async Task ExecuteAsync(CancellationToken cancel) { await BackgroundProcessing(cancel); }
private async Task BackgroundProcessing(CancellationToken cancel) {
while (!cancel.IsCancellationRequested) {
try {
await Task.Delay(1000,cancel);
bool ok = q.q.TryDequeue(out var item);
if (!ok) { continue; }
L.LogInformation($"item found! {item}");
await treatItemScope(item);
} catch (Exception ex) {
L.LogCritical("An error occurred when processing. Exception: {#Exception}", ex);
}
}
}
private async Task<bool> treatItemScope(QueueItem Qitem) {
using (var scope = scopeFactory.CreateScope()) {
var ris = scope.ServiceProvider.GetRequiredService<IRevisionIntegrationService>();
return await treatItem(Qitem, ris);
}
}
private async Task<bool> treatItem(QueueItem Qitem, IRevisionIntegrationService ris) {
await Task.Delay(0);
L.LogWarning($"TryAddValue from P {Qitem.sessionId}");
bool addOK = q.p.TryAdd(Qitem.sessionId, Qitem);
if (!addOK) {
L.LogError($"why couldnt we add session {Qitem.sessionId} to processing-queue?");
return false;
}
var startTime = DateTime.UtcNow;
Guid revisionId = Qitem.revisionId;
string[] filePaths = getFilePaths(Qitem.sessionId);
Stream[] streams = filePaths.Select(fileName => new FileStream(fileName, FileMode.Open)).ToArray();
MyMultiStream multiStream = new MyMultiStream(filePaths, streams, this.L, Qitem);
BimRevisionStatus brs = await ris.UploadRevision(revisionId, multiStream, startTime);
// (launchDeletes is my current hack/workaround,
// it is not part of the problem)
// await multiStream.launchDeletes();
Qitem.status = brs;
return true;
}
..

SevenZipSharp doesn't extract archive

I'm trying to use SevenZipSharp from https://github.com/squid-box/SevenZipSharp to extract a zip archive. The dll setup is as follows:
public class Paths
{
private static readonly string SynthEBDexeDirPath = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
public static readonly string ResourcesFolderPath = Path.Combine(SynthEBDexeDirPath, "Resources");
// Toggle between the x86 and x64 bit dll
public readonly string SevenZipPath = Path.Combine(ResourcesFolderPath, "7Zip", Environment.Is64BitProcess ? "x64" : "x86", "7z.dll");
The dll files are copied into my Resources folder from the latest version of 7-Zip. The calling code looks as follows:
using System.IO;
using System.Windows.Controls;
using SevenZip;
namespace SynthEBD;
public class VM_ZipArchiveHandler : VM
{
public VM_ZipArchiveHandler(Window_SevenZipArchiveHandler window)
{
if (File.Exists(PatcherSettings.Paths.SevenZipPath))
{
SevenZipBase.SetLibraryPath(PatcherSettings.Paths.SevenZipPath);
Initialized = true;
}
else
{
CustomMessageBox.DisplayNotificationOK("Initialization Error", "Could not initialize Seven Zip from " + PatcherSettings.Paths.SevenZipPath);
}
Window = window;
}
public string DispString { get; set; } = string.Empty;
public ProgressBar Prog = new ProgressBar();
public Window_SevenZipArchiveHandler Window { get; set; }
private bool Initialized { get; set; } = false;
public void Unzip(string archivePath, string destinationFolder)
{
if (!Initialized)
{
return;
}
Prog.Minimum = 0;
Prog.Maximum = 100;
Prog.Value = 0;
Window.Show();
var progressHandler = new Progress<byte>(
percentDone => Prog.Value = percentDone);
var progress = progressHandler as IProgress<byte>;
var file = new SevenZipExtractor(archivePath);
file.Extracting += (sender, args) =>
{
progress.Report(args.PercentDone);
};
file.ExtractionFinished += (sender, args) =>
{
// Do stuff when done
};
Task.Run(() =>
{
//Extract the stuff
file.ExtractArchive(destinationFolder);
});
Window.Close();
}
public static void UnzipArchive(string archivePath, string destinationDir)
{
Window_SevenZipArchiveHandler window = new Window_SevenZipArchiveHandler();
VM_ZipArchiveHandler handler = new(window);
window.DataContext = handler;
handler.Unzip(archivePath, destinationDir);
}
}
I call UnzipArchive():
string tempFolderPath = Path.Combine(PatcherSettings.ModManagerIntegration.TempExtractionFolder, DateTime.Now.ToString("yyyy-MM-dd-HH-mm", System.Globalization.CultureInfo.InvariantCulture));
try
{
Directory.CreateDirectory(tempFolderPath);
}
catch (Exception ex)
{
Logger.LogError("Could not create or access the temp folder at " + tempFolderPath + ". Details: " + ex.Message);
return installedConfigs;
}
try
{
VM_ZipArchiveHandler.UnzipArchive(path, tempFolderPath);
}
In the end I get an empty directory; the .7z contents are never extracted to it. I've tried using both a .zip and .7z file as inputs, each containing two json files and nothing else. When I set a breakpoint at file.ExtractArchive(destinationFolder), it seems semi-correctly initialized: https://imgur.com/qjYpDur
It looks like it's correctly recognized as a SevenZip archive, but fields like _filesCount are null.
Am I doing something wrong with my setup?
I believe the issue is that your ExtractArchive is wrapped inside a Task and the calling thread returns before the extraction completes and isn't awaited. Not 100% on the details but as an experiment I found what works and what leaves the destination directory empty:
public static void Main(string[] args)
{
SevenZipBase.SetLibraryPath(
Path.Combine(
Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location),"7z.dll"));
string archivePath = "D:\\Downloads\\imgs.7z";
var file = new SevenZipExtractor(archivePath);
// works
file.ExtractArchive("D:\\Downloads\\unzipped");
// doesnt work
Task.Run(() =>
{
file.ExtractArchive("D:\\Downloads\\unzipped");
});
// works
Task.Run(() =>
{
file.ExtractArchive("D:\\Downloads\\unzipped");
}).Wait();
}

Simple Queue with 2 threads only processes half the items

I have implemented a simple queue to store a list of filenames, and a method that Reads the queue, takes the next available filename, and moves the file from one folder to another folder.
This class is used to keep track of files in a folder.
internal class FileItem
{
public string FullFileName { get; set; }
public bool isLocked { get; set; }
}
This is my Simple Queue implementation
internal class MyQueue
{
List<FileItem> FileList;
public MyQueue(string FilePath)
{
FileList = new List<FileItem>();
string[] files = Directory.GetFiles(FilePath);
foreach (string file in files)
{
FileItem fileitem = new FileItem
{
FullFileName = file,
isLocked = false
};
FileList.Add(fileitem);
}
}
public FileItem GetNextAvailableItem()
{
FileItem item = FileList.Where(i => i.isLocked == false).FirstOrDefault();
if (item != null) item.isLocked = true;
return item;
}
public void RemoveProcessedItem(FileItem item)
{
FileList.Remove(item);
}
}
When I run this from Single thread, it works fine.
But I am using two threads like this.
static void ProcessFilesInMultiThread()
{
Task task1 = Task.Factory.StartNew(() => ReadFromQueueAndMoveFile("Thread 1"));
Task task2 = Task.Factory.StartNew(() => ReadFromQueueAndMoveFile("Thread 2"));
Task.WaitAll(task1, task2);
}
This is the ReadFromQueueAndMoveFile method.
static void ReadFromQueueAndMoveFile(string ThreadName)
{
while (_queue.GetNextAvailableItem() != null)
{
//get next available item from queue.
FileItem item = _queue.GetNextAvailableItem();
if(item != null)
{
string FileName = Path.GetFileName(item.FullFileName);
string SourceFilePath = Path.Combine(sourcePath, FileName);
string DestinationFilePath = Path.Combine(destinationPath, FileName);
File.Move(SourceFilePath, DestinationFilePath);
Thread.Sleep(2000);
Console.WriteLine("Successfully moved: " + FileName + " Via " + ThreadName);
//remove item from queue.
_queue.RemoveProcessedItem(item);
}
}
}
The problem is when I run it from 2 threads, always only half of the files are being moved and I am not sure why. If the folder has 6 files then only 3 files are getting moved randomly.
Why this is happening?
I think that your main problem is here:
while (_queue.GetNextAvailableItem() != null)
{
//get next available item from queue.
FileItem item = _queue.GetNextAvailableItem();
You are calling the GetNextAvailableItem twice, and the value returned from the first call is discarded.
One way to solve this problem is this:
while (true)
{
//get next available item from queue.
FileItem item = _queue.GetNextAvailableItem();
if (item == null) break;
Of course you should also ensure that the MyQueue class is thread-safe, as Gabriel suggests in their answer.
To prevent the threads from influencing each other, a lock must be set. For this the lock statement is helpful. See: https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/statements/lock
For this purpose a lock object is defined:
private readonly object fileListLock = new object();
And then it is used in the GetNextAvailableItem() method:
public FileItem GetNextAvailableItem()
{
lock (fileListLock)
{
FileItem item = FileList.Where(i => i.isLocked == false).FirstOrDefault();
if (item != null) item.isLocked = true;
return item;
}
}
and also in the RemoveProcessedItem() method:
public void RemoveProcessedItem(FileItem item)
{
lock(fileListLock)
{
FileList.Remove(item);
}
}
I was able to rewrite my queue using ConcurrentQueue<T> This solved my issue.
code here:
internal class MyQueue
{
ConcurrentQueue<FileItem> FileList = new ConcurrentQueue<FileItem>();
public MyQueue(string FilePath)
{
string[] files = Directory.GetFiles(FilePath);
foreach (string file in files)
{
FileItem fileitem = new FileItem
{
FullFileName = file,
isLocked = false
};
FileList.Enqueue(fileitem);
}
}
public FileItem GetNextAvailableItem()
{
//Deque and return object.
FileItem DequeItem = new FileItem();
bool isDequeSuccess = FileList.TryDequeue(out DequeItem);
if (isDequeSuccess) return DequeItem;
else return null;
}
public bool PeekIfAnyFilesLeftInQueue()
{
FileItem PeekItem = new FileItem();
bool isFileExists = FileList.TryPeek(out PeekItem);
return isFileExists;
}
}
Also I had to change the invoking of these methods from multi threaded method.
static void ReadFromQueueAndMoveFile(string ThreadName)
{
do
{
//get next available item from queue.
FileItem item = _queue.GetNextAvailableItem();
if (item != null)
{
string FileName = Path.GetFileName(item.FullFileName);
string SourceFilePath = Path.Combine(sourcePath, FileName);
string DestinationFilePath = Path.Combine(destinationPath, FileName);
// Let's assume this is a long running process.
File.Move(SourceFilePath, DestinationFilePath);
Thread.Sleep(2000);
Console.WriteLine("Successfully moved: " + FileName + " Via " + ThreadName);
}
}
while (_queue.PeekIfAnyFilesLeftInQueue());
}

Content Observer doesn't monitor the DownloadManager's download progress on Android

I implemented the download functionality in my android app using the download manager.
The download manager dows its job well, and once download is completed, the broadcast receiver I set is called successfully.
But, I want to monitor the download progress and display it inside my app, and not rely only on the download manager's notification.
So, I implemented a "ContentProvider and a ContentObserver" to query frequently the download manager for download progress.
Here is my content provider:
[ContentProvider(new string[] { DownloadsContentProvider.Authority })]
public class DownloadsContentProvider : ContentProvider
{
public const string Authority = "com.myapp.Myapp.DownloadProvider";
public DownloadsContentProvider()
{
}
public static Android.Net.Uri ProviderUri(long downloadId)
{
Android.Net.Uri uri = Android.Net.Uri.Parse($"http://content//downloads/my_downloads/{downloadId}");
var builder = new Android.Net.Uri.Builder()
.Authority(Authority)
.Scheme(ContentResolver.SchemeFile)
.Path(uri.Path)
.Query(uri.Query)
.Fragment(uri.Fragment);
return builder.Build();
}
public override int Delete(Android.Net.Uri uri, string selection, string[] selectionArgs)
{
return 0;
}
public override string GetType(Android.Net.Uri uri)
{
return null;
}
public override Android.Net.Uri Insert(Android.Net.Uri uri, ContentValues values)
{
return null;
}
public override bool OnCreate()
{
return true;
}
public override ICursor Query(Android.Net.Uri uri, string[] projection, string selection, string[] selectionArgs, string sortOrder)
{
throw new NotImplementedException();
}
public override int Update(Android.Net.Uri uri, ContentValues values, string selection, string[] selectionArgs)
{
return 0;
}
}
Then, I created a content observer to observe what happens and trigger the query of downloads progress.
public DownloadProgressContentObserver(IntPtr javaReference, JniHandleOwnership transfer) : base(javaReference,
transfer)
{
}
public DownloadProgressContentObserver(Handler? handler) : base(handler)
{
}
public DownloadProgressContentObserver() : base(null)
{
}
public override void OnChange(bool selfChange, Uri? uri)
{
base.OnChange(selfChange, uri);
var downloadId = uri.ToString().Substring(uri.ToString().LastIndexOf(Path.PathSeparator) + 1);
if (!string.IsNullOrEmpty(downloadId))
{
ComputeDownloadStatus(Convert.ToInt64(downloadId));
//TODO: dispatch this download percentage to the whole app, and the database
}
}
public void ComputeDownloadStatus(long downloadId)
{
long downloadedBytes = 0;
long totalSize = 0;
int status = 0;
DownloadManager.Query query = new DownloadManager.Query().SetFilterById(downloadId);
var downloadManager = DownloadManager.FromContext(Android.App.Application.Context);
var cursor = downloadManager.InvokeQuery(query);
String downloadFilePath = (cursor.GetString(cursor.GetColumnIndex(DownloadManager.ColumnLocalUri))).Replace("file://", "");
try
{
if (cursor != null && cursor.MoveToFirst())
{
downloadedBytes =
cursor.GetLong(cursor.GetColumnIndexOrThrow(DownloadManager.ColumnBytesDownloadedSoFar));
totalSize =
cursor.GetInt(cursor.GetColumnIndexOrThrow(DownloadManager.ColumnTotalSizeBytes));
}
}
finally
{
if (cursor != null)
{
cursor.Close();
}
}
var percentage = (downloadedBytes / totalSize) * 100;
}
}
This is how I use both, and register them in the download manager to monitor the download progress.
var manager = DownloadManager.FromContext(Android.App.Application.Context);
var request = new DownloadManager.Request(Android.Net.Uri.Parse(downloadUrl));
request.SetNotificationVisibility(DownloadVisibility.VisibleNotifyCompleted);
request.SetDestinationInExternalPublicDir(downloadsPath, fileName);
request.SetTitle(productTitle);
request.SetDescription(downloadDescription);
long downloadId = manager.Enqueue(request);
//I provide a valid URI with my content provicer
var uri = DownloadsContentProvider.ProviderUri(downloadId);
var contentResolver = Android.App.Application.Context.ContentResolver;
var observer = new DownloadProgressContentObserver();
contentResolver.RegisterContentObserver(uri, true, observer);
ProductContentObservers.Add(downloadId, observer);
I have read a lot of doc, and my implementation seems to be ok. But the content observer's "OnCHange" method is never called.
Can someone please point out what I might be doing wrong ?

ObjectDisposedException when trying to upload a file

I have this service class:
public delegate string AsyncMethodCaller(string id, HttpPostedFileBase file);
public class ObjectService : IDisposable
{
private readonly IObjectRepository repository;
private readonly IAmazonS3 client;
private readonly string bucketName;
private static object syncRoot = new object();
private static IDictionary<string, int> processStatus { get; set; }
public ObjectService(string accessKey, string secretKey, string bucketName)
{
var credentials = new BasicAWSCredentials(accessKey, secretKey);
this.bucketName = bucketName;
this.client = new AmazonS3Client(credentials, RegionEndpoint.EUWest1);
this.repository = new ObjectRepository(this.client, this.bucketName);
if (processStatus == null)
processStatus = new Dictionary<string, int>();
}
public IList<S3Object> GetAll()
{
return this.repository.GetAll();
}
public S3Object Get(string key)
{
return this.GetAll().Where(model => model.Key.Equals(key, StringComparison.OrdinalIgnoreCase)).SingleOrDefault();
}
/// <summary>
/// Note: You can upload objects of up to 5 GB in size in a single operation. For objects greater than 5 GB you must use the multipart upload API.
/// Using the multipart upload API you can upload objects up to 5 TB each. For more information, see http://docs.aws.amazon.com/AmazonS3/latest/dev/uploadobjusingmpu.html.
/// </summary>
/// <param name="id">Unique id for tracking the upload progress</param>
/// <param name="bucketName">The name of the bucket that the object is being uploaded to</param>
/// <param name="file">The file that will be uploaded</param>
/// <returns>The unique id</returns>
public string Upload(string id, HttpPostedFileBase file)
{
var reader = new BinaryReader(file.InputStream);
var data = reader.ReadBytes((int)file.InputStream.Length);
var stream = new MemoryStream(data);
var utility = new TransferUtility(client);
var request = new TransferUtilityUploadRequest()
{
BucketName = this.bucketName,
Key = file.FileName,
InputStream = stream
};
request.UploadProgressEvent += (sender, e) => request_UploadProgressEvent(sender, e, id);
utility.Upload(request);
return id;
}
private void request_UploadProgressEvent(object sender, UploadProgressArgs e, string id)
{
lock (syncRoot)
{
processStatus[id] = e.PercentDone;
}
}
public void Add(string id)
{
lock (syncRoot)
{
processStatus.Add(id, 0);
}
}
public void Remove(string id)
{
lock (syncRoot)
{
processStatus.Remove(id);
}
}
public int GetStatus(string id)
{
lock (syncRoot)
{
if (processStatus.Keys.Count(x => x == id) == 1)
{
return processStatus[id];
}
else
{
return 100;
}
}
}
public void Dispose()
{
this.repository.Dispose();
this.client.Dispose();
}
}
and my controller looks like this:
public class _UploadController : Controller
{
public void StartUpload(string id, HttpPostedFileBase file)
{
var bucketName = CompanyProvider.CurrentCompanyId();
using (var service = new ObjectService(ConfigurationManager.AppSettings["AWSAccessKey"], ConfigurationManager.AppSettings["AWSSecretKey"], bucketName))
{
service.Add(id);
var caller = new AsyncMethodCaller(service.Upload);
var result = caller.BeginInvoke(id, file, new AsyncCallback(CompleteUpload), caller);
}
}
public void CompleteUpload(IAsyncResult result)
{
var caller = (AsyncMethodCaller)result.AsyncState;
var id = caller.EndInvoke(result);
}
//
// GET: /_Upload/GetCurrentProgress
public JsonResult GetCurrentProgress(string id)
{
try
{
var bucketName = CompanyProvider.CurrentCompanyId();
this.ControllerContext.HttpContext.Response.AddHeader("cache-control", "no-cache");
using (var service = new ObjectService(ConfigurationManager.AppSettings["AWSAccessKey"], ConfigurationManager.AppSettings["AWSSecretKey"], bucketName))
{
return new JsonResult { Data = new { success = true, progress = service.GetStatus(id) } };
}
}
catch (Exception ex)
{
return new JsonResult { Data = new { success = false, error = ex.Message } };
}
}
}
Now, I have found that sometimes, I get the error ObjectDisposedException when trying to upload a file on this line: var data = reader.ReadBytes((int)file.InputStream.Length);. I read that I should not be using the using keyword because of the asynchronous calls but it still seems to be disposing the stream.
Can anyone tell me why?
Update 1
I have changed my controller to this:
private ObjectService service = new ObjectService(ConfigurationManager.AppSettings["AWSAccessKey"], ConfigurationManager.AppSettings["AWSSecretKey"], CompanyProvider.CurrentCompanyId());
public void StartUpload(string id, HttpPostedFileBase file)
{
service.Add(id);
var caller = new AsyncMethodCaller(service.Upload);
var result = caller.BeginInvoke(id, file, new AsyncCallback(CompleteUpload), caller);
}
public void CompleteUpload(IAsyncResult result)
{
var caller = (AsyncMethodCaller)result.AsyncState;
var id = caller.EndInvoke(result);
this.service.Dispose();
}
but I am still getting the error on the file.InputStream line.
Update 2
The problem seems to be with the BinaryReader.
I changed the code to look like this:
var inputStream = file.InputStream;
var i = inputStream.Length;
var n = (int)i;
using (var reader = new BinaryReader(inputStream))
{
var data = reader.ReadBytes(n);
var stream = new MemoryStream(data);
var request = new TransferUtilityUploadRequest()
{
BucketName = this.bucketName,
Key = file.FileName,
InputStream = stream
};
try
{
request.UploadProgressEvent += (sender, e) => request_UploadProgressEvent(sender, e, id);
utility.Upload(request);
}
catch
{
file.InputStream.Dispose(); // Close our stream
}
}
If the upload fails and I try to re-upload the item, that is when the error is thrown. It is like the item is locked or something.
You are disposing the service with the using statement when you are calling the service with BeginInvoke.
using (var service = new ObjectService(ConfigurationManager.AppSettings["AWSAccessKey"], ConfigurationManager.AppSettings["AWSSecretKey"], bucketName))
{
service.Add(id);
var caller = new AsyncMethodCaller(service.Upload);
var result = caller.BeginInvoke(id, file, new AsyncCallback(CompleteUpload), caller);
}
You have to dispose your service when the job is done:
var service = new ObjectService(ConfigurationManager.AppSettings["AWSAccessKey"], ConfigurationManager.AppSettings["AWSSecretKey"], bucketName)
public void StartUpload(string id, HttpPostedFileBase file)
{
var bucketName = CompanyProvider.CurrentCompanyId();
service.Add(id);
var caller = new AsyncMethodCaller(service.Upload);
var result = caller.BeginInvoke(id, file, new AsyncCallback(CompleteUpload), caller);
}
public void CompleteUpload(IAsyncResult result)
{
var caller = (AsyncMethodCaller)result.AsyncState;
var id = caller.EndInvoke(result);
service.Close();
service.Dispose();
}
Also your file might be corrupted, try this code:
byte[] buffer = new byte[file.InputStream.Length];
file.InputStream.Seek(0, SeekOrigin.Begin);
file.InputStream.Read(buffer, 0, file.InputStream.Length);

Categories