[PATCH v2] Add tracking to check for changes in exclude files

2 views
Skip to first unread message

Imran M Yousuf

unread,
Aug 11, 2009, 10:30:32 PM8/11/09
to nb...@googlegroups.com
This is done so that PathPattern cache for exclusion patterns can be evicted on
change/modified event of the file. It tracks both FileSystem API
changes and local
file system change.

Next step would be to ensure Turbo cache is updated.

Signed-off-by: Imran M Yousuf <imyo...@smartitengineering.com>
---
src/org/nbgit/util/exclude/Excludes.java | 79 +++++++++++++++++++++++++++---
1 files changed, 72 insertions(+), 7 deletions(-)

diff --git a/src/org/nbgit/util/exclude/Excludes.java
b/src/org/nbgit/util/exclude/Excludes.java
index 9b7cba3..9aa8d12 100644
--- a/src/org/nbgit/util/exclude/Excludes.java
+++ b/src/org/nbgit/util/exclude/Excludes.java
@@ -45,14 +45,24 @@
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
-import java.util.HashMap;
+import java.util.Collections;
+import java.util.Date;
import java.util.HashSet;
+import java.util.Hashtable;
import java.util.List;
+import java.util.Map;
import java.util.Set;
+import java.util.Timer;
+import java.util.TimerTask;
import java.util.Vector;
import org.nbgit.Git;
import org.netbeans.api.project.ProjectManager;
import org.netbeans.api.queries.SharabilityQuery;
+import org.openide.filesystems.FileAttributeEvent;
+import org.openide.filesystems.FileChangeAdapter;
+import org.openide.filesystems.FileEvent;
+import org.openide.filesystems.FileObject;
+import org.openide.filesystems.FileRenameEvent;
import org.openide.filesystems.FileUtil;
import org.spearce.jgit.lib.Repository;

@@ -65,21 +75,46 @@

private Excludes() {
}
-
private static final String FILENAME_GITIGNORE = ".gitignore"; // NOI18N
- private static HashMap<String, List<PathPattern>> ignorePatterns;
+ private final static Map<String, List<PathPattern>>
ignorePatterns = new Hashtable<String, List<PathPattern>>();
+ private final static Timer timer = new Timer();
+ private final static TimerTask externalChangeTrackerTask;
+ private final static int DELAY = 5000;
+
+ static {
+ externalChangeTrackerTask = new TimerTask() {
+
+ private final Date lastChecked = new Date();
+
+ @Override
+ public void run() {
+ final Set<String> absPathSet = ignorePatterns.keySet();
+ for (String absPath : absPathSet) {
+ final File file = new File(absPath);
+ FileObject fileObject = FileUtil.toFileObject(file);
+ if (file.exists() &&
fileObject.lastModified().after(lastChecked)) {
+ fileObject.refresh();
+ }
+ }
+ lastChecked.setTime(System.currentTimeMillis());
+ }
+ };
+ timer.schedule(externalChangeTrackerTask, DELAY, DELAY);
+ }

