/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
*
* 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
* or nbbuild/licenses/CDDL-GPL-2-CP. 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 and include the License file at
* nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
* particular file as 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):
*
* The Original Software is NetBeans. The Initial Developer of the Original
* Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
* Microsystems, Inc. All Rights Reserved.
* Portions Copyright 2008 Alexander Coles (Ikonoklastik Productions).
*
* 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;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import org.netbeans.modules.turbo.TurboProvider;
import org.openide.filesystems.FileUtil;
/**
* Storage of file attributes with shortcut to retrieve all stored values.
*
* @author Maros Sandor
*/
class DiskMapTurboProvider implements TurboProvider {
static final String ATTR_STATUS_MAP = "git.STATUS_MAP"; // NOI18N
private static final int STATUS_VALUABLE = StatusInfo.STATUS_MANAGED & ~StatusInfo.STATUS_VERSIONED_UPTODATE;
private static final String CACHE_DIRECTORY = "gitcache"; // NOI18N
private File cacheStore;
private int storeSerial;
private int cachedStoreSerial = -1;
private Map<File, StatusInfo> cachedValues;
DiskMapTurboProvider() {
initCacheStore();
}
synchronized Map<File, StatusInfo> getAllModifiedValues() {
if (cachedStoreSerial != storeSerial || cachedValues == null) {
cachedValues = new HashMap<File, StatusInfo>();
File [] files = cacheStore.listFiles();
for (int i = 0; i < files.length; i++) {
File file = files[i];
if (file.getName().endsWith(".bin") == false) { // NOI18N
// on windows list returns already deleted .new files
continue;
}
DataInputStream dis = null;
try {
int retry = 0;
while (true) {
try {
dis = new DataInputStream(new BufferedInputStream(new FileInputStream(file)));
break;
} catch (IOException ioex) {
retry++;
if (retry > 7) {
throw ioex;
}
Thread.sleep(retry * 30);
}
}
for (;;) {
int pathLen = dis.readInt();
dis.readInt();
String path = readChars(dis, pathLen);
Map<File, StatusInfo> value = readValue(dis, path);
for (File f : value.keySet()) {
StatusInfo info = value.get(f);
if ((info.getStatus() & DiskMapTurboProvider.STATUS_VALUABLE) != 0) {
cachedValues.put(f, info);
}
}
}
} catch (EOFException e) {
// reached EOF, no entry for this key
} catch (Exception e) {
Git.LOG.log(Level.WARNING, null, e);
} finally {
if (dis != null) try { dis.close(); } catch (IOException e) {}
}
}
cachedStoreSerial = storeSerial;
cachedValues = Collections.unmodifiableMap(cachedValues);
}
return cachedValues;
}
public boolean recognizesAttribute(String name) {
return DiskMapTurboProvider.ATTR_STATUS_MAP.equals(name);
}
public boolean recognizesEntity(Object key) {
return key instanceof File;
}
public synchronized Object readEntry(Object key, String name, MemoryCache memoryCache) {
assert key instanceof File;
assert name != null;
boolean readFailed = false;
File dir = (File) key;
File store = getStore(dir);
if (!store.isFile()) {
return null;
}
String dirPath = dir.getAbsolutePath();
int dirPathLen = dirPath.length();
DataInputStream dis = null;
try {
int retry = 0;
while (true) {
try {
dis = new DataInputStream(new BufferedInputStream(new FileInputStream(store)));
break;
} catch (IOException ioex) {
retry++;
if (retry > 7) {
throw ioex;
}
Thread.sleep(retry * 30);
}
}
for (;;) {
int pathLen = dis.readInt();
int mapLen = dis.readInt();
if (pathLen != dirPathLen) {
skip(dis, pathLen * 2 + mapLen);
} else {
String path = readChars(dis, pathLen);
if (dirPath.equals(path)) {
return readValue(dis, path);
} else {
skip(dis, mapLen);
}
}
}
} catch (EOFException e) {
// reached EOF, no entry for this key
} catch (Exception e) {
Git.LOG.log(Level.INFO, null, e);
readFailed = true;
} finally {
if (dis != null) try { dis.close(); } catch (IOException e) {}
}
if (readFailed) store.delete();
return null;
}
public synchronized boolean writeEntry(Object key, String name, Object value) {
assert key instanceof File;
assert name != null;
if (value != null) {
if (!(value instanceof Map)) return false;
if (!isValuable(value)) value = null;
}
File dir = (File) key;
String dirPath = dir.getAbsolutePath();
int dirPathLen = dirPath.length();
File store = getStore(dir);
if (value == null && !store.exists()) return true;
File storeNew = new File(store.getParentFile(), store.getName() + ".new"); // NOI18N
DataOutputStream oos = null;
DataInputStream dis = null;
try {
oos = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(storeNew)));
if (value != null) {
writeEntry(oos, dirPath, value);
}
if (store.exists()) {
int retry = 0;
while (true) {
try {
dis = new DataInputStream(new BufferedInputStream(new FileInputStream(store)));
break;
} catch (IOException ioex) {
retry++;
if (retry > 7) {
throw ioex;
}
Thread.sleep(retry * 30);
}
}
for (;;) {
int pathLen;
try {
pathLen = dis.readInt();
} catch (EOFException e) {
break;
}
int mapLen = dis.readInt();
if (pathLen == dirPathLen) {
String path = readChars(dis, pathLen);
if (dirPath.equals(path)) {
skip(dis, mapLen);
} else {
oos.writeInt(pathLen);
oos.writeInt(mapLen);
oos.writeChars(path);
DiskMapTurboProvider.copyStreams(oos, dis, mapLen);
}
} else {
oos.writeInt(pathLen);
oos.writeInt(mapLen);
DiskMapTurboProvider.copyStreams(oos, dis, mapLen + pathLen * 2);
}
}
}
} catch (Exception e) {
Git.LOG.log(Level.WARNING, "writeEntry(): Copy: {0} to: {1}", new Object[] {store.getAbsolutePath(), storeNew.getAbsolutePath()}); //NOI18N
return true;
} finally {
if (oos != null) try { oos.close(); } catch (IOException e) {}
if (dis != null) try { dis.close(); } catch (IOException e) {}
}
storeSerial++;
store.delete();
storeNew.renameTo(store);
return true;
}
private void skip(InputStream is, long len) throws IOException {
while (len > 0) {
long n = is.skip(len);
if (n < 0) throw new EOFException("Missing " + len + " bytes."); // NOI18N
len -= n;
}
}
private String readChars(DataInputStream dis, int len) throws IOException {
StringBuffer sb = new StringBuffer(len);
while (len-- > 0) {
sb.append(dis.readChar());
}
return sb.toString();
}
private Map<File, StatusInfo> readValue(DataInputStream dis, String dirPath) throws IOException {
Map<File, StatusInfo> map = new HashMap<File, StatusInfo>();
int len = dis.readInt();
while (len-- > 0) {
int nameLen = dis.readInt();
String name = readChars(dis, nameLen);
File file = new File(dirPath, name);
int status = dis.readInt();
StatusInfo info = new StatusInfo(status & 65535, status > 65535);
map.put(file, info);
}
return map;
}
private void writeEntry(DataOutputStream dos, String dirPath, Object value) throws IOException {
Map map = (Map) value;
Set set = map.keySet();
ByteArrayOutputStream baos = new ByteArrayOutputStream(set.size() * 50);
DataOutputStream temp = new DataOutputStream(baos);
temp.writeInt(set.size());
for (Iterator i = set.iterator(); i.hasNext();) {
File file = (File) i.next();
StatusInfo info = (StatusInfo) map.get(file);
temp.writeInt(file.getName().length());
temp.writeChars(file.getName());
temp.writeInt(info.getStatus() + (info.isDirectory() ? 65536 : 0));
}
temp.close();
byte [] valueBytes = baos.toByteArray();
dos.writeInt(dirPath.length());
dos.writeInt(valueBytes.length);
dos.writeChars(dirPath);
dos.write(valueBytes);
}
private boolean isValuable(Object value) {
Map map = (Map) value;
for (Iterator i = map.values().iterator(); i.hasNext();) {
StatusInfo info = (StatusInfo) i.next();
if ((info.getStatus() & DiskMapTurboProvider.STATUS_VALUABLE) != 0) return true;
}
return false;
}
private File getStore(File dir) {
String dirPath = dir.getAbsolutePath();
int dirHash = dirPath.hashCode();
return new File(cacheStore, Integer.toString(dirHash % 173 + 172) + ".bin"); // NOI18N
}
private void initCacheStore() {
String userDir = System.getProperty("netbeans.user"); // NOI18N
File cacheRoot;
if (userDir != null) {
cacheRoot = new File(new File(userDir, "var"), "cache"); // NOI18N
} else {
cacheRoot = FileUtil.toFile(FileUtil.getConfigRoot());
}
cacheStore = new File(cacheRoot, DiskMapTurboProvider.CACHE_DIRECTORY); // NOI18N
cacheStore.mkdirs();
}
private static void copyStreams(OutputStream out, InputStream in, int len) throws IOException {
byte [] buffer = new byte[4096];
for (;;) {
int n = (len <= 4096) ? len : 4096;
n = in.read(buffer, 0, n);
if (n < 0) throw new EOFException("Missing " + len + " bytes."); // NOI18N
out.write(buffer, 0, n);
if ((len -= n) == 0) break;
}
out.flush();
}
}