| // 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