/* * Copyright 2000-2016 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jetbrains.idea.svn.svnkit.lowLevel; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.util.text.StringUtil; import java.io.BufferedInputStream; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Field; import java.lang.reflect.Method; /** * Created with IntelliJ IDEA. * User: Irina.Chernushina * Date: 7/30/12 * Time: 6:23 PM * * SVNLogInputStream is not used, since it does not check available() * */ public class SVNStoppableInputStream extends InputStream { private final static Logger LOG = Logger.getInstance(SVNStoppableInputStream.class); private final static String ourCheckAvalilable = "svn.check.available"; private final InputStream myOriginalIs; private final InputStream myIn; private boolean myAvailableChecked; private final boolean myCheckAvailable; public SVNStoppableInputStream(InputStream original, InputStream in) { final String property = System.getProperty(ourCheckAvalilable); myCheckAvailable = ! StringUtil.isEmptyOrSpaces(property) && Boolean.parseBoolean(property); //myCheckAvailable = Boolean.parseBoolean(property); myOriginalIs = myCheckAvailable ? digOriginal(original) : original; myIn = in; myAvailableChecked = false; } private InputStream digOriginal(InputStream original) { // because of many delegates in the chain possible InputStream current = original; try { while (true) { final String name = current.getClass().getName(); if ("org.tmatesoft.svn.core.internal.io.dav.http.SpoolFile.SpoolInputStream".equals(name)) { current = byName(current, "myCurrentInput"); } else if ("org.tmatesoft.svn.core.internal.util.ChunkedInputStream".equals(name)) { current = byName(current, "myInputStream"); } else if ("org.tmatesoft.svn.core.internal.util.FixedSizeInputStream".equals(name)) { current = byName(current, "mySource"); } else if (current instanceof BufferedInputStream) { return createReadingProxy(current); } else { // maybe ok class, maybe some unknown proxy Method[] methods = current.getClass().getDeclaredMethods(); for (Method method : methods) { if ("available".equals(method.getName())) { return current; } } return createReadingProxy(current); } } } catch (NoSuchFieldException | IllegalAccessException e) { LOG.info(e); return createReadingProxy(current); } } private InputStream createReadingProxy(final InputStream current) { return new InputStream() { @Override public int read() throws IOException { return current.read(); } public int read(byte[] b) throws IOException { return current.read(b); } public int read(byte[] b, int off, int len) throws IOException { return current.read(b, off, len); } public long skip(long n) throws IOException { return current.skip(n); } public void close() throws IOException { current.close(); } public void mark(int readlimit) { current.mark(readlimit); } public void reset() throws IOException { current.reset(); } public boolean markSupported() { return current.markSupported(); } @Override public int available() throws IOException { return 1; } }; } private InputStream byName(InputStream current, final String name) throws NoSuchFieldException, IllegalAccessException { final Field input = current.getClass().getDeclaredField(name); input.setAccessible(true); current = (InputStream) input.get(current); return current; } @Override public int read() throws IOException { waitForAvailable(); return myIn.read(); } @Override public int read(byte[] b, int off, int len) throws IOException { waitForAvailable(); return myIn.read(b, off, len); } @Override public long skip(long n) throws IOException { if (n <= 0) return 0; check(); if (available() <= 0) return 0; return myIn.skip(n); } @Override public int available() throws IOException { check(); if (! myAvailableChecked) { int available = myOriginalIs.available(); if (available > 0) { myAvailableChecked = true; } return available; } return 1; } @Override public void close() throws IOException { check(); myIn.close(); } @Override public synchronized void mark(int readlimit) { myIn.mark(readlimit); } @Override public synchronized void reset() throws IOException { check(); myIn.reset(); } @Override public boolean markSupported() { return myIn.markSupported(); } private void check() throws IOException { ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator(); if (indicator != null && indicator.isCanceled()) { throw new IOException("Read request to canceled by user"); } } private void waitForAvailable() throws IOException { if (! myCheckAvailable) return; final Object lock = new Object(); synchronized (lock) { while (available() <= 0) { check(); try { lock.wait(100); } catch (InterruptedException e) { // } } } } }