blob: 6854607d5300eb1d2775392ec73ce65cf3c8cb8c [file] [log] [blame]
/*
* Copyright 2000-2009 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 com.intellij.openapi.vfs.impl.http;
import com.intellij.openapi.application.Result;
import com.intellij.openapi.application.WriteAction;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.util.AsyncResult;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VfsBundle;
import com.intellij.openapi.vfs.VfsUtilCore;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.Url;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* @author nik
*/
public class RemoteFileInfoImpl implements RemoteContentProvider.DownloadingCallback, RemoteFileInfo {
private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.vfs.impl.http.RemoteFileInfoImpl");
private final Object myLock = new Object();
private final Url myUrl;
private final RemoteFileManagerImpl myManager;
private @Nullable RemoteContentProvider myContentProvider;
private File myLocalFile;
private VirtualFile myLocalVirtualFile;
private VirtualFile myPrevLocalFile;
private RemoteFileState myState = RemoteFileState.DOWNLOADING_NOT_STARTED;
private String myErrorMessage;
private final AtomicBoolean myCancelled = new AtomicBoolean();
private final List<FileDownloadingListener> myListeners = ContainerUtil.createLockFreeCopyOnWriteList();
public RemoteFileInfoImpl(final @NotNull Url url, final @NotNull RemoteFileManagerImpl manager) {
myUrl = url;
myManager = manager;
}
@Override
public void addDownloadingListener(@NotNull FileDownloadingListener listener) {
myListeners.add(listener);
}
@Override
public void removeDownloadingListener(final @NotNull FileDownloadingListener listener) {
myListeners.remove(listener);
}
@Override
public void restartDownloading() {
synchronized (myLock) {
myErrorMessage = null;
myPrevLocalFile = myLocalVirtualFile;
myLocalVirtualFile = null;
myState = RemoteFileState.DOWNLOADING_NOT_STARTED;
myLocalFile = null;
startDownloading();
}
}
@Override
public void startDownloading() {
LOG.debug("Downloading requested");
File localFile;
synchronized (myLock) {
if (myState != RemoteFileState.DOWNLOADING_NOT_STARTED) {
LOG.debug("File already downloaded: file = " + myLocalVirtualFile + ", state = " + myState);
return;
}
myState = RemoteFileState.DOWNLOADING_IN_PROGRESS;
try {
myLocalFile = myManager.getStorage().createLocalFile(myUrl);
LOG.debug("Local file created: " + myLocalFile.getAbsolutePath());
}
catch (IOException e) {
LOG.info(e);
errorOccurred(VfsBundle.message("cannot.create.local.file", e.getMessage()), false);
return;
}
myCancelled.set(false);
localFile = myLocalFile;
}
for (FileDownloadingListener listener : myListeners) {
listener.downloadingStarted();
}
if (myContentProvider == null) {
myContentProvider = myManager.findContentProvider(myUrl);
}
myContentProvider.saveContent(myUrl, localFile, this);
}
@Override
public void finished(@Nullable final FileType fileType) {
final File localIOFile;
synchronized (myLock) {
LOG.debug("Downloading finished, size = " + myLocalFile.length() + ", file type=" + (fileType != null ? fileType.getName() : "null"));
if (fileType != null) {
String fileName = myLocalFile.getName();
int dot = fileName.lastIndexOf('.');
String extension = fileType.getDefaultExtension();
if (dot == -1 || !extension.regionMatches(true, 0, fileName, dot + 1, extension.length())) {
File newFile = FileUtil.findSequentNonexistentFile(myLocalFile.getParentFile(), fileName, extension);
try {
FileUtil.rename(myLocalFile, newFile);
myLocalFile = newFile;
}
catch (IOException e) {
LOG.debug(e);
}
}
}
localIOFile = myLocalFile;
}
VirtualFile localFile = new WriteAction<VirtualFile>() {
@Override
protected void run(@NotNull final Result<VirtualFile> result) {
final VirtualFile file = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(localIOFile);
if (file != null) {
file.refresh(false, false);
}
result.setResult(file);
}
}.execute().getResultObject();
LOG.assertTrue(localFile != null, "Virtual local file not found for " + localIOFile.getAbsolutePath());
LOG.debug("Virtual local file: " + localFile + ", size = " + localFile.getLength());
synchronized (myLock) {
myLocalVirtualFile = localFile;
myPrevLocalFile = null;
myState = RemoteFileState.DOWNLOADED;
myErrorMessage = null;
}
for (FileDownloadingListener listener : myListeners) {
listener.fileDownloaded(localFile);
}
}
@Override
public boolean isCancelled() {
return myCancelled.get();
}
@Override
public String getErrorMessage() {
synchronized (myLock) {
return myErrorMessage;
}
}
@Override
public void errorOccurred(@NotNull final String errorMessage, boolean cancelled) {
LOG.debug("Error: " + errorMessage);
synchronized (myLock) {
myLocalVirtualFile = null;
myPrevLocalFile = null;
myState = RemoteFileState.ERROR_OCCURRED;
myErrorMessage = errorMessage;
}
for (FileDownloadingListener listener : myListeners) {
if (!cancelled) {
listener.errorOccurred(errorMessage);
}
}
}
@Override
public void setProgressFraction(final double fraction) {
for (FileDownloadingListener listener : myListeners) {
listener.progressFractionChanged(fraction);
}
}
@Override
public void setProgressText(@NotNull final String text, final boolean indeterminate) {
for (FileDownloadingListener listener : myListeners) {
listener.progressMessageChanged(indeterminate, text);
}
}
@Override
public VirtualFile getLocalFile() {
synchronized (myLock) {
return myLocalVirtualFile;
}
}
@Override
public String toString() {
final String errorMessage = getErrorMessage();
return "state=" + getState()
+ ", local file=" + myLocalFile
+ (errorMessage != null ? ", error=" + errorMessage : "")
+ (isCancelled() ? ", cancelled" : "");
}
@Override
public RemoteFileState getState() {
synchronized (myLock) {
return myState;
}
}
@Override
public void cancelDownloading() {
synchronized (myLock) {
myCancelled.set(true);
if (myPrevLocalFile != null) {
myLocalVirtualFile = myPrevLocalFile;
myLocalFile = VfsUtilCore.virtualToIoFile(myLocalVirtualFile);
myState = RemoteFileState.DOWNLOADED;
myErrorMessage = null;
}
else {
myState = RemoteFileState.ERROR_OCCURRED;
}
}
for (FileDownloadingListener listener : myListeners) {
listener.downloadingCancelled();
}
}
public void refresh(final @Nullable Runnable postRunnable) {
VirtualFile localVirtualFile;
synchronized (myLock) {
localVirtualFile = myLocalVirtualFile;
}
final RemoteContentProvider contentProvider = myManager.findContentProvider(myUrl);
if ((localVirtualFile == null || !contentProvider.equals(myContentProvider) || !contentProvider.isUpToDate(myUrl, localVirtualFile))) {
myContentProvider = contentProvider;
addDownloadingListener(new MyRefreshingDownloadingListener(postRunnable));
restartDownloading();
}
}
private class MyRefreshingDownloadingListener extends FileDownloadingAdapter {
private final Runnable myPostRunnable;
public MyRefreshingDownloadingListener(final Runnable postRunnable) {
myPostRunnable = postRunnable;
}
@Override
public void downloadingCancelled() {
removeDownloadingListener(this);
if (myPostRunnable != null) {
myPostRunnable.run();
}
}
@Override
public void fileDownloaded(final VirtualFile localFile) {
removeDownloadingListener(this);
if (myPostRunnable != null) {
myPostRunnable.run();
}
}
@Override
public void errorOccurred(@NotNull final String errorMessage) {
removeDownloadingListener(this);
if (myPostRunnable != null) {
myPostRunnable.run();
}
}
}
@NotNull
@Override
public AsyncResult<VirtualFile> download() {
synchronized (myLock) {
switch (getState()) {
case DOWNLOADING_NOT_STARTED:
startDownloading();
return createDownloadedCallback(this);
case DOWNLOADING_IN_PROGRESS:
return createDownloadedCallback(this);
case DOWNLOADED:
return new AsyncResult.Done<VirtualFile>(myLocalVirtualFile);
case ERROR_OCCURRED:
default:
return new AsyncResult.Rejected<VirtualFile>();
}
}
}
private static AsyncResult<VirtualFile> createDownloadedCallback(@NotNull final RemoteFileInfo remoteFileInfo) {
final AsyncResult<VirtualFile> callback = new AsyncResult<VirtualFile>();
remoteFileInfo.addDownloadingListener(new FileDownloadingAdapter() {
@Override
public void fileDownloaded(VirtualFile localFile) {
try {
remoteFileInfo.removeDownloadingListener(this);
}
finally {
callback.setDone(localFile);
}
}
@Override
public void errorOccurred(@NotNull String errorMessage) {
try {
remoteFileInfo.removeDownloadingListener(this);
}
finally {
callback.reject(errorMessage);
}
}
@Override
public void downloadingCancelled() {
try {
remoteFileInfo.removeDownloadingListener(this);
}
finally {
callback.setRejected();
}
}
});
return callback;
}
}