Removing ShellFile Properties - c#

I ran into a problem this week regarding the Windows Shell Property System when applied to TIFF/TIF files. I'm using Microsoft.WindowsAPICodePack 1.1.0.0 to access the property system.
When adding properties, the file gets corrupted because it seems to get stored where the first IFD pointer would be expected. Now I'm not sure it simply inserts itself at the 5th byte, after the file header (0x49 0x49 0x2A 0x00), of if it overwrites any existing data. Additionally, when comparing the hexadecimal of the IDF entries headers, the bytes looks different. Now when I say corrupted, it is only when programmatically opening the file as a byte stream, not knowing if the file has a property system container added to it. It opens fine in Windows Image Preview, but not in the software my clients are using to view TIFF files.
Here's how I'm adding the properties (as an array of key=value strings).
public void SetFileTag(string fileName, string tagName, string tagValue)
{
try
{
using (var shellFile = ShellFile.FromFilePath(fileName))
{
var keywords = shellFile.Properties.System.Keywords.Value;
var keyValue = string.Concat(tagName, "=", tagValue);
var list = keywords == null ? new List<string>() : new List<string>(keywords);
if (list.Contains(keyValue))
{
return;
}
list.Add(keyValue);
using (var writer = shellFile.Properties.GetPropertyWriter())
{
writer.WriteProperty(shellFile.Properties.System.Keywords, list.ToArray(), true);
writer.Close();
}
}
}
finally
{
GC.Collect();
GC.WaitForPendingFinalizers();
}
}
I looked in the code pack for anything available to entirely remove the properties, but I can't find any method to do so, I can only remove the keywords value. Anyone would have an idea on how to perform this? It doesn't have to be .NET code, it can very well be a command-line tool or win32 code.

Related

Problem in overwriting Application.persistentDataPath

I have a JSON file which contains data for my Localization in my game. The file is stored in Application.persistentDataPath . I read in the documentation that the file is not deleted or overwritten when updating the game. Currently I launch the app and the file is there but when I add more content and send an update to save again, it only has the original content.
But is there a way to update this file when I update the game?
What if in the future I want to add more content to this JSON file, I would have to delete it and upload it again?
Any way to update and overwrite it?
private static void Request(string file)
{
var loadingRequest = UnityWebRequest.Get(Path.Combine(Application.streamingAssetsPath, file));
loadingRequest.SendWebRequest();
while (!loadingRequest.isDone)
{
if (loadingRequest.isNetworkError || loadingRequest.isHttpError)
{
break;
}
}
if (loadingRequest.isNetworkError || loadingRequest.isHttpError)
{
// Some error happened.
}
else
{
File.WriteAllBytes(Path.Combine(Application.persistentDataPath, file), loadingRequest.downloadHandler.data);
}
}
// Loads key from "StreamingAssets/language_data.json" file.
public static string LoadJson(string key)
{
Request("data.json");
var jsonPath = Path.Combine(Application.persistentDataPath, jsonFileName);
using (StreamReader r = new StreamReader(jsonPath))
{
var N = JSON.Parse(r.ReadToEnd());
string result = N[GetLanguage()][key];
return result;
}
}
I think the issue is the following:
You are only creating a file named data.json NOT languages_data.json!
In Request (you pass in "data.json") you copy
Application.streamingAssetsPath, file
to
Application.persistentDataPath, file
where file = "data.json"
Then in LoadJson you are trying to load values from
Application.persistentDataPath, jsonFileName
where - as to your comments - jsonFileName = "languages_data.json"
=> It is a different path!
In the Editor/on your PC you probably still have an old languages_data.json in the persistent data from some time ago. Therefore there is no error but it is also never updated. Delete the persistent/languages_data.json on your PC and it will throw a FileNotFoundException.
To solve this make 100% sure the file names are the same everywhere. The simplest fix code wise would be to simply rather use
Request(jsonFileName);
and make sure that jsonFileName matches the name of your existing file in StreamingAssets.

How can I set PdfFormField image in iTextSharp 7 and release the file after using it?

