/******************************************************************************* * Copyright (c) 2008, 2017 xored software, Inc. and others. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * xored software, Inc. - initial API and Implementation (Andrei Sobolev) *******************************************************************************/ package org.eclipse.dltk.core.internal.rse; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.URI; import java.net.URISyntaxException; import java.util.HashMap; import java.util.Map; import org.eclipse.core.filesystem.EFS; import org.eclipse.core.filesystem.IFileInfo; import org.eclipse.core.filesystem.IFileStore; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.Path; import org.eclipse.dltk.core.DLTKCore; import org.eclipse.dltk.core.RuntimePerformanceMonitor; import org.eclipse.dltk.core.RuntimePerformanceMonitor.PerformanceNode; import org.eclipse.dltk.core.environment.EnvironmentPathUtils; import org.eclipse.dltk.core.environment.FileHandles; import org.eclipse.dltk.core.environment.IEnvironment; import org.eclipse.dltk.core.environment.IFileHandle; import org.eclipse.dltk.core.environment.IFileStoreProvider; import org.eclipse.dltk.core.internal.rse.perfomance.RSEPerfomanceStatistics; import org.eclipse.dltk.core.internal.rse.ssh.RSESshManager; import org.eclipse.dltk.ssh.core.ISshConnection; import org.eclipse.dltk.ssh.core.ISshFileHandle; import org.eclipse.rse.core.model.IHost; public class RSEFileHandle implements IFileHandle, IFileStoreProvider { private static final int SYMLINK_CONNECTION_TIMEOUT = 30 * 1000; private static final int CACHE_LIMIT = 1000; private static final long CACHE_ENTRY_LIFETIME = 10 * 1000; private static class CacheEntry { final IFileInfo fileInfo; final long timestamp; public CacheEntry(IFileInfo fileInfo, long timestamp) { this.fileInfo = fileInfo; this.timestamp = timestamp; } } private static final Map<IFileStore, CacheEntry> cache = new HashMap<>(); private final IFileStore file; private final IEnvironment environment; private ISshFileHandle sshFile; /** * @param infos * @since 2.0 */ public RSEFileHandle(IEnvironment env, IFileStore file) { this.environment = env; this.file = file; } private void fetchSshFile() { if (sshFile != null) { return; } if (environment instanceof RSEEnvironment) { RSEEnvironment rseEnv = (RSEEnvironment) environment; IHost host = rseEnv.getHost(); ISshConnection connection = RSESshManager.getConnection(host); if (connection != null) { // This is ssh connection, and it's alive. try { sshFile = connection.getHandle(new Path(getPathString())); } catch (Exception e) { DLTKRSEPlugin.log("Failed to locate direct ssh connection", //$NON-NLS-1$ e); } } } } /** * @since 2.0 */ public RSEFileHandle(IEnvironment env, IFileStore file, ISshFileHandle sshFile) { this.environment = env; this.file = file; this.sshFile = sshFile; } public RSEFileHandle(IEnvironment env, URI locationURI) { this(env, RSEEnvironment.getStoreFor(locationURI)); } @Override public boolean exists() { if (!environment.connect()) { return false; } fetchSshFile(); if (sshFile != null) { return sshFile.exists(); } try { return fetchInfo(false).exists(); } catch (RuntimeException e) { return false; } } private IFileInfo fetchInfo(boolean force) { final boolean isRemote = !environment.isLocal(); long now = 0; if (isRemote && !force) { CacheEntry entry; synchronized (cache) { entry = cache.get(getCacheKey()); } if (entry != null) { now = System.currentTimeMillis(); if (now - entry.timestamp < CACHE_ENTRY_LIFETIME) { return entry.fileInfo; } } } final IFileInfo info = file.fetchInfo(); if (isRemote) { if (now == 0) { now = System.currentTimeMillis(); } synchronized (cache) { checkCacheLimit(); cache.put(getCacheKey(), new CacheEntry(info, now)); } } return info; } private static void checkCacheLimit() { if (cache.size() > CACHE_LIMIT) { cache.clear(); } } /** * @return */ private final IFileStore getCacheKey() { return file; } @Override public String toOSString() { return this.environment.convertPathToString(getPath()); } @Override public String getCanonicalPath() { return this.environment.getCanonicalPath(getPath()); } @Override public IFileHandle getChild(final String childname) { if (!environment.connect()) { URI childURI; try { childURI = new URI(toURI().toString() + "/" + childname); //$NON-NLS-1$ return new RSEFileHandle(environment, childURI); } catch (URISyntaxException e) { DLTKRSEPlugin.log(e); } } fetchSshFile(); IFileStore childStore = file.getChild(childname); if (sshFile != null) { return new RSEFileHandle(environment, childStore, sshFile .getChild(childname)); } return new RSEFileHandle(environment, childStore); } @Override public IFileHandle[] getChildren() { if (!environment.connect()) { return null; } fetchSshFile(); if (sshFile != null) { try { final ISshFileHandle[] children = sshFile .getChildren(new NullProgressMonitor()); final IFileHandle rseChildren[] = new IFileHandle[children.length]; for (int i = 0; i < children.length; i++) { final ISshFileHandle child = children[i]; final IFileStore childStore = file .getChild(child.getName()); rseChildren[i] = new RSEFileHandle(environment, childStore, child); } return rseChildren; } catch (CoreException e) { DLTKRSEPlugin.log(e); } } try { final IFileInfo[] infos = file.childInfos(EFS.NONE, new NullProgressMonitor()); if (infos.length != 0) { synchronized (cache) { checkCacheLimit(); } } final IFileHandle[] children = new IFileHandle[infos.length]; final long now = System.currentTimeMillis(); for (int i = 0; i < infos.length; i++) { final IFileInfo childInfo = infos[i]; children[i] = new RSEFileHandle(environment, file .getChild(childInfo.getName())); final IFileStore childCacheKey = ((RSEFileHandle) children[i]) .getCacheKey(); synchronized (cache) { cache.put(childCacheKey, new CacheEntry(childInfo, now)); } } return children; } catch (CoreException e) { if (DLTKCore.DEBUG) e.printStackTrace(); return null; } } @Override public IEnvironment getEnvironment() { return environment; } @Override public URI toURI() { return file.toURI(); } @Override public String getName() { return file.getName(); } @Override public IFileHandle getParent() { IFileStore parent = file.getParent(); if (parent == null) return null; return new RSEFileHandle(environment, parent); } @Override public IPath getPath() { return new Path(getPathString()); } private String getPathString() { return file.toURI().getPath(); } @Override public boolean isDirectory() { if (!environment.connect()) { return false; } fetchSshFile(); if (sshFile != null) { return sshFile.isDirectory(); } return fetchInfo(false).isDirectory(); } @Override public boolean isFile() { if (!environment.connect()) { return false; } fetchSshFile(); if (sshFile != null) { return sshFile.exists() && !sshFile.isDirectory(); } final IFileInfo info = fetchInfo(false); return info.exists() && !info.isDirectory(); } @Override public boolean isSymlink() { if (!environment.connect()) { return false; } fetchSshFileWait(); if (sshFile != null) { return sshFile.isSymlink(); } return fetchInfo(false).getAttribute(EFS.ATTRIBUTE_SYMLINK); } private void fetchSshFileWait() { final long startTime = System.currentTimeMillis(); while (sshFile == null && (System.currentTimeMillis() - startTime < SYMLINK_CONNECTION_TIMEOUT)) { fetchSshFile(); try { Thread.sleep(50); } catch (InterruptedException e) { if (DLTKCore.DEBUG) { e.printStackTrace(); } } } } private InputStream internalOpenInputStream(IProgressMonitor monitor) throws IOException { if (!environment.connect()) { return null; } fetchSshFile(); if (sshFile != null) { try { return sshFile.getInputStream(monitor); } catch (CoreException e) { throw new IOException(e.getLocalizedMessage()); } } try { return file.openInputStream(EFS.NONE, monitor); } catch (CoreException e) { if (DLTKCore.DEBUG) e.printStackTrace(); throw new IOException(e.getLocalizedMessage()); } } @Override public InputStream openInputStream(IProgressMonitor monitor) throws IOException { if (!environment.connect()) { return null; } if (RSEPerfomanceStatistics.PERFOMANCE_TRACING) { return new CountStream(this.internalOpenInputStream(monitor)); } return this.internalOpenInputStream(monitor); } @Override public OutputStream openOutputStream(IProgressMonitor monitor) throws IOException { if (!environment.connect()) { return null; } synchronized (cache) { cache.clear(); } fetchSshFile(); if (sshFile != null) { try { return sshFile.getOutputStream(monitor); } catch (CoreException e) { throw new IOException(e.getLocalizedMessage()); } } try { return new BufferedOutputStream(file.openOutputStream(EFS.NONE, monitor)) { @Override public void close() throws IOException { super.close(); clearLastModifiedCache(); } }; } catch (CoreException e) { if (DLTKCore.DEBUG) e.printStackTrace(); throw new IOException(e.getLocalizedMessage()); } } @Override public boolean equals(Object obj) { if (obj instanceof RSEFileHandle) { RSEFileHandle anotherFile = (RSEFileHandle) obj; return this.file.equals(anotherFile.file); } return false; } @Override public int hashCode() { return file.hashCode(); } @Override public String toString() { return toOSString(); } @Override public long lastModified() { if (!environment.connect()) { return 0; } fetchSshFile(); PerformanceNode p = RuntimePerformanceMonitor.begin(); long lm = 0; if (sshFile != null) { lm = sshFile.lastModificationTime(); } else { lm = fetchInfo(false).getLastModified(); } p.done("#", "Return file timestamp", 0); //$NON-NLS-1$//$NON-NLS-2$ return lm; } @Override public long length() { if (!environment.connect()) { return 0; } fetchSshFile(); if (sshFile != null) { return sshFile.getSize(); } return fetchInfo(false).getLength(); } @Override public IPath getFullPath() { return EnvironmentPathUtils.getFullPath(environment, getPath()); } @Override public String getEnvironmentId() { return environment.getId(); } private static final class CountStream extends BufferedInputStream { private InputStream stream; public CountStream(InputStream stream) { super(stream); } @Override public int read() throws IOException { int read = stream.read(); if (read != -1) { RSEPerfomanceStatistics .inc(RSEPerfomanceStatistics.TOTAL_BYTES_RECEIVED); } return read; } @Override public int read(byte[] b, int off, int len) throws IOException { int read = this.stream.read(b, off, len); if (read != -1) { RSEPerfomanceStatistics.inc( RSEPerfomanceStatistics.TOTAL_BYTES_RECEIVED, read); } return read; } @Override public int read(byte[] b) throws IOException { int read = this.stream.read(b); if (read != -1) { RSEPerfomanceStatistics.inc( RSEPerfomanceStatistics.TOTAL_BYTES_RECEIVED, read); } return read; } } /** * @since 2.0 */ @Override public IFileStore getFileStore() { return this.file; } /** * Removes saved timestamp for this element. * * @since 2.0 */ public void clearLastModifiedCache() { synchronized (cache) { cache.remove(getCacheKey()); } } /** * @since 2.0 */ public String resolvePath() { final String currentPath = getPathString(); if (environment.connect() && isSymlink()) { // Try to resolve canonical path using direct ssh connection if (sshFile != null) { final String link = sshFile.readLink(); if (link != null) { if (link.startsWith("/")) { //$NON-NLS-1$ if (!link.equals(currentPath)) { return environment.getFile(new Path(link)) .getCanonicalPath(); } } else { final IPath fullLink = getPath().removeLastSegments(1) .append(link); if (!currentPath.equals(fullLink.toString())) { return environment.getFile(fullLink) .getCanonicalPath(); } } } } else { final IFileInfo info = file.fetchInfo(); if (info != null && info.getAttribute(EFS.ATTRIBUTE_SYMLINK)) { final String linkTarget = info .getStringAttribute(EFS.ATTRIBUTE_LINK_TARGET); if (linkTarget != null && !currentPath.equals(linkTarget)) { final Path link = new Path(linkTarget); final IFileStore resolved; if (link.isAbsolute()) { resolved = file.getFileSystem().getStore(link); } else { resolved = file.getFileStore(link); } return resolved.toURI().getPath(); } } } } return currentPath; } @Override public void move(IFileHandle destination) throws CoreException { fetchSshFile(); if (sshFile != null) { sshFile.move(FileHandles.asPath(destination, environment)); } else { file .move(FileHandles.asFileStore(destination), EFS.OVERWRITE, null); } } }