I would like my file name to be changed dynamically and include a sequence number. The file name should be changed (increment the number) at each archiving process/log rotation.
When archiving the file, the archive file name should be the same as the file name with the sequence number (before the increment).
Starting with file name to be 'file.log.1', when archiving the archive file name will be 'file.log.1' and the file name will be changed to 'file.log.2'.
The next archive file name will be 'file.log.2' and so on...
Is it possible using NLog?
Didn't find any clue in the web or the NLog source code.
So I implemented a custom FileTarget that answer my needs:
internal sealed class CustomFileTarget : NLog.Targets.FileTarget
{
private const int _maxOldFilesToKeep = 10;
private readonly string _directory;
private readonly long _fileMaxSize = (long)10.Megabytes().Bytes;
private readonly string _fileNamePrefix;
private int _sequential;
private string FullFileName => $"{Path.Combine(_directory, _fileNamePrefix)}.{_sequential}.log";
public CustomFileTarget(string directory, string fileNamePrefix)
{
_directory = directory;
_fileNamePrefix = fileNamePrefix;
_sequential = GetLatestSequence() ?? 0;
ConcurrentWrites = false;
FileName = FullFileName;
KeepFileOpen = false;
}
protected override void Write(IList<AsyncLogEventInfo> logEvents)
{
base.Write(logEvents);
if (GetFileSize() >= _fileMaxSize)
{
ChangeName();
DeleteOld();
}
}
private long GetFileSize() =>
new FileInfo(FullFileName).Length;
private void ChangeName()
{
_sequential++;
FileName = FullFileName;
LogManager.ReconfigExistingLoggers();
}
private void DeleteOld()
{
var fileNamesAndSequences = GetFileNamesAndSequences();
if (fileNamesAndSequences.Count() > _maxOldFilesToKeep + 1)
{
fileNamesAndSequences.Take(
fileNamesAndSequences.Count() - _maxOldFilesToKeep + 1)
.ForEach(
_ => Directory.Delete(Path.Combine(_directory, _.FileName)));
}
}
private int? GetLatestSequence()
{
var fileNamesAndSequences = GetFileNamesAndSequences();
return fileNamesAndSequences.Any()
? fileNamesAndSequences.Last().Sequence
: (int?)null;
}
private IEnumerable<(string FileName, int Sequence)> GetFileNamesAndSequences() =>
Directory.GetFiles(_directory, $"{_fileNamePrefix}*.log").
Select(
_ =>
{
var fileNameParts = _.Split('.');
return (_, Sequence: int.Parse(fileNameParts[fileNameParts.Length - 2]));
}).
OrderBy(_ => _.Sequence);
}
Related
I'm trying to use SevenZipSharp from https://github.com/squid-box/SevenZipSharp to extract a zip archive. The dll setup is as follows:
public class Paths
{
private static readonly string SynthEBDexeDirPath = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
public static readonly string ResourcesFolderPath = Path.Combine(SynthEBDexeDirPath, "Resources");
// Toggle between the x86 and x64 bit dll
public readonly string SevenZipPath = Path.Combine(ResourcesFolderPath, "7Zip", Environment.Is64BitProcess ? "x64" : "x86", "7z.dll");
The dll files are copied into my Resources folder from the latest version of 7-Zip. The calling code looks as follows:
using System.IO;
using System.Windows.Controls;
using SevenZip;
namespace SynthEBD;
public class VM_ZipArchiveHandler : VM
{
public VM_ZipArchiveHandler(Window_SevenZipArchiveHandler window)
{
if (File.Exists(PatcherSettings.Paths.SevenZipPath))
{
SevenZipBase.SetLibraryPath(PatcherSettings.Paths.SevenZipPath);
Initialized = true;
}
else
{
CustomMessageBox.DisplayNotificationOK("Initialization Error", "Could not initialize Seven Zip from " + PatcherSettings.Paths.SevenZipPath);
}
Window = window;
}
public string DispString { get; set; } = string.Empty;
public ProgressBar Prog = new ProgressBar();
public Window_SevenZipArchiveHandler Window { get; set; }
private bool Initialized { get; set; } = false;
public void Unzip(string archivePath, string destinationFolder)
{
if (!Initialized)
{
return;
}
Prog.Minimum = 0;
Prog.Maximum = 100;
Prog.Value = 0;
Window.Show();
var progressHandler = new Progress<byte>(
percentDone => Prog.Value = percentDone);
var progress = progressHandler as IProgress<byte>;
var file = new SevenZipExtractor(archivePath);
file.Extracting += (sender, args) =>
{
progress.Report(args.PercentDone);
};
file.ExtractionFinished += (sender, args) =>
{
// Do stuff when done
};
Task.Run(() =>
{
//Extract the stuff
file.ExtractArchive(destinationFolder);
});
Window.Close();
}
public static void UnzipArchive(string archivePath, string destinationDir)
{
Window_SevenZipArchiveHandler window = new Window_SevenZipArchiveHandler();
VM_ZipArchiveHandler handler = new(window);
window.DataContext = handler;
handler.Unzip(archivePath, destinationDir);
}
}
I call UnzipArchive():
string tempFolderPath = Path.Combine(PatcherSettings.ModManagerIntegration.TempExtractionFolder, DateTime.Now.ToString("yyyy-MM-dd-HH-mm", System.Globalization.CultureInfo.InvariantCulture));
try
{
Directory.CreateDirectory(tempFolderPath);
}
catch (Exception ex)
{
Logger.LogError("Could not create or access the temp folder at " + tempFolderPath + ". Details: " + ex.Message);
return installedConfigs;
}
try
{
VM_ZipArchiveHandler.UnzipArchive(path, tempFolderPath);
}
In the end I get an empty directory; the .7z contents are never extracted to it. I've tried using both a .zip and .7z file as inputs, each containing two json files and nothing else. When I set a breakpoint at file.ExtractArchive(destinationFolder), it seems semi-correctly initialized: https://imgur.com/qjYpDur
It looks like it's correctly recognized as a SevenZip archive, but fields like _filesCount are null.
Am I doing something wrong with my setup?
I believe the issue is that your ExtractArchive is wrapped inside a Task and the calling thread returns before the extraction completes and isn't awaited. Not 100% on the details but as an experiment I found what works and what leaves the destination directory empty:
public static void Main(string[] args)
{
SevenZipBase.SetLibraryPath(
Path.Combine(
Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location),"7z.dll"));
string archivePath = "D:\\Downloads\\imgs.7z";
var file = new SevenZipExtractor(archivePath);
// works
file.ExtractArchive("D:\\Downloads\\unzipped");
// doesnt work
Task.Run(() =>
{
file.ExtractArchive("D:\\Downloads\\unzipped");
});
// works
Task.Run(() =>
{
file.ExtractArchive("D:\\Downloads\\unzipped");
}).Wait();
}
Is there a way to export Visual Studio "Solution Explorer" structure into excel?
I tried "tree" command and clip it via Windows Command Prompt like this:
but it's little hard to splite to each Excel sells.
I expecting result like this:
so any good idea for it?
Thanks.
Here you have a solution. Preety simple recursive solution to list files in the format you want
public class GetFilesTreeList
{
private static List<Files> files = new List<Files>();
public static void Main(string[] args)
{
var path = #"C:\Users\Lukasz\Desktop";
files.Add(new Files(Path.GetFileName(path), 0));
WriteFilesRec(path, 1);
foreach (var filese in files)
{
Console.WriteLine(filese);
}
}
public class Files {
public int column;
public string name;
public Files(string name, int column)
{
this.column = column;
this.name = name;
}
public override string ToString()
{
return new String('+', column) + name;
}
}
public static void WriteFilesRec(string path, int i) {
DirectoryInfo directory = new DirectoryInfo(path);
foreach(var d in directory.GetDirectories()) {
files.Add(new Files(d.Name, i));
WriteFilesRec(Path.Combine(path, d.Name), i+1);
}
foreach(var f in directory.GetFiles()) {
files.Add(new Files(f.Name, i));
}
}
}
How i can use an progress bar in this case?
void Client_DownloadFileCompleted(object sender, AsyncCompletedEventArgs e)
{
//System.Windows.MessageBox.Show("Update Complete!", "Message", MessageBoxButton.OK, MessageBoxImage.Information);
Uri uri = new Uri(url);
string filename = System.IO.Path.GetFileName(uri.AbsolutePath);
ZipFile.ExtractToDirectory(filePathDir + "/" + filename, filePathDir);
}
EDIT:
#Alessandro D'Andria , But in this case?:
WebClient wc = new WebClient();
Stream zipReadingStream = wc.OpenRead(url);
ZipArchive zip = new ZipArchive(zipReadingStream);
ZipFileExtensions.ExtractToDirectory(zip, filePathDir);
You can see the source of ExtractToDirectory on GitHub, the only thing you need to do is pass in a Progress<ZipProgress> and call it inside the foreach loop.
//This is a new class that represents a progress object.
public class ZipProgress
{
public ZipProgress(int total, int processed, string currentItem)
{
Total = total;
Processed = processed;
CurrentItem = currentItem;
}
public int Total { get; }
public int Processed { get; }
public string CurrentItem { get; }
}
public static class MyZipFileExtensions
{
public static void ExtractToDirectory(this ZipArchive source, string destinationDirectoryName, IProgress<ZipProgress> progress)
{
ExtractToDirectory(source, destinationDirectoryName, progress, overwrite: false);
}
public static void ExtractToDirectory(this ZipArchive source, string destinationDirectoryName, IProgress<ZipProgress> progress, bool overwrite)
{
if (source == null)
throw new ArgumentNullException(nameof(source));
if (destinationDirectoryName == null)
throw new ArgumentNullException(nameof(destinationDirectoryName));
// Rely on Directory.CreateDirectory for validation of destinationDirectoryName.
// Note that this will give us a good DirectoryInfo even if destinationDirectoryName exists:
DirectoryInfo di = Directory.CreateDirectory(destinationDirectoryName);
string destinationDirectoryFullPath = di.FullName;
int count = 0;
foreach (ZipArchiveEntry entry in source.Entries)
{
count++;
string fileDestinationPath = Path.GetFullPath(Path.Combine(destinationDirectoryFullPath, entry.FullName));
if (!fileDestinationPath.StartsWith(destinationDirectoryFullPath, StringComparison.OrdinalIgnoreCase))
throw new IOException("File is extracting to outside of the folder specified.");
var zipProgress = new ZipProgress(source.Entries.Count, count, entry.FullName);
progress.Report(zipProgress);
if (Path.GetFileName(fileDestinationPath).Length == 0)
{
// If it is a directory:
if (entry.Length != 0)
throw new IOException("Directory entry with data.");
Directory.CreateDirectory(fileDestinationPath);
}
else
{
// If it is a file:
// Create containing directory:
Directory.CreateDirectory(Path.GetDirectoryName(fileDestinationPath));
entry.ExtractToFile(fileDestinationPath, overwrite: overwrite);
}
}
}
}
This is used like
public class YourClass
{
public Progress<ZipProgress> _progress;
public YourClass()
{
// Create the progress object in the constructor, it will call it's ReportProgress using the sync context it was constructed on.
// If your program is a UI program that means you want to new it up on the UI thread.
_progress = new Progress<ZipProgress>();
_progress.ProgressChanged += Report
}
private void Report(object sender, ZipProgress zipProgress)
{
//Use zipProgress here to update the UI on the progress.
}
//I assume you have a `Task.Run(() => Download(url, filePathDir);` calling this so it is on a background thread.
public void Download(string url, string filePathDir)
{
WebClient wc = new WebClient();
Stream zipReadingStream = wc.OpenRead(url);
ZipArchive zip = new ZipArchive(zipReadingStream);
zip.ExtractToDirectory(filePathDir, _progress);
}
//...
Maybe something like this can work for you:
using (var archive = new ZipArchive(zipReadingStream))
{
var totalProgress = archive.Entries.Count;
foreach (var entry in archive.Entries)
{
entry.ExtractToFile(destinationFileName); // specify the output path of thi entry
// update progess there
}
}
It's simple a workaround to keep track of the progress.
I've successfuly added files programatically to my project using the following code:
var project = new Microsoft.Build.Evaluation.Project(projPath);
project.AddItem("Compile", filePath);
However, removing a file programatically is giving me a hard time.
Signature:
public bool RemoveItem(
ProjectItem item
)
How can I instantiate a ProjectItem? I couldn't find any examples.
Reference: https://msdn.microsoft.com/en-us/library/microsoft.build.evaluation.project.removeitem.aspx
you did
private static ProjectItem GetProjectItem(this Project project, string filePath)
{
var includePath = filePath.Substring(project.DirectoryPath.Length + 1);
var projectItem = project.GetItems(CompileType).FirstOrDefault(item => item.EvaluatedInclude.Equals(includePath));
return projectItem;
}
in your GetProjectItem method:
replace that:
var projectItem = project.GetItems(CompileType).FirstOrDefault(item => item.EvaluatedInclude.Equals(includePath));
with this:
var projectItem = project.GetItems("Compile").ToList()
.Where(item => item.EvaluatedInclude.Equals(includePath)).FirstOrDefault();
using .FirstOrDefault() will bring it to have just first item of all files. i used .ToList() and made it work with all my items which have same EvaluatedInclude. its totally worked for my.
This is the class I ended up writing. No simple solution for remove.
public static class SourceControlHelper
{
public static void CheckoutFile(string filePath)
{
TFSAction((workspace) => workspace.PendEdit(filePath), filePath);
}
public static void AddFile(this Project project, string filePath)
{
CheckoutFile(project.FullPath);
var projectItem = project.GetProjectItem(filePath);
if (projectItem != null)
{
return;
}
var includePath = filePath.Substring(project.DirectoryPath.Length + 1);
project.AddItem(CompileType, includePath);
project.Save();
TFSAction(workspace => workspace.PendAdd(filePath), filePath);
}
public static void DeleteFile(this Project project, string filePath)
{
CheckoutFile(project.FullPath);
var projectItem = project.GetProjectItem(filePath);
if (projectItem == null)
{
return;
}
project.RemoveItem(projectItem);
project.Save();
TFSAction(workspace => workspace.PendDelete(filePath), filePath);
}
private static ProjectItem GetProjectItem(this Project project, string filePath)
{
var includePath = filePath.Substring(project.DirectoryPath.Length + 1);
var projectItem = project.GetItems(CompileType).FirstOrDefault(item => item.EvaluatedInclude.Equals(includePath));
return projectItem;
}
private static void TFSAction(Action<Workspace> action, string filePath)
{
var workspaceInfo = Workstation.Current.GetLocalWorkspaceInfo(filePath);
if (workspaceInfo == null)
{
Console.WriteLine("Failed to initialize workspace info");
return;
}
using (var server = new TfsTeamProjectCollection(workspaceInfo.ServerUri))
{
var workspace = workspaceInfo.GetWorkspace(server);
action(workspace);
}
}
private static string CompileType
{
get { return CopyTool.Extension.Equals("ts") ? "TypeScriptCompile" : "Compile"; }
}
}
I am currently developing a software that will be used by users that should not be able to access the back-end of it all but should still be able to easily change configuration/settings for the application.
I decided the best approach would be a custom "configuration file (.cfg)" located in the root of the final build.
Simple example of the .cfg file:
serveraddress='10.10.10.10'
serverport='1234'
servertimeout='15000'
Since I wanted the configuration file to easily be extended I decided to use some custom attributes and some simple LINQ.
This does work like I expect it to, but since I am still a novice in .net I am afraid I have not gone with the best approach and my question is therefor:
Is there anything I can do to improve this?
Or is there just generally a better approach for this?
This is my code for reading the configuration file and assigning the values to it's corresponding properties.
ConfigFileHandler.cs
public void ReadConfigFile()
{
var cfgFile = new ConfigFile();
var configLines = File.ReadAllLines("configfile.cfg");
var testList = configLines.Select(line => line.Split('='))
.Select(splitString => new Tuple<string, string>(splitString[0], splitString[1].Replace("'", "")))
.ToList();
foreach (var prop in typeof(ConfigFile).GetProperties())
{
var attrs = (ConfigFileFieldAttribute[])prop.GetCustomAttributes
(typeof(ConfigFileFieldAttribute), false);
foreach (var t in from attr in attrs from t in testList where t.Item1 == attr.Name select t)
{
prop.SetValue(cfgFile, t.Item2);
}
}
}
ConfigFile.cs
class ConfigFile
{
private static string _serverAddress;
private static int _serverPort;
private static int _serverTimeout;
[ConfigFileField(#"serveraddress")]
public string ServerAddress
{
get { return _serverAddress; }
set { _serverAddress= value; }
}
[ConfigFileField(#"serverport")]
public string ServerPort
{
get { return _serverPort.ToString(); }
set { _serverPort= int.Parse(value); }
}
[ConfigFileField(#"servertimeout")]
public string ServerTimeout
{
get { return _serverTimeout.ToString(); }
set { _serverTimeout= int.Parse(value); }
}
}
any tips on writing better looking code would be highly appreciated!
UPDATE:
Thanks for all the feedback.
Below is the final classes!
https://dotnetfiddle.net/bPMnJA for a live example
Please note, this is C# 6.0
ConfigFileHandler.cs
public class ConfigFileHandler
{
public void ReadConfigFile()
{
var configLines = File.ReadAllLines("configfile.cfg");
var configDictionary = configLines.Select(line => line.Split('='))
.Select(splitString => new Tuple<string, string>(splitString[0], splitString[1].Replace("'", "")))
.ToDictionary(kvp => kvp.Item1, kvp => kvp.Item2);
ConfigFile.SetDictionary(configDictionary);
}
}
ConfigFile.cs
public class ConfigFile
{
private static Dictionary<string, string> _configDictionary;
public string ServerAddress => PullValueFromConfig<string>("serveraddress", "10.1.1.10");
public int ServerPort => PullValueFromConfig<int>("serverport", "3306");
public long ServerTimeout => PullValueFromConfig<long>("servertimeout", "");
private static T PullValueFromConfig<T>(string key, string defaultValue)
{
string value;
if (_configDictionary.TryGetValue(key, out value) && value.Length > 0)
return (T) Convert.ChangeType(value, typeof (T));
return (T) Convert.ChangeType(defaultValue, typeof (T));
}
public static void SetDictionary(Dictionary<string, string> configValues)
{
_configDictionary = configValues;
}
}
You could keep the simplicity of your config file and get rid of the nested loops by loading the values into a dictionary and then passing that into your ConfigFile class.
public static void ReadConfigFile()
{
var configLines = File.ReadAllLines("configfile.cfg");
var testList = configLines.Select(line => line.Split('='))
.Select(splitString => new Tuple<string, string>(splitString[0], splitString[1].Replace("'", "")))
.ToDictionary(kvp => kvp.Item1, kvp => kvp.Item2);
var cfgFile = new ConfigFile(testList);
}
The new ConfigFile class:
class ConfigFile
{
private Dictionary<string, string> _configDictionary;
public ConfigFile(Dictionary<string, string> configValues)
{
_configDictionary = configValues;
}
public string ServerAddress
{
get { return PullValueFromConfig("serveraddress", "192.168.1.1"); }
}
public string ServerPort
{
get { return PullValueFromConfig("serverport", "80"); }
}
public string ServerTimeout
{
get { return PullValueFromConfig("servertimeout", "900"); }
}
private string PullValueFromConfig(string key, string defaultValue)
{
string value;
if (_configDictionary.TryGetValue(key, out value))
return value;
return defaultValue;
}
}
I decided to use a custom "configuration file (.cfg)" located in the root of the final build.
Good idea. For cleaner code, you could use JSON and JSON.NET for de/serialization and put the read/write into the ConfigFile class. Here is an example that is live as a fiddle.
The ConfigFile class is responsible for loading and saving itself and uses JSON.NET for de/serialization.
public class ConfigFile
{
private readonly static string path = "somePath.json";
public string ServerAddress { get; set; }
public string ServerPort { get; set; }
public string ServerTimeout { get; set; }
public void Save()
{
var json = JsonConvert.SerializeObject(this, Formatting.Indented);
File.WriteAllText(path, json)
}
public static ConfigFile Load()
{
var json = File.ReadAllText(path);
return JsonConvert.DeserializeObject<ConfigFile>(json);
}
}
Here is how you would use it to load the file, change its properties, and save.
ConfigFile f = ConfigFile.Load();
f.ServerAddress = "0.0.0.0";
f.ServerPort = "8080";
f.ServerTimeout = "400";
f.Save();
We use the .json file extension as a convention. You could still use .cfg because it's just plain text with a specific syntax. The resultant config file content from the above usage is this:
{
"ServerAddress":"0.0.0.0",
"ServerPort":"8080",
"ServerTimeout":"400"
}
You could just tell your clients to "change the numbers only". Your approach is fine, as far as I'm concerned. The above is just a cleaner implementation.
Firstly, I would do what Phil did, and store your testlist in a Dictionary.
var configLines = File.ReadAllLines("configfile.cfg");
var testDict = configLines.Select(line => line.Split('=', 2))
.ToDictionary(s => s[0], s => s[1].Replace("'", ""));
Then you can clean up the property assignment LINQ a bit:
foreach (var prop in typeof(ConfigFile).GetProperties())
{
var attr = prop.GetCustomAttributes(false)
.OfType<ConfigFileFieldAttribute>()
.FirstOrDefault();
string val;
if (attr != null && testDict.TryGetValue(attr.Name, out val))
prop.SetValue(cfgFile, val);
}
You might even be able to call:
var attr = prop.GetCustomAttributes<ConfigFileFieldAttribute>(false).FirstOrDefault();
Don't have an IDE on me so I can't check right now