I'm having a lot of trouble trying to set a PdfFormField image and release the file used by the itext process. Here is what I'm doing now (setting the image itself works just fine, but the file still being used by the process...):
public void SetImageField(string idFormField, string imagePath)
{
PdfButtonFormField pdfButtonFormField = (PdfButtonFormField)_pdfAcroForm
.GetField(idFormField);
if (pdfButtonFormField == null)
throw new InstanceNotFoundException("Não foi encontrado o campo de assinatura no pdf!");
pdfButtonFormField
.SetImage(imagePath);
pdfButtonFormField
.SetBorderWidth(0);
pdfButtonFormField.Flush();
pdfButtonFormField.Release();
}
As you can see, I'm setting the pdfButtonFormField image through pdfButtonFormField.SetImage(imagePath). The thing is, I need to delete this file (imagePath) after using it, and it seems that itext process still using the resource, even if I call pdfButtonFormField.Flush() and also pdfButtonFormField.Release().
So you may say, "why don't you just open a filestream, and call fileStream.Dispose after using?". Because the file itself is not in my hands, its being managed by itext api.
So please, I'd like to know if theres any way to do it.
Looking at the iText 7 source code, PdfButtonFormField.SetImage does the following:
Opens a FileStream using the image path (which it does not release).
Calls an internal utility method to read the FileStream into a byte array.
Calls Convert.ToBase64String to convert the byte array into a string.
Passes the resulting string to PdfButtonFormField.SetValue.
You can do the first three steps yourself and then call SetValue on the PdfButtonFormField.
Assuming you've written your own method ReadFileToArray to read the image file and return it as an array of bytes, this should work:
public void SetImage(PdfAcroForm pdfAcroForm, string idFormField, string imagePath)
{
var pdfButtonFormField = (PdfButtonFormField) pdfAcroForm.GetField(idFormField);
if (pdfButtonFormField == null)
throw new InstanceNotFoundException();
var imageBytes = ReadFileToArray(imagePath);
var imageStr = Convert.ToBase64String(imageBytes);
pdfButtonFormField.SetValue(imageStr);
pdfButtonFormField.SetBorderWidth(0);
}
Here is a link to the source for PdfButtonFormField:
PdfButtonFormField.cs

c# Novacode.Picture to System.Drawing.Image

