forked from j62/ctbrec
1
0
Fork 0

Add columns "last recorded" and "last seen" to models table

This commit is contained in:
0xboobface 2020-01-05 16:02:22 +01:00
parent 60826323f3
commit 3bb161c055
11 changed files with 149 additions and 23 deletions

View File

@ -0,0 +1,18 @@
package ctbrec.ui;
import java.time.Instant;
import javafx.beans.property.ObjectPropertyBase;
public class InstantProperty extends ObjectPropertyBase<Instant> {
@Override
public Object getBean() {
return get();
}
@Override
public String getName() {
return "Instant";
}
}

View File

@ -1,6 +1,7 @@
package ctbrec.ui; package ctbrec.ui;
import java.io.IOException; import java.io.IOException;
import java.time.Instant;
import java.util.List; import java.util.List;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
@ -18,6 +19,7 @@ import ctbrec.sites.Site;
import javafx.beans.property.BooleanProperty; import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleIntegerProperty; import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleObjectProperty;
/** /**
* Just a wrapper for Model, which augments it with JavaFX value binding properties, so that UI widgets get updated proeprly * Just a wrapper for Model, which augments it with JavaFX value binding properties, so that UI widgets get updated proeprly
@ -27,11 +29,16 @@ public class JavaFxModel implements Model {
private transient BooleanProperty recordingProperty = new SimpleBooleanProperty(); private transient BooleanProperty recordingProperty = new SimpleBooleanProperty();
private transient BooleanProperty pausedProperty = new SimpleBooleanProperty(); private transient BooleanProperty pausedProperty = new SimpleBooleanProperty();
private transient SimpleIntegerProperty priorityProperty = new SimpleIntegerProperty(); private transient SimpleIntegerProperty priorityProperty = new SimpleIntegerProperty();
private transient SimpleObjectProperty<Instant> lastSeenProperty = new SimpleObjectProperty<>();
private transient SimpleObjectProperty<Instant> lastRecordedProperty = new SimpleObjectProperty<>();
private Model delegate; private Model delegate;
public JavaFxModel(Model delegate) { public JavaFxModel(Model delegate) {
this.delegate = delegate; this.delegate = delegate;
setPriority(delegate.getPriority()); setPriority(delegate.getPriority());
setLastSeen(delegate.getLastSeen());
setLastRecorded(delegate.getLastRecorded());
} }
@Override @Override
@ -234,6 +241,36 @@ public class JavaFxModel implements Model {
return delegate.getPriority(); return delegate.getPriority();
} }
public SimpleObjectProperty<Instant> lastSeenProperty() {
return lastSeenProperty;
}
@Override
public void setLastSeen(Instant timestamp) {
delegate.setLastSeen(timestamp);
lastSeenProperty.set(timestamp);
}
@Override
public Instant getLastSeen() {
return delegate.getLastSeen();
}
public SimpleObjectProperty<Instant> lastRecordedProperty() {
return lastRecordedProperty;
}
@Override
public void setLastRecorded(Instant timestamp) {
delegate.setLastRecorded(timestamp);
lastRecordedProperty.set(timestamp);
}
@Override
public Instant getLastRecorded() {
return delegate.getLastRecorded();
}
@Override @Override
public int compareTo(Model o) { public int compareTo(Model o) {
return delegate.compareTo(o); return delegate.compareTo(o);

View File

@ -3,6 +3,7 @@ package ctbrec.ui;
import java.io.IOException; import java.io.IOException;
import java.security.InvalidKeyException; import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.time.Instant;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Iterator; import java.util.Iterator;
@ -34,6 +35,7 @@ import ctbrec.ui.action.PlayAction;
import ctbrec.ui.action.ResumeAction; import ctbrec.ui.action.ResumeAction;
import ctbrec.ui.action.StopRecordingAction; import ctbrec.ui.action.StopRecordingAction;
import ctbrec.ui.controls.AutoFillTextField; import ctbrec.ui.controls.AutoFillTextField;
import ctbrec.ui.controls.DateTimeCellFactory;
import ctbrec.ui.controls.Dialogs; import ctbrec.ui.controls.Dialogs;
import ctbrec.ui.controls.SearchBox; import ctbrec.ui.controls.SearchBox;
import javafx.application.Platform; import javafx.application.Platform;
@ -176,6 +178,16 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
priority.setOnEditStart(e -> cellEditing = true); priority.setOnEditStart(e -> cellEditing = true);
priority.setOnEditCommit(this::onUpdatePriority); priority.setOnEditCommit(this::onUpdatePriority);
priority.setOnEditCancel(e -> cellEditing = false); priority.setOnEditCancel(e -> cellEditing = false);
TableColumn<JavaFxModel, Instant> lastSeen = new TableColumn<>("last seen");
lastSeen.setCellValueFactory(cdf -> cdf.getValue().lastSeenProperty());
lastSeen.setCellFactory(new DateTimeCellFactory<JavaFxModel>());
lastSeen.setPrefWidth(150);
lastSeen.setEditable(false);
TableColumn<JavaFxModel, Instant> lastRecorded = new TableColumn<>("last recorded");
lastRecorded.setCellValueFactory(cdf -> cdf.getValue().lastRecordedProperty());
lastRecorded.setCellFactory(new DateTimeCellFactory<JavaFxModel>());
lastRecorded.setPrefWidth(150);
lastRecorded.setEditable(false);
TableColumn<JavaFxModel, String> notes = new TableColumn<>("Notes"); TableColumn<JavaFxModel, String> notes = new TableColumn<>("Notes");
notes.setCellValueFactory(cdf -> { notes.setCellValueFactory(cdf -> {
JavaFxModel m = cdf.getValue(); JavaFxModel m = cdf.getValue();
@ -199,7 +211,7 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
}); });
notes.setPrefWidth(400); notes.setPrefWidth(400);
notes.setEditable(false); notes.setEditable(false);
table.getColumns().addAll(preview, name, url, online, recording, paused, priority, notes); table.getColumns().addAll(preview, name, url, online, recording, paused, priority, lastSeen, lastRecorded, notes);
table.setItems(observableModels); table.setItems(observableModels);
table.addEventHandler(ContextMenuEvent.CONTEXT_MENU_REQUESTED, event -> { table.addEventHandler(ContextMenuEvent.CONTEXT_MENU_REQUESTED, event -> {
popup = createContextMenu(); popup = createContextMenu();
@ -417,6 +429,8 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
oldModel.setSuspended(updatedModel.isSuspended()); oldModel.setSuspended(updatedModel.isSuspended());
oldModel.getOnlineProperty().set(updatedModel.getOnlineProperty().get()); oldModel.getOnlineProperty().set(updatedModel.getOnlineProperty().get());
oldModel.getRecordingProperty().set(updatedModel.getRecordingProperty().get()); oldModel.getRecordingProperty().set(updatedModel.getRecordingProperty().get());
oldModel.lastRecordedProperty().set(updatedModel.lastRecordedProperty().get());
oldModel.lastSeenProperty().set(updatedModel.lastSeenProperty().get());
} }
} }
} }

View File

@ -12,10 +12,6 @@ import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.text.DecimalFormat; import java.text.DecimalFormat;
import java.time.Instant; import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
@ -37,6 +33,7 @@ import ctbrec.recorder.ProgressListener;
import ctbrec.recorder.Recorder; import ctbrec.recorder.Recorder;
import ctbrec.recorder.download.hls.MergedHlsDownload; import ctbrec.recorder.download.hls.MergedHlsDownload;
import ctbrec.sites.Site; import ctbrec.sites.Site;
import ctbrec.ui.controls.DateTimeCellFactory;
import ctbrec.ui.controls.Toast; import ctbrec.ui.controls.Toast;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleObjectProperty;
@ -127,7 +124,7 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
Instant instant = cdf.getValue().getStartDate(); Instant instant = cdf.getValue().getStartDate();
return new SimpleObjectProperty<Instant>(instant); return new SimpleObjectProperty<Instant>(instant);
}); });
date.setCellFactory(param -> createDateCell()); date.setCellFactory(new DateTimeCellFactory<JavaFxRecording>());
date.setPrefWidth(200); date.setPrefWidth(200);
TableColumn<JavaFxRecording, String> status = new TableColumn<>("Status"); TableColumn<JavaFxRecording, String> status = new TableColumn<>("Status");
status.setCellValueFactory(cdf -> cdf.getValue().getStatusProperty()); status.setCellValueFactory(cdf -> cdf.getValue().getStatusProperty());
@ -193,23 +190,6 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
return cell; return cell;
} }
private TableCell<JavaFxRecording, Instant> createDateCell() {
TableCell<JavaFxRecording, Instant> cell = new TableCell<JavaFxRecording, Instant>() {
@Override
protected void updateItem(Instant instant, boolean empty) {
if(empty || instant == null) {
setText(null);
} else {
ZonedDateTime time = instant.atZone(ZoneId.systemDefault());
DateTimeFormatter dtf = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM, FormatStyle.MEDIUM);
setText(dtf.format(time));
}
}
};
return cell;
}
private void onContextMenuRequested(ContextMenuEvent event) { private void onContextMenuRequested(ContextMenuEvent event) {
List<JavaFxRecording> recordings = table.getSelectionModel().getSelectedItems(); List<JavaFxRecording> recordings = table.getSelectionModel().getSelectedItems();
if (recordings != null && !recordings.isEmpty()) { if (recordings != null && !recordings.isEmpty()) {

View File

@ -0,0 +1,30 @@
package ctbrec.ui.controls;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.util.Callback;
public class DateTimeCellFactory<T> implements Callback<TableColumn<T, Instant>, TableCell<T, Instant>> {
@Override
public TableCell<T, Instant> call(TableColumn<T, Instant> param) {
return new TableCell<T, Instant>() {
@Override
protected void updateItem(Instant item, boolean empty) {
if (empty || item == null) {
setText("");
} else {
LocalDateTime dateTime = LocalDateTime.ofInstant(item, ZoneId.systemDefault());
DateTimeFormatter formatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT);
String formattedDateTime = formatter.format(dateTime);
setText(item.equals(Instant.EPOCH) ? "" : formattedDateTime);
}
}
};
}
}

View File

@ -1,6 +1,7 @@
package ctbrec; package ctbrec;
import java.io.IOException; import java.io.IOException;
import java.time.Instant;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
@ -27,6 +28,8 @@ public abstract class AbstractModel implements Model {
private boolean suspended = false; private boolean suspended = false;
protected transient Site site; protected transient Site site;
protected State onlineState = State.UNKNOWN; protected State onlineState = State.UNKNOWN;
private Instant lastSeen;
private Instant lastRecorded;
@Override @Override
public boolean isOnline() throws IOException, ExecutionException, InterruptedException { public boolean isOnline() throws IOException, ExecutionException, InterruptedException {
@ -205,6 +208,26 @@ public abstract class AbstractModel implements Model {
this.priority = priority; this.priority = priority;
} }
@Override
public Instant getLastSeen() {
return Optional.ofNullable(lastSeen).orElse(Instant.EPOCH);
}
@Override
public void setLastSeen(Instant lastSeen) {
this.lastSeen = lastSeen;
}
@Override
public Instant getLastRecorded() {
return Optional.ofNullable(lastRecorded).orElse(Instant.EPOCH);
}
@Override
public void setLastRecorded(Instant lastRecorded) {
this.lastRecorded = lastRecorded;
}
@Override @Override
public Download createDownload() { public Download createDownload() {
if(Config.isServerMode()) { if(Config.isServerMode()) {

View File

@ -2,6 +2,7 @@ package ctbrec;
import java.io.IOException; import java.io.IOException;
import java.io.Serializable; import java.io.Serializable;
import java.time.Instant;
import java.util.List; import java.util.List;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
@ -82,6 +83,14 @@ public interface Model extends Comparable<Model>, Serializable {
public void receiveTip(Double tokens) throws IOException; public void receiveTip(Double tokens) throws IOException;
public void setLastSeen(Instant timestamp);
public Instant getLastSeen();
public void setLastRecorded(Instant timestamp);
public Instant getLastRecorded();
/** /**
* Determines the stream resolution for this model * Determines the stream resolution for this model
* *

View File

@ -2,6 +2,7 @@ package ctbrec.io;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.time.Instant;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
@ -69,6 +70,10 @@ public class ModelJsonAdapter extends JsonAdapter<Model> {
} else if(key.equals("suspended")) { } else if(key.equals("suspended")) {
suspended = reader.nextBoolean(); suspended = reader.nextBoolean();
model.setSuspended(suspended); model.setSuspended(suspended);
} else if(key.equals("lastSeen")) {
model.setLastSeen(Instant.ofEpochMilli(reader.nextLong()));
} else if(key.equals("lastRecorded")) {
model.setLastRecorded(Instant.ofEpochMilli(reader.nextLong()));
} else if(key.equals("siteSpecific")) { } else if(key.equals("siteSpecific")) {
reader.beginObject(); reader.beginObject();
try { try {
@ -108,6 +113,8 @@ public class ModelJsonAdapter extends JsonAdapter<Model> {
writer.name("priority").value(model.getPriority()); writer.name("priority").value(model.getPriority());
writer.name("streamUrlIndex").value(model.getStreamUrlIndex()); writer.name("streamUrlIndex").value(model.getStreamUrlIndex());
writer.name("suspended").value(model.isSuspended()); writer.name("suspended").value(model.isSuspended());
writer.name("lastSeen").value(model.getLastSeen().toEpochMilli());
writer.name("lastRecorded").value(model.getLastRecorded().toEpochMilli());
writer.name("siteSpecific"); writer.name("siteSpecific");
writer.beginObject(); writer.beginObject();
model.writeSiteSpecificData(writer); model.writeSiteSpecificData(writer);

View File

@ -273,6 +273,7 @@ public class NextGenLocalRecorder implements Recorder {
completionService.submit(() -> { completionService.submit(() -> {
try { try {
setRecordingStatus(rec, State.RECORDING); setRecordingStatus(rec, State.RECORDING);
model.setLastRecorded(rec.getStartDate());
recordingManager.saveRecording(rec); recordingManager.saveRecording(rec);
download.start(); download.start();
} catch (IOException e) { } catch (IOException e) {

View File

@ -108,6 +108,7 @@ public class OnlineMonitor extends Thread {
try { try {
if (model.isOnline(IGNORE_CACHE)) { if (model.isOnline(IGNORE_CACHE)) {
EventBusHolder.BUS.post(new ModelIsOnlineEvent(model)); EventBusHolder.BUS.post(new ModelIsOnlineEvent(model));
model.setLastSeen(Instant.now());
} }
Model.State state = model.getOnlineState(false); Model.State state = model.getOnlineState(false);
LOG.trace("Model online state: {} {}", model.getName(), state); LOG.trace("Model online state: {} {}", model.getName(), state);

View File

@ -177,6 +177,12 @@ public class HlsDownload extends AbstractHlsDownload {
} catch (Exception e) { } catch (Exception e) {
throw new IOException("Couldn't download segment", e); throw new IOException("Couldn't download segment", e);
} finally { } finally {
try {
Thread.sleep(10_000);
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
downloadThreadPool.shutdown(); downloadThreadPool.shutdown();
try { try {
LOG.debug("Waiting for last segments for {}", model); LOG.debug("Waiting for last segments for {}", model);