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@525: import java.nio.ByteBuffer; tikhomirov@525: import java.nio.channels.FileChannel; tikhomirov@525: tikhomirov@525: import org.tmatesoft.hg.repo.HgDataFile; tikhomirov@580: import org.tmatesoft.hg.repo.HgManifest; 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@572: */ tikhomirov@580: public void processFile(HgDataFile df, int fileRevIndex, HgManifest.Flags flags) throws IOException { tikhomirov@525: try { tikhomirov@525: prepare(df.getPath()); tikhomirov@580: if (flags != HgManifest.Flags.Link) { tikhomirov@580: destChannel = new FileOutputStream(dest).getChannel(); tikhomirov@580: } else { tikhomirov@580: linkChannel = new ByteArrayChannel(); tikhomirov@580: } tikhomirov@525: df.contentWithFilters(fileRevIndex, this); tikhomirov@525: } catch (CancelledException ex) { tikhomirov@526: hgRepo.getSessionContext().getLog().dump(getClass(), Severity.Error, ex, "Our impl doesn't throw cancellation"); tikhomirov@580: } finally { tikhomirov@580: if (flags != HgManifest.Flags.Link) { tikhomirov@580: destChannel.close(); tikhomirov@580: destChannel = null; tikhomirov@580: // leave dest in case anyone enquires with #getDestinationFile tikhomirov@580: } tikhomirov@525: } tikhomirov@580: if (linkChannel != null && symlinkCap) { tikhomirov@580: assert flags == HgManifest.Flags.Link; tikhomirov@580: fileFlagsHelper.createSymlink(dest.getParentFile(), dest.getName(), linkChannel.toArray()); tikhomirov@580: } else if (flags == HgManifest.Flags.Exec && execCap) { tikhomirov@580: fileFlagsHelper.setExecutableBit(dest.getParentFile(), dest.getName()); tikhomirov@580: } tikhomirov@580: // Although HgWCStatusCollector treats 644 (`hg manifest -v`) and 664 (my fs) the same, it's better tikhomirov@580: // to detect actual flags here tikhomirov@580: fmode = flags.fsMode(); // default to one from manifest tikhomirov@580: if (fileFlagsHelper != null) { tikhomirov@580: // if neither execBit nor link is supported by fs, it's unlikely file mode is supported, too. tikhomirov@580: try { tikhomirov@580: fmode = fileFlagsHelper.getFileMode(dest, fmode); tikhomirov@580: } catch (IOException ex) { tikhomirov@580: // Warn, we've got default value and can live with it tikhomirov@580: hgRepo.getSessionContext().getLog().dump(getClass(), Warn, ex, "Failed get file access rights"); tikhomirov@580: } tikhomirov@580: } tikhomirov@525: } tikhomirov@525: tikhomirov@525: public void prepare(Path fname) 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@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@525: }