Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial

Monitorare i cambiamenti dei file in una directory con Java

Un esempio pratico basato su Java per la gestione della funzionalità file change notification con cui monitorare i cambiamenti a carico dei file contenuti in una directory..
Un esempio pratico basato su Java per la gestione della funzionalità file change notification con cui monitorare i cambiamenti a carico dei file contenuti in una directory..
Link copiato negli appunti

Utilizzando un IDE (come per esempio Eclipse o Visual Studio), oppure un qualsiasi word processor, vi sarà capitato di vedere una finestra di dialogo che informa riguardo alle modifiche avvenute a carico di un file sul quale si sta lavorando. Questa funzionalità è detta file change notification e permette ad un programma di monitorare le modifiche avvenute su una particolare directory (watching).

Il watching potrebbe essere implementato facilmente come una successione di richieste inoltrate ad intervalli regolari al file system (polling), ma questa soluzione risulta essere piuttosto inefficiente, in quanto impegna molto la CPU, specialmente nel caso in cui sia necessario monitorare una directory che contiene numerosi file e/o sottodirectory.

In questo articolo parleremo di come Java implementa il watching come una API presente all'interno del package java.nio.file. L'API in questione e basata sul concetto di Watch Service, l'implementazione nativa del file change notification presente in alcuni filesystem di Linux e all'interno di Windows. Nel caso in cui il filesystem non abbia un implementazione nativa, l'API sfrutterà il polling per poter ricavare gli eventi.

Funzionamento base

I passi principali da compiere per usare il watching in Java sono i seguenti:

  • Creazione di un apposito WatchService per il proprio filesystem usando l'interfaccia WatchService presente all'interno del package java.nio.file.
  • Registrazione di ogni directory che si vuol monitorare presso il WachService creato, specificando il tipo di evento che si desidera ottenere, facendo ciò il WatchService restituirà una WatchKey per ogni directory registrata.
  • Implementazione di un ciclo infinito per poter gestire gli eventi in arrivo. Quando un evento si verifica, la WatchKey a cui è associato viene posizionata all'interno di una coda presente nel WatchService.
  • Recupero della WatchKey dalla coda del WatcherService.
  • Recupero e gestione di ogni evento pendente associato a quella determitata key (a cui possono essere associati più eventi).
  • Reset della WatchKey e gestione degli altri eventi in attesa.
  • Chiusura del servizio che può avvenire o tramite una chiamata esplicita ad un metodo di chiusura oppure quando termina il thread in cui si era creato il WatchService.

Per poter spiegare meglio il funzionamento di questa API, verrà usato come esempio una semplice applicazione, denominata "Email", che simula il watching di una cartella contenente file che rappresentano mail in scrittura. In particolare, per ogni nuovo file di testo salvato nella cartella verrà visualizzato il nome del file appena creato se esso è effettivamente un file di testo.

Creazione di un Watch Service e registrazione agli eventi

Il primo passo che l'applicazione Email deve svolgere e quello di creare un nuovo WatchService usando il metodo newWatchService della classe

FileSystems nel seguente modo:

public Email(Path dir) throws IOException {
        this.watcher = FileSystems.getDefault().newWatchService();
	  …
}

Successivamente, si possono registrare uno o più oggetti che implementano l'interfaccia Watchable usando il WatchService appena istanziato. Ad esempio, le istanze della classe Path, possono essere registrate. Le classi come Path che implementano Watchable implementano anche il metodo register(WatchService, WatchEvent.Kind...); dalla firma notiamo che il metodo prende, oltre al WatchService, anche l'elenco di eventi a cui si è interessati ad avere una notifica. Tale elenco è costituito da istanze di oggetti che implementano l'interfaccia WatchEvent.Kind.

Le istanze più comuni sono contenute nel package java.nio.file.StandardWatchEventKinds e definiscono chiaramente gli eventi:

Istanza Evento
ENTRY_CREATE creazione di un nuovo elemento nella directory.
ENTRY_DELETE cancellazione di un elemento nella directory.
ENTRY_MODIFY modifica di un elemento.
OVERFLOW indica che l'evento è stato perso oppure "assorbito" da altre modifiche avvenute successivamente. Non è possibile effettuare una registrazione per quanto riguarda quest'evento.

