/*
* Copyright (c) 2012, the Dart project authors.
*
* Licensed under the Eclipse Public License v1.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.eclipse.org/legal/epl-v10.html
*
* 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.google.dart.tools.update.core.internal;
import com.google.dart.tools.core.DartCoreDebug;
import com.google.dart.tools.update.core.Revision;
import com.google.dart.tools.update.core.UpdateAdapter;
import com.google.dart.tools.update.core.UpdateCore;
import com.google.dart.tools.update.core.internal.UpdateModel.State;
import com.google.dart.tools.update.core.internal.jobs.CheckForUpdatesJob;
import com.google.dart.tools.update.core.internal.jobs.DownloadUpdatesJob;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.jobs.IJobChangeEvent;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.core.runtime.jobs.JobChangeAdapter;
import java.io.File;
import java.io.IOException;
import java.util.Timer;
import java.util.TimerTask;
/**
* Manages the downloading of updates.
*/
public class DownloadManager {
private class StateChangeListener extends UpdateAdapter {
@Override
public void updateAvailable(final Revision revision) {
if (UpdateCore.isAutoDownloadEnabled()) {
try {
new Timer().schedule(new TimerTask() {
@Override
public void run() {
scheduleDownload(revision);
}
}, DOWNLOAD_DELAY);
} catch (Exception e) {
UpdateCore.logError(e);
}
}
}
}
// Wait before initiating an auto DL (in ms)
private static final int DOWNLOAD_DELAY = 3000;
//cached to ensure proper cancellation at workbench disposal (set to null after each use)
private Job updateJob;
private final UpdateModel model;
private UpdateResolver updateResolver;
/**
* Create a manager instance.
*/
public DownloadManager(UpdateModel model) {
this.model = model;
model.addListener(new StateChangeListener());
updateResolver = UpdateResolver.forIntegration();
}
/**
* Get the latest revision number for update testing.
*
* @return the latest revision
* @throws IOException if an error occurs accessing revision info
*/
public Revision getLatestRevision() throws IOException {
return updateResolver.getLatest();
}
/**
* Get the latest staged revision number.
*
* @return the latest staged revision
*/
public Revision getLatestStaged() {
IPath updateDirPath = UpdateCore.getUpdateDirPath();
File dir = updateDirPath.toFile();
Revision latest = Revision.UNKNOWN;
if (dir.exists() && dir.isDirectory()) {
for (File file : dir.listFiles()) {
Revision revision = getRevisionNumber(file);
if (revision.isMoreCurrentThan(latest)) {
latest = revision;
}
}
}
return latest;
}
/**
* Check to see if there is an update staged and ready to be applied.
*
* @return <code>true</code> if there is an update ready to be applied, <code>false</code>
* otherwise
*/
public boolean isUpdateStaged() {
Revision current = UpdateCore.getCurrentRevision();
Revision staged = getLatestStaged();
return staged.isMoreCurrentThan(current);
}
/**
* Schedule an update download.
*/
public void scheduleDownload(Revision revision) {
//don't over-schedule a download
if (!model.isDownloadingUpdate()) {
doDownloadUpdate(revision);
}
}
/**
* Schedule a check for available updates.
*/
public void scheduleUpdateCheck() {
//don't over-check
if (model.isIdle()) {
doCheckForUpdate();
}
}
/**
* Start the download manager.
*/
public void start() {
cleanupStagingArea();
}
/**
* Stop the download manager.
*/
public void stop() {
if (updateJob != null) {
if (!updateJob.cancel()) {
try {
updateJob.join();
} catch (InterruptedException e) {
//TODO(pquitslund): sysout for debugging
e.printStackTrace();
//ignored since we're tearing down and there may be no log to write to!
}
}
}
}
private void cleanupStagingArea() {
//ensure last DL was sound
Revision latestStaged = getLatestStaged();
File zip = latestStaged.getLocalPath().toFile();
if (zip.exists()) {
if (!UpdateUtils.isZipValid(zip)) {
if (DartCoreDebug.TRACE_UPDATE) {
UpdateCore.logInfo("deleting invalid zip: " + zip.getName());//$NON-NLS-1$
}
zip.delete();
}
}
}
private void doCheckForUpdate() {
if (DartCoreDebug.TRACE_UPDATE) {
UpdateCore.logInfo("DownloadManager.doCheckForUpdate()");//$NON-NLS-1$
}
//ensure jobs don't stack
if (updateJob != null) {
if (DartCoreDebug.TRACE_UPDATE) {
UpdateCore.logInfo("(update job active -- check canceled)");//$NON-NLS-1$
}
return;
}
updateJob = new CheckForUpdatesJob(this);
updateJob.addJobChangeListener(new JobChangeAdapter() {
@Override
public void done(IJobChangeEvent event) {
CheckForUpdatesJob updateCheckJob = (CheckForUpdatesJob) updateJob;
//Signal that the job is finished
updateJob = null;
Revision latest = updateCheckJob.getLatest();
if (latest == null) {
model.setErrorMessage(updateCheckJob.getErrorMessage());
model.enterState(State.FAILED);
} else {
UpdateCore.updateChecked();
model.setLatestAvailableRevision(latest);
model.enterState(State.CHECKED);
Revision staged = getLatestStaged();
if (latest.isEqualTo(staged)) {
model.enterState(State.DOWNLOADED);
} else if (latest.isMoreCurrentThan(UpdateCore.getCurrentRevision())) {
model.enterState(State.AVAILABLE);
}
}
}
@Override
public void running(IJobChangeEvent event) {
model.enterState(State.CHECKING);
}
});
updateJob.schedule();
}
private void doDownloadUpdate(Revision revision) {
//ensure jobs don't stack
if (updateJob != null) {
return;
}
updateJob = new DownloadUpdatesJob(revision);
updateJob.addJobChangeListener(new JobChangeAdapter() {
@Override
public void done(IJobChangeEvent event) {
updateJob = null;
IStatus result = event.getResult();
if (result.isOK()) {
model.enterState(State.DOWNLOADED);
} else {
UpdateCore.logWarning("Download cancelled [" + result.getMessage() + "]");
model.enterState(State.DOWNLOAD_CANCELLED);
}
}
@Override
public void running(IJobChangeEvent event) {
model.enterState(State.DOWNLOADING);
}
});
updateJob.schedule();
}
private Revision getRevisionNumber(File file) {
String name = file.getName();
if (name.endsWith(".msi")) {
return Revision.forValue(name.substring(0, name.length() - 4));
}
return Revision.forValue(name.split(".zip")[0]);
}
}