816 lines
33 KiB
Java
816 lines
33 KiB
Java
package ctbrec.ui.sites.myfreecams;
|
|
|
|
import static java.nio.charset.StandardCharsets.*;
|
|
import static java.nio.file.StandardOpenOption.*;
|
|
|
|
import java.io.File;
|
|
import java.io.FileOutputStream;
|
|
import java.io.IOException;
|
|
import java.io.PrintStream;
|
|
import java.io.UnsupportedEncodingException;
|
|
import java.net.URLDecoder;
|
|
import java.nio.file.Files;
|
|
import java.util.ArrayList;
|
|
import java.util.Collection;
|
|
import java.util.Collections;
|
|
import java.util.Iterator;
|
|
import java.util.List;
|
|
import java.util.Objects;
|
|
import java.util.Optional;
|
|
import java.util.Set;
|
|
import java.util.concurrent.ExecutionException;
|
|
import java.util.concurrent.TimeUnit;
|
|
import java.util.concurrent.locks.ReentrantLock;
|
|
|
|
import javax.xml.bind.JAXBException;
|
|
|
|
import org.json.JSONArray;
|
|
import org.json.JSONObject;
|
|
import org.slf4j.Logger;
|
|
import org.slf4j.LoggerFactory;
|
|
|
|
import com.iheartradio.m3u8.ParseException;
|
|
import com.iheartradio.m3u8.PlaylistException;
|
|
|
|
import ctbrec.Config;
|
|
import ctbrec.Model;
|
|
import ctbrec.Settings;
|
|
import ctbrec.StringUtil;
|
|
import ctbrec.recorder.download.StreamSource;
|
|
import ctbrec.sites.mfc.MyFreeCams;
|
|
import ctbrec.sites.mfc.MyFreeCamsModel;
|
|
import ctbrec.sites.mfc.SessionState;
|
|
import ctbrec.sites.mfc.User;
|
|
import ctbrec.ui.DesktopIntegration;
|
|
import ctbrec.ui.action.FollowAction;
|
|
import ctbrec.ui.action.PlayAction;
|
|
import ctbrec.ui.action.StartRecordingAction;
|
|
import ctbrec.ui.controls.SearchBox;
|
|
import ctbrec.ui.tabs.TabSelectionListener;
|
|
import javafx.beans.property.BooleanProperty;
|
|
import javafx.beans.property.DoubleProperty;
|
|
import javafx.beans.property.IntegerProperty;
|
|
import javafx.beans.property.Property;
|
|
import javafx.beans.property.SimpleBooleanProperty;
|
|
import javafx.beans.property.SimpleDoubleProperty;
|
|
import javafx.beans.property.SimpleIntegerProperty;
|
|
import javafx.beans.property.SimpleStringProperty;
|
|
import javafx.beans.property.StringProperty;
|
|
import javafx.collections.FXCollections;
|
|
import javafx.collections.ListChangeListener;
|
|
import javafx.collections.ObservableList;
|
|
import javafx.concurrent.Worker.State;
|
|
import javafx.concurrent.WorkerStateEvent;
|
|
import javafx.event.ActionEvent;
|
|
import javafx.geometry.Insets;
|
|
import javafx.geometry.Point2D;
|
|
import javafx.geometry.Pos;
|
|
import javafx.scene.control.Button;
|
|
import javafx.scene.control.CheckMenuItem;
|
|
import javafx.scene.control.ContextMenu;
|
|
import javafx.scene.control.Label;
|
|
import javafx.scene.control.MenuItem;
|
|
import javafx.scene.control.ScrollPane;
|
|
import javafx.scene.control.SelectionMode;
|
|
import javafx.scene.control.Tab;
|
|
import javafx.scene.control.TableColumn;
|
|
import javafx.scene.control.TableColumn.SortType;
|
|
import javafx.scene.control.TableView;
|
|
import javafx.scene.control.Tooltip;
|
|
import javafx.scene.input.Clipboard;
|
|
import javafx.scene.input.ClipboardContent;
|
|
import javafx.scene.input.ContextMenuEvent;
|
|
import javafx.scene.input.MouseEvent;
|
|
import javafx.scene.layout.BorderPane;
|
|
import javafx.scene.layout.HBox;
|
|
import javafx.scene.layout.Priority;
|
|
import javafx.stage.FileChooser;
|
|
import javafx.util.Duration;
|
|
|
|
public class MyFreeCamsTableTab extends Tab implements TabSelectionListener {
|
|
private static final Logger LOG = LoggerFactory.getLogger(MyFreeCamsTableTab.class);
|
|
private ScrollPane scrollPane = new ScrollPane();
|
|
private TableView<ModelTableRow> table = new TableView<>();
|
|
private ObservableList<ModelTableRow> filteredModels = FXCollections.observableArrayList();
|
|
private ObservableList<ModelTableRow> observableModels = FXCollections.observableArrayList();
|
|
private TableUpdateService updateService;
|
|
private MyFreeCams mfc;
|
|
private ReentrantLock lock = new ReentrantLock();
|
|
private SearchBox filterInput;
|
|
private Label count = new Label("models");
|
|
private List<TableColumn<ModelTableRow, ?>> columns = new ArrayList<>();
|
|
private ContextMenu popup;
|
|
private long lastJsonWrite = 0;
|
|
|
|
public MyFreeCamsTableTab(MyFreeCams mfc) {
|
|
this.mfc = mfc;
|
|
setText("Tabular");
|
|
setClosable(false);
|
|
createGui();
|
|
restoreState();
|
|
loadData();
|
|
initUpdateService();
|
|
filter(filterInput.getText());
|
|
}
|
|
|
|
private void initUpdateService() {
|
|
updateService = new TableUpdateService(mfc);
|
|
updateService.setPeriod(new Duration(TimeUnit.SECONDS.toMillis(1)));
|
|
updateService.setOnSucceeded(this::onSuccess);
|
|
updateService.setOnFailed(event -> LOG.info("Couldn't update MyFreeCams model table", event.getSource().getException()));
|
|
}
|
|
|
|
private void onSuccess(WorkerStateEvent evt) {
|
|
Collection<SessionState> sessionStates = updateService.getValue();
|
|
if (sessionStates == null) {
|
|
return;
|
|
}
|
|
|
|
lock.lock();
|
|
try {
|
|
for (SessionState updatedModel : sessionStates) {
|
|
ModelTableRow row = new ModelTableRow(updatedModel);
|
|
int index = observableModels.indexOf(row);
|
|
if (index == -1) {
|
|
observableModels.add(row);
|
|
} else {
|
|
observableModels.get(index).update(updatedModel);
|
|
}
|
|
}
|
|
|
|
for (Iterator<ModelTableRow> iterator = observableModels.iterator(); iterator.hasNext();) {
|
|
ModelTableRow model = iterator.next();
|
|
boolean found = false;
|
|
for (SessionState sessionState : sessionStates) {
|
|
if(Objects.equals(sessionState.getUid(), model.uid)) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if(!found) {
|
|
iterator.remove();
|
|
}
|
|
}
|
|
} finally {
|
|
lock.unlock();
|
|
}
|
|
|
|
filteredModels.clear();
|
|
filter(filterInput.getText());
|
|
table.sort();
|
|
|
|
long now = System.currentTimeMillis();
|
|
if( (now - lastJsonWrite) > TimeUnit.SECONDS.toMillis(30)) {
|
|
lastJsonWrite = now;
|
|
saveData();
|
|
}
|
|
}
|
|
|
|
private void createGui() {
|
|
BorderPane layout = new BorderPane();
|
|
layout.setPadding(new Insets(5, 10, 10, 10));
|
|
|
|
filterInput = new SearchBox(false);
|
|
filterInput.setPromptText("Filter");
|
|
filterInput.textProperty().addListener( (observableValue, oldValue, newValue) -> {
|
|
String filter = filterInput.getText();
|
|
Config.getInstance().getSettings().mfcModelsTableFilter = filter;
|
|
lock.lock();
|
|
try {
|
|
filter(filter);
|
|
} finally {
|
|
lock.unlock();
|
|
}
|
|
});
|
|
filterInput.getStyleClass().remove("search-box-icon");
|
|
HBox.setHgrow(filterInput, Priority.ALWAYS);
|
|
Button export = new Button("⬇");
|
|
export.setOnAction(this::export);
|
|
export.setTooltip(new Tooltip("Export data"));
|
|
|
|
Button columnSelection = new Button("⚙");
|
|
columnSelection.setOnAction(this::showColumnSelection);
|
|
columnSelection.setTooltip(new Tooltip("Select columns"));
|
|
HBox topBar = new HBox(5);
|
|
topBar.getChildren().addAll(filterInput, count, export, columnSelection);
|
|
count.prefHeightProperty().bind(filterInput.heightProperty());
|
|
count.setAlignment(Pos.CENTER);
|
|
layout.setTop(topBar);
|
|
BorderPane.setMargin(topBar, new Insets(0, 0, 5, 0));
|
|
|
|
table.setItems(observableModels);
|
|
table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
|
|
table.getSortOrder().addListener(createSortOrderChangedListener());
|
|
table.addEventHandler(ContextMenuEvent.CONTEXT_MENU_REQUESTED, event -> {
|
|
popup = createContextMenu();
|
|
if (popup != null) {
|
|
popup.show(table, event.getScreenX(), event.getScreenY());
|
|
}
|
|
event.consume();
|
|
});
|
|
table.addEventHandler(MouseEvent.MOUSE_PRESSED, event -> {
|
|
if (popup != null) {
|
|
popup.hide();
|
|
}
|
|
});
|
|
table.getColumns().addListener((ListChangeListener<TableColumn<?, ?>>)(e -> saveState()));
|
|
|
|
int idx = 0;
|
|
TableColumn<ModelTableRow, Number> uid = createTableColumn("UID", 65, idx++);
|
|
uid.setId("uid");
|
|
uid.setCellValueFactory(cdf -> cdf.getValue().uidProperty());
|
|
addTableColumnIfEnabled(uid);
|
|
|
|
TableColumn<ModelTableRow, String> name = createTableColumn("Name", 200, idx++);
|
|
name.setId("name");
|
|
name.setCellValueFactory(cdf -> cdf.getValue().nameProperty());
|
|
addTableColumnIfEnabled(name);
|
|
|
|
TableColumn<ModelTableRow, String> state = createTableColumn("State", 130, idx++);
|
|
state.setId("state");
|
|
state.setCellValueFactory(cdf -> cdf.getValue().stateProperty());
|
|
addTableColumnIfEnabled(state);
|
|
|
|
TableColumn<ModelTableRow, Boolean> hd = createTableColumn("HD", 130, idx++);
|
|
hd.setId("hd");
|
|
hd.setCellValueFactory(cdf -> cdf.getValue().hdProperty());
|
|
addTableColumnIfEnabled(hd);
|
|
|
|
TableColumn<ModelTableRow, Boolean> webrtc = createTableColumn("webrtc", 130, idx++);
|
|
webrtc.setId("webrtc");
|
|
webrtc.setCellValueFactory(cdf -> cdf.getValue().webrtcProperty());
|
|
addTableColumnIfEnabled(webrtc);
|
|
|
|
TableColumn<ModelTableRow, Number> flags = createTableColumn("Flags", 75, idx++);
|
|
flags.setId("flags");
|
|
flags.setCellValueFactory(cdf -> cdf.getValue().flagsProperty());
|
|
addTableColumnIfEnabled(flags);
|
|
|
|
TableColumn<ModelTableRow, Number> camscore = createTableColumn("Score", 75, idx++);
|
|
camscore.setId("camscore");
|
|
camscore.setCellValueFactory(cdf -> cdf.getValue().camScoreProperty());
|
|
addTableColumnIfEnabled(camscore);
|
|
|
|
// this is always 0, use https://api.myfreecams.com/missmfc and https://api.myfreecams.com/missmfc/online
|
|
// TableColumn<SessionState, Number> missMfc = createTableColumn("Miss MFC", 75, idx++);
|
|
// missMfc.setId("missMfc");
|
|
// missMfc.setCellValueFactory(cdf -> {
|
|
// Integer mmfc = Optional.ofNullable(cdf.getValue().getM()).map(m -> m.getMissmfc()).orElse(-1);
|
|
// return new SimpleIntegerProperty(mmfc);
|
|
// });
|
|
// addTableColumnIfEnabled(missMfc);
|
|
|
|
TableColumn<ModelTableRow, String> newModel = createTableColumn("New", 60, idx++);
|
|
newModel.setId("newModel");
|
|
newModel.setCellValueFactory(cdf -> cdf.getValue().newModelProperty());
|
|
addTableColumnIfEnabled(newModel);
|
|
|
|
TableColumn<ModelTableRow, String> ethnic = createTableColumn("Ethnicity", 130, idx++);
|
|
ethnic.setId("ethnic");
|
|
ethnic.setCellValueFactory(cdf -> cdf.getValue().ethnicityProperty());
|
|
addTableColumnIfEnabled(ethnic);
|
|
|
|
TableColumn<ModelTableRow, String> country = createTableColumn("Country", 160, idx++);
|
|
country.setId("country");
|
|
country.setCellValueFactory(cdf -> cdf.getValue().countryProperty());
|
|
addTableColumnIfEnabled(country);
|
|
|
|
TableColumn<ModelTableRow, String> continent = createTableColumn("Continent", 100, idx++);
|
|
continent.setId("continent");
|
|
continent.setCellValueFactory(cdf -> cdf.getValue().continentProperty());
|
|
addTableColumnIfEnabled(continent);
|
|
|
|
TableColumn<ModelTableRow, String> occupation = createTableColumn("Occupation", 160, idx++);
|
|
occupation.setId("occupation");
|
|
occupation.setCellValueFactory(cdf -> cdf.getValue().occupationProperty());
|
|
addTableColumnIfEnabled(occupation);
|
|
|
|
TableColumn<ModelTableRow, String> tags = createTableColumn("Tags", 300, idx++);
|
|
tags.setId("tags");
|
|
tags.setCellValueFactory(cdf -> cdf.getValue().tagsProperty());
|
|
addTableColumnIfEnabled(tags);
|
|
|
|
TableColumn<ModelTableRow, String> blurp = createTableColumn("Blurp", 300, idx++);
|
|
blurp.setId("blurp");
|
|
blurp.setCellValueFactory(cdf -> cdf.getValue().blurpProperty());
|
|
addTableColumnIfEnabled(blurp);
|
|
|
|
TableColumn<ModelTableRow, String> topic = createTableColumn("Topic", 600, idx);
|
|
topic.setId("topic");
|
|
topic.setCellValueFactory(cdf -> cdf.getValue().topicProperty());
|
|
addTableColumnIfEnabled(topic);
|
|
|
|
scrollPane.setFitToHeight(true);
|
|
scrollPane.setFitToWidth(true);
|
|
scrollPane.setContent(table);
|
|
scrollPane.setStyle("-fx-background-color: -fx-background");
|
|
layout.setCenter(scrollPane);
|
|
setContent(layout);
|
|
}
|
|
|
|
private ContextMenu createContextMenu() {
|
|
ObservableList<ModelTableRow> selectedStates = table.getSelectionModel().getSelectedItems();
|
|
if (selectedStates.isEmpty()) {
|
|
return null;
|
|
}
|
|
|
|
List<Model> selectedModels = new ArrayList<>();
|
|
for (ModelTableRow sessionState : selectedStates) {
|
|
if(sessionState.name.get() != null) {
|
|
MyFreeCamsModel model = mfc.createModel(sessionState.name.get());
|
|
mfc.getClient().update(model);
|
|
selectedModels.add(model);
|
|
}
|
|
}
|
|
|
|
MenuItem copyUrl = new MenuItem("Copy URL");
|
|
copyUrl.setOnAction(e -> {
|
|
Model selected = selectedModels.get(0);
|
|
final Clipboard clipboard = Clipboard.getSystemClipboard();
|
|
final ClipboardContent content = new ClipboardContent();
|
|
content.putString(selected.getUrl());
|
|
clipboard.setContent(content);
|
|
});
|
|
|
|
MenuItem startRecording = new MenuItem("Start Recording");
|
|
startRecording.setOnAction(e -> startRecording(selectedModels));
|
|
MenuItem openInBrowser = new MenuItem("Open in Browser");
|
|
openInBrowser.setOnAction(e -> DesktopIntegration.open(selectedModels.get(0).getUrl()));
|
|
MenuItem openInPlayer = new MenuItem("Open in Player");
|
|
openInPlayer.setOnAction(e -> openInPlayer(selectedModels.get(0)));
|
|
MenuItem follow = new MenuItem("Follow");
|
|
follow.setOnAction(e -> new FollowAction(getTabPane(), selectedModels).execute());
|
|
|
|
ContextMenu menu = new ContextMenu();
|
|
menu.getItems().addAll(startRecording, copyUrl, openInPlayer, openInBrowser, follow);
|
|
|
|
if (selectedModels.size() > 1) {
|
|
copyUrl.setDisable(true);
|
|
openInPlayer.setDisable(true);
|
|
openInBrowser.setDisable(true);
|
|
}
|
|
|
|
if (Objects.equals(System.getenv("CTBREC_DEV"), "1")) {
|
|
MenuItem debug = new MenuItem("debug");
|
|
debug.setOnAction(e -> new Thread(() -> {
|
|
for (Model m : selectedModels) {
|
|
try {
|
|
List<StreamSource> sources = m.getStreamSources();
|
|
for (StreamSource src : sources) {
|
|
LOG.info("m:{} s:{} bandwidth:{} height:{}", m.getName(), src.mediaPlaylistUrl, src.bandwidth, src.height);
|
|
}
|
|
LOG.info("===============");
|
|
} catch (IOException | ExecutionException | ParseException | PlaylistException | JAXBException e1) {
|
|
LOG.error("Couldn't get stream sources", e1);
|
|
}
|
|
}
|
|
}).start());
|
|
menu.getItems().add(debug);
|
|
}
|
|
|
|
return menu;
|
|
}
|
|
|
|
private void startRecording(List<Model> selectedModels) {
|
|
new StartRecordingAction(getTabPane(), selectedModels, mfc.getRecorder()).execute();
|
|
}
|
|
|
|
private void openInPlayer(Model selectedModel) {
|
|
new PlayAction(getTabPane(), selectedModel).execute();
|
|
}
|
|
|
|
private void addTableColumnIfEnabled(TableColumn<ModelTableRow, ?> tc) {
|
|
if(isColumnEnabled(tc)) {
|
|
table.getColumns().add(tc);
|
|
}
|
|
}
|
|
|
|
private void filter(String filter) {
|
|
lock.lock();
|
|
try {
|
|
if (StringUtil.isBlank(filter)) {
|
|
observableModels.addAll(filteredModels);
|
|
filteredModels.clear();
|
|
return;
|
|
}
|
|
|
|
String[] tokens = filter.split(" ");
|
|
observableModels.addAll(filteredModels);
|
|
filteredModels.clear();
|
|
for (int i = 0; i < table.getItems().size(); i++) {
|
|
StringBuilder sb = new StringBuilder();
|
|
for (TableColumn<ModelTableRow, ?> tc : table.getColumns()) {
|
|
Object cellData = tc.getCellData(i);
|
|
if(cellData != null) {
|
|
String content = cellData.toString();
|
|
sb.append(content).append(' ');
|
|
}
|
|
}
|
|
String searchText = sb.toString();
|
|
|
|
boolean tokensMissing = false;
|
|
for (String token : tokens) {
|
|
if(!searchText.toLowerCase().contains(token.toLowerCase())) {
|
|
tokensMissing = true;
|
|
break;
|
|
}
|
|
}
|
|
if(tokensMissing) {
|
|
ModelTableRow sessionState = table.getItems().get(i);
|
|
filteredModels.add(sessionState);
|
|
}
|
|
}
|
|
observableModels.removeAll(filteredModels);
|
|
} finally {
|
|
lock.unlock();
|
|
int filtered = filteredModels.size();
|
|
int showing = observableModels.size();
|
|
int total = showing + filtered;
|
|
count.setText(showing + "/" + total);
|
|
}
|
|
}
|
|
|
|
private void export(ActionEvent evt) {
|
|
FileChooser chooser = new FileChooser();
|
|
File file = chooser.showSaveDialog(getTabPane().getScene().getWindow());
|
|
if(file != null) {
|
|
try(FileOutputStream fout = new FileOutputStream(file)) {
|
|
PrintStream ps = new PrintStream(fout);
|
|
List<ModelTableRow> union = new ArrayList<>();
|
|
union.addAll(filteredModels);
|
|
union.addAll(observableModels);
|
|
ps.println("\"uid\",\"blurp\",\"camScore\",\"continent\",\"country\",\"ethnic\",\"name\",\"new\",\"occupation\",\"state\",\"tags\",\"topic\"");
|
|
for (ModelTableRow row : union) {
|
|
ps.print("\"" + row.uid + "\""); ps.print(',');
|
|
ps.print(escape(row.blurp)); ps.print(',');
|
|
ps.print(escape(row.camScore)); ps.print(',');
|
|
ps.print(escape(row.continent)); ps.print(',');
|
|
ps.print(escape(row.country)); ps.print(',');
|
|
ps.print(escape(row.ethnic)); ps.print(',');
|
|
ps.print(escape(row.name)); ps.print(',');
|
|
ps.print(escape(row.newModel)); ps.print(',');
|
|
ps.print(escape(row.occupation)); ps.print(',');
|
|
ps.print(escape(row.state)); ps.print(',');
|
|
ps.print(escape(row.tags)); ps.print(',');
|
|
ps.print(escape(row.topic));
|
|
ps.println();
|
|
}
|
|
} catch (Exception e) {
|
|
LOG.debug("Couldn't write mfc models table data", e);
|
|
}
|
|
}
|
|
}
|
|
|
|
private String escape(Property<?> prop) {
|
|
String value = prop.getValue() != null ? prop.getValue().toString() : "";
|
|
return "\"" + value.replaceAll("\"", "\"\"") + "\"";
|
|
}
|
|
|
|
private void showColumnSelection(ActionEvent evt) {
|
|
ContextMenu menu = new ContextMenu();
|
|
for (TableColumn<ModelTableRow, ?> tc : columns) {
|
|
CheckMenuItem item = new CheckMenuItem(tc.getText());
|
|
item.setSelected(isColumnEnabled(tc));
|
|
menu.getItems().add(item);
|
|
item.setOnAction(e -> {
|
|
if(item.isSelected()) {
|
|
Config.getInstance().getSettings().mfcDisabledModelsTableColumns.remove(tc.getText());
|
|
for (int i = table.getColumns().size()-1; i>=0; i--) {
|
|
TableColumn<ModelTableRow, ?> other = table.getColumns().get(i);
|
|
int idx = (int) tc.getUserData();
|
|
int otherIdx = (int) other.getUserData();
|
|
if(otherIdx < idx) {
|
|
table.getColumns().add(i+1, tc);
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
Config.getInstance().getSettings().mfcDisabledModelsTableColumns.add(tc.getText());
|
|
table.getColumns().remove(tc);
|
|
}
|
|
});
|
|
}
|
|
Button src = (Button) evt.getSource();
|
|
Point2D location = src.localToScreen(src.getTranslateX(), src.getTranslateY());
|
|
menu.show(getTabPane().getScene().getWindow(), location.getX(), location.getY() + src.getHeight() + 5);
|
|
}
|
|
|
|
private boolean isColumnEnabled(TableColumn<ModelTableRow, ?> tc) {
|
|
return !Config.getInstance().getSettings().mfcDisabledModelsTableColumns.contains(tc.getText());
|
|
}
|
|
|
|
private <T> TableColumn<ModelTableRow, T> createTableColumn(String text, int width, int idx) {
|
|
TableColumn<ModelTableRow, T> tc = new TableColumn<>(text);
|
|
tc.setPrefWidth(width);
|
|
tc.sortTypeProperty().addListener((obs, o, n) -> saveState());
|
|
tc.widthProperty().addListener((obs, o, n) -> saveState());
|
|
tc.setUserData(idx);
|
|
columns.add(tc);
|
|
return tc;
|
|
}
|
|
|
|
@Override
|
|
public void selected() {
|
|
if(updateService != null) {
|
|
State s = updateService.getState();
|
|
if (s != State.SCHEDULED && s != State.RUNNING) {
|
|
updateService.reset();
|
|
updateService.restart();
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void deselected() {
|
|
if(updateService != null) {
|
|
updateService.cancel();
|
|
}
|
|
saveData();
|
|
observableModels.clear();
|
|
}
|
|
|
|
private void saveData() {
|
|
try {
|
|
List<ModelTableRow> union = new ArrayList<>();
|
|
union.addAll(filteredModels);
|
|
union.addAll(observableModels);
|
|
JSONArray data = new JSONArray();
|
|
for (ModelTableRow row : union) {
|
|
JSONObject model = new JSONObject();
|
|
model.put("uid", row.uid);
|
|
model.put("blurp", row.blurp.get());
|
|
model.put("camScore", row.camScore.get());
|
|
model.put("continent", row.continent.get());
|
|
model.put("country", row.country.get());
|
|
model.put("ethnic", row.ethnic.get());
|
|
model.put("name", row.name.get());
|
|
model.put("newModel", row.newModel.get());
|
|
model.put("occupation", row.occupation.get());
|
|
model.put("state", row.state.get());
|
|
model.put("tags", row.tags.get());
|
|
model.put("topic", row.topic.get());
|
|
data.put(model);
|
|
}
|
|
File file = new File(Config.getInstance().getConfigDir(), "mfc-models.json");
|
|
Files.write(file.toPath(), data.toString(2).getBytes(UTF_8), CREATE, WRITE);
|
|
saveState();
|
|
} catch (Exception e) {
|
|
LOG.debug("Couldn't write mfc models table data: {}", e.getMessage());
|
|
}
|
|
}
|
|
|
|
private void loadData() {
|
|
try {
|
|
File file = new File(Config.getInstance().getConfigDir(), "mfc-models.json");
|
|
if(!file.exists()) {
|
|
return;
|
|
}
|
|
String json = new String(Files.readAllBytes(file.toPath()), "utf-8");
|
|
JSONArray data = new JSONArray(json);
|
|
for (int i = 0; i < data.length(); i++) {
|
|
try {
|
|
ModelTableRow row = new ModelTableRow();
|
|
JSONObject model = data.getJSONObject(i);
|
|
row.uid = model.getInt("uid");
|
|
row.blurp.set(model.optString("blurp"));
|
|
row.camScore.set(model.optDouble("camScore"));
|
|
row.continent.set(model.optString("continent"));
|
|
row.country.set(model.optString("country"));
|
|
row.ethnic.set(model.optString("ethnic"));
|
|
row.name.set(model.optString("name"));
|
|
row.newModel.set(model.optString("newModel"));
|
|
row.occupation.set(model.optString("occupation"));
|
|
row.state.set(model.optString("state"));
|
|
row.tags.set(model.optString("tags"));
|
|
row.topic.set(model.optString("topic"));
|
|
observableModels.add(row);
|
|
} catch (Exception e) {
|
|
// ignore this error
|
|
}
|
|
}
|
|
} catch (Exception e) {
|
|
LOG.debug("Couldn't read mfc models table data: {}", e.getMessage());
|
|
}
|
|
}
|
|
|
|
private void saveState() {
|
|
Settings settings = Config.getInstance().getSettings();
|
|
if (!table.getSortOrder().isEmpty()) {
|
|
TableColumn<ModelTableRow, ?> col = table.getSortOrder().get(0);
|
|
settings.mfcModelsTableSortColumn = col.getText();
|
|
settings.mfcModelsTableSortType = col.getSortType().toString();
|
|
}
|
|
int size = table.getColumns().size();
|
|
double[] columnWidths = new double[size];
|
|
String[] columnIds = new String[size];
|
|
for (int i = 0; i < columnWidths.length; i++) {
|
|
columnWidths[i] = table.getColumns().get(i).getWidth();
|
|
columnIds[i] = table.getColumns().get(i).getId();
|
|
}
|
|
settings.mfcModelsTableColumnWidths = columnWidths;
|
|
settings.mfcModelsTableColumnIds = columnIds;
|
|
}
|
|
|
|
private void restoreState() {
|
|
restoreColumnOrder();
|
|
restoreColumnWidths();
|
|
restoreSorting();
|
|
|
|
filterInput.setText(Config.getInstance().getSettings().mfcModelsTableFilter);
|
|
}
|
|
|
|
private void restoreSorting() {
|
|
String sortCol = Config.getInstance().getSettings().mfcModelsTableSortColumn;
|
|
if (StringUtil.isNotBlank(sortCol)) {
|
|
for (TableColumn<ModelTableRow, ?> col : table.getColumns()) {
|
|
if (Objects.equals(sortCol, col.getText())) {
|
|
col.setSortType(SortType.valueOf(Config.getInstance().getSettings().mfcModelsTableSortType));
|
|
table.getSortOrder().clear();
|
|
table.getSortOrder().add(col);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void restoreColumnOrder() {
|
|
String[] columnIds = Config.getInstance().getSettings().mfcModelsTableColumnIds;
|
|
ObservableList<TableColumn<ModelTableRow,?>> tableColumns = table.getColumns();
|
|
for (int i = 0; i < columnIds.length; i++) {
|
|
for (int j = 0; j < table.getColumns().size(); j++) {
|
|
if(Objects.equals(columnIds[i], tableColumns.get(j).getId())) {
|
|
TableColumn<ModelTableRow, ?> col = tableColumns.get(j);
|
|
tableColumns.remove(j); // NOSONAR
|
|
tableColumns.add(i, col);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void restoreColumnWidths() {
|
|
double[] columnWidths = Config.getInstance().getSettings().mfcModelsTableColumnWidths;
|
|
if (columnWidths != null && columnWidths.length == table.getColumns().size()) {
|
|
for (int i = 0; i < columnWidths.length; i++) {
|
|
table.getColumns().get(i).setPrefWidth(columnWidths[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
private ListChangeListener<TableColumn<ModelTableRow, ?>> createSortOrderChangedListener() {
|
|
return c -> saveState();
|
|
}
|
|
|
|
private static class ModelTableRow {
|
|
private Integer uid;
|
|
private StringProperty name = new SimpleStringProperty();
|
|
private StringProperty state = new SimpleStringProperty();
|
|
private DoubleProperty camScore = new SimpleDoubleProperty();
|
|
private StringProperty newModel = new SimpleStringProperty();
|
|
private StringProperty ethnic = new SimpleStringProperty();
|
|
private StringProperty country = new SimpleStringProperty();
|
|
private StringProperty continent = new SimpleStringProperty();
|
|
private StringProperty occupation = new SimpleStringProperty();
|
|
private StringProperty tags = new SimpleStringProperty();
|
|
private StringProperty blurp = new SimpleStringProperty();
|
|
private StringProperty topic = new SimpleStringProperty();
|
|
private BooleanProperty isHd = new SimpleBooleanProperty();
|
|
private BooleanProperty isWebrtc = new SimpleBooleanProperty();
|
|
private SimpleIntegerProperty uidProperty = new SimpleIntegerProperty();
|
|
private SimpleIntegerProperty flagsProperty = new SimpleIntegerProperty();
|
|
|
|
public ModelTableRow(SessionState st) {
|
|
update(st);
|
|
}
|
|
|
|
private ModelTableRow() {
|
|
}
|
|
|
|
public void update(SessionState st) {
|
|
uid = st.getUid();
|
|
uidProperty.set(uid);
|
|
setProperty(name, Optional.ofNullable(st.getNm()));
|
|
setProperty(state, Optional.ofNullable(st.getVs()).map(vs -> ctbrec.sites.mfc.State.of(vs).toString()));
|
|
setProperty(camScore, Optional.ofNullable(st.getM()).map(ctbrec.sites.mfc.Model::getCamscore));
|
|
Optional<Integer> isNew = Optional.ofNullable(st.getM()).map(ctbrec.sites.mfc.Model::getNewModel);
|
|
if(isNew.isPresent()) {
|
|
newModel.set(isNew.get() == 1 ? "new" : "");
|
|
}
|
|
setProperty(ethnic, Optional.ofNullable(st.getU()).map(User::getEthnic));
|
|
setProperty(country, Optional.ofNullable(st.getU()).map(User::getCountry));
|
|
setProperty(continent, Optional.ofNullable(st.getM()).map(ctbrec.sites.mfc.Model::getContinent));
|
|
setProperty(occupation, Optional.ofNullable(st.getU()).map(User::getOccupation));
|
|
int flags = Optional.ofNullable(st.getM()).map(ctbrec.sites.mfc.Model::getFlags).orElse(0);
|
|
//isHd.set((flags & 1024) == 1024);
|
|
isWebrtc.set((flags & 524288) == 524288);
|
|
isHd.set(Optional.ofNullable(st.getU()).map(User::getPhase).orElse("z").equalsIgnoreCase("a"));
|
|
flagsProperty.setValue(flags);
|
|
Set<String> tagSet = Optional.ofNullable(st.getM()).map(ctbrec.sites.mfc.Model::getTags).orElse(Collections.emptySet());
|
|
if(!tagSet.isEmpty()) {
|
|
StringBuilder sb = new StringBuilder();
|
|
for (String t : tagSet) {
|
|
sb.append(t).append(',').append(' ');
|
|
}
|
|
tags.set(sb.substring(0, sb.length()-2));
|
|
}
|
|
setProperty(blurp, Optional.ofNullable(st.getU()).map(User::getBlurb));
|
|
String tpc = Optional.ofNullable(st.getM()).map(ctbrec.sites.mfc.Model::getTopic).orElse("n/a");
|
|
try {
|
|
tpc = URLDecoder.decode(tpc, "utf-8");
|
|
} catch (UnsupportedEncodingException e) {
|
|
LOG.warn("Couldn't url decode topic", e);
|
|
}
|
|
topic.set(tpc);
|
|
}
|
|
|
|
private <T> void setProperty(Property<T> prop, Optional<T> value) {
|
|
if(value.isPresent() && !Objects.equals(value.get(), prop.getValue())) {
|
|
prop.setValue(value.get());
|
|
}
|
|
}
|
|
|
|
public StringProperty nameProperty() {
|
|
return name;
|
|
}
|
|
|
|
public StringProperty stateProperty() {
|
|
return state;
|
|
}
|
|
|
|
public DoubleProperty camScoreProperty() {
|
|
return camScore;
|
|
}
|
|
|
|
public StringProperty newModelProperty() {
|
|
return newModel;
|
|
}
|
|
|
|
public StringProperty ethnicityProperty() {
|
|
return ethnic;
|
|
}
|
|
|
|
public StringProperty countryProperty() {
|
|
return country;
|
|
}
|
|
|
|
public StringProperty continentProperty() {
|
|
return continent;
|
|
}
|
|
|
|
public StringProperty occupationProperty() {
|
|
return occupation;
|
|
}
|
|
|
|
public StringProperty tagsProperty() {
|
|
return tags;
|
|
}
|
|
|
|
public StringProperty blurpProperty() {
|
|
return blurp;
|
|
}
|
|
|
|
public StringProperty topicProperty() {
|
|
return topic;
|
|
}
|
|
|
|
public BooleanProperty hdProperty() {
|
|
return isHd;
|
|
}
|
|
|
|
public BooleanProperty webrtcProperty() {
|
|
return isWebrtc;
|
|
}
|
|
|
|
public IntegerProperty flagsProperty() {
|
|
return flagsProperty;
|
|
}
|
|
|
|
public IntegerProperty uidProperty() {
|
|
return uidProperty;
|
|
}
|
|
|
|
@Override
|
|
public int hashCode() {
|
|
final int prime = 31;
|
|
int result = 1;
|
|
result = prime * result + ((uid == null) ? 0 : uid.hashCode());
|
|
return result;
|
|
}
|
|
|
|
@Override
|
|
public boolean equals(Object obj) {
|
|
if (this == obj)
|
|
return true;
|
|
if (obj == null)
|
|
return false;
|
|
if (getClass() != obj.getClass())
|
|
return false;
|
|
ModelTableRow other = (ModelTableRow) obj;
|
|
if (uid == null) {
|
|
if (other.uid != null)
|
|
return false;
|
|
} else if (!uid.equals(other.uid))
|
|
return false;
|
|
return true;
|
|
}
|
|
}
|
|
} |