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