/* * ==================================================================== * Copyright (c) 2004-2012 TMate Software Ltd. All rights reserved. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms * are also available at http://svnkit.com/license.html. * If newer versions of this license are posted there, you may use a * newer version instead, at your option. * ==================================================================== */ package org.tmatesoft.svn.core.internal.io.fs.repcache; import java.io.File; import org.tmatesoft.sqljet.core.SqlJetErrorCode; import org.tmatesoft.sqljet.core.SqlJetException; import org.tmatesoft.sqljet.core.internal.SqlJetSafetyLevel; import org.tmatesoft.sqljet.core.table.ISqlJetCursor; import org.tmatesoft.sqljet.core.table.ISqlJetRunnableWithLock; import org.tmatesoft.sqljet.core.table.ISqlJetTable; import org.tmatesoft.sqljet.core.table.ISqlJetTransaction; import org.tmatesoft.sqljet.core.table.SqlJetDb; import org.tmatesoft.svn.core.SVNErrorCode; import org.tmatesoft.svn.core.SVNErrorMessage; import org.tmatesoft.svn.core.SVNException; import org.tmatesoft.svn.core.internal.io.fs.FSFS; import org.tmatesoft.svn.core.internal.io.fs.FSRepresentation; import org.tmatesoft.svn.core.internal.io.fs.IFSRepresentationCacheManager; import org.tmatesoft.svn.core.internal.io.fs.IFSSqlJetTransaction; import org.tmatesoft.svn.core.internal.wc.SVNErrorManager; import org.tmatesoft.svn.util.SVNDebugLog; import org.tmatesoft.svn.util.SVNLogType; /** * @version 1.3 * @author TMate Software Ltd. */ public class FSRepresentationCacheManager implements IFSRepresentationCacheManager { public static final String REP_CACHE_TABLE = "rep_cache"; private static final int REP_CACHE_DB_FORMAT = 1; private static final String REP_CACHE_DB_SQL = "create table rep_cache (hash text not null primary key, " + " revision integer not null, " + " offset integer not null, " + " size integer not null, " + " expanded_size integer not null); "; private SqlJetDb myRepCacheDB; private ISqlJetTable myTable; private FSFS myFSFS; public static IFSRepresentationCacheManager openRepresentationCache(FSFS fsfs) throws SVNException { final FSRepresentationCacheManager cacheObj = new FSRepresentationCacheManager(); try { cacheObj.myRepCacheDB = SqlJetDb.open(fsfs.getRepositoryCacheFile(), true); cacheObj.myRepCacheDB.setSafetyLevel(SqlJetSafetyLevel.OFF); checkFormat(cacheObj.myRepCacheDB); cacheObj.myTable = cacheObj.myRepCacheDB.getTable(REP_CACHE_TABLE); } catch (SqlJetException e) { SVNDebugLog.getDefaultLog().logError(SVNLogType.FSFS, e); return new FSEmptyRepresentationCacheManager(); } return cacheObj; } public static void createRepresentationCache(File path) throws SVNException { SqlJetDb db = null; try { db = SqlJetDb.open(path, true); checkFormat(db); } catch (SqlJetException e) { SVNDebugLog.getDefaultLog().logError(SVNLogType.FSFS, e); } finally { if (db != null) { try { db.close(); } catch (SqlJetException e) { SVNDebugLog.getDefaultLog().logFine(SVNLogType.FSFS, e); } } } } private static void checkFormat(final SqlJetDb db) throws SqlJetException { db.runWithLock(new ISqlJetRunnableWithLock() { public Object runWithLock(SqlJetDb db) throws SqlJetException { int version = db.getOptions().getUserVersion(); if (version < REP_CACHE_DB_FORMAT) { db.getOptions().setAutovacuum(true); db.runWriteTransaction(new ISqlJetTransaction() { public Object run(SqlJetDb db) throws SqlJetException { db.getOptions().setUserVersion(REP_CACHE_DB_FORMAT); db.createTable(FSRepresentationCacheManager.REP_CACHE_DB_SQL); return null; } }); } else if (version > REP_CACHE_DB_FORMAT) { throw new SqlJetException("Schema format " + version + " not recognized"); } return null; } }); } public void insert(final FSRepresentation representation, boolean rejectDup) throws SVNException { if (representation.getSHA1HexDigest() == null) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.BAD_CHECKSUM_KIND, "Only SHA1 checksums can be used as keys in the rep_cache table.\n"); SVNErrorManager.error(err, SVNLogType.FSFS); } FSRepresentation oldRep = getRepresentationByHash(representation.getSHA1HexDigest()); if (oldRep != null) { if (rejectDup && (oldRep.getRevision() != representation.getRevision() || oldRep.getOffset() != representation.getOffset() || oldRep.getSize() != representation.getSize() || oldRep.getExpandedSize() != representation.getExpandedSize())) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_CORRUPT, "Representation key for checksum ''{0}'' exists in " + "filesystem ''{1}'' with a different value ({2},{3},{4},{5}) than what we were about to store ({6},{7},{8},{9})", new Object[] { representation.getSHA1HexDigest(), myFSFS.getRepositoryRoot(), String.valueOf(oldRep.getRevision()), String.valueOf(oldRep.getOffset()), String.valueOf(oldRep.getSize()), String.valueOf(oldRep.getExpandedSize()), String.valueOf(representation.getRevision()), String.valueOf(representation.getOffset()), String.valueOf(representation.getSize()), String.valueOf(representation.getExpandedSize()) }); SVNErrorManager.error(err, SVNLogType.FSFS); } return; } try { myTable.insert(new Object[] { representation.getSHA1HexDigest(), new Long(representation.getRevision()), new Long(representation.getOffset()), new Long(representation.getSize()), new Long(representation.getExpandedSize()) }); } catch (SqlJetException e) { SVNErrorManager.error(convertError(e), SVNLogType.FSFS); } } public void close() throws SVNException { if (myRepCacheDB != null) { try { myRepCacheDB.close(); } catch (SqlJetException e) { SVNErrorManager.error(convertError(e), SVNLogType.FSFS); } finally { myTable = null; myRepCacheDB = null; myFSFS = null; } } } public FSRepresentation getRepresentationByHash(String hash) throws SVNException { FSRepresentationCacheRecord cache = getByHash(hash); if (cache != null) { FSRepresentation representation = new FSRepresentation(); representation.setExpandedSize(cache.getExpandedSize()); representation.setOffset(cache.getOffset()); representation.setRevision(cache.getRevision()); representation.setSize(cache.getSize()); representation.setSHA1HexDigest(cache.getHash()); return representation; } return null; } private FSRepresentationCacheRecord getByHash(final String hash) throws SVNException { ISqlJetCursor lookup = null; try { lookup = myTable.lookup(myTable.getPrimaryKeyIndexName(), new Object[] { hash }); if (!lookup.eof()) { return new FSRepresentationCacheRecord(lookup); } } catch (SqlJetException e) { SVNErrorManager.error(convertError(e), SVNLogType.FSFS); } finally { if (lookup != null) { try { lookup.close(); } catch (SqlJetException e) { SVNErrorManager.error(convertError(e), SVNLogType.FSFS); } } } return null; } private static SVNErrorMessage convertError(SqlJetException e) { SVNErrorMessage err = SVNErrorMessage.create(convertErrorCode(e), e.getMessage()); return err; } private static SVNErrorCode convertErrorCode(SqlJetException e) { SqlJetErrorCode sqlCode = e.getErrorCode(); if (sqlCode == SqlJetErrorCode.READONLY) { return SVNErrorCode.SQLITE_READONLY; } return SVNErrorCode.SQLITE_ERROR; } public void runWriteTransaction(final IFSSqlJetTransaction transaction) throws SVNException { if (myRepCacheDB != null) { try { myRepCacheDB.runWriteTransaction(new ISqlJetTransaction() { public Object run(SqlJetDb db) throws SqlJetException { try { transaction.run(); } catch (SVNException e) { throw new SqlJetException(e); } return null; } }); } catch (SqlJetException e) { SVNErrorManager.error(convertError(e), SVNLogType.FSFS); } } } public void runReadTransaction(final IFSSqlJetTransaction transaction) throws SVNException { if (myRepCacheDB != null) { try { myRepCacheDB.runReadTransaction(new ISqlJetTransaction() { public Object run(SqlJetDb db) throws SqlJetException { try { transaction.run(); } catch (SVNException e) { throw new SqlJetException(e); } return null; } }); } catch (SqlJetException e) { SVNErrorManager.error(convertError(e), SVNLogType.FSFS); } } } }