Skip to content

Instantly share code, notes, and snippets.

@glasserc
Created September 14, 2011 09:40
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save glasserc/1216198 to your computer and use it in GitHub Desktop.
Save glasserc/1216198 to your computer and use it in GitHub Desktop.
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);
}
@naipotato
Copy link

naipotato commented Oct 1, 2020

Well, I think this code can be improved as follows 🤔

const int MAX_NOTIFICATIONS = 100;
int seen_notifications = 0;

async void monitor_all (File dir, Gee.List<FileMonitor> list) throws Error {
	var attributes = @"$(FileAttribute.STANDARD_TYPE),$(FileAttribute.STANDARD_NAME)";

	var dir_info = yield dir.query_info_async (attributes, NOFOLLOW_SYMLINKS);
	if (dir_info.get_file_type () != DIRECTORY) return;

	var monitor = dir.monitor (NONE);
	print ("Monitoring: %s (%i)\n", dir.get_path (), list.size);

	monitor.changed.connect (file => {
		print ("Notification on %s\n", file.get_path ());
		seen_notifications++;
	});

	list.add (monitor);

	var enumerator = yield dir.enumerate_children_async (attributes, NOFOLLOW_SYMLINKS);

	for (
		var subdir_info = enumerator.next_file ();
		subdir_info != null;
		subdir_info = enumerator.next_file ()
	) {
		var subdir = dir.get_child (subdir_info.get_name ());
		yield monitor_all (subdir, list);
	}
}

async void main (string[] args) {
	var dir = args.length > 1
		? File.new_for_commandline_arg (args[1])
		: File.new_for_path (".");

	var list = new Gee.ArrayList<FileMonitor> ();
	var timer = new Timer ();

	try {
		yield monitor_all (dir, list);
	} catch (Error e) {
		printerr ("Error when registering monitors: %s\n", e.message);
		Process.exit (Posix.EXIT_FAILURE);
	}

	timer.stop ();

	ulong microseconds;
	var seconds = timer.elapsed (out microseconds);

	print ("Monitors registered! Took %.2f seconds (%lu microseconds)\n", seconds, microseconds);
	print ("Monitoring %i directories\n", list.size);

	while (seen_notifications <= MAX_NOTIFICATIONS) {
		MainContext.@default ().iteration (true);
	}
}

It is still true that certain things in the GLib are not entirely comfortable to work with from the Vala syntax, but this is the case since the GLib is mainly intended for C, not so much for Vala. It would be necessary to adapt many remaining things of the GLib to make it more comfortable, although it really is not something of great importance


EDIT: Updated with latest Vala things :p

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment