I'm trying to implement what I'd call a "one time cache" for specific files in my ASP.NET 3.5 site. Upon request, the file is downloaded from a remote file server onto the web server, then served up by my page. The problem arises when I go to delete it. If I delete the file in the OnUnload method, the client doesn't have time to retrieve it. Is there some way of detecting the download of a file and deleting it immediately after it is first accessed? My code is like this:
private bool _deleteFlag = false;
private string _deleteFile = string.Empty;
protected void Page_Load(object sender, EventArgs e) {
if (!string.IsNullOrEmpty(Request.QueryString["file"])) {
_deleteFlag = Request.QueryString["file"].Contains(#"cache/");
_deleteFile = Request.QueryString["file"];
}
}
protected override void OnUnload(EventArgs e) {
if (_deleteFlag) {
// Can't delete here; the client is still trying to retrieve it.
System.IO.File.Delete(Server.MapPath(_deleteFile));
}
base.OnUnload(e);
}
I'm leaning towards writing an IHttpHandler that overrides the file request, but that feels like overkill.
Try this:
When you add your file, insert it into the HttpRuntimeCache and set the Key to the FileName and set the TimeOut to say 10 seconds. Then on the CacheItemRemoved callback, delete the file. It's not exact, but it should roughly meet your use case.
public static object LoadFile()
{
var filename = ParseFileNameFromHttpRequest();
var a = loadFileSomeHow(filename);
HttpRuntime.Cache.Insert(
filename ,
a,
null,
System.Web.Caching.Cache.NoAbsoluteExpiration,
new TimeSpan(0, 0, 0,10,0,0),
CacheItemPriority.Default,
new CacheItemRemovedCallback(DeleteFile));
return a;
}
public void DeleteFile(String key, object value,
CacheItemRemovedReason removedReason)
{
File.Delete(key);
}
For more info: http://msdn.microsoft.com/en-us/library/vstudio/7kxdx246(v=vs.100).aspx
Related
I'm trying to read every #define in a C++ header, and display an editor for each of the values, so I can quickly redefine the constants.
I'm trying to parse the file, getting each item on the left side to be key. I want to display the value on the left, edit the values and save back out. Currently, when I save my file it messes up the format.
This is the code i currently have :
private String header;
private Dictionary<String, String> tokens;
private String file = Properties.Settings.Default.path_location;
public TextBox elf_naparm_zombie_developer;
public harrs_gsh_editor(string file)//TextBox elf_naparm_zombie_developer
{
Properties.Settings.Default.path_location = file;
Properties.Settings.Default.Save();
loadweapon();
// this.elf_naparm_zombie_developer = elf_naparm_zombie_developer;
}
public void loadweapon()
{
this.tokens = File.ReadLines(this.file)
.Select(line => Regex.Match(line, #"^\s*#define\s+(.+?)(\s+(.+?))?((\s*//)|$)"))
.Where(m => m.Success && m.Groups[3].Success)
.ToDictionary(m => m.Groups[1].Value, m => m.Groups[3].Value);
}
public String Search(String name)
{
foreach (KeyValuePair<String, String> pair in tokens)
{
if (pair.Key == name)
return pair.Value;
}
return null;
}
public void Set(String key, String val)
{
tokens[key] = val;
}
public void Save(String file)
{
using (StreamWriter sw = new StreamWriter(File.OpenWrite(file)))
{
sw.Write(header);
foreach (KeyValuePair<String, String> pair in tokens)
{
sw.Write("\r\n" + pair.Key + "#define " + pair.Value);
}
}
}
public void Save()
{
this.Save(file);
}
The issue I have is when I save it back, it messes up the file and removes the #define. What im trying to do is just change the value and write it back to the program with changed value
this is the file i want to edit
So what im trying to do is load up the selected key and display the value witch i have managed to do with this code i created but the issue im having is writing it back
the code i use to load and populate the text box
namespace Harry_s_Template_Editor
{
public partial class Form1 : Form
{
harrs_gsh_editor elfghc;
string path = System.Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) + #"/hb21_zm_ai_napalm.gsh";
public Form1()
{
InitializeComponent();
elfghc = new harrs_gsh_editor(path);//TextBox elf_naparm_zombie_developer
elf_naparm_zombie_developer = elf_naparm_zombie_developer;
// MessageBox.Show(path);
}
private void button1_Click(object sender, EventArgs e)
{
elfghc.loadweapon();
this.elf_naparm_zombie_developer.Text = elfghc.Search("NAPALM_ZOMBIE_DEVELOPER_DEBUG_PRINTS");
elfghc.Set("NAPALM_ZOMBIE_DEVELOPER_DEBUG_PRINTS", elf_naparm_zombie_developer.Text);
}
private void Form1_Load(object sender, EventArgs e)
{
}
private void button2_Click(object sender, EventArgs e)
{
elfghc.Set("NAPALM_ZOMBIE_DEVELOPER_DEBUG_PRINTS", elf_naparm_zombie_developer.Text);
elfghc.Save(path);
}
}
}
when i save out the file i now get this error when i try to load the program again
error
thanks in advance Yuki
file when saved back
Original answer:
Why are you writing it back to the file in this order: sw.Write("\r\n" + pair.Key + "#define " + pair.Value);? Shouldn't the #define come first on the line after \r\n? I would have thought it should be something like this: sw.Write("\r\n#define " + pair.Key + " " + pair.Value);
New answer (the question has changed)
There are two problems with what you are doing:
You are writing key/value pairs back to a file, ignoring the headers and credits that were in the original file. If you're happy to strip those out then that's fine, but it sounds like this is important to you. So instead you need to read the file, find the keys in the file, then update the values and save the updated file back to disk. That's a harder problem than simply writing a bunch of key/value pairs to a file.
The second issue is the way you are writing the file back to disk. According to this MSDN page, File.OpenWrite has the following behaviour:
The OpenWrite method opens a file if one already exists for the file
path, or creates a new file if one does not exist. For an existing
file, it does not append the new text to the existing text. Instead,
it overwrites the existing characters with the new characters. If you
overwrite a longer string (such as “This is a test of the OpenWrite
method”) with a shorter string (such as “Second run”), the file will
contain a mix of the strings (“Second runtest of the OpenWrite
method”).
So this is why you're getting a mangled file. You're overwriting the first N bytes with your updated key/value pairs, and the rest of the file is duplicated data from the original file. You probably want to use the following instead, which will overwrite the original file entirely:
using (StreamWriter sw = new StreamWriter(file, false))
I'm trying to build a PostScript to PDF Converter using Ghostscript.Net.
The Args that GetArgs return, are the ones I usually use to call gswin32c.exe and they work fine.
But every time i call Process, i get an error Saying "An error occured when call to 'gsapi_init_with_args' is made: -100". Googling that error didn't bring anything up so I thought I might ask here.
Are there differnet arguments to consider when calling the .dll directly with Ghostscript.net? Or did I made a mistake somewhere else?
Here's my class:
public class PdfConverter
{
#region Private Fields
private List<GhostscriptVersionInfo> _Versions = GhostscriptVersionInfo.GetInstalledVersions(GhostscriptLicense.GPL | GhostscriptLicense.AFPL | GhostscriptLicense.Artifex);
#endregion
#region Private Properties
private GhostscriptVersionInfo Version { get; set; }
#endregion
#region Construction
public PdfConverter()
{
Version = GhostscriptVersionInfo.GetLastInstalledVersion();
}
#endregion
#region Public Members
public bool ConvertToPdf(DirectoryInfo dir)
{
var d = dir;
if(!d.Exists)
return false;
var postScriptFiles = d.GetFiles("*.ps");
var pdfFiles = postScriptFiles.Select(psf => new FileInfo(Path.ChangeExtension(psf.FullName, ".pdf")));
foreach(var file in postScriptFiles) {
//ThreadPool.QueueUserWorkItem(new WaitCallback((o) => {
Process(file, new FileInfo(Path.ChangeExtension(file.FullName, ".pdf")));
//}));
}
pdfFiles.ForEach(pdf => pdf?.Refresh());
return pdfFiles.All(pdf => pdf.Exists);
}
#endregion
#region Private Helpers
private void Process(FileInfo inputFile, FileInfo outputFile)
{
Console.WriteLine($"Converting {inputFile} to {outputFile}");
var proc = new GhostscriptProcessor(Version, true);
proc.Process(GetArgs(inputFile, outputFile).ToArray(), new ConsoleStdIO(true, true, true));
}
private IEnumerable<string> GetArgs(FileInfo inputFile, FileInfo outputFile)
{
return new [] {
$"-q ",
$"-sDEVICE=pdfwrite",
$"-dSAFER",
$"-dNOPAUSE",
$"-dBATCH",
$"-sPAPERSIZE=a4",
$"-dEmbedAllFonts=true",
$"-dAutoRotatePages=/None",
$"-sOutputFile=\"{outputFile.FullName}\"",
$"-dCompatibilityLevel#1.4",
$"-c .setpdfwrite",
$"-f \"{inputFile.FullName}\""
};
}
#endregion
}
Edit:
I forgot to mention: To implement it i had to make my own GhostsdcriptStdIO class. I admit that I'm not entirely sure if I did this right. Although it does get instanciated without exceptions, and override StdOut(...) get's called, and the output is written to the console as expected. override void StdError(...) get's called as well. And also written to the console as expeted.
The Output of the error btw is:
"**** Could not open the file "c:\temp\test.pdf""
"**** Unable to open the initial device, quitting."
Here's my ConsoleStdIO class:
public class ConsoleStdIO : Ghostscript.NET.GhostscriptStdIO
{
#region Construction
public ConsoleStdIO(bool handleStdIn, bool handleStdOut, bool handleStdError) : base(handleStdIn, handleStdOut, handleStdError) { }
#endregion
#region Overrides
public override void StdError(string error)
{
var foo = Encoding.Default.GetBytes(error);
var lenght = foo.Length;
using (var err = Console.OpenStandardError()) {
if(err.CanWrite)
err.Write(foo, 0, lenght);
}
}
public override void StdIn(out string input, int count)
{
byte[] bytes = new byte[0];
using(var stdInput = Console.OpenStandardInput()) {
stdInput.Read(bytes, 0, count);
}
input = Encoding.Default.GetString(bytes);
}
public override void StdOut(string output)
{
var foo = Encoding.Default.GetBytes(output);
var lenght = foo.Length;
using (var err = Console.OpenStandardError()) {
if(err.CanWrite)
err.Write(foo, 0, lenght);
}
}
#endregion
}
Again: doing the same operation with the exact same files and arguments using gswin32c.exe works fine.
Happy Hacking
Error -100 is gs_error_Fatal, which means 'something catastrophic went wrong'. Its an indication that the program failed to start up properly and we can't tell why. The back channel may contain more information.
And indeed, the back channel tells you what's wrong:
**** Could not open the file "c:\temp\test.pdf
**** Unable to open the initial device, quitting.
Ghostscript is unable to open the output file, which means it can't open the pdfwrite device (because that requires an output file) so it aborts the operation.
There could be a number of reasons why Ghostscript can't open the output file. The first thing I'd do is trim down the number of arguments;
You don't want -q (quiet) when you are trying to debug a problem, you want all the information you can get.
I'd remove -dSAFER at least to start with, because that prevents Ghostscript accessing directories outside the current working directory and certain 'special' ones. It may well prevent you accessing the temp directory.
You don't need to set EmbedAllFonts when its the same value as the default.
You could drop the CompatibilityLevel (and note that you've used a # there instead of an =) switch, and the AutoRotatePages while getting this to work.
The "-c .setpdfwrite -f" string has been pointless for years but people still keep using it. All that does these days is slow down the start of processing, ditch it.
Finally you can try changing the backslash ('\') characters to forward slash ('/') in case your string handling is messing that up, or use double backslashes (I'd use the forward slash myself).
You should also check that c:\test\temp.pdf doesn't exist, or if it does exist is not read-only or already open in a different application.
So I solved the problem...
After taking KenS' advice I could run the application without Ghostscript (not Ghostscript.NET) giving me any errors. But it did not produce an actual PDF File.
So KenS's answer did not quite solve the problem, but since 'less is more' and since he took the time to talk to me on IRC to verify that my args in itself were correct, I'll give the answer points nonetheless.
What actually solved my was the following:
Here my original GetArgs(...)
private IEnumerable<string> GetArgs(FileInfo inputFile, FileInfo outputFile)
{
return new [] {
$"-sDEVICE=pdfwrite",
$"-dNOPAUSE",
$"-dBATCH",
$"-sPAPERSIZE=a4",
#"-sFONTPATH=" + System.Environment.GetFolderPath(System.Environment.SpecialFolder.Fonts),
$"-sOutputFile={outputFile.FullName}",
$"{inputFile.FullName}",
};
}
Someone in #csharp pointed out to me, that in c, the first argument is always the name of the command. So he suggested to just put "gs" as the first argument (as a dummy) and try... And that's what actually solved my problem.
So this is how the working GetArgs(...) looks:
private IEnumerable<string> GetArgs(FileInfo inputFile, FileInfo outputFile)
{
return new [] {
$"gs",
$"-sDEVICE=pdfwrite",
$"-dNOPAUSE",
$"-dBATCH",
$"-sPAPERSIZE=a4",
#"-sFONTPATH=" + System.Environment.GetFolderPath(System.Environment.SpecialFolder.Fonts),
$"-sOutputFile={outputFile.FullName}",
$"{inputFile.FullName}",
};
}
I'm trying to make a service that creates a text file, as a tutorial project.
However when I debug it, I get The process cannot access the file C:\myfilepath.OnStart.txt because it is being used by another process.
I'm expecting it to keep creating the file in a format like OnStart(n).txt
public void OnDebug()
{
OnStart(null);
}
protected override void OnStart(string[] args)
{
Timer t = new Timer(WriteTxt, null, 0, 5000);
}
public static void WriteTxt(Object i)
{
System.IO.File.Create(AppDomain.CurrentDomain.BaseDirectory + "OnStart.txt");
}
When creating the file you're mistakenly leaving it open which is why you can't access it next time and receive an error. You must call .Dispose() after you've created it to let go of the reference to the file like this:
File.Create(AppDomain.CurrentDomain.BaseDirectory + "OnStart.txt").Dispose();
If you want to keep creating files then they'll need a different name each time. You could keep a global variable to keep track of this or possibly pass a value into the Write method.
Global variable method
// Keeps track of the last file number with this global variable
private int fileCount;
public void OnDebug()
{
OnStart(null);
}
protected override void OnStart(string[] args)
{
fileCount = 0; // Or whatever you want to start with
Timer t = new Timer(WriteTxt, null, 0, 5000);
}
public static void WriteTxt(Object i)
{
// Creates the file name eg: OnStart1.txt
var fileName = string.Format("OnStart{0}.txt", fileCount);
// Use Path.Combine to make paths
var filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fileName);
// Create the file
File.Create(filePath).Dispose();
// Adds 1 to the value
fileCount++;
}
Results:
OnStart0.txt
OnStart1.txt
OnStart2.txt
...
...
OnStart4999.txt
I develop asp.net web forms application in VS2012 a I have one project with Web.App and one project is a Class Library.
In a class library i reading configuration from web.config (in a project is a lot of app settings like path for menu file, log file etc...) and in this assembly i have a class for caching and logging events to file.
This is a part of cache class:
public static class MyCache
{
public const String CACHE_MENU_USER = "CACHE_MENU_USER_{0}";
public const String CACHE_LOG_FILE_PATH = "LOG_FILE_PATH";
public static void AddToCache(String cacheKey, Object value, DateTime absoluteExpiration, bool loggToFile = true)
{
HttpContext.Current.Cache.Add(cacheKey,
value,
null,
absoluteExpiration,
Cache.NoSlidingExpiration,
CacheItemPriority.High,
(k, v, r) => //key, value, reason of remove from cache
{
String reasonOfRemove = String.Empty;
switch (r)
{
case CacheItemRemovedReason.DependencyChanged:
reasonOfRemove = String.Format("CacheItemRemovedReason.DependencyChanged");
break;
case CacheItemRemovedReason.Expired:
reasonOfRemove = String.Format("CacheItemRemovedReason.Expired");
break;
case CacheItemRemovedReason.Removed:
reasonOfRemove = String.Format("CacheItemRemovedReason.Removed");
break;
case CacheItemRemovedReason.Underused:
reasonOfRemove = String.Format("CacheItemRemovedReason.Underused");
break;
}
AppConfiguration.Logger.WriteMessage(String.Format("Key {0} was removed for reason: {1}", cacheKey, reasonOfRemove));
});
if (loggToFile)
AppConfiguration.Logger.WriteMessage(String.Format("Insert cache key {0} from now to: {1}", cacheKey, absoluteExpiration));
}
}
AppConfiguration is a static class with static properties with values from web.config.
Logger is a instance of Logger class.
In global.asax is a piece of code:
protected void Application_Start(object sender, EventArgs e)
{
AppConfiguration.ReadConfiguration();
AppConfiguration.Logger.WriteMessage("Start app");
}
protected void Application_End(object sender, EventArgs e)
{
AppConfiguration.Logger.WriteMessage("End app");
}
Problem is, that App_Start is called on every request a inserted value to cache is immediately removed...
this is a part of log file:
10.8.2013 17:14:22: Start app.
10.8.2013 17:14:23: Insert cache key CACHE_MENU_USER_DEMO\Administrator from now to: 8/17/2013 5:14:23 PM
10.8.2013 17:14:23: Key CACHE_MENU_USER_DEMO\Administrator was removed for reason: CacheItemRemovedReason.Removed
10.8.2013 17:14:23: End app.
a this is repeated with every request.
I was trying start application in IIS Express, i trying start application in server (IIS7, Windows server 2008 R2) and it is still the same...
Any idea what is wrong with cache and global.asax?
Thanks
EDIT:
*Problem solved. More is in comments from FlopScientist*
Edit: Sorry - now that I've understood the problem a bit better, I think my problem lies elsewhere
I have 2 asynchronus requests.
The first is this:
public void DownloadWebData(Uri apiUrl)
{
WebClient client = new WebClient();
client.DownloadDataCompleted += DownloadDataCompleted;
client.DownloadDataAsync(apiUrl);
}
public void DownloadDataCompleted(object sender, DownloadDataCompletedEventArgs e)
{
string result = System.Text.Encoding.UTF8.GetString (e.Result);
Uri downloadLink = (GetUri(result));
}
Basically it makes a simple url based API request to a remote webserver which returns some basic textual data over http. GetUri() just parses that data to extract an address from the data for an image to download.
I'm then using imageLoader in monotouch.dialog to download the image. All code is in the same class.
Edit: added the imageLoader code (I left the Console lines in because they serve reasonably well as comments).
public void downloadImage (Uri imageUri)
{
var tmp_img = ImageLoader.DefaultRequestImage (imageUri, this);
if (tmp_img != null)
{
adView.Image = tmp_img;
Console.WriteLine ("Image already cached, displaying");
}
else
{
adView.Image = UIImage.FromFile ("Images/downloading.jpg");
Console.WriteLine ("Image not cached. Using placeholder.");
}
}
public void UpdatedImage (System.Uri uri)
{
adView.Image = ImageLoader.DefaultRequestImage(uri, this);
}
You missed to check if e.Result actually contains something. The download might as well have failed and e.Result is null. Add some basic error handling to your code.
if you are using DownloadWebData inside a for loop, it will be better you generate seperate functions for DownloadDataCompleted event.
You can use anonymous function inside DownloadWebData().
client.DownloadDataCompleted +=(s,e)=>{
string result = System.Text.Encoding.UTF8.GetString (e.Result);
Uri downloadLink = (GetUri(result));
};
After realizing I was asking the wrong question, I finally figured it out here:
Hand back control to main UI thread to update UI after asynchronus image download