tikhomirov@539: /*
tikhomirov@539: * Copyright (c) 2013 TMate Software Ltd
tikhomirov@539: *
tikhomirov@539: * This program is free software; you can redistribute it and/or modify
tikhomirov@539: * it under the terms of the GNU General Public License as published by
tikhomirov@539: * the Free Software Foundation; version 2 of the License.
tikhomirov@539: *
tikhomirov@539: * This program is distributed in the hope that it will be useful,
tikhomirov@539: * but WITHOUT ANY WARRANTY; without even the implied warranty of
tikhomirov@539: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
tikhomirov@539: * GNU General Public License for more details.
tikhomirov@539: *
tikhomirov@539: * For information on how to redistribute this software under
tikhomirov@539: * the terms of a license other than GNU General Public License
tikhomirov@539: * contact TMate Software at support@hg4j.com
tikhomirov@539: */
tikhomirov@539: package org.tmatesoft.hg.internal;
tikhomirov@539:
tikhomirov@647: import static org.tmatesoft.hg.repo.HgRepositoryFiles.FNCache;
tikhomirov@647:
tikhomirov@539: import java.io.File;
tikhomirov@539: import java.io.FileOutputStream;
tikhomirov@539: import java.io.IOException;
tikhomirov@616: import java.nio.ByteBuffer;
tikhomirov@616: import java.nio.CharBuffer;
tikhomirov@616: import java.nio.channels.FileChannel;
tikhomirov@539: import java.util.ArrayList;
tikhomirov@559: import java.util.List;
tikhomirov@539:
tikhomirov@664: import org.tmatesoft.hg.core.HgIOException;
tikhomirov@539: import org.tmatesoft.hg.util.Path;
tikhomirov@539:
tikhomirov@539: /**
tikhomirov@559: * Append-only fncache support
tikhomirov@559: *
tikhomirov@539: *
tikhomirov@539: * The fncache file contains the paths of all filelog files in the store as encoded by mercurial.filelog.encodedir. The paths are separated by '\n' (LF).
tikhomirov@539: *
tikhomirov@539: * @see http://mercurial.selenic.com/wiki/fncacheRepoFormat
tikhomirov@559: *
tikhomirov@559: *
tikhomirov@539: * @author Artem Tikhomirov
tikhomirov@539: * @author TMate Software Ltd.
tikhomirov@539: */
tikhomirov@539: public class FNCacheFile {
tikhomirov@539:
tikhomirov@539: private final Internals repo;
tikhomirov@559: // private final List files;
tikhomirov@616: private final List addedDotI;
tikhomirov@616: private final List addedDotD;
tikhomirov@616: private final FNCachePathHelper pathHelper;
tikhomirov@539:
tikhomirov@539: public FNCacheFile(Internals internalRepo) {
tikhomirov@539: repo = internalRepo;
tikhomirov@559: // files = new ArrayList();
tikhomirov@616: pathHelper = new FNCachePathHelper();
tikhomirov@616: addedDotI = new ArrayList(5);
tikhomirov@616: addedDotD = new ArrayList(5);
tikhomirov@539: }
tikhomirov@539:
tikhomirov@559: /*
tikhomirov@559: * For append-only option, we don't care reading the original content
tikhomirov@539: public void read(Path.Source pathFactory) throws IOException {
tikhomirov@539: File f = fncacheFile();
tikhomirov@539: files.clear();
tikhomirov@539: if (!f.exists()) {
tikhomirov@539: return;
tikhomirov@539: }
tikhomirov@539: ArrayList entries = new ArrayList();
tikhomirov@539: // names in fncache are in local encoding, shall translate to unicode
tikhomirov@539: new LineReader(f, repo.getSessionContext().getLog(), repo.getFilenameEncoding()).read(new LineReader.SimpleLineCollector(), entries);
tikhomirov@539: for (String e : entries) {
tikhomirov@624: // XXX plain wrong, need either to decode paths and strip off .i/.d or (if keep names as is) change write()
tikhomirov@539: files.add(pathFactory.path(e));
tikhomirov@539: }
tikhomirov@539: }
tikhomirov@559: */
tikhomirov@539:
tikhomirov@664: public void write(Transaction tr) throws HgIOException {
tikhomirov@616: if (addedDotI.isEmpty() && addedDotD.isEmpty()) {
tikhomirov@539: return;
tikhomirov@539: }
tikhomirov@647: File f = repo.getRepositoryFile(FNCache);
tikhomirov@539: f.getParentFile().mkdirs();
tikhomirov@667: final EncodingHelper fnEncoder = repo.buildFileNameEncodingHelper();
tikhomirov@616: ArrayList added = new ArrayList();
tikhomirov@616: for (Path p : addedDotI) {
tikhomirov@616: added.add(CharBuffer.wrap(pathHelper.rewrite(p)));
tikhomirov@616: }
tikhomirov@616: for (Path p : addedDotD) {
tikhomirov@616: // XXX FNCachePathHelper always return name of an index file, need to change it into a name of data file,
tikhomirov@616: // although the approach (to replace last char) is depressingly awful
tikhomirov@616: CharSequence cs = pathHelper.rewrite(p);
tikhomirov@616: CharBuffer cb = CharBuffer.allocate(cs.length());
tikhomirov@616: cb.append(cs);
tikhomirov@616: cb.put(cs.length()-1, 'd');
tikhomirov@616: cb.flip();
tikhomirov@616: added.add(cb);
tikhomirov@616: }
tikhomirov@664: FileOutputStream fos = null;
tikhomirov@664: f = tr.prepare(f);
tikhomirov@664: try {
tikhomirov@664: fos = new FileOutputStream(f, true);
tikhomirov@664: FileChannel fncacheFile = fos.getChannel();
tikhomirov@664: ByteBuffer lf = ByteBuffer.wrap(new byte[] { 0x0A });
tikhomirov@664: for (CharBuffer b : added) {
tikhomirov@667: fncacheFile.write(fnEncoder.toFNCache(b));
tikhomirov@664: fncacheFile.write(lf);
tikhomirov@664: lf.rewind();
tikhomirov@664: }
tikhomirov@664: fncacheFile.force(true);
tikhomirov@664: tr.done(f);
tikhomirov@664: } catch (IOException ex) {
tikhomirov@664: tr.failure(f, ex);
tikhomirov@664: throw new HgIOException("Failed to write fncache", ex, f);
tikhomirov@664: } finally {
tikhomirov@664: new FileUtils(repo.getLog(), this).closeQuietly(fos, f);
tikhomirov@539: }
tikhomirov@664:
tikhomirov@539: }
tikhomirov@539:
tikhomirov@616: public void addIndex(Path p) {
tikhomirov@616: addedDotI.add(p);
tikhomirov@616: }
tikhomirov@616:
tikhomirov@616: public void addData(Path p) {
tikhomirov@616: addedDotD.add(p);
tikhomirov@539: }
tikhomirov@663:
tikhomirov@663: /**
tikhomirov@663: * Register new files with fncache if one is enabled for the repo, do nothing otherwise
tikhomirov@663: */
tikhomirov@663: public static class Mediator {
tikhomirov@663: private final Internals repo;
tikhomirov@663: private FNCacheFile fncache;
tikhomirov@664: private final Transaction tr;
tikhomirov@663:
tikhomirov@664: public Mediator(Internals internalRepo, Transaction transaction) {
tikhomirov@663: repo = internalRepo;
tikhomirov@664: tr = transaction;
tikhomirov@663: }
tikhomirov@663:
tikhomirov@663: public void registerNew(Path f, RevlogStream rs) {
tikhomirov@663: if (fncache != null || repo.fncacheInUse()) {
tikhomirov@663: if (fncache == null) {
tikhomirov@663: fncache = new FNCacheFile(repo);
tikhomirov@663: }
tikhomirov@663: fncache.addIndex(f);
tikhomirov@663: if (!rs.isInlineData()) {
tikhomirov@663: fncache.addData(f);
tikhomirov@663: }
tikhomirov@663: }
tikhomirov@663: }
tikhomirov@663:
tikhomirov@664: public void complete() throws HgIOException {
tikhomirov@663: if (fncache != null) {
tikhomirov@664: fncache.write(tr);
tikhomirov@663: }
tikhomirov@663: }
tikhomirov@663: }
tikhomirov@539: }