I want to check a particular file exist in Azure Blob Storage. Is it possible to check by specifying it's file name? Each time i got File Not Found Error.
var blob = client.GetContainerReference(containerName).GetBlockBlobReference(blobFileName);
if (blob.Exists())
//do your stuff
This extension method should help you:
public static class BlobExtensions
{
public static bool Exists(this CloudBlob blob)
{
try
{
blob.FetchAttributes();
return true;
}
catch (StorageClientException e)
{
if (e.ErrorCode == StorageErrorCode.ResourceNotFound)
{
return false;
}
else
{
throw;
}
}
}
}
Usage:
static void Main(string[] args)
{
var blob = CloudStorageAccount.DevelopmentStorageAccount
.CreateCloudBlobClient().GetBlobReference(args[0]);
// or CloudStorageAccount.Parse("<your connection string>")
if (blob.Exists())
{
Console.WriteLine("The blob exists!");
}
else
{
Console.WriteLine("The blob doesn't exist.");
}
}
http://blog.smarx.com/posts/testing-existence-of-a-windows-azure-blob
With the updated SDK, once you have the CloudBlobReference you can call Exists() on your reference.
UPDATE
The relevant documentation has been moved to https://learn.microsoft.com/en-us/dotnet/api/microsoft.windowsazure.storage.blob.cloudblob.exists?view=azurestorage-8.1.3#Microsoft_WindowsAzure_Storage_Blob_CloudBlob_Exists_Microsoft_WindowsAzure_Storage_Blob_BlobRequestOptions_Microsoft_WindowsAzure_Storage_OperationContext_
My implementation using WindowsAzure.Storage v2.0.6.1
private CloudBlockBlob GetBlobReference(string filePath, bool createContainerIfMissing = true)
{
CloudBlobClient client = _account.CreateCloudBlobClient();
CloudBlobContainer container = client.GetContainerReference("my-container");
if ( createContainerIfMissing && container.CreateIfNotExists())
{
//Public blobs allow for public access to the image via the URI
//But first, make sure the blob exists
container.SetPermissions(new BlobContainerPermissions { PublicAccess = BlobContainerPublicAccessType.Blob });
}
CloudBlockBlob blob = container.GetBlockBlobReference(filePath);
return blob;
}
public bool Exists(String filepath)
{
var blob = GetBlobReference(filepath, false);
return blob.Exists();
}
Use the ExistsAsync method of CloudBlockBlob.
bool blobExists = await cloudBlobContainer.GetBlockBlobReference("<name of blob>").ExistsAsync();
Using Microsoft.WindowsAzure.Storage.Blob version 4.3.0.0, the following code should work (there are a lot of breaking changes with older versions of this assembly):
Using container/blob name, and the given API (seems now Microsoft have actualy implemented this):
return _blobClient.GetContainerReference(containerName).GetBlockBlobReference(blobName).Exists();
Using blob URI (workaround):
try
{
CloudBlockBlob cb = (CloudBlockBlob) _blobClient.GetBlobReferenceFromServer(new Uri(url));
cb.FetchAttributes();
}
catch (StorageException se)
{
if (se.Message.Contains("404") || se.Message.Contains("Not Found"))
{
return false;
}
}
return true;
(Fetch attributes will fail if the blob is not existing. Dirty, I know :)
Using the new package Azure.Storage.Blobs
BlobServiceClient blobServiceClient = new BlobServiceClient("YourStorageConnectionString");
BlobContainerClient containerClient = blobServiceClient.GetBlobContainerClient("YourContainerName");
BlobClient blobClient = containerClient.GetBlobClient("YourFileName");
then check if exists
if (blobClient.Exists()){
//your code
}
With the latest version of SDK, you need to use the ExistsAsync Method,
public async Task<bool> FileExists(string fileName)
{
return await directory.GetBlockBlobReference(fileName).ExistsAsync();
}
Here is the code sample.
This complete example is here to help.
public class TestBlobStorage
{
public bool BlobExists(string containerName, string blobName)
{
BlobServiceClient blobServiceClient = new BlobServiceClient(#"<connection string here>");
var container = blobServiceClient.GetBlobContainerClient(containerName);
var blob = container.GetBlobClient(blobName);
return blob.Exists();
}
}
then you can test in main
static void Main(string[] args)
{
TestBlobStorage t = new TestBlobStorage();
Console.WriteLine("blob exists: {0}", t.BlobExists("image-test", "AE665.jpg"));
Console.WriteLine("--done--");
Console.ReadLine();
}
Important I found the file names are case sensitive
## dbutils.widgets.get to call the key-value from data bricks job
storage_account_name= dbutils.widgets.get("storage_account_name")
container_name= dbutils.widgets.get("container_name")
transcripts_path_intent= dbutils.widgets.get("transcripts_path_intent")
# Read azure blob access key from dbutils
storage_account_access_key = dbutils.secrets.get(scope = "inteliserve-blob-storage-secret-scope", key = "storage-account-key")
from azure.storage.blob import BlockBlobService
block_blob_service = BlockBlobService(account_name=storage_account_name, account_key=storage_account_access_key)
def blob_exists():
container_name2 = container_name
blob_name = transcripts_path_intent
exists=(block_blob_service.exists(container_name2, blob_name))
return exists
blobstat = blob_exists()
print(blobstat)
Related
I am using Azure File share I want to create zip file only once but wants to update it multiple times (upload multiple files after once created).
is it possible to create .zip file only once and add more files in it later without **overriding **existing files in zip.?
when i tried to add more files in .zip it overrides existing files in zip with new file.
private static async Task OpenZipFile()
{
try
{
using (var zipFileStream = await OpenZipFileStream())
{
using (var zipFileOutputStream = CreateZipOutputStream(zipFileStream))
{
var level = 0;
zipFileOutputStream.SetLevel(level);
BlobClient blob = new BlobClient(new Uri(String.Format("https://{0}.blob.core.windows.net/{1}", "rtsatestdata", "comm/2/10029.txt")), _currentTenantTokenCredential);
var zipEntry = new ZipEntry("newtestdata")
{
Size = 1170
};
zipFileOutputStream.PutNextEntry(zipEntry);
blob.DownloadToAsync(zipFileOutputStream).Wait();
zipFileOutputStream.CloseEntry();
}
}
}
catch (TaskCanceledException)
{
throw;
}
}
private static async Task<Stream> OpenZipFileStream()
{
BlobContainerClient mainContainer = _blobServiceClient.GetBlobContainerClient("comm");
var blobItems = mainContainer.GetBlobs(BlobTraits.Metadata, BlobStates.None);
foreach (var item in blobItems)
{
if (item.Name == "testdata.zip")
{
BlobClient blob = new BlobClient(new Uri(String.Format("https://{0}.blob.core.windows.net/{1}", "rtsatestdata", "comm/testdata.zip")), _currentTenantTokenCredential);
return await blob.OpenWriteAsync(true
, options: new BlobOpenWriteOptions
{
HttpHeaders = new BlobHttpHeaders
{
ContentType = "application/zip"
}
}
);
}
}
}
private static ZipOutputStream CreateZipOutputStream(Stream zipFileStream)
{
return new ZipOutputStream(zipFileStream)
{
IsStreamOwner = false,
};
}
This is not possible in Azure storage. The workaround would be to download the zip, unzip it, add more files, re-zip it, and re-upload to storage.
I'm trying to print the name of the blobs in a container, however nothing prints after the line
List<BlobItem> segment = await blobContainer.GetBlobsAsync().ToListAsync();
Full code:
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Azure;
using Azure.Storage.Blobs;
using Azure.Storage.Blobs.Models;
using System.Data.Entity;
using Azure.Storage.Sas;
using System.Linq;
namespace IterateBlobs
{
/*Main code for iterating over blobs*/
class Program
{
static void Main(string[] args)
{
Task iterateBlobs = IterateBlobsAsync();
}
private static async Task IterateBlobsAsync()
{
String connect = "connection string";
BlobServiceClient sourceClient = new BlobServiceClient(connect);
BlobContainerClient blobContainer = sourceClient.GetBlobContainerClient("container name");
// Iterate through the blobs in a container
List<BlobItem> segment = await blobContainer.GetBlobsAsync().ToListAsync();
foreach (BlobItem blobItem in segment)
{
Console.WriteLine(blobItem.Name + " ");
BlobClient blob = blobContainer.GetBlobClient(blobItem.Name);
// Check the source file's metadata
Response<BlobProperties> propertiesResponse = await blob.GetPropertiesAsync();
BlobProperties properties = propertiesResponse.Value;
}
}
}
}
Glad that you have fixed the issue.
Thanks #n0rd posting your suggestion as an answer so that it will be beneficial to other community members.
If you are iterating the Blob containers and files you have to use the async Task.
In your code you are iterating the blob containers but in a main block it doesn't wait for all iteration to complete. It will return once get any response from IterateBlobsAsync. It will exit from the main methods that is the reason you are not getting any container information.
To avoid this, you have to follow the below
Modified Code
class Program
{
static async Task Main(string[] args)
{
Task iterateBlobs = await IterateBlobsAsync();
}
private static async Task IterateBlobsAsync()
{
String connect = "connection string";
BlobServiceClient sourceClient = new BlobServiceClient(connect);
BlobContainerClient blobContainer = sourceClient.GetBlobContainerClient("container name");
// Iterate through the blobs in a container
List<BlobItem> segment = await blobContainer.GetBlobsAsync();
foreach (BlobItem blobItem in segment)
{
Console.WriteLine(blobItem.Name + " ");
BlobClient blob = blobContainer.GetBlobClient(blobItem.Name);
// Check the source file's metadata
Response<BlobProperties> propertiesResponse = await blob.GetPropertiesAsync();
BlobProperties properties = propertiesResponse.Value;
}
}
}
}
Refer MSDOC to list the blob containers.
While trying to access all files of the Azure blob folder, getting sample code for container.ListBlobs(); however it looks like an old one.
Old Code : container.ListBlobs();
New Code trying : container.ListBlobsSegmentedAsync(continuationToken);
I am trying to use the below code :
container.ListBlobsSegmentedAsync(continuationToken);
Folders are like :
Container/F1/file.json
Container/F1/F2/file.json
Container/F2/file.json
Looking for the updated version to get all files from an Azure folder.
Any sample code would help, thanks!
C# code:
//connection string
string storageAccount_connectionString = "**NOTE: CONNECTION STRING**";
// Retrieve storage account from connection string.
CloudStorageAccount storageAccount = CloudStorageAccount.Parse(storageAccount_connectionString);
// Create the blob client.
CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
// Retrieve reference to a previously created container.
CloudBlobContainer container = blobClient.GetContainerReference("**NOTE:NAME OF CONTAINER**");
//The specified container does not exist
try
{
//root directory
CloudBlobDirectory dira = container.GetDirectoryReference(string.Empty);
//true for all sub directories else false
var rootDirFolders = dira.ListBlobsSegmentedAsync(true, BlobListingDetails.Metadata, null, null, null, null).Result;
foreach (var blob in rootDirFolders.Results)
{
Console.WriteLine("Blob", blob);
}
}
catch (Exception e)
{
// Block of code to handle errors
Console.WriteLine("Error", e);
}
Here is the code for the Answer :
private async Task<List<IListBlobItem>> ListBlobsAsync(CloudBlobContainer container)
{
BlobContinuationToken continuationToken = null;
List<IListBlobItem> results = new List<IListBlobItem>();
do
{
bool useFlatBlobListing = true;
BlobListingDetails blobListingDetails = BlobListingDetails.None;
int maxBlobsPerRequest = 500;
var response = await container.ListBlobsSegmentedAsync(BOAppSettings.ConfigServiceEnvironment, useFlatBlobListing, blobListingDetails, maxBlobsPerRequest, continuationToken, null, null);
continuationToken = response.ContinuationToken;
results.AddRange(response.Results);
}
while (continuationToken != null);
return results;
}
And then you can return values like:
IEnumerable<IListBlobItem> listBlobs = await this.ListBlobsAsync(container);
foreach(CloudBlockBlob cloudBlockBlob in listBlobs)
{
BOBlobFilesViewModel boBlobFilesViewModel = new BOBlobFilesViewModel
{
CacheKey = cloudBlockBlob.Name,
Name = cloudBlockBlob.Name
};
listBOBlobFilesViewModel.Add(boBlobFilesViewModel);
}
//return listBOBlobFilesViewModel;
Update:
Getting all files name from a directory with Azure.Storage.Blobs v12 - Package
var storageConnectionString = "DefaultEndpointsProtocol=...........=core.windows.net";
var blobServiceClient = new BlobServiceClient(storageConnectionString);
//get container
var container = blobServiceClient.GetBlobContainerClient("container_name");
List<string> blobNames = new List<string>();
//Enumerating the blobs may make multiple requests to the service while fetching all the values
//Blobs are ordered lexicographically by name
//if you want metadata set BlobTraits - BlobTraits.Metadata
var blobHierarchyItems = container.GetBlobsByHierarchyAsync(BlobTraits.None, BlobStates.None, "/");
await foreach (var blobHierarchyItem in blobHierarchyItems)
{
//check if the blob is a virtual directory.
if (blobHierarchyItem.IsPrefix)
{
// You can also access files under nested folders in this way,
// of course you will need to create a function accordingly (you can do a recursive function)
// var prefix = blobHierarchyItem.Name;
// blobHierarchyItem.Name = "folderA\"
// var blobHierarchyItems= container.GetBlobsByHierarchyAsync(BlobTraits.None, BlobStates.None, "/", prefix);
}
else
{
blobNames.Add(blobHierarchyItem.Blob.Name);
}
}
There are more option and example you can find it here.
This is the link to the nuget package.
The method CloudBlobClient.ListBlobsSegmentedAsync is used to return a result segment containing a collection of blob items in the container.
To list all blobs, we can use ListBlobs method,
Here is a demo for your reference:
public static List<V> ListAllBlobs<T, V>(Expression<Func<T, V>> expression, string containerName,string prefix)
{
CloudStorageAccount storageAccount = CloudStorageAccount.Parse("YourConnectionString;");
CloudBlobClient cloudBlobClient = storageAccount.CreateCloudBlobClient();
CloudBlobContainer container = cloudBlobClient.GetContainerReference(containerName);
container.CreateIfNotExists();
var list = container.ListBlobs(prefix: prefix,useFlatBlobListing: true);
List<V> data = list.OfType<T>().Select(expression.Compile()).ToList();
return data;
}
Usage and screenshots:
List all blobs' names under one folder:
List all blobs' URLs under one folder:
I'm trying to create a new MediaFileSystemProvider which seamlessly stores Media in Azure Blob Storage.
I copied the MediaFileSystem class from Umbraco v6.1 source for my starting point.
I then edited the /config/FileSystemProviders.config file inserting my new class details.
When I restart Umbraco, the new class is called but I get the error:
"Could not find constructor for type 'mysite.core.umbracoExtensions.FileSystemProviders.AzureBlobStorageProvider, mysite.core' which accepts 0 parameters"
Here's my class:
...
[FileSystemProvider("media")]
public class AzureBlobStorageProvider : FileSystemWrapper
{
private string rootUrl;
CloudStorageAccount storageAccount;
CloudBlobClient blobClient;
CloudBlobContainer container;
public AzureBlobStorageProvider(IFileSystem wrapped)
: base(wrapped)
{
var constring = ConfigurationManager.ConnectionStrings["StorageConnectionString"].ConnectionString;
// Retrieve storage account from connection string.
storageAccount = CloudStorageAccount.Parse(constring);
// Create the blob client.
blobClient = storageAccount.CreateCloudBlobClient();
// Retrieve reference to a previously created container.
container = blobClient.GetContainerReference("mymedia");
//container.CreateIfNotExists();
//container.SetPermissions(new BlobContainerPermissions { PublicAccess = BlobContainerPublicAccessType.Blob });
rootUrl = "https://mysite.blob.core.windows.net/media";
}
...
methods
Any idea what I'm doing wrong here?
Cheers
I couldn't leave this half done considering all the help I've received from folks here :)
I setup a fresh Umbraco v6.1.6 and confirmed the MediaService.Deleted event definitely does not get fired/hooked for me with the below code. I'll go find out how to submit a bug...
For anyone else interested in storing Umbraco Media Items in Azure Storage, below is how I did it. You can turn the CDN on/off for displaying image content with the "CDNEnabled" key and turn uploading on/off with the "AzureCDNUploadEnabled" key without having to touch your Views each time.
FYI, the actual CDN part of Azure Blob Storage is not available at the moment. It was and now isn't and apparently will be again some day.
You can limit your data usage and speed up image delivery by setting the "AzureCDNCacheControlHeader" value to update the Cache-Control header as you upload. The values below set the images to expire in 30 days and then revalidate.
Add this to your web.config appsettings node:
<!-- cdn config -->
<!-- used for razor rendering -->
<add key="CDNPath" value="https://utest.blob.core.windows.net"/>
<add key="CDNEnabled" value="true"/>
<!-- used for media uploads -->
<add key="AzureCDNStorageConnectionString" value="DefaultEndpointsProtocol=https;AccountName={yourAccount};AccountKey={yourKey}"/>
<add key="AzureCDNStorageAccountName" value="{yourStorageAccount}"/>
<add key="AzureCDNBlobContainerName" value="media"/>
<add key="AzureCDNRootUrl" value="https://{yourAccount}.blob.core.windows.net"/>
<add key="AzureCDNUploadEnabled" value="true"/>
<add key="AzureCDNCacheControlHeader" value="must-revalidate, public, max-age=604800"/> <!-- change to whatever suits you -->
<!-- end cdn -->
This is the EventHandler:
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Blob;
using System;
using System.Configuration;
using System.Web;
using System.Linq;
using System.IO;
using Umbraco.Core;
using Umbraco.Core.Events;
using Umbraco.Core.Models;
using Umbraco.Core.Services;
namespace utest1.umbracoExtensions.events
{
public class SaveMediaToAzure : ApplicationEventHandler
{
/* either add your own logging class or remove this and all calls to 'log' */
private log4net.ILog log = log4net.LogManager.GetLogger(typeof(utest1.logging.PublicLogger));
CloudStorageAccount storageAccount;
private string blobContainerName;
CloudBlobClient blobClient;
CloudBlobContainer container;
string cacheControlHeader;
private bool uploadEnabled;
public SaveMediaToAzure()
{
try
{
storageAccount = CloudStorageAccount.Parse(ConfigurationManager.AppSettings["AzureCDNStorageConnectionString"]);
blobContainerName = ConfigurationManager.AppSettings["AzureCDNStorageAccountName"];
blobClient = storageAccount.CreateCloudBlobClient();
container = blobClient.GetContainerReference(ConfigurationManager.AppSettings["AzureCDNBlobContainerName"]);
uploadEnabled = Convert.ToBoolean(ConfigurationManager.AppSettings["AzureCDNUploadEnabled"]);
cacheControlHeader = ConfigurationManager.AppSettings["AzureCDNCacheControlHeader"];
MediaService.Saved += MediaServiceSaved;
MediaService.Trashed += MediaServiceTrashed;
MediaService.Deleted += MediaServiceDeleted; // not firing
}
catch (Exception x)
{
log.Error("SaveMediaToAzure Config Error", x);
}
}
void MediaServiceSaved(IMediaService sender, SaveEventArgs<IMedia> e)
{
if (uploadEnabled)
{
foreach (var fileItem in e.SavedEntities)
{
try
{
log.Info("Saving media to Azure:" + e.SavedEntities.First().Name);
var path = fileItem.GetValue("umbracoFile").ToString();
var filePath = HttpContext.Current.Server.MapPath(path);
UploadToAzure(filePath, path);
if (fileItem.GetType() == typeof(Umbraco.Core.Models.Media))
{
UploadThumbToAzure(filePath, path);
}
}
catch (Exception x)
{
log.Error("Error saving media to Azure: " + fileItem.Name, x);
}
}
}
}
/*
* Using this because MediaServiceDeleted event is not firing in v6.1.6
*
*/
void MediaServiceTrashed(IMediaService sender, MoveEventArgs<IMedia> e)
{
if (uploadEnabled)
{
try
{
log.Info("Deleting media from Azure:" + e.Entity.Name);
var path = e.Entity.GetValue("umbracoFile").ToString();
CloudBlockBlob imageBlob = container.GetBlockBlobReference(StripContainerNameFromPath(path));
imageBlob.Delete();
CloudBlockBlob thumbBlob = container.GetBlockBlobReference(StripContainerNameFromPath(GetThumbPath(path)));
thumbBlob.Delete();
}
catch (Exception x)
{
log.Error("Error deleting media from Azure: " + e.Entity.Name, x);
}
}
}
/*
* MediaServiceDeleted event not firing in v6.1.6
*
*/
void MediaServiceDeleted(IMediaService sender, DeleteEventArgs<IMedia> e)
{
//if (uploadEnabled)
//{
// try
// {
// log.Info("Deleting media from Azure:" + e.DeletedEntities.First().Name);
// var path = e.DeletedEntities.First().GetValue("umbracoFile").ToString();
// CloudBlockBlob imageBlob = container.GetBlockBlobReference(StripContainerNameFromPath(path));
// imageBlob.Delete();
// CloudBlockBlob thumbBlob = container.GetBlockBlobReference(StripContainerNameFromPath(GetThumbPath(path)));
// thumbBlob.Delete();
// }
// catch (Exception x)
// {
// log.Error("Error deleting media from Azure: " + e.DeletedEntities.First().Name, x);
// }
//}
Console.WriteLine(e.DeletedEntities.First().Name); // still not working
}
private string StripContainerNameFromPath(string path)
{
return path.Replace("/media/", String.Empty);
}
/*
*
*
*/
private void UploadToAzure(string filePath, string relativePath)
{
System.IO.MemoryStream data = new System.IO.MemoryStream();
System.IO.Stream str = System.IO.File.OpenRead(filePath);
str.CopyTo(data);
data.Seek(0, SeekOrigin.Begin);
byte[] buf = new byte[data.Length];
data.Read(buf, 0, buf.Length);
Stream stream = data as Stream;
if (stream.CanSeek)
{
stream.Position = 0;
CloudBlockBlob blob = container.GetBlockBlobReference(StripContainerNameFromPath(relativePath));
blob.UploadFromStream(stream);
SetCacheControl(blob);
}
else
{
log.Error("Could not read image for upload: " + relativePath);
}
}
private void SetCacheControl(CloudBlockBlob blob)
{
blob.Properties.CacheControl = cacheControlHeader;
blob.SetProperties();
}
private void UploadThumbToAzure(string filePath, string relativePath)
{
var thumbFilePath = GetThumbPath(filePath);
var thumbRelativePath = GetThumbPath(relativePath);
UploadToAzure(thumbFilePath, thumbRelativePath);
}
private string GetThumbPath(string path)
{
var parts = path.Split('.');
var filename = parts[parts.Length - 2];
return path.Replace(filename, filename + "_thumb");
}
}
}
This is the RenderHelper:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace utest1.umbracoExtensions.helpers
{
public class CDNImage
{
public static string ConvertUrlToCDN(string source)
{
if (String.IsNullOrEmpty(source))
{
return null;
}
var cdnUrl = System.Configuration.ConfigurationManager.AppSettings["CDNPath"];
var cdnOn = System.Configuration.ConfigurationManager.AppSettings["CDNEnabled"];
if (cdnOn == "true")
{
/*
* check if the url is absolute or not and whether it should be intercepted - eg. an external image url
* if it's absolute you'll need to strip out everything before /media...
*/
if (source.Contains(GetBaseUrl()))
{
source = StripBaseUrl(source);
}
}
return source;
}
private static string GetBaseUrl()
{
var url = System.Web.HttpContext.Current.Request.Url;
var baseUrl = url.Scheme + "//" + url.Host;
if (url.Port != 80 && url.Port != 443)
{
baseUrl += ":" + url.Port;
}
return baseUrl;
}
private static string StripBaseUrl(string path)
{
return path.Replace(GetBaseUrl(), String.Empty);
}
}
}
and finally displaying in the RazorView:
#inherits Umbraco.Web.Mvc.UmbracoTemplatePage
#{
Layout = "BasePage.cshtml";
}
#using utest1.umbracoExtensions.helpers
#{
var ms = ApplicationContext.Current.Services.MediaService;
var img = ms.GetById(int.Parse(CurrentPage.Image));
}
<h1>Umbraco on Azure is getting there!</h1>
<p>#img.Name</p>
<img alt="#img.Name" src="#CDNImage.ConvertUrlToCDN(img.GetValue("umbracoFile").ToString())" />
Suggestions for improvement are welcome.
Aaaah, it feels good to give back :)
Why is it that I spend hours trying to find the answer and then find it soon after I post?
The issue was two fold:
1) I should've been implementing IFileSystem (took inspiration from AmazonS3Provider source)
2) the parameter name being passed from FileSystemProviders.config file was not included in the constructor
I'm trying to make an epub parsing app in a Windows Store with C#, and it won't wait for the archive (epubs are actually zip files) to finish extracting before it tries to parse the not-yet-existing table of contents. How do I make my app be a bit more patient?
I've tried making my UnZip() function return a task and having the epub constructor (epub is a class) use UnZip().Wait(), but that just freezes the app. What do I do?
Edit: Here's my relevant code:
public class epub
{
public string filename;
private StorageFolder unzipFolder;
private IList<epubChapter> _contents;
private bool _parsed = false;
public bool parsed { get { return _parsed; } } //Epub and contents are fully parsed
public epub(string newFilename)
{
_contents = new List<epubChapter>();
filename = newFilename;
UnZipFile().Wait();
getTableOfContents();
}
private async Task UnZipFile()
{
var sourceFolder = Windows.ApplicationModel.Package.Current.InstalledLocation;
StorageFolder localFolder = ApplicationData.Current.LocalFolder;
unzipFolder = await localFolder.CreateFolderAsync(filename, CreationCollisionOption.OpenIfExists);
using (var zipStream = await sourceFolder.OpenStreamForReadAsync(filename))
{
using (MemoryStream zipMemoryStream = new MemoryStream((int)zipStream.Length))
{
await zipStream.CopyToAsync(zipMemoryStream);
using (var archive = new ZipArchive(zipMemoryStream, ZipArchiveMode.Read))
{
foreach (ZipArchiveEntry entry in archive.Entries)
{
if (entry.Name != "")
{
using (Stream fileData = entry.Open())
{
try
{
await unzipFolder.GetFileAsync(entry.Name);
Debug.WriteLine("File at {0} already exists", entry.Name);
continue;
}
catch (FileNotFoundException)
{
Debug.WriteLine("Creating file {0}", entry.Name);
}
StorageFile outputFile = await unzipFolder.CreateFileAsync(entry.Name, CreationCollisionOption.OpenIfExists);
//Debug.WriteLine("Output file created at {0}", outputFile.Path);
using (Stream outputFileStream = await outputFile.OpenStreamForWriteAsync())
{
await fileData.CopyToAsync(outputFileStream);
await outputFileStream.FlushAsync();
}
}
if (entry.Name == "toc.ncx")
{
Debug.WriteLine("toc.ncx found in epub file; parsing it");
getTableOfContents();
}
}
}
}
}
}
}
public void getTableOfContents()
{
string contentsPath = unzipFolder.Path + #"\toc.ncx"; //The file is always called this in valid epubs
try
{
XDocument toc = XDocument.Load(contentsPath);
string nameSpace = getNameSpace(toc);
XElement navMap = firstElementNamed(toc.Root, "navMap");
parseNavPoints(navMap, nameSpace, 0);
_parsed = true;
}
catch(FileNotFoundException)
{
Debug.WriteLine("File toc.ncx was not found!");
}
}
Basically, your question seems to be: How do I call an async method from a constructor?
The short answer is that you don't, instead create an async factory method for your class.
Longer answer: As you noticed, if you call Wait(), your code will block. You can't use await, because constructors can't be async. And if you don't do anything, the constructor is going to return too early.
The solution here is to use an async factory method instead of a constructor. Something like:
private epub(string newFilename)
{
_contents = new List<epubChapter>();
filename = newFilename;
}
public static async Task<epub> CreateAsync(string newFilename)
{
var result = new epub(newFilename);
await result.UnZipFile();
result.getTableOfContents();
return result;
}
For some more information and alternative solutions, see Stephen Cleary's article about async and contructors.