Just learning RX and wanted to make a program that iterated the file system. Here is what I came up with that works:
using System;
using System.IO;
using System.Reactive.Disposables;
using System.Reactive.Linq;
namespace ConsoleApplication8
{
internal class Program
{
private static IObservable<string> GetFiles(string folder, string filePattern)
{
return Observable.Create<string>(
o =>
{
var files = Directory.GetFiles(folder, filePattern);
foreach (var file in files)
{
o.OnNext(file);
}
var folders = Directory.GetDirectories(folder);
foreach (var f in folders)
{
var x = GetFiles(f, filePattern);
x.Subscribe(p => { o.OnNext(p); });
}
o.OnCompleted();
return Disposable.Empty;
});
}
private static void Main(string[] args)
{
var o = GetFiles(#"d:\temp", "*.*");
o.Subscribe(p => { Console.WriteLine(p); });
Console.Read();
}
}
}
(Note the use of recursion by calling GetFiles again and subscribing)
While it works it seems very clumsy, I can't help thinking that I should be using something like Concat to combine the sequences instead of just bubbling them back up.
Also I would like to change that Foreach to a Parallel.ForEach but I'm unsure the ramifications this would have using RX. I can't seem to find much for documentation.
Any tips on how to write this better using RX?
To solve a problem like this, it can help to write a LINQ version of the function first. eg:
static IEnumerable<string> GetFiles(string folder, string filePattern)
{
return Directory.GetFiles(folder, filePattern)
.Concat(Directory.GetDirectories(folder).SelectMany(f => GetFilesEnumerable(f, filePattern)));
}
Then just change the IEnumerables to IObservables:
static IObservable<string> GetFiles(string folder, string filePattern)
{
return Directory.GetFiles(folder, filePattern).ToObservable()
.Concat(Directory.GetDirectories(folder).ToObservable().SelectMany(f => GetFilesEnumerable(f, filePattern)));
}
Turns out, based on my discovery of SearchOption.AllDirectories (why in all my years have I never noticed this) it can be boiled down into two real lines of code:
using System;
using System.IO;
using System.Reactive.Linq;
namespace ConsoleApplication8
{
internal class Program
{
private static void Main(string[] args)
{
var o = Directory.GetFiles(#"e:\code", "*.*", SearchOption.AllDirectories).ToObservable();
o.Subscribe(f => Console.WriteLine(f));
Console.Read();
}
}
}
Crazy how simple it really is now. Need a new RX problem to play with.
Related
I want to make a program in C# that replaces the text of all files in a folder.
I tried this:
using System;
using System.IO;
namespace Stackoverflow
{
class Program
{
static void Main(string[] args)
{
String[] files = Directory.GetFiles(#"C:\Users\test\folder", "*", SearchOption.AllDirectories);
File.WriteAllText(files, "Test");
}
}
}
But I get an error that says that it cannot be converted from "string[]" to "string"
Does anyone know how to solve this?
Instead of
File.WriteAllText(files, "Test");
try using
files.ForEach(file => File.WriteAllText(file, "Test"));
This should work.
EDIT: Or as Heinzi commented, you can similarly solve it in a loop instead of a lambda expression:
foreach (string file in files)
{
File.WriteAllText(file, "Test");
}
I have many files in folder. and whenever there is any update in any file I Receive an event for that in my windows service application.
And I am looking for something by which I can validate the file with specific pattern. If it matches then only that file should be processed or else it should be ignored.
Something like this
if(File.Matches("genprice*.xml"))
{
DoSomething();
}
genprice20212604.xml
genprice20212704.xml
price20212604.xml
genprice20212704.txt
From above only #1 and #2 should be processed others should be ignored.
Your can try with regular expressions:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Text.RegularExpressions;
namespace ConsoleAppRegex
{
class Program
{
static void Main(string[] args)
{
string[] fileNames = new string[] { "genprice20212604.xml",
"genprice20212704.xml",
"price20212604.xml",
"genprice20212704.txt"};
Regex re = new Regex(#"genprice[^\.]*.xml");
foreach (string fileName in fileNames)
{
if (re.Match(fileName).Success)
{
Console.WriteLine(fileName);
}
}
Console.ReadLine();
}
}
}
I suggest to use Regex:
using System.Text.RegularExpressions;
using System.IO;
var reg = new Regex(#"genprice\d{8}$");
var fileNamesFromFolder = Direcotory.GetFiles(" #FolderĀ“s path ", "*.xml")
.Where(path => reg.IsMatch(Path.GetFileNameWithoutExtension(path)))
.Select(Folder=>
Path.GetFileNameWithoutExtension(Folder));
foreach (var file in fileNamesFromFolder )
{
//Do something...
}
Here is my code:
private static bool checkifDirectoryContainsFilesWithSpecifiedExtention(string path, string fileExestention) //Like C:\\smth, *.html
{
foreach (string f in Directory.GetFiles(path,fileExestention))
{
return true;
}
foreach (string d in Directory.GetDirectories(path))
{
return checkifDirectoryContainsFilesWithSpecifiedExtention(d,fileExestention);
}
return false;
}
In this function the program returns every file with specified extention BUT If I contain only one file with the specified extention in the last folder, the function returns false, which makes no sence because it exists
My question is why it does that...
Try it out. I can't really find the bug.
The problem is the "return" inside the foreach (string d in Directory.GetDirectories(path)) loop. It will only check the first directory per level. You need something like this (not nice, but keeping your style/format)
bool ok = false;
foreach (string d in Directory.GetDirectories(path))
{
ok = ok || checkifDirectoryContainsFilesWithSpecifiedExtention(d,
fileExestention);
}
return ok;
Better yet, use the linq that Marc provided.
Turned this into a LINQ query
private static bool CheckifDirectoryContainsFilesWithSpecifiedExtention(string path, string fileExestention)
//Like C:\\smth, *.html
{
return Directory.GetFiles(path, fileExestention).Any() ||
Directory.GetDirectories(path)
.Select(d => CheckifDirectoryContainsFilesWithSpecifiedExtention(d, fileExestention))
.FirstOrDefault();
}
Tried it on the a few debug folders using *.exe as the mask and it returns true. It traverses sub folders as well. Not sure what you meant by one file in the last folder.
See if it works for you.
this works for me
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace StackOverflowSnippets
{
class Program
{
static void Main(string[] args)
{
String path = #"C:\smth"; String pattern = "*.html";
Console.WriteLine("__" + checkifDirectoryContainsFilesWithSpecifiedExtention(path, pattern));
Console.ReadLine();
}
private static bool checkifDirectoryContainsFilesWithSpecifiedExtention(string path, string fileExestention) //Like C:\\smth, *.html
{
foreach (string f in Directory.GetFiles(path, fileExestention))
{
return true;
}
foreach (string d in Directory.GetDirectories(path))
{
if (checkifDirectoryContainsFilesWithSpecifiedExtention(Path.Combine(path, d), fileExestention))
{
return true;
}
}
return false;
}
}
}
my project is about recording screen as sequence of images then instead of make it as video i planed to load all image directories to list and use timer to view them image by image, but i get files in wrong order like this:
this code is to load files from directory:
string[] array1 = Directory.GetFiles("C:\\Secret\\" + label1.Text, "*.Jpeg");
Array.Sort(array1);
foreach (string name in array1)
{
listBox1.Items.Add(name);
}
timer2.Start();
this code to view them
int x = 0;
private void timer2_Tick(object sender, EventArgs e)
{
if (x >= listBox1.Items.Count)
{
timer2.Stop();
}
else
{
ssWithMouseViewer.Image = Image.FromFile(listBox1.Items[x].ToString());
x++;
}
}
i need to view them in order like 0.jpeg, 1.jpeg, 2.jpeg.....10.jpeg, 11..jpeg...
The strings are sorted: in lexicographic order...
you have two options: rename the files so they be ordered in lexicographic order (eg: 001, 002, 003...), or, using linq, and file name manipulations:
IEnumerable<string> sorted = from filename in array1
orderby int.Parse(Path.GetFileNameWithoutExtension(filename))
select filename;
Presumably your array should already be sorted as you enter label1.text in numerical order? If not it might be easier for you to sort the label1.text values into numerical order before calling your method.
You need to use a "natural sort order" comparer when sorting the array of strings.
The easiest way to do this is to use P/Invoke to call the Windows built-in one, StrCmpLogicalW(), like so (this is a compilable Console application):
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Security;
namespace ConsoleApp1
{
[SuppressUnmanagedCodeSecurity]
internal static class NativeMethods
{
[DllImport("shlwapi.dll", CharSet = CharSet.Unicode)]
public static extern int StrCmpLogicalW(string psz1, string psz2);
}
public sealed class NaturalStringComparer: IComparer<string>
{
public int Compare(string a, string b)
{
return NativeMethods.StrCmpLogicalW(a, b);
}
}
sealed class Program
{
void run()
{
string[] filenames =
{
"0.jpeg",
"1.jpeg",
"10.jpeg",
"11.jpeg",
"2.jpeg",
"20.jpeg",
"21.jpeg"
};
Array.Sort(filenames); // Sorts in the wrong order.
foreach (var filename in filenames)
Console.WriteLine(filename);
Console.WriteLine("\n");
Array.Sort(filenames, new NaturalStringComparer()); // Sorts correctly.
foreach (var filename in filenames)
Console.WriteLine(filename);
}
static void Main(string[] args)
{
new Program().run();
}
}
}
NOTE: This example is based on code from this original answer.
The most simple way is to use the OrderBy function in LINQ in combination with Path.GetFileNameWithoutExtension like so:
string[] array1 = Directory.GetFiles("C:\\Secret\\" + label1.Text, "*.Jpeg");
array1 = array1.OrderBy(x => int.Parse(System.IO.Path.GetFileNameWithoutExtension(x))).ToArray();
To use the OrderBy function, you need to add a using statement for the namespace System.Linq;
So I'm trying to run through every file on my harddrive, but it stops once it gets to the 2115th (I think) loop. I believe it is a stack overflow due to my use of recursion, but I'm new to C# and really have no idea. Here's my code, thank you very much.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Data;
namespace test_data
{
class Program
{
static string drive = Path.GetPathRoot(Environment.CurrentDirectory);
static void CrawlDir(string dir)
{
string[] dir_package = {};
List<string> dir_list = new List<string>();
foreach (string scan_dir in Directory.GetDirectories(dir))
{
try
{
dir_list.Add(scan_dir);
}
catch (System.Exception error)
{
Console.WriteLine(error.Message);
}
}
dir_package = dir_list.ToArray();
Process_Package(dir_package);
}
static void Main(string[] args)
{
CrawlDir(drive);
Console.ReadLine();
}
static void Process_Package(string[] package)
{
foreach (string dir in package)
{
Console.WriteLine(dir);
try
{
CrawlDir(dir);
}
catch (Exception)
{
Console.WriteLine("Error!");
}
}
}
}
}
Just use what's built in - Directory.GetDirectories supports an optional parameter to specify if you want to get all directories recursively:
var dirs = Directory.GetDirectories(drive, "*.*", SearchOption.AllDirectories);
Note that this is a blocking call - instead you could use Directory.EnumerateDirectories to receive the directory names one by one as they are being found:
var dirs = Directory.EnumerateDirectories(drive, "*.*", SearchOption.AllDirectories);
foreach(string dir in dirs)
{
Console.WriteLine(dir);
}
Use this overload with SearchOption.AllDirectories so you don't have to do any recursion:
public static string[] GetDirectories(
string path,
string searchPattern,
SearchOption searchOption
)
Recursion looks more or less ok, although you do not really need a list in CrawlDir. If you had a stack overflow you'd get a nasty message.
My guess is - the program completes and stops at ReadLine() waiting for you to press Enter.
You're adding a lot of overhead to each level of recursion by using a List (dir_list), an array (dir_package) and two try/catch loops. I don't know what you're really trying to do, but for this example it's way overkill.
Still, it would take a lot to cause a stack overflow on a modern OS. What error message are you getting?
This code should be equivalent.
using System;
using System.IO;
namespace test_data
{
class Program
{
static string drive = Path.GetPathRoot(Environment.CurrentDirectory);
static void CrawlDir(string dir)
{
foreach (string subDir in Directory.GetDirectories(dir))
{
try
{
Console.WriteLine(subDir);
CrawlDir(subDir);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
static void Main(string[] args)
{
CrawlDir(drive);
Console.ReadLine();
}
}
}