Thursday, October 13, 2011

Not happy with .NET's FileSystemWatcher

I created a Windows Service to watch for a specific file, which heavily relies on the FileSystemWatcher class. I'm not sure it was worth the hassle over using Timer to just check for the file at a certain interval.

FileSystemWatcher fires an event when a file is created, which is great, except that it seems that it fires the millisecond the file starts to be created (acting more like a "Creating" event...). This was causing problems for me, because my relatively small file was apparently still being written to while I was trying to send it as an attachment and subsequently delete it. There is no "ReallyDoneBeingCreated" event. There is a "Change" event, but that was causing me even more problems.

I ended up adding "Thread.Sleep(30000)" (waits for 30 seconds) after it sees there is a new file. This is crap. I HATE doing stuff like this, but it works, since it takes about a second to create the file in my case. The only other thing I could think of doing is having some crazy recursive try/catch loop that fails, tries again and again until it succeeds. This is ugly, too, but would probably be better if the file size was unknown.

My kludge is upsetting me to the point that I may just end up re-writing the code to use a timer interval to check for a new file, but that is a bit backwards,  too. Can't win. Anybody know of a better way of doing this?

Friday, September 9, 2011

Converting a QuickTime .mov (ProRes codec) to a Windows AVI

On the phone last month to our video production agency, I told them I could work with any video format. Yesterday I was afraid they had proved me wrong.

They sent me a QuickTime .mov encoded with the ProRes codec. This was their source format, and the goal was to ensure no loss of quality on their side, so this wasn't just some crazy attempt on their part to trip me up :-). In any case, I figured any standard video converter (like AVC) could handle this, but nothing I tried worked. I then remembered the amazing FFMPEG from my DVR-building days, and was sure this could easily do the conversion if I could just figure out the correct command-line args. Still no luck. I kept getting the error, "swScalar: Unknown format is not supported as input pixel format". Some searching provided some clues that this was due to mapping, so based on a forum post suggestion, I tried adding the flags, "-map 0:0 -map 0:1". I then got a "codec type mismatch error". I installed the ProRes QuickTime decoder, but this didn't help either.

Finally, I happened upon the following solution:

- install AviSynth (http://avisynth.org/mediawiki/Main_Page)
- add the QTSource dll to AviSynths "plugins" directory (http://www.tateu.net/software/dl.php?f=QTSource)
- install VirtualDub (http://www.virtualdub.org/)

Then you just need to write an AviSynth script in the same directory as the video file and name it with an ".avs" extension:
QTInput("C:\test.mov", audio=1)
BicubicResize(720,486,0.1,1.00).AssumeFPS(29.97)

(BTW, the documentation for QTSource says to use audio=true, but AviSynth was having none of that.)

- open the .avs file as if it was a video in VirtualDub
- save as .avi

Whew. I was about five minutes away from admitting defeat and asking them for a different format. Aside from bruising my ego a bit, this would have wasted at least another day or two that I didn't have to spare.

Thursday, July 28, 2011

C# recursive file counter

I am constantly surprised with how easy .NET makes things sometimes. I needed a quick recursive file counter output to a report, and was able to write this in just a few minutes, using some new methods available in .NET 4 (EnumerateFiles/Directories) (UPDATE: not sure what I was thinking here - .GetFiles()/.GetDirectories() provides pretty much the same thing - array vs. generic list):

static void Main(string[] args)
{
   string startingDir = @"C:\files";
   string reportFile = @"C:\file_count.txt";

   using (StreamWriter report = new StreamWriter(reportFile))
   {
        countFiles(startingDir, report);
   }
}

private static void countFiles(string dir, StreamWriter report) {
   DirectoryInfo directory = new DirectoryInfo(dir);
   IEnumerable<FileInfo> files = directory.EnumerateFiles();
   IEnumerable<DirectoryInfo> dirs = directory.EnumerateDirectories();

   report.WriteLine(String.Format("{0}: {1}", dir, files.Count()));

   foreach (var d in dirs)
   {
        countFiles(d.FullName, report);
   }
}


It works very quickly (100,000 files in as much as 8 directories deep in less than 30 seconds on a modest machine), and couldn't have been more straightforward to write.

BTW, if all you need is just a total file count, this works well:

DirectoryInfo d = new DirectoryInfo(startingDir);
int totalCount = d.EnumerateFiles("*.*", SearchOption.AllDirectories).Count();


After thinking about this some more, I wonder if this may even be easier/more efficient with LINQ. I've got to try that for fun, and check the performance of each.

Wednesday, July 20, 2011

Graphical Saccades

My son is working through some vision issues, and one of the exercises his vision therapist has him do is called "saccades", basically quick movements of the eyes in the same direction. She gave us a web site that had an animated gif that sequentially displayed a bunch of lines of numbers, at varying distances apart.

This was quite boring for my son, so my wife had an idea for me to make a version of this with pictures of things that he liked. He absolutely LOVES this version of the "game", and wants to play it all the time!

The exercise randomly displays 20 images at varying distances (five lines with four images per line), and can include the same image more than once. The speed can be adjusted before the run, or in progress.

From what I understand, the important part of the exercise is just to track and identify sequentially revealed items at the same pace, but at varying distances apart. I would love to hear from any vision therapists out there, though, to make sure that this is correct, and that a graphical version still satisfies the objective. Our therapist is out for a few weeks, so I can't ask her about it now, but I'll update the post when I get any new information.

If you want to try it out yourself, here's a zip file (under the Google Docs icon, click "File" --> "Download Original") with the code and a few sample images. It's set up to go with 30 random images, but it's easily customizable if you want to use your own images, or add more. To run it, just unzip the .zip file, and double-click on the .html file that best fits your screen.

In the meantime, I'm working on getting the exercise set up on its own web site, but I'm having a hard time finding a free web site provider that allows HTML.

Technically, this was a breeze to do with jQuery. I define an array of images, and use code similar to JavaScript built-in "setTimeout", but with the ability to adjust the interval in progress (thanks, Peter Bailey, for the brilliant function). The key lines are:

vi = setVariableInterval(function() {
if (currentItem == 20) {
this.stop();
}

randomSpacer = Math.floor(Math.random() * maxPixelLength) + minPixelLength;
randomPicture = Math.floor(Math.random() * pictures.length);

replacementPic = "<img class='p' src='" + pictures[randomPicture].src + "' width='100' height='75' style='margin-left: " + randomSpacer + "px;' />";
$(pics[currentItem]).replaceWith(replacementPic);

currentItem++;
}, initialInterval);

"randomSpacer" and "randomPicture" create random numbers up to the defined maximums. And the jQuery line just replaces each placeholder with a random image. "pics" lets me access via index all images that use the "p" class (they are initially set up with placeholders).

pics = $(".p");

Thursday, June 9, 2011

Avoiding the "Destination configuration already initialized" error in SAP .NET Connector 3

SAP's documentation suggests using a line similar to the following to create a connection to SAP:

RfcDestinationManager.RegisterDestinationConfiguration(new Config());

where Config() is a class that contains connection settings.

This works fine, except, you occasionally get the error, "Destination configuration already initialized", because the connection hasn't timed out. SAP's documentation that I've seen (the .chm help file and the "Overview" document) doesn't address this issue at all, and their sample code doesn't contain any examples of how to close a connection.

I've found that the following code works:

Config c = new Config();
RfcDestinationManager.RegisterDestinationConfiguration(c);
...
(processing code)
...
RfcDestinationManager.UnregisterDestinationConfiguration(c);

However, if you've just tried to connect without using "unregister", you'll have to wait until the old connection times out itself before trying the code above (the sample config code SAP provides keeps the connection open for 10 minutes).

Overall, I've found the new SAP Connector 3 to be a HUGE step up from v2, though it is quite a pain having to re-write all of my v2 applications. But I easily prefer a few months of re-writing over being tied to VS2003. Thanks, SAP, for updating this - I thought you had forgotten about us .NET developers.


Wednesday, April 13, 2011

Displaying ActiveDirectory's "whenChanged" attribute in C#

"whenChanged" comes through without the time offset. Not a big deal to fix it, but a quick Google search yielded no results on how to deal with it. Here's my solution to properly display Eastern Daylight/Standard time:
TimeZone localZone = TimeZone.CurrentTimeZone;
DateTime generalizedDate = DateTime.Parse(result.Properties["whenChanged"][0].ToString());
double hourDelta = localZone.IsDaylightSavingTime(generalizedDate) ? -4D : -5D;
DateTime lastUpdated = generalizedDate.AddHours(hourDelta);
BTW, Sysinternals' AD Explorer is great for finding out meta information (i.e. data type, property name) about AD attributes.