package lighthouse.subwindows;
import com.google.common.util.concurrent.*;
import com.vinumeris.updatefx.*;
import de.jensd.fx.fontawesome.*;
import javafx.beans.property.*;
import javafx.collections.*;
import javafx.concurrent.*;
import javafx.event.*;
import javafx.fxml.*;
import javafx.scene.control.*;
import javafx.scene.text.*;
import lighthouse.*;
import javax.annotation.*;
import java.io.*;
import java.nio.file.*;
import java.util.*;
import static com.google.common.base.Preconditions.*;
import static java.lang.String.format;
import static java.nio.file.Files.*;
import static lighthouse.utils.GuiUtils.*;
import static lighthouse.utils.GuiUtils.log;
import static lighthouse.utils.I18nUtil.*;
/**
* Lets the user select a version to pin themselves to.
*/
public class UpdateFXWindow {
public static final String CACHED_UPDATE_SUMMARY = "cached-update-summary";
public Main.OverlayUI<UpdateFXWindow> overlayUI;
@FXML Text descriptionLabel;
@FXML ListView<UFXProtocol.Update> updatesList;
@FXML Button pinBtn;
private ObservableList<UFXProtocol.Update> updates = FXCollections.observableArrayList();
private SimpleIntegerProperty currentPin = new SimpleIntegerProperty();
private UpdateSummary summary;
public void initialize() {
currentPin.set(UpdateFX.getVersionPin(Main.unadjustedAppDir));
updatesList.setCellFactory(param -> new ListCell<UFXProtocol.Update>() {
@Override
protected void updateItem(@Nullable UFXProtocol.Update item, boolean empty) {
super.updateItem(item, empty);
if (empty || summary == null) {
setText("");
setGraphic(null);
setStyle("");
} else {
setStyle("");
setText(item == null ? "Latest version" : item.getDescription(0).getOneLiner());
Icon icon = Icon.create().icon(AwesomeIcon.THUMB_TACK);
icon.setStyle("-fx-font-family: FontAwesome; -fx-font-size: 1.5em");
icon.visibleProperty().bind(currentPin.isEqualTo(item != null ? item.getVersion() : 0));
setGraphic(icon);
// Bold the current version that's running, or latest if we're up to date.
if ((item == null && Main.VERSION == summary.highestVersion) ||
(item != null && item.getVersion() == Main.VERSION && item.getVersion() != summary.highestVersion))
setStyle("-fx-font-weight: bold");
else
setStyle("");
}
}
});
updatesList.setItems(updates);
updatesList.getSelectionModel().selectFirst();
updatesList.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
updatesList.getSelectionModel().selectedIndexProperty().addListener((x, prev, cur) -> {
if (cur.intValue() > 0)
descriptionLabel.setText(updates.get(cur.intValue()).getDescription(0).getDescription());
else
descriptionLabel.setText("");
});
}
public static Main.OverlayUI<UpdateFXWindow> open(Updater updater) {
Main.OverlayUI<UpdateFXWindow> result = Main.instance.<UpdateFXWindow>overlayUI("subwindows/updatefx.fxml", tr("Application updates"));
result.controller.setUpdater(checkNotNull(updater));
return result;
}
@FXML
public void pinClicked(ActionEvent event) {
UFXProtocol.Update selected = updatesList.getSelectionModel().getSelectedItem();
if (selected == null) {
UpdateFX.unpin(Main.unadjustedAppDir);
currentPin.set(0);
informationalAlert(tr("Version change"),
tr("You will be switched to always track the latest version. The app will now restart."));
Main.restart();
} else {
int ver = selected.getVersion();
UpdateFX.pinToVersion(Main.unadjustedAppDir, ver);
currentPin.set(ver);
informationalAlert(tr("Version change"),
// TRANS: %d = version number
tr("You will be switched to always use version %d. The app will now restart."), ver);
Main.restart();
}
}
@FXML
public void closeClicked(ActionEvent event) {
overlayUI.done();
}
public void setUpdater(Updater updater) {
if (updater.isDone() || Main.bitcoin.isOffline())
processUpdater(updater);
else
updater.addEventHandler(WorkerStateEvent.WORKER_STATE_SUCCEEDED, event -> {
processUpdater(updater);
});
}
private void processUpdater(Updater updater) {
updates.clear();
updates.add(null); // Sentinel for "latest"
UpdateSummary s = null;
try {
if (!Main.bitcoin.isOffline())
s = Futures.getUnchecked(updater);
} catch (Exception e) {
log.warn("Failed to get online updates index, trying to fall back to disk cache: {}", e.getMessage());
}
if (s != null)
summary = s;
else
summary = loadCachedIndex();
if (summary == null) {
log.error("Not online and failed to load cached updates, showing blank window.");
pinBtn.setDisable(true);
return; // Not online and no cached updates, or some other issue, so give up.
}
List<UFXProtocol.Update> list = new ArrayList<>(summary.updates.getUpdatesList());
Collections.reverse(list);
for (UFXProtocol.Update update : list) {
// For each update in the index, check if we have it on disk (the index can contain updates older than
// what we can roll back to).
if (exists(Main.unadjustedAppDir.resolve(format("%d.jar", update.getVersion())))) {
if (update.getDescriptionCount() > 0)
updates.add(update);
}
}
}
// Returns an UpdateSummary object that we previously had downloaded, so we can still display update info if
// we are offline, or null if not found or some other error.
@Nullable
private UpdateSummary loadCachedIndex() {
try (InputStream is = Files.newInputStream(Main.unadjustedAppDir.resolve(CACHED_UPDATE_SUMMARY))) {
return new UpdateSummary(Main.VERSION, UFXProtocol.Updates.parseDelimitedFrom(is));
} catch (IOException e) {
return null;
}
}
public static void saveCachedIndex(UFXProtocol.Updates updates) {
try (OutputStream os = Files.newOutputStream(Main.unadjustedAppDir.resolve(CACHED_UPDATE_SUMMARY))) {
updates.writeDelimitedTo(os);
} catch (IOException e) {
log.error("Failed to save cached update index", e);
}
}
}