Watchgopher
In the last couple of months I’ve been playing a lot with Go and did what I always do when learning a new language: use it for a small project. That project has been open to the public on GitHub for a few weeks now and is now in a usable state: Watchgopher.
Watchgopher allows you to watch certain directories and run custom commands whenever a file in a specified directory changes. It is supposed to be simple and give you control of what happens to your files: it only notifies one of your commands whenever something happens it should know about.
Let me guide you through a simple example to show you what Watchgopher can do. But first, make sure you have Go installed and then run the following command to install Watchgopher on your system:
go get -u github.com/mrnugget/watchgopher
Now the watchgopher
command should be available on your system and it’s time
to tell Watchgopher which directories to watch and what to do about any file
events occurring in them. So let’s create a simple configuration file in JSON
format:
{
"/Users/mrnugget/Downloads": [
{"pattern": "*.zip", "run": "/Users/mrnugget/.watchgophers/unzip.rb"}
]
}
This file tells Watchgopher to watch the Downloads
directory in my home
directory and whenever something happens to a file whose name matches *.zip
,
Watchgopher will run the specified command. If the command is in your $PATH
you won’t need to put the absolute
When running the command it will pass two arguments to it:
- The type of the file event. This can be
CREATE
,MODIFY
,DELETE
, orRENAME
. - The absolute path to the file triggering the event.
After saving the config file we need to create the command which will be run. As
you may have guessed by reading the file names, I want to create a small command
that unzips every newly created zip file in my Downloads
directory. The code
to do this is pretty simple:
#!/usr/bin/env ruby
exit(0) if ARGV[0] != "CREATE"
success = system("unzip #{ARGV[1]} -d #{File.dirname(ARGV[1])}")
success ? exit(0) : exit(1)
This script does three things:
- Exit with exit code 0 if the file event is not
CREATE
, since I’m not interested inDELETE
events here, because unzipping deleted files is a pretty difficult thing to do. - Run the
unzip
command with the filename as first argument and the directory the file is in as-d
option, which means it will unzip to the same directory the zip file was created in, no matter from where you run Watchgopher. - It checks whether the
unzip
command exited successfully. If it did, it exits successfully too and if it didn’t, it exits with error code 1. (You can achieve the same thing with Ruby’sKernel#exec
, but explaining what exec does is not part of this post)
Let’s the save the file at the specified place
(/Users/mrnugget/.watchgophers/unzip.rb
) and give it the right permissions:
$ chmod +x ~/.watchgophers/unzip.rb
Now is the time to launch Watchgopher and point it towards the configuration file:
$ watchgopher watchgopher_config.json
2013/04/06 13:50:25 Successfully loaded configuration file. Number of rules: 1
2013/04/06 13:50:25 Watchgopher is now ready process file events
Watchgopher now watches the Downloads
folder for zip files. We should give him
one:
$ mv Octocats.zip ~/Downloads/
This should prompt Watchgopher to output the following:
2013/04/06 13:53:23 /Users/mrnugget/.watchgophers/unzip_files.rb, ARGS: [CREATE /Users/mrnugget/tmp/zips/Octocats.zip] -- SUCCESS
And if we take a look inside the Downloads
directory we will find a newly
extracted Octocat
directory, which means everything went as planned! Great!
As you can see, this concept of handing over the handling of certain file events over to specified commands gives you a lot of freedom when dealing with file changes on your system. Since Watchgopher passes the two arguments to every specified command, the command can decide what to do about it: delete newly created files, move them to another directory, upload them to a server, or ignore the file events altogether and just have a look around a directory for old files and delete them whenever something happens in there. To make it short: It’s up to you how you will react to file changes, Watchgopher just tells you about them.
There are still a lot more features that I want to implement that are currently missing: including the output of the specified commands in Watchgopher’s log output, allow to specify the current working directory of a command, allow path expansion and many more features and tweaks.
I’d be happy if you give Watchgopher a try and I’m also thankful for every comment, question, issue opened and pull request sent, so don’t hesitate!