| /******************************************************************************* |
| * 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); |
| } |
| } |
| } |