tikhomirov@525: /* tikhomirov@525: * Copyright (c) 2012-2013 TMate Software Ltd tikhomirov@525: * tikhomirov@525: * This program is free software; you can redistribute it and/or modify tikhomirov@525: * it under the terms of the GNU General Public License as published by tikhomirov@525: * the Free Software Foundation; version 2 of the License. tikhomirov@525: * tikhomirov@525: * This program is distributed in the hope that it will be useful, tikhomirov@525: * but WITHOUT ANY WARRANTY; without even the implied warranty of tikhomirov@525: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the tikhomirov@525: * GNU General Public License for more details. tikhomirov@525: * tikhomirov@525: * For information on how to redistribute this software under tikhomirov@525: * the terms of a license other than GNU General Public License tikhomirov@525: * contact TMate Software at support@hg4j.com tikhomirov@525: */ tikhomirov@525: package org.tmatesoft.hg.internal; tikhomirov@525: tikhomirov@580: import static org.tmatesoft.hg.util.LogFacility.Severity.Warn; tikhomirov@580: tikhomirov@525: import java.io.File; tikhomirov@525: import java.io.FileOutputStream; tikhomirov@525: import java.io.IOException; tikhomirov@705: import java.io.InputStream; tikhomirov@525: import java.nio.ByteBuffer; tikhomirov@525: import java.nio.channels.FileChannel; tikhomirov@525: tikhomirov@705: import org.tmatesoft.hg.core.HgFileRevision; tikhomirov@705: import org.tmatesoft.hg.core.HgIOException; tikhomirov@525: import org.tmatesoft.hg.repo.HgDataFile; tikhomirov@580: import org.tmatesoft.hg.repo.HgManifest; tikhomirov@628: import org.tmatesoft.hg.repo.HgRuntimeException; tikhomirov@525: import org.tmatesoft.hg.util.ByteChannel; tikhomirov@525: import org.tmatesoft.hg.util.CancelledException; tikhomirov@526: import org.tmatesoft.hg.util.LogFacility.Severity; tikhomirov@525: import org.tmatesoft.hg.util.Path; tikhomirov@525: tikhomirov@525: /** tikhomirov@525: * tikhomirov@525: * @author Artem Tikhomirov tikhomirov@525: * @author TMate Software Ltd. tikhomirov@525: */ tikhomirov@525: public class WorkingDirFileWriter implements ByteChannel { tikhomirov@525: tikhomirov@525: tikhomirov@526: private final Internals hgRepo; tikhomirov@580: private final boolean execCap, symlinkCap; tikhomirov@580: private final FileSystemHelper fileFlagsHelper; tikhomirov@525: private File dest; tikhomirov@525: private FileChannel destChannel; tikhomirov@525: private int totalBytesWritten; tikhomirov@580: private ByteArrayChannel linkChannel; tikhomirov@580: private int fmode; tikhomirov@525: tikhomirov@526: public WorkingDirFileWriter(Internals internalRepo) { tikhomirov@526: hgRepo = internalRepo; tikhomirov@580: execCap = Internals.checkSupportsExecutables(internalRepo.getRepo().getWorkingDir()); tikhomirov@580: symlinkCap = Internals.checkSupportsSymlinks(internalRepo.getRepo().getWorkingDir()); tikhomirov@580: if (symlinkCap || execCap) { tikhomirov@580: fileFlagsHelper = new FileSystemHelper(internalRepo.getSessionContext()); tikhomirov@580: } else { tikhomirov@580: fileFlagsHelper = null; tikhomirov@580: } tikhomirov@525: } tikhomirov@525: tikhomirov@572: /** tikhomirov@580: * Writes content of specified file revision into local filesystem, or create a symlink according to flags. tikhomirov@580: * Executable bit is set if specified and filesystem supports it. tikhomirov@628: * @throws HgRuntimeException tikhomirov@572: */ tikhomirov@705: public void processFile(final HgDataFile df, final int fileRevIndex, HgManifest.Flags flags) throws HgIOException, HgRuntimeException { tikhomirov@705: processFile(df.getPath(), new Fetch() { tikhomirov@705: public void readInto(ByteChannel ch) { tikhomirov@705: try { tikhomirov@705: df.contentWithFilters(fileRevIndex, ch); tikhomirov@705: } catch (CancelledException ex) { tikhomirov@705: handleUnexpectedCancel(ex); tikhomirov@705: } tikhomirov@580: } tikhomirov@705: }, flags); tikhomirov@705: } tikhomirov@705: tikhomirov@705: public void processFile(final HgFileRevision fr) throws HgIOException, HgRuntimeException { tikhomirov@705: processFile(fr.getPath(), new Fetch() { tikhomirov@705: tikhomirov@705: public void readInto(ByteChannel ch) throws IOException, HgRuntimeException { tikhomirov@705: try { tikhomirov@705: fr.putContentTo(ch); tikhomirov@705: } catch (CancelledException ex) { tikhomirov@705: handleUnexpectedCancel(ex); tikhomirov@705: } tikhomirov@705: } tikhomirov@705: }, fr.getFileFlags()); tikhomirov@705: } tikhomirov@705: tikhomirov@705: /** tikhomirov@705: * Closes supplied content stream tikhomirov@705: */ tikhomirov@705: public void processFile(Path fname, final InputStream content, HgManifest.Flags flags) throws HgIOException, HgRuntimeException { tikhomirov@705: processFile(fname, new Fetch() { tikhomirov@705: tikhomirov@705: public void readInto(ByteChannel ch) throws IOException, HgRuntimeException { tikhomirov@705: try { tikhomirov@705: ByteBuffer bb = ByteBuffer.wrap(new byte[8*1024]); tikhomirov@705: int r; tikhomirov@705: while ((r = content.read(bb.array())) != -1) { tikhomirov@705: bb.position(0).limit(r); tikhomirov@705: for (int wrote = 0; wrote < r; ) { tikhomirov@705: r -= wrote; tikhomirov@705: wrote = ch.write(bb); tikhomirov@705: assert bb.remaining() == r - wrote; tikhomirov@705: } tikhomirov@705: } tikhomirov@705: } catch (CancelledException ex) { tikhomirov@705: handleUnexpectedCancel(ex); tikhomirov@705: } tikhomirov@705: } tikhomirov@705: }, flags); tikhomirov@705: } tikhomirov@705: tikhomirov@705: private interface Fetch { tikhomirov@705: void readInto(ByteChannel ch) throws IOException, HgRuntimeException; tikhomirov@705: } tikhomirov@705: tikhomirov@705: private void processFile(Path fname, Fetch fetch, HgManifest.Flags flags) throws HgIOException, HgRuntimeException { tikhomirov@705: try { tikhomirov@705: byte[] symlinkContent = null; tikhomirov@580: try { tikhomirov@705: prepare(fname, flags); tikhomirov@705: fetch.readInto(this); tikhomirov@705: } finally { tikhomirov@705: symlinkContent = close(fname, flags); tikhomirov@580: } tikhomirov@705: if (flags == HgManifest.Flags.Link && symlinkCap) { tikhomirov@705: assert symlinkContent != null; tikhomirov@705: fileFlagsHelper.createSymlink(dest.getParentFile(), dest.getName(), symlinkContent); tikhomirov@705: } else if (flags == HgManifest.Flags.Exec && execCap) { tikhomirov@705: fileFlagsHelper.setExecutableBit(dest.getParentFile(), dest.getName()); tikhomirov@705: } tikhomirov@705: // Although HgWCStatusCollector treats 644 (`hg manifest -v`) and 664 (my fs) the same, it's better tikhomirov@705: // to detect actual flags here tikhomirov@705: fmode = flags.fsMode(); // default to one from manifest tikhomirov@705: if (fileFlagsHelper != null) { tikhomirov@705: // if neither execBit nor link is supported by fs, it's unlikely file mode is supported, too. tikhomirov@705: try { tikhomirov@705: fmode = fileFlagsHelper.getFileMode(dest, fmode); tikhomirov@705: } catch (IOException ex) { tikhomirov@705: // Warn, we've got default value and can live with it tikhomirov@705: hgRepo.getSessionContext().getLog().dump(getClass(), Warn, ex, "Failed get file access rights"); tikhomirov@705: } tikhomirov@705: } tikhomirov@705: } catch (IOException ex) { tikhomirov@705: String msg = String.format("Failed to write file %s to the working directory", fname); tikhomirov@705: throw new HgIOException(msg, ex, dest); tikhomirov@580: } tikhomirov@525: } tikhomirov@525: tikhomirov@705: private void prepare(Path fname, HgManifest.Flags flags) throws IOException { tikhomirov@525: String fpath = fname.toString(); tikhomirov@526: dest = new File(hgRepo.getRepo().getWorkingDir(), fpath); tikhomirov@525: if (fpath.indexOf('/') != -1) { tikhomirov@525: dest.getParentFile().mkdirs(); tikhomirov@525: } tikhomirov@580: destChannel = null; tikhomirov@580: linkChannel = null; tikhomirov@525: totalBytesWritten = 0; tikhomirov@580: fmode = 0; tikhomirov@705: if (flags != HgManifest.Flags.Link) { tikhomirov@705: destChannel = new FileOutputStream(dest).getChannel(); tikhomirov@705: } else { tikhomirov@705: linkChannel = new ByteArrayChannel(); tikhomirov@705: } tikhomirov@705: } tikhomirov@705: tikhomirov@705: private byte[] close(Path fname, HgManifest.Flags flags) throws IOException { tikhomirov@705: if (flags != HgManifest.Flags.Link) { tikhomirov@705: destChannel.close(); tikhomirov@705: destChannel = null; tikhomirov@705: // leave dest in case anyone enquires with #getDestinationFile tikhomirov@705: } tikhomirov@705: if (linkChannel != null) { tikhomirov@705: final byte[] rv = linkChannel.toArray(); tikhomirov@705: linkChannel = null; tikhomirov@705: return rv; tikhomirov@705: } tikhomirov@705: return null; tikhomirov@525: } tikhomirov@525: tikhomirov@525: public int write(ByteBuffer buffer) throws IOException, CancelledException { tikhomirov@580: final int written; tikhomirov@580: if (linkChannel != null) { tikhomirov@580: written = linkChannel.write(buffer); tikhomirov@580: } else { tikhomirov@580: written = destChannel.write(buffer); tikhomirov@580: } tikhomirov@525: totalBytesWritten += written; tikhomirov@525: return written; tikhomirov@525: } tikhomirov@525: tikhomirov@580: /** tikhomirov@580: * Information purposes only, to find out trouble location if {@link #processFile(HgDataFile, int)} fails tikhomirov@580: */ tikhomirov@580: public File getDestinationFile() { tikhomirov@580: return dest; tikhomirov@525: } tikhomirov@525: tikhomirov@525: public int bytesWritten() { tikhomirov@525: return totalBytesWritten; tikhomirov@525: } tikhomirov@580: tikhomirov@580: public int fmode() { tikhomirov@580: return fmode; tikhomirov@580: } tikhomirov@580: tikhomirov@580: public int mtime() { tikhomirov@580: return (int) (dest.lastModified() / 1000); tikhomirov@580: } tikhomirov@705: tikhomirov@705: private void handleUnexpectedCancel(CancelledException ex) { tikhomirov@707: hgRepo.getLog().dump(WorkingDirFileWriter.class, Severity.Error, ex, "Our impl doesn't throw cancellation"); tikhomirov@705: } tikhomirov@525: }