Source

NPAPI-chrome-file-api / X11 / FileWatcherTaskX11.cpp

Full commit
/*
* Copyright 2012 Ryan Ackley (ryanackley@gmail.com)
*
* This file is part of NPAPI Chrome File API
*
* NPAPI Chrome File API is free software: you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
* 
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
* 
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/


#include "FileWatcherTaskX11.h"
#include <sys/inotify.h>
#include <sys/signal.h>
#include <iostream>
#include "variant_list.h"

#define EVENT_SIZE  ( sizeof (struct inotify_event) )
#define EVENT_BUF_LEN     ( 1024 * ( EVENT_SIZE + 16 ) )

using namespace boost::filesystem;

void FileWatcherTaskX11::Watch(boost::filesystem::path p, FB::JSObjectPtr callback)
{
	thread = boost::thread(boost::bind(&FileWatcherTaskX11::ListenForFileEvents, this, p, callback));
	//thread.detach();
}

/* Signal handler that does nothing. This is necessary because
 * I send a signal to interrupt pselect. Without a signal handler for my signal,
 * the process will crash. I only care about interrupting the pselect.
 * */
static void hdl (int sig)
{
	//do nothing
}

void FileWatcherTaskX11::ListenForFileEvents(boost::filesystem::path path, FB::JSObjectPtr callback)
{
	int length;//, i = 0;
	int fd;
	int wd;
	char buffer[EVENT_BUF_LEN];
	sigset_t mask;
	sigset_t orig_mask;
	struct sigaction act;

	memset (&act, 0, sizeof(act));
	act.sa_handler = hdl;

	/* This server should shut down on SIGTERM. */
	if (sigaction(SIGUSR1, &act, 0)) {
		std::cerr << ("sigaction");
		return;
	}

	sigemptyset (&mask);
    sigaddset (&mask, SIGUSR1);

    if (sigprocmask(SIG_BLOCK, &mask, &orig_mask) < 0) {
		std::cerr << "sigprocmask";
		return;
	}

	/*creating the INOTIFY instance*/
	fd = inotify_init();

	/*checking for error*/
	if ( fd < 0 ) {
		std::cerr << "inotify_init" ;
	}

	std::map<int, boost::filesystem::path*> wdMap;

	this->AddWatchToDir(fd, path, wdMap );

	while (!this->_ShouldStop())
	{
		int i = 0;
		struct timeval time;
		fd_set set;

		time.tv_sec = 10;
		time.tv_usec = 0;

		FD_ZERO(&set);
		FD_SET(fd, &set);

		int sr = pselect (FD_SETSIZE, &set, NULL, NULL, NULL, &orig_mask);
		if (!sr) //timeout
		{
			continue;
		}
		else if (sr > 0)
		{
			length = read( fd, buffer, EVENT_BUF_LEN );

			/*checking for error*/
			if ( length < 0 )
			{
				std::cerr << "read";
			}

			/*actually read return the list of change events happens. Here, read the change event one by one and process it accordingly.*/
			while ( i < length )
			{
				struct inotify_event *event = ( struct inotify_event * ) &buffer[ i ];
				if ( event->len )
				{
					if ( ((event->mask & IN_MODIFY) || (event->mask & IN_MOVED_TO)) &&  !(event->mask & IN_ISDIR) )
					{
						std::string filename = event->name;
						if (filename[0] != '.')
						{
							boost::filesystem::path* root = wdMap[event->wd];
							boost::filesystem::path fsPath = *root / filename;
							callback->Invoke("", FB::variant_list_of(fsPath.generic_string()));
						}
					}
				}
				i += EVENT_SIZE + event->len;
			}
		}
		else
		{
			std::cerr << "select";
			break;
		}
	}
	//cleanup
	std::map<int, boost::filesystem::path*>::iterator it;
	for (it = wdMap.begin(); it != wdMap.end(); it++)
	{
		inotify_rm_watch( fd, (*it).first );
		delete (*it).second;
	}

	/*closing the INOTIFY instance*/
	close( fd );
}

void FileWatcherTaskX11::AddWatchToDir(int fd, boost::filesystem::path p, std::map<int, boost::filesystem::path*> &watchDescriptors)
{
	int wd = inotify_add_watch( fd, p.generic_string().c_str(), IN_MODIFY | IN_MOVED_TO);
	watchDescriptors[wd] = new path(p);

	directory_iterator di(p);
	directory_iterator eos;

	while(di != eos)
	{
		directory_entry de = *di;
		if (is_directory(de.status())){
			this->AddWatchToDir(fd, de.path(), watchDescriptors);
		}
		di++;
	}
}

void FileWatcherTaskX11::StopWatching()
{
	this->stop = 1;
	pthread_kill(thread.native_handle(), SIGUSR1);

	boost::posix_time::time_duration timeout = boost::posix_time::seconds(5);
	thread.timed_join(timeout);
}

bool FileWatcherTaskX11::_ShouldStop()
{
	return this->stop != 0;
}