/*
* Copyright 2011 Future Systems
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.krakenapps.filemon.impl;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.regex.Pattern;
import org.apache.felix.ipojo.annotations.Component;
import org.apache.felix.ipojo.annotations.Provides;
import org.apache.felix.ipojo.annotations.Validate;
import org.krakenapps.codec.EncodingRule;
import org.krakenapps.filemon.FileMonitorEventListener;
import org.krakenapps.filemon.FileMonitorService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Component(name = "file-monitor-service")
@Provides
public class DefaultFileMonitorService implements FileMonitorService {
private final Logger logger = LoggerFactory.getLogger(DefaultFileMonitorService.class.getName());
private final File baseDir;
private Set<File> inclusionPaths;
private Set<Pattern> excludePatterns;
private CopyOnWriteArraySet<FileMonitorEventListener> callbacks;
private Date created = null;
private Integer fileCount;
public DefaultFileMonitorService() {
this.baseDir = getBaseDirectory();
this.inclusionPaths = Collections.synchronizedSet(new TreeSet<File>());
this.excludePatterns = Collections.synchronizedSet(new TreeSet<Pattern>());
this.callbacks = new CopyOnWriteArraySet<FileMonitorEventListener>();
}
@Override
public String getMd5(File f) throws IOException {
return HashUtils.getMd5(f);
}
@Override
public String getSha1(File f) throws IOException {
return HashUtils.getSha1(f);
}
@Override
public Integer getLastFileCount() {
return fileCount;
}
@Override
public Date getLastTimestamp() {
return created;
}
@Override
public void createBaseline() throws IOException {
BaselineBuilder builder = new BaselineBuilder(inclusionPaths, excludePatterns);
builder.build();
}
@Validate
public void start() {
try {
Map<String, Object> headers = readMetadata();
created = (Date) headers.get("created");
fileCount = (Integer) headers.get("file_count");
Object[] inclusions = (Object[]) headers.get("includes");
for (Object inclusion : inclusions) {
inclusionPaths.add(new File((String) inclusion));
}
Object[] patterns = (Object[]) headers.get("excludes");
for (Object exclusion : patterns) {
excludePatterns.add(Pattern.compile((String) exclusion));
}
} catch (IOException e) {
logger.error("kraken filemon: cannot open baseline db", e);
}
}
private Map<String, Object> readMetadata() throws IOException {
File db = new File(baseDir, "kraken-filemon-baseline.db");
FileInputStream is = null;
try {
is = new FileInputStream(db);
return readHeaders(is);
} finally {
if (is != null)
try {
is.close();
} catch (IOException e) {
}
}
}
@Override
public void check() {
File db = new File(baseDir, "kraken-filemon-baseline.db");
FileInputStream is = null;
try {
is = new FileInputStream(db);
readHeaders(is);
while (true) {
if (is.available() <= 0)
break;
ByteBuffer bb = readNext(is);
Object[] record = (Object[]) EncodingRule.decode(bb);
if (record == null)
break;
Baseline baseline = new Baseline(new File((String) record[0]), (Long) record[1], (Long) record[2],
(Boolean) record[3], (String) record[4]);
fireCheckCallback(baseline);
if (baseline.isDeleted()) {
fireDeleteCallback(baseline);
} else if (baseline.isModified()) {
fireModifiedCallback(baseline);
}
}
} catch (IOException e) {
logger.error("kraken filemon: cannot open baseline db", e);
} finally {
if (is != null)
try {
is.close();
} catch (IOException e) {
}
}
}
private void fireCheckCallback(Baseline baseline) {
for (FileMonitorEventListener callback : callbacks) {
try {
callback.onCheck(baseline.getFile());
} catch (Exception e) {
logger.warn("kraken filemon: file monitor callback should not throw any exception", e);
}
}
}
private void fireModifiedCallback(Baseline baseline) throws IOException {
logger.trace("kraken filemon: file modified, %s", baseline.getFileChange());
for (FileMonitorEventListener callback : callbacks) {
try {
callback.onModified(baseline.getFileChange());
} catch (Exception e) {
logger.warn("kraken filemon: file monitor callback should not throw any exception", e);
}
}
}
private void fireDeleteCallback(Baseline baseline) {
for (FileMonitorEventListener callback : callbacks) {
try {
callback.onDeleted(baseline.getFile());
} catch (Exception e) {
logger.warn("kraken filemon: file monitor callback should not throw any exception", e);
}
}
}
private Map<String, Object> readHeaders(FileInputStream is) throws IOException {
ByteBuffer bb = readNext(is);
return EncodingRule.decodeMap(bb);
}
private ByteBuffer readNext(FileInputStream is) throws IOException {
byte[] b = new byte[5];
ByteBuffer bb = ByteBuffer.wrap(b);
int type = is.read();
int i = 0;
for (;;) {
b[i] = (byte) is.read();
if ((b[i] & 0x80) == 0)
break;
i++;
}
int payloadLen = (int) EncodingRule.decodeRawNumber(bb);
int headerLength = 1 + EncodingRule.lengthOfRawNumber(int.class, payloadLen);
int totalLength = headerLength + payloadLen;
b = new byte[totalLength];
bb = ByteBuffer.wrap(b);
// rebuild bytes in memory
bb.put((byte) type);
EncodingRule.encodeNumber(bb, Long.class, payloadLen);
is.read(b, headerLength, payloadLen);
bb.flip();
bb.limit(totalLength);
return bb;
}
@Override
public Collection<File> getInclusionPaths() {
return inclusionPaths;
}
@Override
public Collection<Pattern> getExclusionPatterns() {
return excludePatterns;
}
@Override
public void addInclusionPath(File f) {
inclusionPaths.add(f);
}
@Override
public void removeInclusionPath(File f) {
inclusionPaths.remove(f);
}
@Override
public void addExclusionPattern(String regex) {
excludePatterns.add(Pattern.compile(regex));
}
@Override
public void removeExclusionPattern(String regex) {
excludePatterns.remove(Pattern.compile(regex));
}
@Override
public void addEventListener(FileMonitorEventListener callback) {
callbacks.add(callback);
}
@Override
public void removeEventListener(FileMonitorEventListener callback) {
callbacks.remove(callback);
}
public static void main(String[] args) {
new DefaultFileMonitorService().check();
}
private File getBaseDirectory() {
File dir = new File(System.getProperty("kraken.data.dir"), "kraken-filemon/");
dir.mkdirs();
return dir;
}
}