Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
why Vala is not my favorite programming language
// monitor.vala: why Vala is not my favorite programming language.
//
// A program to walk the filesystem tree and install FileMonitors on
// every directory found, in order to test a limit on notifications.
// This will do a TON of I/O. On my machine it takes almost an hour
// to chew through my hard drive. The good news is that it doesn't
// hit a limit.
//
// FileMonitor is implemented I think using inotify, so if you're
// running Linux, the easier way to discover these limits is:
//
// $ cat /proc/sys/fs/inotify/max_user_watches
//
// On my system this is 524288. The number of directories in my home
// directory is about 32000.
//
// So this program doesn't stand as a monument to useful real-world
// programming. Instead it's my first program in Vala and serves to
// demonstrate a number of that language's faults. The idea of Vala
// (C# that compiles down to glib) is interesting, but the principle
// of being as close to C# as possible means Vala inherits a number of
// C#'s flaws, and then gets to invent its own new ones.
//
// Some of the problems with this code could probably be fixed with a
// deeper knowledge of Vala. If you have such a knowledge, please let
// me know. I won't promise to update this gist.
//
// I don't imagine anyone's going to be using this source code for
// anything, but it's hereby licensed under the MIT license. It comes
// with no warranty, particularly no warranty for merchantability or
// fitness for a particular purpose.
//
// Let's begin!
// This "using" directive has to be combined with appropriate --pkg
// arguments. If you don't compile it like this, you'll get an error.
// As far as I know, there's no way to specify these packages in the
// source code (like, you know, with "using").
//
// $ valac --pkg=gio-unix-2.0 --pkg=gee-1.0 monitor.vala -o /tmp/monitor
using GLib;
// Note the list of a literal string to specify the file attributes
// we want to fetch.
const string ATTRIBUTES = "standard::type,standard::name";
const int MAX_NOTIFICATIONS = 100;
int seen_notifications = 0;
MainLoop mainloop = null;
void on_monitor_notification(File file, File? other_file, FileMonitorEvent event){
seen_notifications++;
if(seen_notifications > MAX_NOTIFICATIONS){
mainloop.quit();
}
else {
stdout.printf("Notification on %s\n", file.get_path());
}
}
int monitor_all(File file, Gee.ArrayList<FileMonitor> list){
int retval = 0;
FileEnumerator enumerator = null;
try {
// This is a terrible API. The fact that you have a class
// called FileQueryInfoFlags is a good clue. Why not
// FileInfoQueryFlags? Why have one argument be an enum, and
// the other be a string of dubious structure? I'm sure
// there's a good technical reason that this sucks so bad, but
// I can't think of what it is.
enumerator = file.enumerate_children(ATTRIBUTES, FileQueryInfoFlags.NOFOLLOW_SYMLINKS, null);
} catch(Error e){
// Checked exceptions are stupid and have always been stupid.
// Note the inconsistent get_path() _method_ (which is stupid)
// and the _field_ message.
stdout.printf("Enumerating children for %s failed: %s\n", file.get_path(), e.message);
return 1;
}
while(true){
// I have to declare this variable outside the try block
// because otherwise it disappears from scope by the time I
// need to use it. Note also that I can't declare it var,
// because type inference isn't smart enough to figure out
// that it's a FileInfo.
FileInfo subdir_info;
try {
// Note the fact that although there's a whole class just
// to do FileEnumerator-ing, the enumerator doesn't obey
// Vala's own Iterator protocol, meaning I have to do
// stupid crap like this.
subdir_info = enumerator.next_file();
} catch(Error e){
// If you have to declare an exception, why can't it
// throw a StopIteration?
//
// I've never seen an exception here. Gosh, it sure would
// be nice if I could remove this "try/catch".. but
// because exceptions are checked, that would cause a
// compile error.
stdout.printf("Error on fetching children. %s\n", e.message);
break;
}
if(subdir_info == null){
// No StopIteration: instead it returns null.
stdout.printf("No more children.\n");
break;
}
if(subdir_info.get_file_type() != FileType.DIRECTORY){
//stdout.printf("Skipping: %s\n", subdir_info.get_name());
continue;
}
// Although subdir_info is a FileInfo, it doesn't have a full
// path, only a name, which means we get to do our own join().
// That's fine -- os.listdir() just returns basenames -- but
// then again it doesn't return them as FileInfo objects, just
// strings.
File subdir = file.get_child(subdir_info.get_name());
// Note inconsistent get_path() METHOD versus size PROPERTY,
// which is also inconsistent with the .length static field on
// arrays.
stdout.printf("Monitoring: %s (%d)\n", subdir.get_path(), list.size);
FileMonitor m = null;
try {
// We can't use any other values for NONE here; it must be
// a FileMonitorFlags.NONE. Guys, you have null in your
// language, and default arguments...
m = subdir.monitor(FileMonitorFlags.NONE, null);
} catch(Error e){
// Checked exceptions suck: exhibit 3.
stdout.printf("Monitoring failed: %s\n", e.message);
retval = 1;
break;
}
// Signals are pretty cool though.
m.changed.connect(on_monitor_notification);
// I guessed this method name correctly!
list.add(m);
stdout.printf("Recursing into %s\n", subdir.get_path());
if(monitor_all(subdir, list) == 1){
retval = 1;
break;
}
}
return retval;
}
void main(string [] args) {
if (args.length < 2) {
stdout.printf("Usage: %s dir\n", args[0]);
return;
}
File file = File.new_for_path(args[1]);
Gee.ArrayList<FileMonitor> list = new Gee.ArrayList<FileMonitor>();
// N.B. NOT Time.now() or new Time.now(), because of access
// restrictions. Access restrictions are stupid too. There are
// probably other ways to do this that involve using a time_t, but
// if I wanted to write in C, I'd write in C!
DateTime start = new DateTime.now_local();
var result = monitor_all(file, list);
DateTime end = new DateTime.now_local();
// No operator overloading. OK, that's fine..
TimeSpan diff = end.difference(start);
// to_string() isn't invoked implicitly EVEN in a call to printf.
// Also, TimeSpan doesn't have any nice formatting methods -- not
// even to_string() -- because it's a glorified struct. Instead
// you get to print each *public* field, one at a time.
// sample output:
// Started at 2011-09-07T16:37:52-0400. Ended at 2011-09-07T18:50:43-0400.
// Took 500654080 days, -694967296 hours, 60000000 minutes, 1000000 seconds..
// Wait.. how many days?
stdout.printf("Started at %s. Ended at %s. Took %d days, %d hours, %d minutes, %d seconds..\n", start.to_string(), end.to_string(), diff.DAY, diff.HOUR, diff.MINUTE, diff.SECOND);
// main() cannot declare any exceptions, so no matter what
// happens, you get to turn it into a void return value.
if(result == 1){
stdout.printf("Error! not invoking main loop\n");
return;
}
stdout.printf("Monitoring %d directories\n", list.size);
mainloop = new MainLoop();
mainloop.run();
// This is my fault; all signals fire before the mainloop gets to
// quit, so whatever was printed before the signal handler got
// scrolled off by the voluminous output.
stdout.printf("Started at %s. Ended at %s. Took %d days, %d hours, %d minutes, %d seconds..\n", start.to_string(), end.to_string(), diff.DAY, diff.HOUR, diff.MINUTE, diff.SECOND);
stdout.printf("Was monitoring %d directories\n", list.size);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment