I’m trying to fulfill requirements:
- Select a ZIP file on Silverlight side
- Upload it to server
- Unpack it
- Search over extracted file and find manifest.xml
- Collect information from manifest
- Zip file itself it not required anymore and can be deleted from server file system.
- Return information from manifest to Silverlight calling page
For that purpose I implemented following handler:
/// <summary> /// Handler for managing files uploaded by LearningObjectUploadWindow.xaml /// </summary> public class RadUploadLearningObjectHandler : Telerik.Windows.RadUploadHandler { /// <summary> /// this dictionary will be used to transfer data extracted from zip file to calling silverlight control /// </summary> private Dictionary<string, object> _dict; /// <summary> /// files are sent in chunks of data /// </summary> /// <param name="filePath"></param> /// <param name="position"></param> /// <param name="buffer"></param> /// <param name="contentLength"></param> /// <param name="savedBytes"></param> /// <returns></returns> public override bool SaveChunkData(string filePath, long position, byte[] buffer, int contentLength, out int savedBytes) { if (this.IsNewFileRequest()) { PrepareStorageFolder(); } bool result = base.SaveChunkData(filePath, position, buffer, contentLength, out savedBytes); // Checks if this is the last chunk of each file if (this.IsFinalFileRequest()) { // Get the names of all the files that have been uploaded so far string uploadedFiles = this.GetQueryParameter("UploadedFiles"); //if the file is successfully uploaded if (result) { // attach the name of the last file uploadedFiles += this.GetFilePath(); // format the string uploadedFiles = uploadedFiles.Replace(@"\\", @"\"); // create a collection of filenames string[] fileNames = uploadedFiles.Split('\n'); ProcessUploadedFiles(fileNames); } } return result; } /// <summary> /// after files are uploaded and before additional informations are transfered to calling silverlight control /// </summary> /// <param name="fileNames"></param> private void ProcessUploadedFiles(string[] fileNames) { _dict = base.GetAssociatedData(); foreach (var filename in fileNames) { if (filename.EndsWith(".zip")) { _dict.Add("FileType", "ZIP"); string folderPath = this.GetTargetFolder(); //extract all files from archieve using (var package = ZipPackage.OpenFile(filename, FileAccess.Read)) { foreach (var entry in package.ZipPackageEntries) { if (entry.Attributes != FileAttributes.Directory) { UnpackAnSaveFile(entry, folderPath); } } } //PROBLEM! //File is locked by exctractor (or by handler) for a while so it can not be deleted at this moment //todo dkos 20111006 find a way to exctract and delete zip file using another aproach File.Delete(filename); //read manifest file var manifestFileName = folderPath + "\\manifest.xml"; if (File.Exists(manifestFileName)) { ExtractDataFromManifest(manifestFileName); } } else { _dict.Add(Constants.LEARNING_OBJECT_START_PATH_KEY, this.GetFileName()); _dict.Add(Constants.LEARNING_OBJECT_TYPE_KEY, "DOCU"); } } } /// <summary> /// return all data added to dictionary in ProcessUploadedFiles() /// </summary> /// <returns></returns> public override Dictionary<string, object> GetAssociatedData() { return _dict; } /// <summary> /// reading info from manifest file /// </summary> /// <param name="manifestPath"></param> private void ExtractDataFromManifest(string manifestPath) { try { // try to load the manifest.xml file XmlDocument doc = new XmlDocument(); doc.Load(manifestPath); XmlElement root = (XmlElement)doc.GetElementsByTagName("manifest")[0]; // get obligate startUrl var startUrl = root.GetElementsByTagName(Constants.LEARNING_OBJECT_START_PATH_KEY)[0].InnerText; _dict.Add(Constants.LEARNING_OBJECT_START_PATH_KEY, startUrl); // set the optional defaults: // ..short name XmlNodeList nl = root.GetElementsByTagName(Constants.LEARNING_OBJECT_SHORT_NAME_KEY); if (nl.Count == 1) if (nl[0].InnerText.Length > 10) _dict.Add(Constants.LEARNING_OBJECT_SHORT_NAME_KEY, nl[0].InnerText.Substring(0, 10)); else _dict.Add(Constants.LEARNING_OBJECT_SHORT_NAME_KEY, nl[0].InnerText); // ..name nl = root.GetElementsByTagName(Constants.LEARNING_OBJECT_NAME_KEY); if (nl.Count == 1) _dict.Add(Constants.LEARNING_OBJECT_NAME_KEY, nl[0].InnerText); // ..description nl = root.GetElementsByTagName(Constants.LEARNING_OBJECT_DESCRIPTION_KEY); if (nl.Count == 1) _dict.Add(Constants.LEARNING_OBJECT_DESCRIPTION_KEY, nl[0].InnerText); // ..languageId nl = root.GetElementsByTagName(Constants.LEARNING_OBJECT_LANGUAGE_KEY); if (nl.Count == 1) _dict.Add(Constants.LEARNING_OBJECT_LANGUAGE_KEY, nl[0].InnerText); // ..keywords nl = root.GetElementsByTagName(Constants.LEARNING_OBJECT_KEYWORDS_KEY); if (nl.Count == 1) _dict.Add(Constants.LEARNING_OBJECT_KEYWORDS_KEY, nl[0].InnerText); // ..type nl = root.GetElementsByTagName(Constants.LEARNING_OBJECT_TYPE_KEY); if (nl.Count == 1) _dict.Add(Constants.LEARNING_OBJECT_TYPE_KEY, nl[0].InnerText.ToUpper()); // ..workTimeMinutes nl = root.GetElementsByTagName(Constants.LEARNING_OBJECT_WORK_TIME_MINUTES_KEY); if (nl.Count == 1) try { _dict.Add(Constants.LEARNING_OBJECT_WORK_TIME_MINUTES_KEY, int.Parse(nl[0].InnerText)); } catch (Exception exc) { throw new Exception("workTimeMinutes property is not integer", exc); } // ..contentAuthor nl = root.GetElementsByTagName(Constants.LEARNING_OBJECT_CONTENT_AUTHOR_KEY); if (nl.Count == 1) _dict.Add(Constants.LEARNING_OBJECT_CONTENT_AUTHOR_KEY, nl[0].InnerText); } catch (Exception ex) { throw new Exception("Error in reading manifest file", ex); } } /// <summary> /// exctracting each file from zip archive /// </summary> /// <param name="entry"></param> /// <param name="folderPath"></param> private static void UnpackAnSaveFile(ZipPackageEntry entry, string folderPath) { Stream reader = entry.OpenInputStream(); var fileName = folderPath + "\\" + entry.FileNameInZip; var directoryName = Path.GetDirectoryName(fileName); if (directoryName != null) { if (!Directory.Exists(directoryName)) { Directory.CreateDirectory(directoryName); } FileStream writer = File.Create(folderPath + "\\" + entry.FileNameInZip); int size = 2048; byte[] data = new byte[2048]; while (true) { size = reader.Read(data, 0, data.Length); if (size > 0) writer.Write(data, 0, size); else break; } writer.Close(); } } internal void PrepareStorageFolder() { string folderPath = this.GetTargetFolder(); if (Directory.Exists(folderPath)) { //todo dkos 20111006 choose: do we want to delete old content or to move it into archive //note: both proces will fail if modification is immediately after old content is uploaded (problem with locking ZIP archive - lines: 85-96) //move to archive var dest = folderPath + "_" + DateTime.Now.ToString("yyyyMMdd_hhmmss"); Directory.Move(folderPath, dest); //delete //Directory.Delete(folderPath,true); } Directory.CreateDirectory(folderPath); if (!Directory.Exists(folderPath)) { throw new DirectoryNotFoundException(folderPath); } } }
Problem starts when I want to delete ZIP file (line 100: //File.Delete(filename);)
I can live with the fact that ZIP file remains on the server but there is another scenario:
If user wants to modify Learning Object by uploading new archive, entire content in (existing) folder has to be deleted first.
But piece of code:
internal void PrepareStorageFolder() { string folderPath = this.GetTargetFolder(); if (Directory.Exists(folderPath)) { //todo dkos 20111006 choose: do we want to delete old content or to move it into archive //note: both proces will fail if modification is immediately after old content is uploaded (problem with locking ZIP archive - lines: 85-96) //move to archive var dest = folderPath + "_" + DateTime.Now.ToString("yyyyMMdd_hhmmss"); Directory.Move(folderPath, dest); //delete //Directory.Delete(folderPath,true); } Directory.CreateDirectory(folderPath); if (!Directory.Exists(folderPath)) { throw new DirectoryNotFoundException(folderPath); } }
Fails with same error if I try to modify Learning Object immediately after uploading it:
{"The process cannot access the file 'D:\\Projects\\KV-Reform\\Organizer\\production\\src\\T2LOrganizer.Web\\app\\learn_catalog\\content\\e2b00a83-df4b-4c10-b2c8-0269a7a0e7b9\\ST_711_08d.zip' because it is being used by another process."}
at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)
at System.IO.File.Delete(String path)
at T2LOrganizer.Web.AspHandlers.RadUploadLearningObjectHandler.ProcessUploadedFiles(String[] fileNames) in D:\Projects\KV-Reform\Organizer\production\src\T2LOrganizer.Web\AspHandlers\RadUploadLearningObjectHandler.ashx.cs:line 100
at T2LOrganizer.Web.AspHandlers.RadUploadLearningObjectHandler.SaveChunkData(String filePath, Int64 position, Byte[] buffer, Int32 contentLength, Int32& savedBytes) in D:\Projects\KV-Reform\Organizer\production\src\T2LOrganizer.Web\AspHandlers\RadUploadLearningObjectHandler.ashx.cs:line 60
at Telerik.Windows.RadUploadHandler.ProcessStream()
If I try later (e.g. 10 minutes after) everything is fine.
Something is blocking uploaded file for a while: handler it selves or ZipPackage.
Any idea how to solve this?
Thanks in advance.
Dekos