I'm reading in a .docx file using the Novacode API, and am unable to create or display any images within the file to a WinForm app due to not being able to convert from a Novacode Picture (pic) or Image to a system image. I've noticed that there's very little info inside the pic itself, with no way to get any pixel data that I can see. So I have been unable to utilize any of the usual conversion ideas.
I've also looked up how Word saves images inside the files as well as Novacode source for any hints and I've come up with nothing.
My question then is is there a way to convert a Novacode Picture to a system one, or should I use something different to gather the image data like OpenXML? If so, would Novacode and OpenXML conflict in any way?
There's also this answer that might be another place to start.
Any help is much appreciated.
Okay. This is what I ended up doing. Thanks to gattsbr for the advice. This only works if you can grab all the images in order, and have descending names for all the images.
using System.IO.Compression; // Had to add an assembly for this
using Novacode;
// Have to specify to remove ambiguous error from Novacode
Dictionary<string, System.Drawing.Image> images = new Dictionary<string, System.Drawing.Image>();
void LoadTree()
{
// In case of previous exception
if(File.Exists("Images.zip")) { File.Delete("Images.zip"); }
// Allow the file to be open while parsing
using(FileStream stream = File.Open("Images.docx", FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
using(DocX doc = DocX.Load(stream))
{
// Work rest of document
// Still parse here to get the names of the images
// Might have to drag and drop images into the file, rather than insert through Word
foreach(Picture pic in doc.Pictures)
{
string name = pic.Description;
if(null == name) { continue; }
name = name.Substring(name.LastIndexOf("\\") + 1);
name = name.Substring(0, name.Length - 4);
images[name] = null;
}
// Save while still open
doc.SaveAs("Images.zip");
}
}
// Use temp zip directory to extract images
using(ZipArchive zip = ZipFile.OpenRead("Images.zip"))
{
// Gather all image names, in order
// They're retrieved from the bottom up, so reverse
string[] keys = images.Keys.OrderByDescending(o => o).Reverse().ToArray();
for(int i = 1; ; i++)
{
// Also had to add an assembly for ZipArchiveEntry
ZipArchiveEntry entry = zip.GetEntry(String.Format("word/media/image{0}.png", i));
if(null == entry) { break; }
Stream stream = entry.Open();
images[keys[i - 1]] = new Bitmap(stream);
}
}
// Remove temp directory
File.Delete("Images.zip");
}

Write from Resource to file where resource could be text or image

I'm having a problem trying to write my resource files to disk (all resource files part of the same project and assembly).
If I add
var temp = Assembly.GetExecutingAssembly().GetManifestResourceNames();
This returns a string[] in the following format
Gener.OptionsDialogForm.resources
Gener.ProgressDialog.resources
Gener.Properties.Resources.resources
Gener.g.resources
Gener.Resources.reusable.css
Gener.Resources.other.jpg
The last 2 of the array are the only 2 files I want but I assume it's not a guarantee that this will always be the case. The array could come through in another order as code is changed so I cannot explicity call the item at a given index (temp[4])
So, I could do
foreach (string item in Assembly
.GetExecutingAssembly()
.GetManifestResourceNames())
{
if (!item.Contains("Gener.Resources."))
continue;
//Do whatever I need to do
}
But this is just horrible! I face another problem with this approach; This doesn't return the file name with the extension, just the Name and as such, I have no idea what the extension is.
This is the code as it currently is
public void CopyAllFiles()
{
var files = Resources.ResourceManager.GetResourceSet(System.Globalization.CultureInfo.CurrentUICulture, true, true);
//var temp = Assembly.GetExecutingAssembly().GetManifestResourceNames();
foreach (DictionaryEntry item in files)
{
using (var resourceFileStream = Assembly.GetExecutingAssembly().GetManifestResourceStream("Gener.Resources." + item.Key.ToString() + ".css")) // this won't work, I can't hard code .css as the extension could be different
{
Stream stream = new FileStream(this.DirPath, FileMode.Create, FileAccess.Write);
resourceFileStream.CopyTo(stream);
stream.Dispose();
}
}
files.Dispose();
}
But this seems... wrong... Is this how any one else would do this, I'm sure I'm missing something and such a task is common that there is a better solution?
The resource names are predictable, you could just pass the name to the Assembly.GetManifestResourceStream() method.
More productively, Visual Studio supports a designer for this so you don't have to guess at the string you need to pass. Use Project + Properties, Resources tab. Click on the dropdown arrow of the Add Resource button and select your file. You can now refer to the resource in your code with a variable name. Like:
File.WriteAllText(path, Properties.Resources.reusable);
Do consider the so-so wisdom of copying resources to files at runtime. You get the exact same outcome by just using an installer or XCopy to copy the files just once. With the significant advantage is that those resources won't eat memory address space anymore and that you won't get in trouble when you don't have write access to the directory. Which is common with UAC enabled.
This was what I used! Hopefully it will help others. It feels some what hacking, but it works!
/// <summary>
/// Copies all the files from the Resource Manifest to the relevant folders.
/// </summary>
internal void CopyAllFiles()
{
var resourceFiles = Assembly.GetExecutingAssembly().GetManifestResourceNames();
foreach (var item in resourceFiles)
{
string basePath = Resources.ResourceManager.BaseName.Replace("Properties.", "");
if (!item.Contains(basePath))
continue;
var destination = this._rootFolder + "\\" + this._javaScriptFolder + "\\" + item.Replace(basePath + ".", "");
using (Stream resouceFile = Assembly.GetExecutingAssembly().GetManifestResourceStream(item))
using (Stream output = File.Create(destination))
{
resouceFile.CopyTo(output);
}
}
}

Deleting a bitmap from local directory

Alright it has come to this. I searched this website among many others and no one can seem to give me a straight answer so I'm going to try just asking outright. Been on this issue for about a solid 3 days and I can't afford to waste any more time on it.
Goal: The app I am building is in WPF and is going to be used as a bug tracker for a project my design team and I will be undertaking soon. Since we are going to be building a game in C++ most of the errors that occur will have a visual element to them so I inlcuded functionality to provide an image of the error in question when the user adds a bug to the list. I then take that image and save it to a local directory (for testing). Now the image path in the Error object points to a path that leads to the local directory. This functionality has been tested and works fine. My problem showes up when I want to delete a bug from the list. I am getting that very infamous "IO Exception" saying that the image I want to delete is being used by another process.
So Far: At first I tried very elegant solutions, but as with all things you get to a point where you just want to see if you can get the thing to even work at all. So I am at the point where most of the code I am using is experimental and radical. So please when looking at it note that the code being used is out of desperation, so any "simple" solutions have probably already been tried (I did research this a lot becuase I hate having to do this). Things i can think of off the top of my head are the obsurd amount of disposes and forced garbage collections being called so please to not comment on the negative nature of this practice, I am well aware :).
The Code
Saving image to local directory
public void OnBrowseClick()
{
Microsoft.Win32.OpenFileDialog openBox = new Microsoft.Win32.OpenFileDialog();
// Show dialog box to user and grab output
Nullable<bool> result = openBox.ShowDialog();
if (result == true)
{
// Create temp variable to hold local path string
string localPath = Directory.GetCurrentDirectory();
// Grab the extension of the specified file path
string extension = openBox.FileName.Substring(openBox.FileName.LastIndexOf("\\"));
// Add extension to local path
localPath += extension;
// Create local copy of image at given file path (being ridiculous at this point)
using (Stream stream = new FileStream(openBox.FileName, FileMode.Open, FileAccess.ReadWrite))
{
using (Bitmap bmp = LoadImage(stream))
{
using (Bitmap temp = (Bitmap)bmp.Clone())
{
temp.Save(localPath);
temp.Dispose();
}
bmp.Dispose();
}
stream.Dispose();
}
// Set the URL in the image text box (UI stuff)
LocalError.ImagePath = localPath;
}
}
The following is the LoadImage function that is used in the function above
private Bitmap LoadImage(Stream stream)
{
Bitmap retval = null;
using (Bitmap bitmap = new Bitmap(stream))
{
retval = new Bitmap(bitmap.Width, bitmap.Height, bitmap.PixelFormat);
using (Graphics gdi = Graphics.FromImage(retval))
{
gdi.DrawImageUnscaled(bitmap, 0, 0);
gdi.Flush();
gdi.Dispose();
bitmap.Dispose();
}
}
// Garbage collection here to be safe
GC.WaitForPendingFinalizers();
GC.Collect();
return retval;
}
And finally we come to where I try to delete the image
public void OnDeleteClick()
{
// Ask user to make sure they want to delete selected item(s)
MessageBoxResult result = MessageBox.Show("Are you sure you want to delete selected item(s) from the list?",
"Delete", MessageBoxButton.YesNo);
if (result == MessageBoxResult.Yes)
{
for( int i = 0; i < Parent.ErrorListControl.ErrorDataGrid.SelectedItems.Count; ++i)
{
// Get path to image
string path = (Parent.ErrorListControl.ErrorDataGrid.SelectedItems[i] as Error).ImagePath;
// Even tried calling garbage collection here!!!!!
System.GC.WaitForPendingFinalizers();
System.GC.Collect();
File.Delete(path);
// Remove the error from the error list
Parent.ErrorListVM.ErrorList.Remove((Error)Parent.ErrorListControl.ErrorDataGrid.SelectedItems[i]);
// Decrement counter because we altered the list while in a loop
i--;
}
}
}
Notes: If anyone would like me to explain anything further or if you need to know something I left out please just ask I will get back to you ASAP! Any suggestions are helpful at this point I have absolutley no idea what I am doing wrong. I generally only program in a C++ environment so I tend to manage my own memory this whole "garbage collection" thing is really throwing a wrench in our project! (Off topic note: I do not know why I am not getting any color highlighting so I apologize to anyone who takes the time to read this).
Here's a simple way to do what you want. In this example, I'm using Path.GetTempFileName() to generate a random file name in the local user's temp directory. If you don't need to persist the files then it's a good place to store them temporarily. Also, the user could theoretically import two files with the same name. So you want to use some kind of random filename generation or other mechanism to avoid conflicts.
private void browseButton_Click(object sender, RoutedEventArgs e)
{
var openFileDialog = new Microsoft.Win32.OpenFileDialog();
if (openFileDialog.ShowDialog(this) == true)
{
using (Bitmap originalImage = new Bitmap(openFileDialog.FileName))
{
string tempFileName = System.IO.Path.GetTempFileName();
originalImage.Save(tempFileName);
// LocalError.LocalPath
LocalPath = tempFileName;
}
}
}
private void deleteButton_Click(object sender, RoutedEventArgs e)
{
if (File.Exists(LocalPath))
{
File.Delete(LocalPath);
}
}
Although a simple File.Copy should suffice as long as you have the right paths, I was just providing a solution that matched your question.
EDIT:
Actually the current directory does not seem to be changed by the OpenFileDialog. I could swear that it did at some point. So I don't think this is your problem. Regardless, this code still works for me and you shouldn't require anything more complicated than this.
EDIT #2:
It seems the lock is actually caused by the image being databound to the view and presumably locked by the BitmapSource. You should be able to create it without locking the file. Generally, this is slower so don't do it this way unless you need to be able to modify or delete the file.
bitmapSource = new BitmapImage();
bitmapSource.BeginInit();
bitmapSource.CacheOption = BitmapCacheOption.OnLoad;
bitmapSource.CreateOption = BitmapCreateOptions.IgnoreImageCache;
bitmapSource.UriSource = new Uri(ImagePath, UriKind.Absolute);
bitmapSource.EndInit();
Since your LoadImage method does simple copy of the image, why not use File.Copy(source, dest) and avoid all the bitmaps, drawings, etc? Your goal might be to modify local bitmap after it's created, but it can still be done after copy.
Also, when using the using block, explicit .Dispose() is not required, as using block does it for you:
using (var obj = new SomeDisposableObject())
{
// code here
// obj.Dispose(); <-- not needed, since...
} // ...at this point obj.Dispose is called automatically.

Categories