private static List<PathPattern> getIgnorePatterns(File file) {
- if (ignorePatterns == null) {
- ignorePatterns = new HashMap<String, List<PathPattern>>();
- }
String key = file.getAbsolutePath();
List<PathPattern> patterns = ignorePatterns.get(key);
- if (patterns == null) {
+ if (patterns == null && file.exists() && !file.isDirectory()) {
patterns = readIgnorePatterns(file);
+ if (!ignorePatterns.keySet().contains(key)) {
+ monitorFileChange(key);
+ }
if (!patterns.isEmpty()) {
ignorePatterns.put(key, patterns);
}
+ } else if (patterns == null) {
+ patterns = Collections.emptyList();
}
return patterns;
}
@@ -162,6 +197,36 @@ public static boolean isIgnored(File file,
boolean checkSharability) {
return false;
}

+ private static void monitorFileChange(final String key) {
+ final FileObject fileObject = FileUtil.toFileObject(new File(key));
+ fileObject.addFileChangeListener(new FileChangeAdapter() {
+
+ private void evictPatterns() {
+ ignorePatterns.remove(key);
+ }
+
+ @Override
+ public void fileChanged(FileEvent fe) {
+ evictPatterns();
+ }
+
+ @Override
+ public void fileAttributeChanged(FileAttributeEvent fe) {
+ evictPatterns();
+ }
+
+ @Override
+ public void fileDeleted(FileEvent fe) {
+ evictPatterns();
+ }
+
+ @Override
+ public void fileRenamed(FileRenameEvent fe) {
+ evictPatterns();
+ }
+ });
+ }
+
private static String stripWorkDir(File wd, File f) {
int skip = f.getPath().length() > wd.getPath().length() ? 1 : 0;
String relName = f.getPath().substring(wd.getPath().length() + skip);
--
1.6.2.1

Jonas Fonseca

unread,
Aug 15, 2009, 12:42:02 PM8/15/09
to nb...@googlegroups.com
A small update,

I haven't forgotten this, but I wanted to wait until I had landed the
rewritten the exclude code, which has now been pushed to the public
repository. I will look into "porting" the code when I will have time
and post an updated patch here.
--
Jonas Fonseca

Imran M Yousuf

unread,
Aug 16, 2009, 10:07:57 PM8/16/09
to nb...@googlegroups.com
On Sat, Aug 15, 2009 at 11:42 PM, Jonas Fonseca<jonas....@gmail.com> wrote:
>
> A small update,
>
> I haven't forgotten this, but I wanted to wait until I had landed the
> rewritten the exclude code, which has now been pushed to the public
> repository. I will look into "porting" the code when I will have time
> and post an updated patch here.

Thanks for the update. I forgot to mention earlier that I remembered
why I used a separate set for keeping the collection of files being
monitored, its because, "ignorePatterns" patterns will have entries
flushed from map while the files should still be monitored. So I would
like to re-do the patch with a separate collection for maintaining the
list of files being monitored; please let me know when I should start
with it and I would like to help you by porting the code so you can
simply pull from my github fork.

- Imran
--
Imran M Yousuf
Entrepreneur & Software Engineer
Smart IT Engineering
Dhaka, Bangladesh
Email: im...@smartitengineering.com
Blog: http://imyousuf-tech.blogs.smartitengineering.com/
Mobile: +880-1711402557

Jonas Fonseca

unread,
Aug 16, 2009, 11:43:08 PM8/16/09
to nb...@googlegroups.com, Imran M Yousuf
This is done so that PathPattern cache for exclusion patterns can be
evicted on change/modified event of the file. It tracks both FileSystem
API changes and local file system change.

Signed-off-by: Imran M Yousuf <imyo...@smartitengineering.com>

[jf: updated and refactored to move the main logic into the a utility class
to make the code reusable. Also, guard the code against race conditions.]

Signed-off-by: Jonas Fonseca <fon...@diku.dk>
---

On Sun, Aug 16, 2009 at 22:07, Imran M Yousuf<imyo...@gmail.com> wrote:
>
> Thanks for the update. I forgot to mention earlier that I remembered
> why I used a separate set for keeping the collection of files being
> monitored, its because, "ignorePatterns" patterns will have entries
> flushed from map while the files should still be monitored. So I would
> like to re-do the patch with a separate collection for maintaining the
> list of files being monitored; please let me know when I should start
> with it and I would like to help you by porting the code so you can
> simply pull from my github fork.

I still do not see why this is even needed. If a file is removed the
current code will simply reload it if/when it is later recreated.

This patch is my current version of the updated patch, which should
hopefully make it possible to use the code for monitoring other things.
Please review/repost so we can get the tracking code into the tree.

src/org/nbgit/util/exclude/ExcludeCache.java | 10 +-
src/org/nbgit/util/exclude/MonitoredFileMap.java | 162 ++++++++++++++++++++++
2 files changed, 165 insertions(+), 7 deletions(-)
create mode 100644 src/org/nbgit/util/exclude/MonitoredFileMap.java

diff --git a/src/org/nbgit/util/exclude/ExcludeCache.java b/src/org/nbgit/util/exclude/ExcludeCache.java
index 62f4f8e..c05add2 100644
--- a/src/org/nbgit/util/exclude/ExcludeCache.java
+++ b/src/org/nbgit/util/exclude/ExcludeCache.java
@@ -36,15 +36,12 @@
package org.nbgit.util.exclude;

import java.io.File;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Map;
import java.util.Vector;
import org.spearce.jgit.lib.Repository;

class ExcludeCache {

- private final Map<String, PathPatternList> map = new HashMap<String, PathPatternList>();
+ private final MonitoredFileMap<PathPatternList> map = MonitoredFileMap.create();
private final Repository repo;

public static ExcludeCache create(Repository repository) {
@@ -143,12 +140,12 @@ private PathPatternList getUserPatternList() {
}

private PathPatternList getPatternList(File file, String basePath) {
- PathPatternList list = map.get(file.getPath());
+ PathPatternList list = map.get(file);
if (list == null) {
list = ExcludeUtils.readExcludeFile(file, basePath);
if (list == null)
return null;
- map.put(file.getPath(), list);
+ map.put(file, list);
}
return list;
}
@@ -163,5 +160,4 @@ public String toString() {
builder.append("]");
return builder.toString();
}
-
}
diff --git a/src/org/nbgit/util/exclude/MonitoredFileMap.java b/src/org/nbgit/util/exclude/MonitoredFileMap.java
new file mode 100644
index 0000000..7151c9c
--- /dev/null
+++ b/src/org/nbgit/util/exclude/MonitoredFileMap.java
@@ -0,0 +1,162 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2009 Imran M Yousuf <imyo...@smartitengineering.com>
+ * Copyright 2009 Jonas Fonseca <fon...@diku.dk>
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common
+ * Development and Distribution License("CDDL") (collectively, the
+ * "License"). You may not use this file except in compliance with the
+ * License. You can obtain a copy of the License at
+ * http://www.netbeans.org/cddl-gplv2.html. See the License for the
+ * specific language governing permissions and limitations under the
+ * License. When distributing the software, include this License Header
+ * Notice in each file.
+ *
+ * This particular file is subject to the "Classpath" exception as provided
+ * by Sun in the GPL Version 2 section of the License file that
+ * accompanied this code. If applicable, add the following below the
+ * License Header, with the fields enclosed by brackets [] replaced by
+ * your own identifying information:
+ * "Portions Copyrighted [year] [name of copyright owner]"
+ *
+ * Contributor(s):
+ *
+ * If you wish your version of this file to be governed by only the CDDL
+ * or only the GPL Version 2, indicate your decision by adding
+ * "[Contributor] elects to include this software in this distribution
+ * under the [CDDL or GPL Version 2] license." If you do not indicate a
+ * single choice of license, a recipient has the option to distribute
+ * your version of this file under either the CDDL, the GPL Version 2 or
+ * to extend the choice of license to its licensees as provided above.
+ * However, if you add GPL Version 2 code and therefore, elected the GPL
+ * Version 2 license, then the option applies only if the new code is
+ * made subject to such option by the copyright holder.
+ */
+package org.nbgit.util.exclude;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.Timer;
+import java.util.TimerTask;


+import org.openide.filesystems.FileAttributeEvent;
+import org.openide.filesystems.FileChangeAdapter;
+import org.openide.filesystems.FileEvent;
+import org.openide.filesystems.FileObject;
+import org.openide.filesystems.FileRenameEvent;

+import org.openide.filesystems.FileUtil;
+
+class MonitoredFileMap<T> {
+
+ private final Map<String, T> map = Collections.synchronizedMap(new HashMap<String, T>());
+ private FileMonitorTask monitorTask;
+
+ private MonitoredFileMap() {
+ }
+
+ public static <T> MonitoredFileMap<T> create() {
+ return new MonitoredFileMap<T>();
+ }
+
+ public T get(File file) {
+ synchronized (map) {
+ return map.get(file.getPath());
+ }
+ }
+
+ public void put(File file, T list) {
+ final String key = file.getPath();
+ synchronized (map) {
+ map.put(key, list);
+ }
+ setupFileMonitoring(key);
+ }
+
+ private void setupFileMonitoring(final String key) {
+ if (monitorTask == null)
+ monitorTask = new FileMonitorTask();
+
+ FileObject fileObject = FileUtil.toFileObject(new File(key));
+ fileObject.addFileChangeListener(new FileChangeAdapter() {
+
+ private void evictFile() {
+ FileMonitorTask task;
+ synchronized (map) {
+ map.remove(key);
+ task = map.isEmpty() ? monitorTask : null;
+ if (task != monitorTask)
+ monitorTask = null;
+ }
+ if (task != null) {
+ task.stop();
+ }


+ }
+
+ @Override
+ public void fileChanged(FileEvent fe) {

+ evictFile();


+ }
+
+ @Override
+ public void fileAttributeChanged(FileAttributeEvent fe) {

+ evictFile();


+ }
+
+ @Override
+ public void fileDeleted(FileEvent fe) {

+ evictFile();


+ }
+
+ @Override
+ public void fileRenamed(FileRenameEvent fe) {

+ evictFile();


+ }
+ });
+ }
+

+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("size=" + map.size());
+ return builder.toString();
+ }
+
+ private class FileMonitorTask extends TimerTask {
+
+ private final static int DELAY = 10000;
+ private final Timer timer = new Timer();
+ private long lastModified;
+
+ private FileMonitorTask() {
+ timer.schedule(this, DELAY, DELAY);
+ }
+
+ private void stop() {
+ timer.cancel();
+ }
+
+ @Override
+ public void run() {
+ Set<File> filesToRefresh = new HashSet<File>();
+ Set<String> paths = map.keySet();
+
+ synchronized (map) {
+ for (String path : paths) {
+ final File file = new File(path);
+ if (!file.exists() || file.lastModified() < lastModified) {
+ filesToRefresh.add(file);
+ }
+ }
+ }
+ for (File file : filesToRefresh) {
+ FileUtil.toFileObject(file).refresh(true);
+ }
+ lastModified = System.currentTimeMillis();
+ }
+ }
+}
--
1.6.4.rc3.195.g2b05f

Reply all
Reply to author
Forward
0 new messages