Il codice che segue, anch'esso presente nel costruttore della classe Email, mostra come effettuare la registrazione di un oggetto Path presso il WatchService, tale registrazione si riferisce al solo evento di creazione di nuovi file.

…
dir.register(watcher, ENTRY_CREATE);
..

Gestione degli eventi

La gestione di un singolo evento proveniente dal WatchService si svolge in quattro fasi che sono:

  1. Ottenimento di una WatchKey con uno dei seguenti tre metodi che differiscono tra loro per essere più o meno bloccanti:
    1. poll: restituisce una WatchKey se essa è disponibile al momento della chiamata.
    2. poll(long, TimeUnit): come sopra, solo che nel caso non fosse disponibile una WatchKey, allora il programma aspetterà per un periodo di tempo prefissato dal parametro timeout che può essere misurato in nanosecondi, milllisecondi oppure un'altra unità di tempo definita nell'oggetto TimeUnit.
    3. take: metodo che rimane in attesa fino a quando non restituisce una WatchKey.
  2. Elaborazione delle richieste pendenti presenti per ogni WatchKey ottenuta nel passaggio precedente; in particolare verrà elaborata la lista degli WatchEvents ottenuta tramite il metodo pollEvents chiamato sul WatchKey.
  3. Recupero del tipo di evento utilizzando il metodo kind; in questa fase è necessario poter gestire l'evento OVERFLOW ossia decidere se ingnorarlo oppure associargli un qualche comportamento.
  4. Recupero del nome del file associato all'evento, tale informazione è memorizzata nel contest dell'evento, per poterlo ricavare basta richiamare il metodo context sulla WatchKey.
  5. Una volta che tutti gli eventi di una WatchKey sono stati elaborati, è necessario far si che il WatchKey sia di nuovo pronto per poter ricevere nuovi eventi. Per far ciò si utilizza il metodo reset. Se questo metodo restituisce false, allora la WatchKey non potrà più ricevere nuovi eventi.

Come anticipato nell'ultimo punto, una WatchKey ha uno stato e, in particolare, può assumere uno dei seguenti stati:

Stato Descrizione
Ready il WatchKey è pronto a ricevere nuovi eventi. Tale stato viene assunto al momento della creazione della WatchKey.
Signaled indica che uno o più eventi sono presenti in coda. Una volta che la WatchKey è in questo stato, non è più pronta a ricevere nuovi eventi, per poter tornare allo stato Ready bisogna invocare il metodo reset.
Invalid presuppone che la WatchKey non sia più attiva. Ciò può avvenire perchè:

  • Il processo ha esplicitamente cancellato la WatchKey chiamando il metodo cancel.
  • La directory associata alla WatchKey diventa inaccessibile.
  • Il WatchService viene chiuso usando il metodo closed.

Nell'esempio mostrato di seguito un metodo denominato processEvents() si occuperà di processare e tutti gli eventi CREATE provenienti dalla directory registrata in precedenza. La fase di gestione consisterà nel verificare se il file appena creato è effettivamente un file di testo tramite il metodo probeContentType(Path):

public void processEvents()
{
	while(true)
	{
		//Si attende che arrivi un nuovo evento CREATE dalla directory registrata
		WatchKey key;
		try {
			key = watcher.take();
		} catch (InterruptedException x) {
			return;
		}
		for (WatchEvent<?> event: key.pollEvents())
		{
			WatchEvent.Kind kind = event.kind();
			if (kind == OVERFLOW) continue; 
			//Recupero del nome del file dal contesto.
			WatchEvent<Path> ev = (WatchEvent<Path>)event;
			Path filename = ev.context();
			//Verifica se il file appena creato è un file di testo.
			try {
				Path child = dir.resolve(filename);
				if (!Files.probeContentType(child).equals("text/plain"))
				{
					System.err.format("Il file '%s'non è un file di testo.%n", filename);
					continue;
				}
			} catch (IOException x) {
				System.err.println(x);
				continue;
			}
			//Stampa del file
			System.out.format("File %s%n", filename);
		}
		//Applichiamo reset a key in modo da poter ricevere un nuovo evento
		boolean valid = key.reset();
		if (!valid) break;
	}
}

Ti consigliamo anche