forked from j62/ctbrec
Get rid of moshi
This commit is contained in:
parent
b53be222fb
commit
619d888bfa
|
@ -1,9 +1,8 @@
|
||||||
package ctbrec.ui;
|
package ctbrec.ui;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
import com.google.common.eventbus.Subscribe;
|
import com.google.common.eventbus.Subscribe;
|
||||||
import com.squareup.moshi.JsonAdapter;
|
|
||||||
import com.squareup.moshi.Moshi;
|
|
||||||
import com.squareup.moshi.Types;
|
|
||||||
import ctbrec.Config;
|
import ctbrec.Config;
|
||||||
import ctbrec.Model;
|
import ctbrec.Model;
|
||||||
import ctbrec.StringUtil;
|
import ctbrec.StringUtil;
|
||||||
|
@ -19,6 +18,7 @@ import ctbrec.io.BandwidthMeter;
|
||||||
import ctbrec.io.ByteUnitFormatter;
|
import ctbrec.io.ByteUnitFormatter;
|
||||||
import ctbrec.io.HttpClient;
|
import ctbrec.io.HttpClient;
|
||||||
import ctbrec.io.HttpException;
|
import ctbrec.io.HttpException;
|
||||||
|
import ctbrec.io.json.ObjectMapperFactory;
|
||||||
import ctbrec.notes.LocalModelNotesService;
|
import ctbrec.notes.LocalModelNotesService;
|
||||||
import ctbrec.notes.ModelNotesService;
|
import ctbrec.notes.ModelNotesService;
|
||||||
import ctbrec.notes.RemoteModelNotesService;
|
import ctbrec.notes.RemoteModelNotesService;
|
||||||
|
@ -63,6 +63,7 @@ import javafx.scene.layout.HBox;
|
||||||
import javafx.scene.paint.Color;
|
import javafx.scene.paint.Color;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
import javafx.stage.WindowEvent;
|
import javafx.stage.WindowEvent;
|
||||||
|
import lombok.Data;
|
||||||
import okhttp3.Request;
|
import okhttp3.Request;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
@ -72,7 +73,6 @@ import java.io.File;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.lang.reflect.Type;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.security.InvalidKeyException;
|
import java.security.InvalidKeyException;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
@ -549,7 +549,6 @@ public class CamrecApplication extends Application {
|
||||||
private void createRecorder() {
|
private void createRecorder() {
|
||||||
if (config.getSettings().localRecording) {
|
if (config.getSettings().localRecording) {
|
||||||
try {
|
try {
|
||||||
//recorder = new NextGenLocalRecorder(config, sites);
|
|
||||||
recorder = new SimplifiedLocalRecorder(config, sites);
|
recorder = new SimplifiedLocalRecorder(config, sites);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
LOG.error("Couldn't initialize recorder", e);
|
LOG.error("Couldn't initialize recorder", e);
|
||||||
|
@ -597,10 +596,8 @@ public class CamrecApplication extends Application {
|
||||||
var body = response.body().string();
|
var body = response.body().string();
|
||||||
LOG.trace("Version check respone: {}", body);
|
LOG.trace("Version check respone: {}", body);
|
||||||
if (response.isSuccessful()) {
|
if (response.isSuccessful()) {
|
||||||
var moshi = new Moshi.Builder().build();
|
List<Release> releases = ObjectMapperFactory.getMapper().readValue(body, new TypeReference<>() {
|
||||||
Type type = Types.newParameterizedType(List.class, Release.class);
|
});
|
||||||
JsonAdapter<List<Release>> adapter = moshi.adapter(type);
|
|
||||||
List<Release> releases = adapter.fromJson(body);
|
|
||||||
var latest = releases.get(0);
|
var latest = releases.get(0);
|
||||||
var latestVersion = latest.getVersion();
|
var latestVersion = latest.getVersion();
|
||||||
var ctbrecVersion = Version.getVersion();
|
var ctbrecVersion = Version.getVersion();
|
||||||
|
@ -622,38 +619,16 @@ public class CamrecApplication extends Application {
|
||||||
updateCheck.start();
|
updateCheck.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
public static class Release {
|
public static class Release {
|
||||||
private String name;
|
private String name;
|
||||||
private String tag_name; // NOSONAR - name pattern is needed by moshi
|
@JsonProperty("tag_name")
|
||||||
private String html_url; // NOSONAR - name pattern is needed by moshi
|
private String tagName;
|
||||||
|
@JsonProperty("html_url")
|
||||||
public String getName() {
|
private String htmlUrl;
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setName(String name) {
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getTagName() {
|
|
||||||
return tag_name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTagName(String tagName) {
|
|
||||||
this.tag_name = tagName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getHtmlUrl() {
|
|
||||||
return html_url;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public void setHtmlUrl(String htmlUrl) {
|
|
||||||
this.html_url = htmlUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Version getVersion() {
|
public Version getVersion() {
|
||||||
return Version.of(tag_name);
|
return Version.of(tagName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,6 @@ package ctbrec.ui;
|
||||||
|
|
||||||
import com.iheartradio.m3u8.ParseException;
|
import com.iheartradio.m3u8.ParseException;
|
||||||
import com.iheartradio.m3u8.PlaylistException;
|
import com.iheartradio.m3u8.PlaylistException;
|
||||||
import com.squareup.moshi.JsonReader;
|
|
||||||
import com.squareup.moshi.JsonWriter;
|
|
||||||
import ctbrec.Model;
|
import ctbrec.Model;
|
||||||
import ctbrec.SubsequentAction;
|
import ctbrec.SubsequentAction;
|
||||||
import ctbrec.recorder.download.HttpHeaderFactory;
|
import ctbrec.recorder.download.HttpHeaderFactory;
|
||||||
|
@ -19,20 +17,21 @@ import javax.xml.bind.JAXBException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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
|
||||||
*/
|
*/
|
||||||
public class JavaFxModel implements Model {
|
public class JavaFxModel implements Model {
|
||||||
private transient BooleanProperty onlineProperty = new SimpleBooleanProperty();
|
private final transient BooleanProperty onlineProperty = new SimpleBooleanProperty();
|
||||||
private transient BooleanProperty recordingProperty = new SimpleBooleanProperty();
|
private final transient BooleanProperty recordingProperty = new SimpleBooleanProperty();
|
||||||
private transient BooleanProperty pausedProperty = new SimpleBooleanProperty();
|
private final transient BooleanProperty pausedProperty = new SimpleBooleanProperty();
|
||||||
private transient SimpleIntegerProperty priorityProperty = new SimpleIntegerProperty();
|
private final transient SimpleIntegerProperty priorityProperty = new SimpleIntegerProperty();
|
||||||
private transient SimpleObjectProperty<Instant> lastSeenProperty = new SimpleObjectProperty<>();
|
private final transient SimpleObjectProperty<Instant> lastSeenProperty = new SimpleObjectProperty<>();
|
||||||
private transient SimpleObjectProperty<Instant> lastRecordedProperty = new SimpleObjectProperty<>();
|
private final transient SimpleObjectProperty<Instant> lastRecordedProperty = new SimpleObjectProperty<>();
|
||||||
|
|
||||||
private Model delegate;
|
private final Model delegate;
|
||||||
|
|
||||||
public JavaFxModel(Model delegate) {
|
public JavaFxModel(Model delegate) {
|
||||||
this.delegate = delegate;
|
this.delegate = delegate;
|
||||||
|
@ -188,13 +187,13 @@ public class JavaFxModel implements Model {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void readSiteSpecificData(JsonReader reader) throws IOException {
|
public void readSiteSpecificData(Map<String, String> data) {
|
||||||
delegate.readSiteSpecificData(reader);
|
delegate.readSiteSpecificData(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void writeSiteSpecificData(JsonWriter writer) throws IOException {
|
public void writeSiteSpecificData(Map<String, String> data) {
|
||||||
delegate.writeSiteSpecificData(writer);
|
delegate.writeSiteSpecificData(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -1,11 +1,16 @@
|
||||||
package ctbrec.ui.event;
|
package ctbrec.ui.event;
|
||||||
|
|
||||||
import java.time.Instant;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
import ctbrec.Model;
|
import ctbrec.Model;
|
||||||
import ctbrec.ui.JavaFxModel;
|
import ctbrec.ui.JavaFxModel;
|
||||||
|
import lombok.*;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@EqualsAndHashCode(of = "timestamp")
|
||||||
|
@ToString
|
||||||
|
@NoArgsConstructor
|
||||||
public class PlayerStartedEvent {
|
public class PlayerStartedEvent {
|
||||||
|
|
||||||
private Model model;
|
private Model model;
|
||||||
|
@ -20,41 +25,16 @@ public class PlayerStartedEvent {
|
||||||
this.timestamp = timestamp;
|
this.timestamp = timestamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Model getModel() {
|
public void setModel(Model model) {
|
||||||
return model;
|
this.model = unwrap(model);
|
||||||
}
|
|
||||||
|
|
||||||
public Instant getTimestamp() {
|
|
||||||
return timestamp;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return Objects.hash(timestamp);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object obj) {
|
|
||||||
if (this == obj)
|
|
||||||
return true;
|
|
||||||
if (obj == null)
|
|
||||||
return false;
|
|
||||||
if (getClass() != obj.getClass())
|
|
||||||
return false;
|
|
||||||
PlayerStartedEvent other = (PlayerStartedEvent) obj;
|
|
||||||
return Objects.equals(timestamp, other.timestamp);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "PlayerStartedEvent [model=" + model + ", timestamp=" + timestamp + "]";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Model unwrap(Model model) {
|
private Model unwrap(Model model) {
|
||||||
if (model instanceof JavaFxModel) {
|
if (model instanceof JavaFxModel fxModel) {
|
||||||
return ((JavaFxModel) model).getDelegate();
|
return fxModel.getDelegate();
|
||||||
} else {
|
} else {
|
||||||
return model;
|
return model;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
package ctbrec.ui.io.json.dto;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||||
|
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||||
|
import ctbrec.io.json.dto.ModelDto;
|
||||||
|
import ctbrec.io.json.dto.converter.InstantToMillisConverter;
|
||||||
|
import ctbrec.io.json.dto.converter.MillisToInstantConverter;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class PlayerStartedEventDto {
|
||||||
|
private ModelDto model;
|
||||||
|
@JsonSerialize(converter = InstantToMillisConverter.class)
|
||||||
|
@JsonDeserialize(converter = MillisToInstantConverter.class)
|
||||||
|
private Instant timestamp;
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
package ctbrec.ui.io.json.mapper;
|
||||||
|
|
||||||
|
import ctbrec.io.json.mapper.ModelMapper;
|
||||||
|
import ctbrec.ui.event.PlayerStartedEvent;
|
||||||
|
import ctbrec.ui.io.json.dto.PlayerStartedEventDto;
|
||||||
|
import org.mapstruct.Mapper;
|
||||||
|
|
||||||
|
@Mapper(uses = ModelMapper.class)
|
||||||
|
public interface PlayerStartedEventMapper {
|
||||||
|
|
||||||
|
PlayerStartedEventDto toDto(PlayerStartedEvent event);
|
||||||
|
|
||||||
|
PlayerStartedEvent toEvent(PlayerStartedEventDto dto);
|
||||||
|
}
|
|
@ -1,190 +1,49 @@
|
||||||
package ctbrec.ui.news;
|
package ctbrec.ui.news;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import com.squareup.moshi.Json;
|
@Data
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
public class Account {
|
public class Account {
|
||||||
|
|
||||||
@Json(name = "emojis")
|
@JsonProperty("emojis")
|
||||||
private List<Object> emojis = null;
|
private List<Object> emojis = null;
|
||||||
@Json(name = "note")
|
@JsonProperty("note")
|
||||||
private String note;
|
private String note;
|
||||||
@Json(name = "bot")
|
@JsonProperty("bot")
|
||||||
private Boolean bot;
|
private Boolean bot;
|
||||||
@Json(name = "created_at")
|
@JsonProperty("created_at")
|
||||||
private String createdAt;
|
private String createdAt;
|
||||||
@Json(name = "avatar")
|
@JsonProperty("avatar")
|
||||||
private String avatar;
|
private String avatar;
|
||||||
@Json(name = "display_name")
|
@JsonProperty("display_name")
|
||||||
private String displayName;
|
private String displayName;
|
||||||
@Json(name = "header_static")
|
@JsonProperty("header_static")
|
||||||
private String headerStatic;
|
private String headerStatic;
|
||||||
@Json(name = "url")
|
@JsonProperty("url")
|
||||||
private String url;
|
private String url;
|
||||||
@Json(name = "following_count")
|
@JsonProperty("following_count")
|
||||||
private Integer followingCount;
|
private Integer followingCount;
|
||||||
@Json(name = "statuses_count")
|
@JsonProperty("statuses_count")
|
||||||
private Integer statusesCount;
|
private Integer statusesCount;
|
||||||
@Json(name = "followers_count")
|
@JsonProperty("followers_count")
|
||||||
private Integer followersCount;
|
private Integer followersCount;
|
||||||
@Json(name = "header")
|
@JsonProperty("header")
|
||||||
private String header;
|
private String header;
|
||||||
@Json(name = "id")
|
@JsonProperty("id")
|
||||||
private String id;
|
private String id;
|
||||||
@Json(name = "locked")
|
@JsonProperty("locked")
|
||||||
private Boolean locked;
|
private Boolean locked;
|
||||||
@Json(name = "avatar_static")
|
@JsonProperty("avatar_static")
|
||||||
private String avatarStatic;
|
private String avatarStatic;
|
||||||
@Json(name = "fields")
|
@JsonProperty("fields")
|
||||||
private List<Object> fields = null;
|
private List<Object> fields = null;
|
||||||
@Json(name = "acct")
|
@JsonProperty("acct")
|
||||||
private String acct;
|
private String acct;
|
||||||
@Json(name = "username")
|
@JsonProperty("username")
|
||||||
private String username;
|
private String username;
|
||||||
|
|
||||||
public List<Object> getEmojis() {
|
|
||||||
return emojis;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setEmojis(List<Object> emojis) {
|
|
||||||
this.emojis = emojis;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getNote() {
|
|
||||||
return note;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setNote(String note) {
|
|
||||||
this.note = note;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean getBot() {
|
|
||||||
return bot;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setBot(Boolean bot) {
|
|
||||||
this.bot = bot;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getCreatedAt() {
|
|
||||||
return createdAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCreatedAt(String createdAt) {
|
|
||||||
this.createdAt = createdAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getAvatar() {
|
|
||||||
return avatar;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setAvatar(String avatar) {
|
|
||||||
this.avatar = avatar;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getDisplayName() {
|
|
||||||
return displayName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setDisplayName(String displayName) {
|
|
||||||
this.displayName = displayName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getHeaderStatic() {
|
|
||||||
return headerStatic;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setHeaderStatic(String headerStatic) {
|
|
||||||
this.headerStatic = headerStatic;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getUrl() {
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setUrl(String url) {
|
|
||||||
this.url = url;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Integer getFollowingCount() {
|
|
||||||
return followingCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFollowingCount(Integer followingCount) {
|
|
||||||
this.followingCount = followingCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Integer getStatusesCount() {
|
|
||||||
return statusesCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setStatusesCount(Integer statusesCount) {
|
|
||||||
this.statusesCount = statusesCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Integer getFollowersCount() {
|
|
||||||
return followersCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFollowersCount(Integer followersCount) {
|
|
||||||
this.followersCount = followersCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getHeader() {
|
|
||||||
return header;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setHeader(String header) {
|
|
||||||
this.header = header;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getId() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setId(String id) {
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean getLocked() {
|
|
||||||
return locked;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setLocked(Boolean locked) {
|
|
||||||
this.locked = locked;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getAvatarStatic() {
|
|
||||||
return avatarStatic;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setAvatarStatic(String avatarStatic) {
|
|
||||||
this.avatarStatic = avatarStatic;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<Object> getFields() {
|
|
||||||
return fields;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFields(List<Object> fields) {
|
|
||||||
this.fields = fields;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getAcct() {
|
|
||||||
return acct;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setAcct(String acct) {
|
|
||||||
this.acct = acct;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getUsername() {
|
|
||||||
return username;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setUsername(String username) {
|
|
||||||
this.username = username;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
package ctbrec.ui.news;
|
package ctbrec.ui.news;
|
||||||
|
|
||||||
import com.squareup.moshi.JsonAdapter;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.squareup.moshi.Moshi;
|
|
||||||
import ctbrec.Config;
|
import ctbrec.Config;
|
||||||
import ctbrec.GlobalThreadPool;
|
import ctbrec.GlobalThreadPool;
|
||||||
import ctbrec.Version;
|
import ctbrec.Version;
|
||||||
import ctbrec.io.HttpException;
|
import ctbrec.io.HttpException;
|
||||||
|
import ctbrec.io.json.ObjectMapperFactory;
|
||||||
import ctbrec.ui.CamrecApplication;
|
import ctbrec.ui.CamrecApplication;
|
||||||
import ctbrec.ui.controls.Dialogs;
|
import ctbrec.ui.controls.Dialogs;
|
||||||
import ctbrec.ui.tabs.TabSelectionListener;
|
import ctbrec.ui.tabs.TabSelectionListener;
|
||||||
|
@ -15,6 +15,7 @@ import javafx.geometry.Pos;
|
||||||
import javafx.scene.control.ScrollPane;
|
import javafx.scene.control.ScrollPane;
|
||||||
import javafx.scene.control.Tab;
|
import javafx.scene.control.Tab;
|
||||||
import javafx.scene.layout.VBox;
|
import javafx.scene.layout.VBox;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import okhttp3.Request;
|
import okhttp3.Request;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
@ -24,12 +25,15 @@ import java.util.Objects;
|
||||||
import static ctbrec.ErrorMessages.HTTP_RESPONSE_BODY_IS_NULL;
|
import static ctbrec.ErrorMessages.HTTP_RESPONSE_BODY_IS_NULL;
|
||||||
import static ctbrec.io.HttpConstants.USER_AGENT;
|
import static ctbrec.io.HttpConstants.USER_AGENT;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
public class NewsTab extends Tab implements TabSelectionListener {
|
public class NewsTab extends Tab implements TabSelectionListener {
|
||||||
private static final String ACCESS_TOKEN = "a2804d73a89951a22e0f8483a6fcec8943afd88b7ba17c459c095aa9e6f94fd0";
|
private static final String ACCESS_TOKEN = "a2804d73a89951a22e0f8483a6fcec8943afd88b7ba17c459c095aa9e6f94fd0";
|
||||||
private static final String URL = "https://mastodon.cloud/api/v1/accounts/480960/statuses?limit=20&exclude_replies=true";
|
private static final String URL = "https://mastodon.cloud/api/v1/accounts/480960/statuses?limit=20&exclude_replies=true";
|
||||||
private final Config config;
|
private final Config config;
|
||||||
private final VBox layout = new VBox();
|
private final VBox layout = new VBox();
|
||||||
|
|
||||||
|
private final ObjectMapper mapper = ObjectMapperFactory.getMapper();
|
||||||
|
|
||||||
public NewsTab(Config config) {
|
public NewsTab(Config config) {
|
||||||
this.config = config;
|
this.config = config;
|
||||||
setText("News");
|
setText("News");
|
||||||
|
@ -53,6 +57,7 @@ public class NewsTab extends Tab implements TabSelectionListener {
|
||||||
try (var response = CamrecApplication.httpClient.execute(request)) {
|
try (var response = CamrecApplication.httpClient.execute(request)) {
|
||||||
if (response.isSuccessful()) {
|
if (response.isSuccessful()) {
|
||||||
var body = Objects.requireNonNull(response.body(), HTTP_RESPONSE_BODY_IS_NULL).string();
|
var body = Objects.requireNonNull(response.body(), HTTP_RESPONSE_BODY_IS_NULL).string();
|
||||||
|
log.debug(body);
|
||||||
if (body.startsWith("[")) {
|
if (body.startsWith("[")) {
|
||||||
onSuccess(body);
|
onSuccess(body);
|
||||||
} else if (body.startsWith("{")) {
|
} else if (body.startsWith("{")) {
|
||||||
|
@ -65,6 +70,7 @@ public class NewsTab extends Tab implements TabSelectionListener {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
log.info("Error while loading news", e);
|
||||||
Dialogs.showError(getTabPane().getScene(), "News", "Couldn't load news from mastodon", e);
|
Dialogs.showError(getTabPane().getScene(), "News", "Couldn't load news from mastodon", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -79,9 +85,7 @@ public class NewsTab extends Tab implements TabSelectionListener {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onSuccess(String body) throws IOException {
|
private void onSuccess(String body) throws IOException {
|
||||||
var moshi = new Moshi.Builder().build();
|
Status[] statusArray = mapper.readValue(body, Status[].class);
|
||||||
JsonAdapter<Status[]> statusListAdapter = moshi.adapter(Status[].class);
|
|
||||||
Status[] statusArray = Objects.requireNonNull(statusListAdapter.fromJson(body));
|
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
layout.getChildren().clear();
|
layout.getChildren().clear();
|
||||||
for (Status status : statusArray) {
|
for (Status status : statusArray) {
|
||||||
|
|
|
@ -1,275 +1,71 @@
|
||||||
package ctbrec.ui.news;
|
package ctbrec.ui.news;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.time.ZoneId;
|
import java.time.ZoneId;
|
||||||
import java.time.ZonedDateTime;
|
import java.time.ZonedDateTime;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import com.squareup.moshi.Json;
|
@Data
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
public class Status {
|
public class Status {
|
||||||
|
|
||||||
@Json(name = "pinned")
|
@JsonProperty("pinned")
|
||||||
private Boolean pinned;
|
private Boolean pinned;
|
||||||
@Json(name = "in_reply_to_id")
|
@JsonProperty("in_reply_to_id")
|
||||||
private Object inReplyToId;
|
private Object inReplyToId;
|
||||||
@Json(name = "favourites_count")
|
@JsonProperty("favourites_count")
|
||||||
private Integer favouritesCount;
|
private Integer favouritesCount;
|
||||||
@Json(name = "media_attachments")
|
@JsonProperty("media_attachments")
|
||||||
private List<Object> mediaAttachments = null;
|
private List<Object> mediaAttachments = null;
|
||||||
@Json(name = "created_at")
|
@JsonProperty("created_at")
|
||||||
private String createdAt;
|
private String createdAt;
|
||||||
@Json(name = "replies_count")
|
@JsonProperty("replies_count")
|
||||||
private Integer repliesCount;
|
private Integer repliesCount;
|
||||||
@Json(name = "language")
|
@JsonProperty("language")
|
||||||
private String language;
|
private String language;
|
||||||
@Json(name = "in_reply_to_account_id")
|
@JsonProperty("in_reply_to_account_id")
|
||||||
private Object inReplyToAccountId;
|
private Object inReplyToAccountId;
|
||||||
@Json(name = "content")
|
@JsonProperty("content")
|
||||||
private String content;
|
private String content;
|
||||||
@Json(name = "reblog")
|
@JsonProperty("reblog")
|
||||||
private Object reblog;
|
private Object reblog;
|
||||||
@Json(name = "spoiler_text")
|
@JsonProperty("spoiler_text")
|
||||||
private String spoilerText;
|
private String spoilerText;
|
||||||
@Json(name = "id")
|
@JsonProperty("id")
|
||||||
private String id;
|
private String id;
|
||||||
@Json(name = "reblogged")
|
@JsonProperty("reblogged")
|
||||||
private Boolean reblogged;
|
private Boolean reblogged;
|
||||||
@Json(name = "muted")
|
@JsonProperty("muted")
|
||||||
private Boolean muted;
|
private Boolean muted;
|
||||||
@Json(name = "emojis")
|
@JsonProperty("emojis")
|
||||||
private List<Object> emojis = null;
|
private List<Object> emojis = null;
|
||||||
@Json(name = "reblogs_count")
|
@JsonProperty("reblogs_count")
|
||||||
private Integer reblogsCount;
|
private Integer reblogsCount;
|
||||||
@Json(name = "visibility")
|
@JsonProperty("visibility")
|
||||||
private String visibility;
|
private String visibility;
|
||||||
@Json(name = "sensitive")
|
@JsonProperty("sensitive")
|
||||||
private Boolean sensitive;
|
private Boolean sensitive;
|
||||||
@Json(name = "uri")
|
@JsonProperty("uri")
|
||||||
private String uri;
|
private String uri;
|
||||||
@Json(name = "url")
|
@JsonProperty("url")
|
||||||
private String url;
|
private String url;
|
||||||
@Json(name = "tags")
|
@JsonProperty("tags")
|
||||||
private List<Object> tags = null;
|
private List<Object> tags = null;
|
||||||
@Json(name = "application")
|
@JsonProperty("application")
|
||||||
private Object application;
|
private Object application;
|
||||||
@Json(name = "favourited")
|
@JsonProperty("favourited")
|
||||||
private Boolean favourited;
|
private Boolean favourited;
|
||||||
@Json(name = "mentions")
|
@JsonProperty("mentions")
|
||||||
private List<Object> mentions = null;
|
private List<Object> mentions = null;
|
||||||
@Json(name = "account")
|
@JsonProperty("account")
|
||||||
private Account account;
|
private Account account;
|
||||||
@Json(name = "card")
|
@JsonProperty("card")
|
||||||
private Object card;
|
private Object card;
|
||||||
|
|
||||||
public Boolean getPinned() {
|
|
||||||
return pinned;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPinned(Boolean pinned) {
|
|
||||||
this.pinned = pinned;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Object getInReplyToId() {
|
|
||||||
return inReplyToId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setInReplyToId(Object inReplyToId) {
|
|
||||||
this.inReplyToId = inReplyToId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Integer getFavouritesCount() {
|
|
||||||
return favouritesCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFavouritesCount(Integer favouritesCount) {
|
|
||||||
this.favouritesCount = favouritesCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<Object> getMediaAttachments() {
|
|
||||||
return mediaAttachments;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMediaAttachments(List<Object> mediaAttachments) {
|
|
||||||
this.mediaAttachments = mediaAttachments;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getCreatedAt() {
|
|
||||||
return createdAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCreatedAt(String createdAt) {
|
|
||||||
this.createdAt = createdAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Integer getRepliesCount() {
|
|
||||||
return repliesCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setRepliesCount(Integer repliesCount) {
|
|
||||||
this.repliesCount = repliesCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getLanguage() {
|
|
||||||
return language;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setLanguage(String language) {
|
|
||||||
this.language = language;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Object getInReplyToAccountId() {
|
|
||||||
return inReplyToAccountId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setInReplyToAccountId(Object inReplyToAccountId) {
|
|
||||||
this.inReplyToAccountId = inReplyToAccountId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getContent() {
|
|
||||||
return content;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setContent(String content) {
|
|
||||||
this.content = content;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Object getReblog() {
|
|
||||||
return reblog;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setReblog(Object reblog) {
|
|
||||||
this.reblog = reblog;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getSpoilerText() {
|
|
||||||
return spoilerText;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSpoilerText(String spoilerText) {
|
|
||||||
this.spoilerText = spoilerText;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getId() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setId(String id) {
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean getReblogged() {
|
|
||||||
return reblogged;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setReblogged(Boolean reblogged) {
|
|
||||||
this.reblogged = reblogged;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean getMuted() {
|
|
||||||
return muted;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMuted(Boolean muted) {
|
|
||||||
this.muted = muted;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<Object> getEmojis() {
|
|
||||||
return emojis;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setEmojis(List<Object> emojis) {
|
|
||||||
this.emojis = emojis;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Integer getReblogsCount() {
|
|
||||||
return reblogsCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setReblogsCount(Integer reblogsCount) {
|
|
||||||
this.reblogsCount = reblogsCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getVisibility() {
|
|
||||||
return visibility;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setVisibility(String visibility) {
|
|
||||||
this.visibility = visibility;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean getSensitive() {
|
|
||||||
return sensitive;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSensitive(Boolean sensitive) {
|
|
||||||
this.sensitive = sensitive;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getUri() {
|
|
||||||
return uri;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setUri(String uri) {
|
|
||||||
this.uri = uri;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getUrl() {
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setUrl(String url) {
|
|
||||||
this.url = url;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<Object> getTags() {
|
|
||||||
return tags;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTags(List<Object> tags) {
|
|
||||||
this.tags = tags;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Object getApplication() {
|
|
||||||
return application;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setApplication(Object application) {
|
|
||||||
this.application = application;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean getFavourited() {
|
|
||||||
return favourited;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFavourited(Boolean favourited) {
|
|
||||||
this.favourited = favourited;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<Object> getMentions() {
|
|
||||||
return mentions;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMentions(List<Object> mentions) {
|
|
||||||
this.mentions = mentions;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Account getAccount() {
|
|
||||||
return account;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setAccount(Account account) {
|
|
||||||
this.account = account;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Object getCard() {
|
|
||||||
return card;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCard(Object card) {
|
|
||||||
this.card = card;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ZonedDateTime getCreationTime() {
|
public ZonedDateTime getCreationTime() {
|
||||||
String timestamp = getCreatedAt();
|
String timestamp = getCreatedAt();
|
||||||
var instant = Instant.parse(timestamp);
|
var instant = Instant.parse(timestamp);
|
||||||
|
|
|
@ -1,52 +1,40 @@
|
||||||
package ctbrec.ui.settings;
|
package ctbrec.ui.settings;
|
||||||
|
|
||||||
import static javafx.scene.control.ButtonType.*;
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.lang.reflect.Type;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import com.squareup.moshi.JsonAdapter;
|
|
||||||
import com.squareup.moshi.Moshi;
|
|
||||||
import com.squareup.moshi.Types;
|
|
||||||
|
|
||||||
import ctbrec.Config;
|
import ctbrec.Config;
|
||||||
import ctbrec.Model;
|
import ctbrec.io.json.ObjectMapperFactory;
|
||||||
import ctbrec.io.ModelJsonAdapter;
|
|
||||||
import ctbrec.sites.Site;
|
|
||||||
import ctbrec.ui.AutosizeAlert;
|
import ctbrec.ui.AutosizeAlert;
|
||||||
import ctbrec.ui.controls.Dialogs;
|
import ctbrec.ui.controls.Dialogs;
|
||||||
import javafx.geometry.Insets;
|
import javafx.geometry.Insets;
|
||||||
import javafx.scene.control.Alert.AlertType;
|
import javafx.scene.control.Alert.AlertType;
|
||||||
import javafx.scene.control.Button;
|
import javafx.scene.control.*;
|
||||||
import javafx.scene.control.ButtonType;
|
|
||||||
import javafx.scene.control.Label;
|
|
||||||
import javafx.scene.control.ListView;
|
|
||||||
import javafx.scene.control.SelectionMode;
|
|
||||||
import javafx.scene.input.KeyCode;
|
import javafx.scene.input.KeyCode;
|
||||||
import javafx.scene.input.KeyEvent;
|
import javafx.scene.input.KeyEvent;
|
||||||
import javafx.scene.layout.GridPane;
|
import javafx.scene.layout.GridPane;
|
||||||
import javafx.scene.layout.HBox;
|
import javafx.scene.layout.HBox;
|
||||||
import javafx.scene.layout.Priority;
|
import javafx.scene.layout.Priority;
|
||||||
import javafx.stage.FileChooser;
|
import javafx.stage.FileChooser;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static javafx.scene.control.ButtonType.NO;
|
||||||
|
import static javafx.scene.control.ButtonType.YES;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
public class IgnoreList extends GridPane {
|
public class IgnoreList extends GridPane {
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(IgnoreList.class);
|
|
||||||
|
|
||||||
private ListView<String> ignoreListView;
|
private ListView<String> ignoreListView;
|
||||||
|
|
||||||
private List<Site> sites;
|
private final ObjectMapper mapper = ObjectMapperFactory.getMapper();
|
||||||
|
|
||||||
public IgnoreList(List<Site> sites) {
|
public IgnoreList() {
|
||||||
this.sites = sites;
|
|
||||||
createGui();
|
createGui();
|
||||||
loadIgnoredModels();
|
loadIgnoredModels();
|
||||||
}
|
}
|
||||||
|
@ -83,16 +71,14 @@ public class IgnoreList extends GridPane {
|
||||||
|
|
||||||
private void removeSelectedModels() {
|
private void removeSelectedModels() {
|
||||||
List<String> selectedModels = ignoreListView.getSelectionModel().getSelectedItems();
|
List<String> selectedModels = ignoreListView.getSelectionModel().getSelectedItems();
|
||||||
if (selectedModels.isEmpty()) {
|
if (!selectedModels.isEmpty()) {
|
||||||
return; // NOSONAR
|
|
||||||
} else {
|
|
||||||
Config.getInstance().getSettings().ignoredModels.removeAll(selectedModels);
|
Config.getInstance().getSettings().ignoredModels.removeAll(selectedModels);
|
||||||
ignoreListView.getItems().removeAll(selectedModels);
|
ignoreListView.getItems().removeAll(selectedModels);
|
||||||
LOG.debug(Config.getInstance().getSettings().ignoredModels.toString());
|
log.debug(Config.getInstance().getSettings().ignoredModels.toString());
|
||||||
try {
|
try {
|
||||||
Config.getInstance().save();
|
Config.getInstance().save();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
LOG.warn("Couldn't save config", e);
|
log.warn("Couldn't save config", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -114,12 +100,8 @@ public class IgnoreList extends GridPane {
|
||||||
chooser.setInitialFileName("ctbrec-ignorelist.json");
|
chooser.setInitialFileName("ctbrec-ignorelist.json");
|
||||||
var file = chooser.showSaveDialog(null);
|
var file = chooser.showSaveDialog(null);
|
||||||
if (file != null) {
|
if (file != null) {
|
||||||
var moshi = new Moshi.Builder().add(Model.class, new ModelJsonAdapter(sites)).build();
|
|
||||||
Type modelListType = Types.newParameterizedType(List.class, String.class);
|
|
||||||
JsonAdapter<List<String>> adapter = moshi.adapter(modelListType);
|
|
||||||
adapter = adapter.indent(" ");
|
|
||||||
try (var out = new FileOutputStream(file)) {
|
try (var out = new FileOutputStream(file)) {
|
||||||
String json = adapter.toJson(Config.getInstance().getSettings().ignoredModels);
|
String json = mapper.writeValueAsString(Config.getInstance().getSettings().ignoredModels);
|
||||||
out.write(json.getBytes(StandardCharsets.UTF_8));
|
out.write(json.getBytes(StandardCharsets.UTF_8));
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Dialogs.showError(getScene(), "Couldn't export ignore list", e.getLocalizedMessage(), e);
|
Dialogs.showError(getScene(), "Couldn't export ignore list", e.getLocalizedMessage(), e);
|
||||||
|
@ -132,12 +114,10 @@ public class IgnoreList extends GridPane {
|
||||||
chooser.setTitle("Import ignore list");
|
chooser.setTitle("Import ignore list");
|
||||||
var file = chooser.showOpenDialog(null);
|
var file = chooser.showOpenDialog(null);
|
||||||
if (file != null) {
|
if (file != null) {
|
||||||
var moshi = new Moshi.Builder().add(Model.class, new ModelJsonAdapter(sites)).build();
|
|
||||||
Type modelListType = Types.newParameterizedType(List.class, String.class);
|
|
||||||
JsonAdapter<List<String>> adapter = moshi.adapter(modelListType);
|
|
||||||
try {
|
try {
|
||||||
byte[] fileContent = Files.readAllBytes(file.toPath());
|
String fileContent = Files.readString(file.toPath());
|
||||||
List<String> ignoredModels = adapter.fromJson(new String(fileContent, StandardCharsets.UTF_8));
|
List<String> ignoredModels = mapper.readValue(fileContent, new TypeReference<>() {
|
||||||
|
});
|
||||||
var confirmed = true;
|
var confirmed = true;
|
||||||
if (!Config.getInstance().getSettings().ignoredModels.isEmpty()) {
|
if (!Config.getInstance().getSettings().ignoredModels.isEmpty()) {
|
||||||
var msg = "This will replace the existing ignore list! Continue?";
|
var msg = "This will replace the existing ignore list! Continue?";
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package ctbrec.ui.settings;
|
package ctbrec.ui.settings;
|
||||||
|
|
||||||
import ctbrec.Config;
|
import ctbrec.Config;
|
||||||
|
import ctbrec.io.json.mapper.PostProcessorMapper;
|
||||||
import ctbrec.recorder.postprocessing.*;
|
import ctbrec.recorder.postprocessing.*;
|
||||||
import ctbrec.ui.controls.Dialogs;
|
import ctbrec.ui.controls.Dialogs;
|
||||||
import javafx.beans.property.SimpleBooleanProperty;
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
|
@ -16,11 +17,14 @@ import javafx.scene.layout.GridPane;
|
||||||
import javafx.scene.layout.Priority;
|
import javafx.scene.layout.Priority;
|
||||||
import javafx.scene.layout.VBox;
|
import javafx.scene.layout.VBox;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
|
import org.mapstruct.factory.Mappers;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public class PostProcessingStepPanel extends GridPane {
|
public class PostProcessingStepPanel extends GridPane {
|
||||||
|
|
||||||
|
@ -64,7 +68,11 @@ public class PostProcessingStepPanel extends GridPane {
|
||||||
edit = createEditButton();
|
edit = createEditButton();
|
||||||
var buttons = new VBox(5, add, edit, up, down, remove);
|
var buttons = new VBox(5, add, edit, up, down, remove);
|
||||||
|
|
||||||
stepList = FXCollections.observableList(config.getSettings().postProcessors);
|
List<PostProcessor> postProcessors = config.getSettings().postProcessors
|
||||||
|
.stream()
|
||||||
|
.map(Mappers.getMapper(PostProcessorMapper.class)::toPostProcessor)
|
||||||
|
.collect(Collectors.toList()); // NOSONAR - toList returns an unmodifiable list
|
||||||
|
stepList = FXCollections.observableList(postProcessors);
|
||||||
stepList.addListener((ListChangeListener<PostProcessor>) change -> safelySaveConfig());
|
stepList.addListener((ListChangeListener<PostProcessor>) change -> safelySaveConfig());
|
||||||
stepView = new TableView<>(stepList);
|
stepView = new TableView<>(stepList);
|
||||||
stepView.setEditable(true);
|
stepView.setEditable(true);
|
||||||
|
@ -107,6 +115,9 @@ public class PostProcessingStepPanel extends GridPane {
|
||||||
|
|
||||||
private void safelySaveConfig() {
|
private void safelySaveConfig() {
|
||||||
try {
|
try {
|
||||||
|
config.getSettings().postProcessors = stepList.stream()
|
||||||
|
.map(Mappers.getMapper(PostProcessorMapper.class)::toDto)
|
||||||
|
.collect(Collectors.toList()); // NOSONAR - toList returns an unmodifiable list
|
||||||
config.save();
|
config.save();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Dialogs.showError(getScene(), "Couldn't save configuration", "An error occurred while saving the configuration", e);
|
Dialogs.showError(getScene(), "Couldn't save configuration", "An error occurred while saving the configuration", e);
|
||||||
|
@ -114,7 +125,7 @@ public class PostProcessingStepPanel extends GridPane {
|
||||||
}
|
}
|
||||||
|
|
||||||
private Button createUpButton() {
|
private Button createUpButton() {
|
||||||
var button = createButton("\u25B4", "Move step up");
|
var button = createButton("▴", "Move step up");
|
||||||
button.setOnAction(evt -> {
|
button.setOnAction(evt -> {
|
||||||
int idx = stepView.getSelectionModel().getSelectedIndex();
|
int idx = stepView.getSelectionModel().getSelectedIndex();
|
||||||
PostProcessor selectedItem = stepView.getSelectionModel().getSelectedItem();
|
PostProcessor selectedItem = stepView.getSelectionModel().getSelectedItem();
|
||||||
|
@ -126,7 +137,7 @@ public class PostProcessingStepPanel extends GridPane {
|
||||||
}
|
}
|
||||||
|
|
||||||
private Button createDownButton() {
|
private Button createDownButton() {
|
||||||
var button = createButton("\u25BE", "Move step down");
|
var button = createButton("▾", "Move step down");
|
||||||
button.setOnAction(evt -> {
|
button.setOnAction(evt -> {
|
||||||
int idx = stepView.getSelectionModel().getSelectedIndex();
|
int idx = stepView.getSelectionModel().getSelectedIndex();
|
||||||
PostProcessor selectedItem = stepView.getSelectionModel().getSelectedItem();
|
PostProcessor selectedItem = stepView.getSelectionModel().getSelectedItem();
|
||||||
|
@ -197,7 +208,7 @@ public class PostProcessingStepPanel extends GridPane {
|
||||||
}
|
}
|
||||||
|
|
||||||
private Button createEditButton() {
|
private Button createEditButton() {
|
||||||
var button = createButton("\u270E", "Edit selected step");
|
var button = createButton("✎", "Edit selected step");
|
||||||
button.setOnAction(evt -> {
|
button.setOnAction(evt -> {
|
||||||
PostProcessor selectedItem = stepView.getSelectionModel().getSelectedItem();
|
PostProcessor selectedItem = stepView.getSelectionModel().getSelectedItem();
|
||||||
PostProcessingDialogFactory.openEditDialog(selectedItem, getScene(), stepList);
|
PostProcessingDialogFactory.openEditDialog(selectedItem, getScene(), stepList);
|
||||||
|
|
|
@ -203,7 +203,7 @@ public class SettingsTab extends Tab implements TabSelectionListener {
|
||||||
private void createGui() {
|
private void createGui() {
|
||||||
var postProcessingStepPanel = new PostProcessingStepPanel(config);
|
var postProcessingStepPanel = new PostProcessingStepPanel(config);
|
||||||
var variablesHelpButton = createHelpButton("Variables", "http://localhost:5689/docs/PostProcessing.md#variables");
|
var variablesHelpButton = createHelpButton("Variables", "http://localhost:5689/docs/PostProcessing.md#variables");
|
||||||
ignoreList = new IgnoreList(sites);
|
ignoreList = new IgnoreList();
|
||||||
List<Category> siteCategories = new ArrayList<>();
|
List<Category> siteCategories = new ArrayList<>();
|
||||||
for (Site site : sites) {
|
for (Site site : sites) {
|
||||||
ofNullable(SiteUiFactory.getUi(site)).map(SiteUI::getConfigUI).map(ConfigUI::createConfigPanel)
|
ofNullable(SiteUiFactory.getUi(site)).map(SiteUI::getConfigUI).map(ConfigUI::createConfigPanel)
|
||||||
|
|
|
@ -1,40 +1,24 @@
|
||||||
package ctbrec.ui.tabs;
|
package ctbrec.ui.tabs;
|
||||||
|
|
||||||
import static java.nio.charset.StandardCharsets.*;
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
import static java.nio.file.StandardOpenOption.*;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.lang.reflect.Type;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.time.Instant;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import com.google.common.eventbus.Subscribe;
|
import com.google.common.eventbus.Subscribe;
|
||||||
import com.squareup.moshi.JsonAdapter;
|
|
||||||
import com.squareup.moshi.Moshi;
|
|
||||||
import com.squareup.moshi.Types;
|
|
||||||
|
|
||||||
import ctbrec.Config;
|
import ctbrec.Config;
|
||||||
import ctbrec.Model;
|
import ctbrec.Model;
|
||||||
import ctbrec.StringUtil;
|
import ctbrec.StringUtil;
|
||||||
import ctbrec.event.EventBusHolder;
|
import ctbrec.event.EventBusHolder;
|
||||||
import ctbrec.io.InstantJsonAdapter;
|
import ctbrec.io.json.ObjectMapperFactory;
|
||||||
import ctbrec.io.ModelJsonAdapter;
|
|
||||||
import ctbrec.recorder.Recorder;
|
import ctbrec.recorder.Recorder;
|
||||||
import ctbrec.sites.Site;
|
import ctbrec.sites.Site;
|
||||||
|
import ctbrec.sites.SiteUtil;
|
||||||
import ctbrec.ui.ShutdownListener;
|
import ctbrec.ui.ShutdownListener;
|
||||||
import ctbrec.ui.action.PlayAction;
|
import ctbrec.ui.action.PlayAction;
|
||||||
import ctbrec.ui.controls.CustomMouseBehaviorContextMenu;
|
import ctbrec.ui.controls.CustomMouseBehaviorContextMenu;
|
||||||
import ctbrec.ui.controls.DateTimeCellFactory;
|
import ctbrec.ui.controls.DateTimeCellFactory;
|
||||||
import ctbrec.ui.controls.SearchBox;
|
import ctbrec.ui.controls.SearchBox;
|
||||||
import ctbrec.ui.event.PlayerStartedEvent;
|
import ctbrec.ui.event.PlayerStartedEvent;
|
||||||
|
import ctbrec.ui.io.json.dto.PlayerStartedEventDto;
|
||||||
|
import ctbrec.ui.io.json.mapper.PlayerStartedEventMapper;
|
||||||
import ctbrec.ui.menu.ModelMenuContributor;
|
import ctbrec.ui.menu.ModelMenuContributor;
|
||||||
import javafx.beans.property.SimpleObjectProperty;
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
import javafx.beans.property.SimpleStringProperty;
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
|
@ -42,36 +26,38 @@ import javafx.collections.FXCollections;
|
||||||
import javafx.collections.ObservableList;
|
import javafx.collections.ObservableList;
|
||||||
import javafx.geometry.Insets;
|
import javafx.geometry.Insets;
|
||||||
import javafx.scene.Cursor;
|
import javafx.scene.Cursor;
|
||||||
import javafx.scene.control.ContextMenu;
|
import javafx.scene.control.*;
|
||||||
import javafx.scene.control.MenuItem;
|
|
||||||
import javafx.scene.control.ScrollPane;
|
|
||||||
import javafx.scene.control.SelectionMode;
|
|
||||||
import javafx.scene.control.SeparatorMenuItem;
|
|
||||||
import javafx.scene.control.Tab;
|
|
||||||
import javafx.scene.control.TableCell;
|
|
||||||
import javafx.scene.control.TableColumn;
|
|
||||||
import javafx.scene.control.TableColumn.SortType;
|
import javafx.scene.control.TableColumn.SortType;
|
||||||
import javafx.scene.control.TableView;
|
import javafx.scene.input.*;
|
||||||
import javafx.scene.input.ContextMenuEvent;
|
|
||||||
import javafx.scene.input.KeyCode;
|
|
||||||
import javafx.scene.input.KeyEvent;
|
|
||||||
import javafx.scene.input.MouseButton;
|
|
||||||
import javafx.scene.input.MouseEvent;
|
|
||||||
import javafx.scene.layout.BorderPane;
|
import javafx.scene.layout.BorderPane;
|
||||||
import javafx.scene.layout.HBox;
|
import javafx.scene.layout.HBox;
|
||||||
import javafx.scene.layout.Priority;
|
import javafx.scene.layout.Priority;
|
||||||
import javafx.util.Callback;
|
import javafx.util.Callback;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.mapstruct.factory.Mappers;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||||
|
import static java.nio.file.StandardOpenOption.*;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
public class RecentlyWatchedTab extends Tab implements ShutdownListener {
|
public class RecentlyWatchedTab extends Tab implements ShutdownListener {
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(RecentlyWatchedTab.class);
|
private final ObservableList<PlayerStartedEvent> filteredModels = FXCollections.observableArrayList();
|
||||||
|
private final ObservableList<PlayerStartedEvent> observableModels = FXCollections.observableArrayList();
|
||||||
private ObservableList<PlayerStartedEvent> filteredModels = FXCollections.observableArrayList();
|
private final TableView<PlayerStartedEvent> table = new TableView<>();
|
||||||
private ObservableList<PlayerStartedEvent> observableModels = FXCollections.observableArrayList();
|
private final ReentrantLock lock = new ReentrantLock();
|
||||||
private TableView<PlayerStartedEvent> table = new TableView<>();
|
private final Recorder recorder;
|
||||||
|
private final ObjectMapper mapper = ObjectMapperFactory.getMapper();
|
||||||
private ContextMenu popup;
|
private ContextMenu popup;
|
||||||
private ReentrantLock lock = new ReentrantLock();
|
|
||||||
private Recorder recorder;
|
|
||||||
private List<Site> sites;
|
private List<Site> sites;
|
||||||
|
|
||||||
public RecentlyWatchedTab(Recorder recorder, List<Site> sites) {
|
public RecentlyWatchedTab(Recorder recorder, List<Site> sites) {
|
||||||
|
@ -91,7 +77,7 @@ public class RecentlyWatchedTab extends Tab implements ShutdownListener {
|
||||||
|
|
||||||
var filterInput = new SearchBox(false);
|
var filterInput = new SearchBox(false);
|
||||||
filterInput.setPromptText("Filter");
|
filterInput.setPromptText("Filter");
|
||||||
filterInput.textProperty().addListener( (observableValue, oldValue, newValue) -> {
|
filterInput.textProperty().addListener((observableValue, oldValue, newValue) -> {
|
||||||
String filter = filterInput.getText();
|
String filter = filterInput.getText();
|
||||||
lock.lock();
|
lock.lock();
|
||||||
try {
|
try {
|
||||||
|
@ -186,7 +172,7 @@ public class RecentlyWatchedTab extends Tab implements ShutdownListener {
|
||||||
var sb = new StringBuilder();
|
var sb = new StringBuilder();
|
||||||
for (TableColumn<PlayerStartedEvent, ?> tc : table.getColumns()) {
|
for (TableColumn<PlayerStartedEvent, ?> tc : table.getColumns()) {
|
||||||
Object cellData = tc.getCellData(i);
|
Object cellData = tc.getCellData(i);
|
||||||
if(cellData != null) {
|
if (cellData != null) {
|
||||||
var content = cellData.toString();
|
var content = cellData.toString();
|
||||||
sb.append(content).append(' ');
|
sb.append(content).append(' ');
|
||||||
}
|
}
|
||||||
|
@ -195,12 +181,12 @@ public class RecentlyWatchedTab extends Tab implements ShutdownListener {
|
||||||
|
|
||||||
var tokensMissing = false;
|
var tokensMissing = false;
|
||||||
for (String token : tokens) {
|
for (String token : tokens) {
|
||||||
if(!searchText.toLowerCase().contains(token.toLowerCase())) {
|
if (!searchText.toLowerCase().contains(token.toLowerCase())) {
|
||||||
tokensMissing = true;
|
tokensMissing = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(tokensMissing) {
|
if (tokensMissing) {
|
||||||
PlayerStartedEvent sessionState = table.getItems().get(i);
|
PlayerStartedEvent sessionState = table.getItems().get(i);
|
||||||
filteredModels.add(sessionState);
|
filteredModels.add(sessionState);
|
||||||
}
|
}
|
||||||
|
@ -221,9 +207,9 @@ public class RecentlyWatchedTab extends Tab implements ShutdownListener {
|
||||||
ContextMenu menu = new CustomMouseBehaviorContextMenu();
|
ContextMenu menu = new CustomMouseBehaviorContextMenu();
|
||||||
|
|
||||||
ModelMenuContributor.newContributor(getTabPane(), Config.getInstance(), recorder) //
|
ModelMenuContributor.newContributor(getTabPane(), Config.getInstance(), recorder) //
|
||||||
.withStartStopCallback(m -> getTabPane().setCursor(Cursor.DEFAULT)) //
|
.withStartStopCallback(m -> getTabPane().setCursor(Cursor.DEFAULT)) //
|
||||||
.afterwards(table::refresh)
|
.afterwards(table::refresh)
|
||||||
.contributeToMenu(selectedModels, menu);
|
.contributeToMenu(selectedModels, menu);
|
||||||
|
|
||||||
menu.getItems().add(new SeparatorMenuItem());
|
menu.getItems().add(new SeparatorMenuItem());
|
||||||
var delete = new MenuItem("Delete");
|
var delete = new MenuItem("Delete");
|
||||||
|
@ -267,7 +253,7 @@ public class RecentlyWatchedTab extends Tab implements ShutdownListener {
|
||||||
cell.addEventFilter(MouseEvent.MOUSE_CLICKED, event -> {
|
cell.addEventFilter(MouseEvent.MOUSE_CLICKED, event -> {
|
||||||
if (event.getButton() == MouseButton.PRIMARY && event.getClickCount() == 2) {
|
if (event.getButton() == MouseButton.PRIMARY && event.getClickCount() == 2) {
|
||||||
var selectedModel = table.getSelectionModel().getSelectedItem().getModel();
|
var selectedModel = table.getSelectionModel().getSelectedItem().getModel();
|
||||||
if(selectedModel != null) {
|
if (selectedModel != null) {
|
||||||
new PlayAction(table, selectedModel).execute();
|
new PlayAction(table, selectedModel).execute();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -277,37 +263,29 @@ public class RecentlyWatchedTab extends Tab implements ShutdownListener {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void saveHistory() throws IOException {
|
private void saveHistory() throws IOException {
|
||||||
var moshi = new Moshi.Builder()
|
String json = mapper.writeValueAsString(observableModels.stream().map(Mappers.getMapper(PlayerStartedEventMapper.class)::toDto).toList());
|
||||||
.add(Model.class, new ModelJsonAdapter(sites))
|
|
||||||
.add(Instant.class, new InstantJsonAdapter())
|
|
||||||
.build();
|
|
||||||
Type type = Types.newParameterizedType(List.class, PlayerStartedEvent.class);
|
|
||||||
JsonAdapter<List<PlayerStartedEvent>> adapter = moshi.adapter(type);
|
|
||||||
String json = adapter.indent(" ").toJson(observableModels);
|
|
||||||
var recentlyWatched = new File(Config.getInstance().getConfigDir(), "recently_watched.json");
|
var recentlyWatched = new File(Config.getInstance().getConfigDir(), "recently_watched.json");
|
||||||
LOG.debug("Saving recently watched models to {}", recentlyWatched.getAbsolutePath());
|
log.debug("Saving recently watched models to {}", recentlyWatched.getAbsolutePath());
|
||||||
Files.createDirectories(recentlyWatched.getParentFile().toPath());
|
Files.createDirectories(recentlyWatched.getParentFile().toPath());
|
||||||
Files.write(recentlyWatched.toPath(), json.getBytes(UTF_8), CREATE, WRITE, TRUNCATE_EXISTING);
|
Files.writeString(recentlyWatched.toPath(), json, CREATE, WRITE, TRUNCATE_EXISTING);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadHistory() {
|
private void loadHistory() {
|
||||||
var recentlyWatched = new File(Config.getInstance().getConfigDir(), "recently_watched.json");
|
var recentlyWatched = new File(Config.getInstance().getConfigDir(), "recently_watched.json");
|
||||||
if(!recentlyWatched.exists()) {
|
if (!recentlyWatched.exists()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG.debug("Loading recently watched models from {}", recentlyWatched.getAbsolutePath());
|
log.debug("Loading recently watched models from {}", recentlyWatched.getAbsolutePath());
|
||||||
var moshi = new Moshi.Builder()
|
|
||||||
.add(Model.class, new ModelJsonAdapter(sites))
|
|
||||||
.add(Instant.class, new InstantJsonAdapter())
|
|
||||||
.build();
|
|
||||||
Type type = Types.newParameterizedType(List.class, PlayerStartedEvent.class);
|
|
||||||
JsonAdapter<List<PlayerStartedEvent>> adapter = moshi.adapter(type);
|
|
||||||
try {
|
try {
|
||||||
List<PlayerStartedEvent> fromJson = adapter.fromJson(Files.readString(recentlyWatched.toPath(), UTF_8));
|
List<PlayerStartedEventDto> fromJson = mapper.readValue(Files.readString(recentlyWatched.toPath(), UTF_8), new TypeReference<List<PlayerStartedEventDto>>() {
|
||||||
observableModels.addAll(fromJson);
|
});
|
||||||
|
observableModels.addAll(fromJson.stream()
|
||||||
|
.map(Mappers.getMapper(PlayerStartedEventMapper.class)::toEvent)
|
||||||
|
.toList());
|
||||||
|
observableModels.forEach(evt -> SiteUtil.getSiteForModel(sites, evt.getModel()).ifPresent(evt.getModel()::setSite));
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
LOG.error("Couldn't load recently watched models", e);
|
log.error("Couldn't load recently watched models", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -316,7 +294,7 @@ public class RecentlyWatchedTab extends Tab implements ShutdownListener {
|
||||||
try {
|
try {
|
||||||
saveHistory();
|
saveHistory();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
LOG.error("Couldn't safe recently watched models", e);
|
log.error("Couldn't safe recently watched models", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,6 @@ import ctbrec.io.UrlUtil;
|
||||||
import ctbrec.notes.ModelNotesService;
|
import ctbrec.notes.ModelNotesService;
|
||||||
import ctbrec.recorder.ProgressListener;
|
import ctbrec.recorder.ProgressListener;
|
||||||
import ctbrec.recorder.Recorder;
|
import ctbrec.recorder.Recorder;
|
||||||
import ctbrec.recorder.RecordingPinnedException;
|
|
||||||
import ctbrec.recorder.download.StreamSource;
|
import ctbrec.recorder.download.StreamSource;
|
||||||
import ctbrec.recorder.postprocessing.PostProcessingContext;
|
import ctbrec.recorder.postprocessing.PostProcessingContext;
|
||||||
import ctbrec.ui.*;
|
import ctbrec.ui.*;
|
||||||
|
@ -844,7 +843,7 @@ public class RecordingsTab extends Tab implements TabSelectionListener, Shutdown
|
||||||
try {
|
try {
|
||||||
recorder.delete(r.getDelegate());
|
recorder.delete(r.getDelegate());
|
||||||
deleted.add(r);
|
deleted.add(r);
|
||||||
} catch (RecordingPinnedException | IOException | InvalidKeyException | NoSuchAlgorithmException | IllegalStateException e1) {
|
} catch (Exception e1) {
|
||||||
exceptions.add(e1);
|
exceptions.add(e1);
|
||||||
LOG.error("Error while deleting recording", e1);
|
LOG.error("Error while deleting recording", e1);
|
||||||
}
|
}
|
||||||
|
|
|
@ -273,6 +273,7 @@ public abstract class AbstractRecordedModelsTab extends Tab implements TabSelect
|
||||||
try {
|
try {
|
||||||
List<Model> models = ModelImportExport.importFrom(target, sites, config);
|
List<Model> models = ModelImportExport.importFrom(target, sites, config);
|
||||||
importModelList(models);
|
importModelList(models);
|
||||||
|
portraitCache.invalidateAll();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
String msg = "An error occurred while importing the model list";
|
String msg = "An error occurred while importing the model list";
|
||||||
Dialogs.showError(getTabPane().getScene(), "Import models", msg, e);
|
Dialogs.showError(getTabPane().getScene(), "Import models", msg, e);
|
||||||
|
|
|
@ -1,32 +1,36 @@
|
||||||
package ctbrec.ui.tabs.recorded;
|
package ctbrec.ui.tabs.recorded;
|
||||||
|
|
||||||
import com.squareup.moshi.*;
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
import com.squareup.moshi.JsonReader.Token;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import ctbrec.Config;
|
import ctbrec.Config;
|
||||||
import ctbrec.Model;
|
import ctbrec.Model;
|
||||||
import ctbrec.ModelGroup;
|
import ctbrec.ModelGroup;
|
||||||
import ctbrec.image.LocalPortraitStore;
|
import ctbrec.image.LocalPortraitStore;
|
||||||
import ctbrec.image.PortraitStore;
|
import ctbrec.image.PortraitStore;
|
||||||
import ctbrec.image.RemotePortraitStore;
|
import ctbrec.image.RemotePortraitStore;
|
||||||
import ctbrec.io.*;
|
import ctbrec.io.HttpClient;
|
||||||
|
import ctbrec.io.json.ObjectMapperFactory;
|
||||||
|
import ctbrec.io.json.dto.ModelDto;
|
||||||
|
import ctbrec.io.json.mapper.MappingException;
|
||||||
|
import ctbrec.io.json.mapper.ModelMapper;
|
||||||
import ctbrec.sites.Site;
|
import ctbrec.sites.Site;
|
||||||
import okio.Buffer;
|
import ctbrec.sites.SiteUtil;
|
||||||
import okio.Okio;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.slf4j.Logger;
|
import org.json.JSONArray;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.json.JSONObject;
|
||||||
|
import org.json.JSONWriter;
|
||||||
|
import org.mapstruct.factory.Mappers;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.time.LocalTime;
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static com.squareup.moshi.JsonReader.Token.*;
|
@Slf4j
|
||||||
|
|
||||||
public class ModelImportExport {
|
public class ModelImportExport {
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(ModelImportExport.class);
|
|
||||||
|
|
||||||
enum ExportIncludes {
|
enum ExportIncludes {
|
||||||
NOTES,
|
NOTES,
|
||||||
|
@ -34,6 +38,8 @@ public class ModelImportExport {
|
||||||
PORTRAITS
|
PORTRAITS
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final ObjectMapper mapper = ObjectMapperFactory.getMapper();
|
||||||
|
|
||||||
record ExportOptions(Set<ExportIncludes> includes, File targetFile) {
|
record ExportOptions(Set<ExportIncludes> includes, File targetFile) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,98 +47,98 @@ public class ModelImportExport {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void exportTo(List<Model> models, Set<ModelGroup> groups, Config config, ExportOptions exportOptions) throws IOException {
|
public static void exportTo(List<Model> models, Set<ModelGroup> groups, Config config, ExportOptions exportOptions) throws IOException {
|
||||||
Moshi moshi = new Moshi.Builder()
|
StringBuilder sb = new StringBuilder();
|
||||||
.add(Model.class, new ModelJsonAdapter())
|
JSONWriter writer = new JSONWriter(sb);
|
||||||
.add(File.class, new FileJsonAdapter())
|
|
||||||
.add(UUID.class, new UuidJSonAdapter())
|
|
||||||
.add(LocalTime.class, new LocalTimeJsonAdapter())
|
|
||||||
.build();
|
|
||||||
JsonAdapter<Map<String, String>> notesAdapter = moshi.adapter(Types.newParameterizedType(Map.class, String.class, String.class));
|
|
||||||
JsonAdapter<List<Model>> modelListAdapter = moshi.adapter(Types.newParameterizedType(List.class, Model.class));
|
|
||||||
JsonAdapter<Set<ModelGroup>> modelGroupAdapter = moshi.adapter(Types.newParameterizedType(Set.class, ModelGroup.class));
|
|
||||||
|
|
||||||
try (JsonWriter writer = JsonWriter.of(Okio.buffer(Okio.sink(exportOptions.targetFile())))) {
|
writer.object();
|
||||||
writer.setIndent(" ");
|
writer.key("models");
|
||||||
writer.beginObject();
|
writer.array();
|
||||||
writer.name("models");
|
var modelArray = models.stream()
|
||||||
modelListAdapter.toJson(writer, models);
|
.map(Mappers.getMapper(ModelMapper.class)::toDto)
|
||||||
if (exportOptions.includes().contains(ExportIncludes.NOTES)) {
|
.map(dto -> {
|
||||||
writer.name("notes");
|
try {
|
||||||
notesAdapter.toJson(writer, config.getSettings().modelNotes);
|
return mapper.writeValueAsString(dto);
|
||||||
}
|
} catch (JsonProcessingException e) {
|
||||||
if (exportOptions.includes().contains(ExportIncludes.GROUPS)) {
|
log.error("Error while serializing model {}", dto);
|
||||||
writer.name("groups");
|
throw new MappingException(e);
|
||||||
modelGroupAdapter.toJson(writer, groups);
|
|
||||||
}
|
|
||||||
if (exportOptions.includes().contains(ExportIncludes.PORTRAITS)) {
|
|
||||||
var portraits = config.getSettings().modelPortraits;
|
|
||||||
PortraitStore portraitLoader;
|
|
||||||
if (config.getSettings().localRecording) {
|
|
||||||
portraitLoader = new LocalPortraitStore(config);
|
|
||||||
} else {
|
|
||||||
var httpClient = new HttpClient("camrec", config) {
|
|
||||||
@Override
|
|
||||||
public boolean login() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
portraitLoader = new RemotePortraitStore(httpClient, config);
|
|
||||||
}
|
|
||||||
if (portraits != null && !portraits.isEmpty()) {
|
|
||||||
writer.name("portraits");
|
|
||||||
writer.beginArray();
|
|
||||||
for (Map.Entry<String, String> entry : config.getSettings().modelPortraits.entrySet()) {
|
|
||||||
String modelUrl = entry.getKey();
|
|
||||||
String portraitId = entry.getValue();
|
|
||||||
Optional<byte[]> portrait = portraitLoader.loadModelPortraitByModelUrl(modelUrl);
|
|
||||||
if (portrait.isPresent()) {
|
|
||||||
writer.beginObject();
|
|
||||||
writer.name("url").value(modelUrl);
|
|
||||||
writer.name("id").value(portraitId);
|
|
||||||
writer.name("data").value(Base64.getEncoder().encodeToString(portrait.get()));
|
|
||||||
writer.endObject();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
writer.endArray();
|
})
|
||||||
}
|
.collect(Collectors.joining(","));
|
||||||
}
|
sb.append(modelArray);
|
||||||
writer.endObject();
|
writer.endArray();
|
||||||
|
if (exportOptions.includes().contains(ExportIncludes.NOTES)) {
|
||||||
|
writer.key("notes");
|
||||||
|
writer.value(config.getSettings().modelNotes);
|
||||||
}
|
}
|
||||||
|
if (exportOptions.includes().contains(ExportIncludes.GROUPS)) {
|
||||||
|
writer.key("groups");
|
||||||
|
writer.array();
|
||||||
|
var groupArray = groups
|
||||||
|
.stream().map(grp -> {
|
||||||
|
try {
|
||||||
|
return mapper.writeValueAsString(grp);
|
||||||
|
} catch (JsonProcessingException e) {
|
||||||
|
log.error("Error while serializing model group {}", grp);
|
||||||
|
throw new MappingException(e);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect(Collectors.joining(","));
|
||||||
|
sb.append(groupArray);
|
||||||
|
writer.endArray();
|
||||||
|
}
|
||||||
|
if (exportOptions.includes().contains(ExportIncludes.PORTRAITS)) {
|
||||||
|
var portraits = config.getSettings().modelPortraits;
|
||||||
|
PortraitStore portraitLoader;
|
||||||
|
if (config.getSettings().localRecording) {
|
||||||
|
portraitLoader = new LocalPortraitStore(config);
|
||||||
|
} else {
|
||||||
|
var httpClient = new HttpClient("camrec", config) {
|
||||||
|
@Override
|
||||||
|
public boolean login() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
portraitLoader = new RemotePortraitStore(httpClient, config);
|
||||||
|
}
|
||||||
|
if (portraits != null && !portraits.isEmpty()) {
|
||||||
|
writer.key("portraits");
|
||||||
|
writer.array();
|
||||||
|
for (Map.Entry<String, String> entry : config.getSettings().modelPortraits.entrySet()) {
|
||||||
|
String modelUrl = entry.getKey();
|
||||||
|
String portraitId = entry.getValue();
|
||||||
|
Optional<byte[]> portrait = portraitLoader.loadModelPortraitByModelUrl(modelUrl);
|
||||||
|
if (portrait.isPresent()) {
|
||||||
|
writer.object();
|
||||||
|
writer.key("url").value(modelUrl);
|
||||||
|
writer.key("id").value(portraitId);
|
||||||
|
writer.key("data").value(Base64.getEncoder().encodeToString(portrait.get()));
|
||||||
|
writer.endObject();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
writer.endArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
writer.endObject();
|
||||||
|
Files.writeString(exportOptions.targetFile().toPath(), new JSONObject(sb.toString()).toString(2));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<Model> importFrom(File target, List<Site> sites, Config config) throws IOException {
|
public static List<Model> importFrom(File target, List<Site> sites, Config config) throws IOException {
|
||||||
Moshi moshi = new Moshi.Builder()
|
JSONObject json = new JSONObject(Files.readString(target.toPath(), StandardCharsets.UTF_8));
|
||||||
.add(Model.class, new ModelJsonAdapter(sites))
|
List<Model> models = readModels(json.getJSONArray("models"));
|
||||||
.add(File.class, new FileJsonAdapter())
|
models.forEach(m -> SiteUtil.getSiteForModel(sites, m).ifPresent(m::setSite));
|
||||||
.add(UUID.class, new UuidJSonAdapter())
|
if (json.has("notes")) {
|
||||||
.add(LocalTime.class, new LocalTimeJsonAdapter())
|
importNotes(json.getJSONObject("notes"), config);
|
||||||
.build();
|
}
|
||||||
JsonAdapter<Model> modelAdapter = moshi.adapter(Model.class);
|
if (json.has("groups")) {
|
||||||
JsonAdapter<Map<String, String>> notesAdapter = moshi.adapter(Types.newParameterizedType(Map.class, String.class, String.class));
|
importGroups(json.getJSONArray("groups"), config);
|
||||||
JsonAdapter<Set<ModelGroup>> modelGroupAdapter = moshi.adapter(Types.newParameterizedType(Set.class, ModelGroup.class));
|
}
|
||||||
|
if (json.has("portraits")) {
|
||||||
List<Model> models = null;
|
importPortraits(json.getJSONArray("portraits"), config);
|
||||||
String json = Files.readString(target.toPath(), StandardCharsets.UTF_8);
|
|
||||||
try (Buffer buffer = new Buffer()) {
|
|
||||||
JsonReader reader = JsonReader.of(buffer.writeUtf8(json));
|
|
||||||
reader.setLenient(true);
|
|
||||||
reader.beginObject();
|
|
||||||
while (reader.hasNext()) {
|
|
||||||
var next = reader.nextName();
|
|
||||||
switch (next) {
|
|
||||||
case "models" -> models = readModels(modelAdapter, reader);
|
|
||||||
case "notes" -> importNotes(reader, notesAdapter, config);
|
|
||||||
case "groups" -> importGroups(reader, modelGroupAdapter, config);
|
|
||||||
case "portraits" -> importPortraits(reader, config);
|
|
||||||
default -> LOG.warn("Element {} unknown", next);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
reader.endObject();
|
|
||||||
}
|
}
|
||||||
return models;
|
return models;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void importPortraits(JsonReader reader, Config config) throws IOException {
|
private static void importPortraits(JSONArray portraits, Config config) throws IOException {
|
||||||
PortraitStore portraitStore;
|
PortraitStore portraitStore;
|
||||||
if (config.getSettings().localRecording) {
|
if (config.getSettings().localRecording) {
|
||||||
portraitStore = new LocalPortraitStore(config);
|
portraitStore = new LocalPortraitStore(config);
|
||||||
|
@ -145,89 +151,50 @@ public class ModelImportExport {
|
||||||
};
|
};
|
||||||
portraitStore = new RemotePortraitStore(httpClient, config);
|
portraitStore = new RemotePortraitStore(httpClient, config);
|
||||||
}
|
}
|
||||||
reader.beginArray();
|
for (int i = 0; i < portraits.length(); i++) {
|
||||||
while (reader.hasNext()) {
|
JSONObject portrait = portraits.getJSONObject(i);
|
||||||
reader.beginObject();
|
String url = portrait.getString("url");
|
||||||
String url = null;
|
String id = portrait.getString("id");
|
||||||
String id = null;
|
String dataBase64 = portrait.getString("data");
|
||||||
String dataBase64 = null;
|
portraitStore.writePortrait(url, Base64.getDecoder().decode(dataBase64));
|
||||||
while (reader.hasNext()) {
|
|
||||||
var name = reader.nextName();
|
|
||||||
switch (name) {
|
|
||||||
case "url" -> url = reader.nextString();
|
|
||||||
case "id" -> id = reader.nextString();
|
|
||||||
case "data" -> dataBase64 = reader.nextString();
|
|
||||||
default -> {
|
|
||||||
LOG.warn("Portrait element {} unknown", name);
|
|
||||||
reader.skipValue();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
portraitStore.writePortrait(id, Base64.getDecoder().decode(dataBase64));
|
|
||||||
config.getSettings().modelPortraits.put(url, id);
|
|
||||||
reader.endObject();
|
|
||||||
}
|
|
||||||
reader.endArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static void importGroups(JsonReader reader, JsonAdapter<Set<ModelGroup>> modelGroupAdapter, Config config) throws IOException {
|
|
||||||
var groups = modelGroupAdapter.fromJson(reader);
|
|
||||||
if (groups != null) {
|
|
||||||
config.getSettings().modelGroups.addAll(groups);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void importNotes(JsonReader reader, JsonAdapter<Map<String, String>> notesAdapter, Config config) throws IOException {
|
|
||||||
var notes = notesAdapter.fromJson(reader);
|
|
||||||
if (notes != null) {
|
|
||||||
config.getSettings().modelNotes.putAll(notes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static List<Model> readModels(JsonAdapter<Model> modelAdapter, JsonReader reader) throws IOException {
|
private static void importGroups(JSONArray groups, Config config) {
|
||||||
List<Model> result = new LinkedList<>();
|
for (int i = 0; i < groups.length(); i++) {
|
||||||
reader.beginArray();
|
JSONObject group = groups.getJSONObject(i);
|
||||||
while (reader.hasNext()) {
|
|
||||||
try {
|
try {
|
||||||
Token token = reader.peek();
|
ModelGroup modelGroup = mapper.readValue(group.toString(), ModelGroup.class);
|
||||||
if (token == BEGIN_OBJECT) {
|
config.getSettings().modelGroups.add(modelGroup);
|
||||||
Model model = modelAdapter.fromJson(reader);
|
} catch (JsonProcessingException e) {
|
||||||
result.add(model);
|
log.error("Error while deserializing model group {}", group);
|
||||||
} else {
|
}
|
||||||
skipToNextModel(reader);
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
|
||||||
LOG.error("Couldn't parse model json", e);
|
private static void importNotes(JSONObject notes, Config config) {
|
||||||
|
var modelNotes = new HashMap<String, String>();
|
||||||
|
JSONArray urls = notes.names();
|
||||||
|
for (int i = 0; i < urls.length(); i++) {
|
||||||
|
String url = urls.getString(i);
|
||||||
|
String note = notes.getString(url);
|
||||||
|
modelNotes.put(url, note);
|
||||||
|
}
|
||||||
|
config.getSettings().modelNotes.putAll(modelNotes);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<Model> readModels(JSONArray models) {
|
||||||
|
List<Model> result = new LinkedList<>();
|
||||||
|
for (int i = 0; i < models.length(); i++) {
|
||||||
|
JSONObject model = models.getJSONObject(i);
|
||||||
|
try {
|
||||||
|
ModelDto dto = mapper.readValue(model.toString(), ModelDto.class);
|
||||||
|
result.add(Mappers.getMapper(ModelMapper.class).toModel(dto));
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Error while deserializing model {}", model.toString(2), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
reader.endArray();
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void skipToNextModel(JsonReader reader) throws IOException {
|
|
||||||
while (true) {
|
|
||||||
Token token = reader.peek();
|
|
||||||
if (token == BEGIN_OBJECT) {
|
|
||||||
reader.beginObject();
|
|
||||||
} else if (token == END_OBJECT) {
|
|
||||||
reader.endObject();
|
|
||||||
if (reader.getPath().matches("\\$\\.models\\[\\d+]")) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else if (token == NAME) {
|
|
||||||
reader.skipName();
|
|
||||||
Token next = reader.peek();
|
|
||||||
if (List.of(NULL, NUMBER, STRING, BOOLEAN).contains(next)) {
|
|
||||||
reader.skipValue();
|
|
||||||
}
|
|
||||||
} else if (token == BEGIN_ARRAY) {
|
|
||||||
reader.beginArray();
|
|
||||||
} else if (token == END_ARRAY) {
|
|
||||||
reader.endArray();
|
|
||||||
} else if (List.of(NULL, NUMBER, STRING, BOOLEAN).contains(token)) {
|
|
||||||
reader.skipValue();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,14 +29,22 @@
|
||||||
<groupId>com.squareup.okhttp3</groupId>
|
<groupId>com.squareup.okhttp3</groupId>
|
||||||
<artifactId>okhttp</artifactId>
|
<artifactId>okhttp</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>com.squareup.moshi</groupId>
|
|
||||||
<artifactId>moshi</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.json</groupId>
|
<groupId>org.json</groupId>
|
||||||
<artifactId>json</artifactId>
|
<artifactId>json</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.fasterxml.jackson.core</groupId>
|
||||||
|
<artifactId>jackson-databind</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||||
|
<artifactId>jackson-datatype-jsr310</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.mapstruct</groupId>
|
||||||
|
<artifactId>mapstruct</artifactId>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.iheartradio.m3u8</groupId>
|
<groupId>com.iheartradio.m3u8</groupId>
|
||||||
<artifactId>open-m3u8</artifactId>
|
<artifactId>open-m3u8</artifactId>
|
||||||
|
@ -96,6 +104,32 @@
|
||||||
</execution>
|
</execution>
|
||||||
</executions>
|
</executions>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<version>3.8.1</version>
|
||||||
|
<configuration>
|
||||||
|
<source>${maven.compiler.source}</source>
|
||||||
|
<target>${maven.compiler.target}</target>
|
||||||
|
<annotationProcessorPaths>
|
||||||
|
<path>
|
||||||
|
<groupId>org.mapstruct</groupId>
|
||||||
|
<artifactId>mapstruct-processor</artifactId>
|
||||||
|
<version>${org.mapstruct.version}</version>
|
||||||
|
</path>
|
||||||
|
<path>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<version>${lombok.version}</version>
|
||||||
|
</path>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok-mapstruct-binding</artifactId>
|
||||||
|
<version>0.2.0</version>
|
||||||
|
</dependency>
|
||||||
|
</annotationProcessorPaths>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</build>
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package ctbrec;
|
package ctbrec;
|
||||||
|
|
||||||
import com.squareup.moshi.JsonReader;
|
|
||||||
import com.squareup.moshi.JsonWriter;
|
|
||||||
import ctbrec.recorder.download.HttpHeaderFactory;
|
import ctbrec.recorder.download.HttpHeaderFactory;
|
||||||
import ctbrec.recorder.download.HttpHeaderFactoryImpl;
|
import ctbrec.recorder.download.HttpHeaderFactoryImpl;
|
||||||
import ctbrec.recorder.download.RecordingProcess;
|
import ctbrec.recorder.download.RecordingProcess;
|
||||||
|
@ -14,10 +13,7 @@ import okhttp3.Response;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
|
|
||||||
import static ctbrec.io.HttpConstants.USER_AGENT;
|
import static ctbrec.io.HttpConstants.USER_AGENT;
|
||||||
|
@ -32,7 +28,7 @@ public abstract class AbstractModel implements Model {
|
||||||
private String description;
|
private String description;
|
||||||
private List<String> tags = new ArrayList<>();
|
private List<String> tags = new ArrayList<>();
|
||||||
private int streamUrlIndex = -1;
|
private int streamUrlIndex = -1;
|
||||||
private int priority = -1;
|
private int priority = new Settings().defaultPriority;
|
||||||
private boolean suspended = false;
|
private boolean suspended = false;
|
||||||
private boolean markedForLaterRecording = false;
|
private boolean markedForLaterRecording = false;
|
||||||
protected transient Site site;
|
protected transient Site site;
|
||||||
|
@ -129,12 +125,12 @@ public abstract class AbstractModel implements Model {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void readSiteSpecificData(JsonReader reader) throws IOException {
|
public void readSiteSpecificData(Map<String, String> data) {
|
||||||
// noop default implementation, can be overriden by concrete models
|
// noop default implementation, can be overriden by concrete models
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void writeSiteSpecificData(JsonWriter writer) throws IOException {
|
public void writeSiteSpecificData(Map<String, String> data) {
|
||||||
// noop default implementation, can be overriden by concrete models
|
// noop default implementation, can be overriden by concrete models
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -222,9 +218,6 @@ public abstract class AbstractModel implements Model {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getPriority() {
|
public int getPriority() {
|
||||||
if (priority == -1) {
|
|
||||||
priority = Config.getInstance().getSettings().defaultPriority;
|
|
||||||
}
|
|
||||||
return priority;
|
return priority;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,34 +1,30 @@
|
||||||
package ctbrec;
|
package ctbrec;
|
||||||
|
|
||||||
import com.squareup.moshi.JsonAdapter;
|
|
||||||
import com.squareup.moshi.Moshi;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import ctbrec.io.*;
|
import ctbrec.io.IoUtils;
|
||||||
import ctbrec.recorder.postprocessing.PostProcessor;
|
import ctbrec.io.json.ObjectMapperFactory;
|
||||||
import ctbrec.sites.Site;
|
import ctbrec.sites.Site;
|
||||||
import ctbrec.sites.chaturbate.ChaturbateModel;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.io.FileUtils;
|
import org.apache.commons.io.FileUtils;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.LocalTime;
|
|
||||||
import java.time.ZoneId;
|
import java.time.ZoneId;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.time.format.FormatStyle;
|
import java.time.format.FormatStyle;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||||
import static java.nio.file.StandardOpenOption.*;
|
import static java.nio.file.StandardOpenOption.*;
|
||||||
|
|
||||||
// TODO don't use singleton pattern
|
// TODO don't use singleton pattern
|
||||||
|
@Slf4j
|
||||||
public class Config {
|
public class Config {
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(Config.class);
|
|
||||||
private static final String SYSPROP_CONFIG_DIR = "ctbrec.config.dir";
|
private static final String SYSPROP_CONFIG_DIR = "ctbrec.config.dir";
|
||||||
private static final String V_4_7_5 = "4.7.5";
|
private static final String V_4_7_5 = "4.7.5";
|
||||||
|
|
||||||
|
@ -45,6 +41,8 @@ public class Config {
|
||||||
private boolean savingDisabled = false;
|
private boolean savingDisabled = false;
|
||||||
public static final String RECORDING_DATE_FORMAT = "yyyy-MM-dd_HH-mm-ss_SSS";
|
public static final String RECORDING_DATE_FORMAT = "yyyy-MM-dd_HH-mm-ss_SSS";
|
||||||
|
|
||||||
|
private final ObjectMapper mapper = ObjectMapperFactory.getMapper();
|
||||||
|
|
||||||
public Config(List<Site> sites) throws IOException {
|
public Config(List<Site> sites) throws IOException {
|
||||||
this.sites = sites;
|
this.sites = sites;
|
||||||
|
|
||||||
|
@ -70,7 +68,7 @@ public class Config {
|
||||||
File src = currentConfigDir;
|
File src = currentConfigDir;
|
||||||
if (src.exists()) {
|
if (src.exists()) {
|
||||||
File target = new File(src.getParentFile(), src.getName() + "_backup_" + dateTimeFormatter.format(LocalDateTime.now()));
|
File target = new File(src.getParentFile(), src.getName() + "_backup_" + dateTimeFormatter.format(LocalDateTime.now()));
|
||||||
LOG.info("Creating a backup of {} the config in {}", src, target);
|
log.info("Creating a backup of the config {} in {}", src, target);
|
||||||
FileUtils.copyDirectory(src, target, pathname -> !(pathname.toString().contains("minimal-browser") && pathname.toString().contains("Cache")), true);
|
FileUtils.copyDirectory(src, target, pathname -> !(pathname.toString().contains("minimal-browser") && pathname.toString().contains("Cache")), true);
|
||||||
deleteOldBackups(currentConfigDir);
|
deleteOldBackups(currentConfigDir);
|
||||||
}
|
}
|
||||||
|
@ -83,10 +81,10 @@ public class Config {
|
||||||
for (int i = 0; i < backupDirectories.length - 5; i++) {
|
for (int i = 0; i < backupDirectories.length - 5; i++) {
|
||||||
File dirToDelete = backupDirectories[i];
|
File dirToDelete = backupDirectories[i];
|
||||||
try {
|
try {
|
||||||
LOG.info("Delete old config backup {}", dirToDelete);
|
log.info("Delete old config backup {}", dirToDelete);
|
||||||
IoUtils.deleteDirectory(dirToDelete);
|
IoUtils.deleteDirectory(dirToDelete);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
LOG.error("Couldn't delete old config backup {}. You might have to delete it manually.", dirToDelete, e);
|
log.error("Couldn't delete old config backup {}. You might have to delete it manually.", dirToDelete, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -118,8 +116,8 @@ public class Config {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!Objects.equals(previousVersion, currentVersion)) {
|
if (!Objects.equals(previousVersion, currentVersion)) {
|
||||||
LOG.debug("Version update {} -> {}", previousVersion, currentVersion);
|
log.debug("Version update {} -> {}", previousVersion, currentVersion);
|
||||||
LOG.debug("Copying config from {} to {}", src, target);
|
log.debug("Copying config from {} to {}", src, target);
|
||||||
FileUtils.copyDirectory(src, target, pathname -> !(pathname.toString().contains("minimal-browser") && pathname.toString().contains("Cache")), true);
|
FileUtils.copyDirectory(src, target, pathname -> !(pathname.toString().contains("minimal-browser") && pathname.toString().contains("Cache")), true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -138,28 +136,20 @@ public class Config {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void load() throws IOException {
|
private void load() throws IOException {
|
||||||
Moshi moshi = new Moshi.Builder()
|
|
||||||
.add(Model.class, new ModelJsonAdapter(sites))
|
|
||||||
.add(PostProcessor.class, new PostProcessorJsonAdapter())
|
|
||||||
.add(File.class, new FileJsonAdapter())
|
|
||||||
.add(UUID.class, new UuidJSonAdapter())
|
|
||||||
.add(LocalTime.class, new LocalTimeJsonAdapter())
|
|
||||||
.build();
|
|
||||||
JsonAdapter<Settings> adapter = moshi.adapter(Settings.class).lenient();
|
|
||||||
File configFile = new File(configDir, filename);
|
File configFile = new File(configDir, filename);
|
||||||
LOG.info("Loading config from {}", configFile.getAbsolutePath());
|
log.info("Loading config from {}", configFile.getAbsolutePath());
|
||||||
if (configFile.exists()) {
|
if (configFile.exists()) {
|
||||||
try {
|
try {
|
||||||
byte[] fileContent = Files.readAllBytes(configFile.toPath());
|
byte[] fileContent = Files.readAllBytes(configFile.toPath());
|
||||||
if (fileContent[0] == -17 && fileContent[1] == -69 && fileContent[2] == -65) {
|
if (fileContent[0] == -17 && fileContent[1] == -69 && fileContent[2] == -65) {
|
||||||
// found BOM (byte order mark)
|
// found BOM (byte order mark)
|
||||||
LOG.debug("Removing BOM from config file");
|
log.debug("Removing BOM from config file");
|
||||||
fileContent[0] = ' ';
|
fileContent[0] = ' ';
|
||||||
fileContent[1] = ' ';
|
fileContent[1] = ' ';
|
||||||
fileContent[2] = ' ';
|
fileContent[2] = ' ';
|
||||||
}
|
}
|
||||||
String json = new String(fileContent, UTF_8).trim();
|
String json = new String(fileContent, UTF_8).trim();
|
||||||
settings = Objects.requireNonNull(adapter.fromJson(json));
|
settings = Objects.requireNonNull(mapper.readValue(json, Settings.class));
|
||||||
settings.httpTimeout = Math.max(settings.httpTimeout, 10_000);
|
settings.httpTimeout = Math.max(settings.httpTimeout, 10_000);
|
||||||
if (settings.recordingsDir.endsWith("/")) {
|
if (settings.recordingsDir.endsWith("/")) {
|
||||||
settings.recordingsDir = settings.recordingsDir.substring(0, settings.recordingsDir.length() - 1);
|
settings.recordingsDir = settings.recordingsDir.substring(0, settings.recordingsDir.length() - 1);
|
||||||
|
@ -172,7 +162,7 @@ public class Config {
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
LOG.error("Config file does not exist. Falling back to default values.");
|
log.error("Config file does not exist. Falling back to default values.");
|
||||||
settings = OS.getDefaultSettings();
|
settings = OS.getDefaultSettings();
|
||||||
}
|
}
|
||||||
for (Site site : sites) {
|
for (Site site : sites) {
|
||||||
|
@ -183,66 +173,6 @@ public class Config {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void migrateOldSettings() {
|
private void migrateOldSettings() {
|
||||||
// 5.0.0
|
|
||||||
convertChaturbateModelNamesToLowerCase();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void convertChaturbateModelNamesToLowerCase() {
|
|
||||||
final String CTB = "chaturbate.com";
|
|
||||||
|
|
||||||
// convert model notes
|
|
||||||
Map<String, String> convertedModelNotes = new HashMap<>();
|
|
||||||
getSettings().modelNotes.forEach((key, value) -> {
|
|
||||||
if (key.contains(CTB)) {
|
|
||||||
convertedModelNotes.put(key.toLowerCase(), value);
|
|
||||||
} else {
|
|
||||||
convertedModelNotes.put(key, value);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
getSettings().modelNotes.clear();
|
|
||||||
getSettings().modelNotes.putAll(convertedModelNotes);
|
|
||||||
|
|
||||||
// convert model portraits
|
|
||||||
Map<String, String> convertedModelPortraits = new HashMap<>();
|
|
||||||
getSettings().modelPortraits.forEach((key, value) -> {
|
|
||||||
if (key.contains(CTB)) {
|
|
||||||
convertedModelPortraits.put(key.toLowerCase(), value);
|
|
||||||
} else {
|
|
||||||
convertedModelPortraits.put(key, value);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
getSettings().modelPortraits.clear();
|
|
||||||
getSettings().modelPortraits.putAll(convertedModelPortraits);
|
|
||||||
|
|
||||||
// convert model groups
|
|
||||||
getSettings().modelGroups.forEach(mg -> mg.setModelUrls(mg.getModelUrls().stream()
|
|
||||||
.filter(Objects::nonNull)
|
|
||||||
.map(url -> url.contains(CTB) ? url.toLowerCase() : url)
|
|
||||||
.collect(Collectors.toList()))); // NOSONAR - has to be mutable
|
|
||||||
|
|
||||||
// convert ignored models
|
|
||||||
getSettings().ignoredModels = getSettings().ignoredModels.stream()
|
|
||||||
.filter(Objects::nonNull)
|
|
||||||
.map(url -> url.contains(CTB) ? url.toLowerCase() : url)
|
|
||||||
.collect(Collectors.toList()); // NOSONAR - has to be mutable
|
|
||||||
|
|
||||||
// change the model objects
|
|
||||||
getSettings().models.stream()
|
|
||||||
.filter(ChaturbateModel.class::isInstance)
|
|
||||||
.filter(m -> m.getUrl() != null)
|
|
||||||
.forEach(m -> {
|
|
||||||
m.setDisplayName(m.getName());
|
|
||||||
m.setName(m.getName().toLowerCase());
|
|
||||||
m.setUrl(m.getUrl().toLowerCase());
|
|
||||||
});
|
|
||||||
getSettings().recordLater.stream()
|
|
||||||
.filter(ChaturbateModel.class::isInstance)
|
|
||||||
.filter(m -> m.getUrl() != null)
|
|
||||||
.forEach(m -> {
|
|
||||||
m.setDisplayName(m.getName());
|
|
||||||
m.setName(m.getName().toLowerCase());
|
|
||||||
m.setUrl(m.getUrl().toLowerCase());
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static synchronized void init(List<Site> sites) throws IOException {
|
public static synchronized void init(List<Site> sites) throws IOException {
|
||||||
|
@ -267,17 +197,9 @@ public class Config {
|
||||||
if (savingDisabled) {
|
if (savingDisabled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Moshi moshi = new Moshi.Builder()
|
String json = mapper.writeValueAsString(settings);
|
||||||
.add(Model.class, new ModelJsonAdapter())
|
|
||||||
.add(PostProcessor.class, new PostProcessorJsonAdapter())
|
|
||||||
.add(File.class, new FileJsonAdapter())
|
|
||||||
.add(UUID.class, new UuidJSonAdapter())
|
|
||||||
.add(LocalTime.class, new LocalTimeJsonAdapter())
|
|
||||||
.build();
|
|
||||||
JsonAdapter<Settings> adapter = moshi.adapter(Settings.class).indent(" ");
|
|
||||||
String json = adapter.toJson(settings);
|
|
||||||
File configFile = new File(configDir, filename);
|
File configFile = new File(configDir, filename);
|
||||||
LOG.debug("Saving config to {}", configFile.getAbsolutePath());
|
log.debug("Saving config to {}", configFile.getAbsolutePath());
|
||||||
Files.createDirectories(configDir.toPath());
|
Files.createDirectories(configDir.toPath());
|
||||||
Files.writeString(configFile.toPath(), json, CREATE, WRITE, TRUNCATE_EXISTING);
|
Files.writeString(configFile.toPath(), json, CREATE, WRITE, TRUNCATE_EXISTING);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,6 @@ package ctbrec;
|
||||||
|
|
||||||
import com.iheartradio.m3u8.ParseException;
|
import com.iheartradio.m3u8.ParseException;
|
||||||
import com.iheartradio.m3u8.PlaylistException;
|
import com.iheartradio.m3u8.PlaylistException;
|
||||||
import com.squareup.moshi.JsonReader;
|
|
||||||
import com.squareup.moshi.JsonWriter;
|
|
||||||
import ctbrec.recorder.download.HttpHeaderFactory;
|
import ctbrec.recorder.download.HttpHeaderFactory;
|
||||||
import ctbrec.recorder.download.RecordingProcess;
|
import ctbrec.recorder.download.RecordingProcess;
|
||||||
import ctbrec.recorder.download.StreamSource;
|
import ctbrec.recorder.download.StreamSource;
|
||||||
|
@ -14,6 +12,7 @@ import java.io.IOException;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
|
|
||||||
public interface Model extends Comparable<Model>, Serializable {
|
public interface Model extends Comparable<Model>, Serializable {
|
||||||
|
@ -116,9 +115,9 @@ public interface Model extends Comparable<Model>, Serializable {
|
||||||
|
|
||||||
Site getSite();
|
Site getSite();
|
||||||
|
|
||||||
void writeSiteSpecificData(JsonWriter writer) throws IOException;
|
void writeSiteSpecificData(Map<String, String> data);
|
||||||
|
|
||||||
void readSiteSpecificData(JsonReader reader) throws IOException;
|
void readSiteSpecificData(Map<String, String> data);
|
||||||
|
|
||||||
boolean isSuspended();
|
boolean isSuspended();
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ import ctbrec.io.IoUtils;
|
||||||
import ctbrec.recorder.download.RecordingProcess;
|
import ctbrec.recorder.download.RecordingProcess;
|
||||||
import ctbrec.recorder.download.StreamSource;
|
import ctbrec.recorder.download.StreamSource;
|
||||||
import ctbrec.recorder.download.VideoLengthDetector;
|
import ctbrec.recorder.download.VideoLengthDetector;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
@ -28,6 +29,7 @@ import static ctbrec.Recording.State.*;
|
||||||
import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE;
|
import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
|
@NoArgsConstructor
|
||||||
public class Recording implements Serializable {
|
public class Recording implements Serializable {
|
||||||
|
|
||||||
private String id;
|
private String id;
|
||||||
|
@ -48,7 +50,11 @@ public class Recording implements Serializable {
|
||||||
private File postProcessedFile = null;
|
private File postProcessedFile = null;
|
||||||
private int selectedResolution = -1;
|
private int selectedResolution = -1;
|
||||||
private long lastSizeUpdate = 0;
|
private long lastSizeUpdate = 0;
|
||||||
|
private String recordingsDir;
|
||||||
|
|
||||||
|
public Recording(String recordingsDir) {
|
||||||
|
this.recordingsDir = recordingsDir;
|
||||||
|
}
|
||||||
|
|
||||||
public enum State {
|
public enum State {
|
||||||
RECORDING("recording"),
|
RECORDING("recording"),
|
||||||
|
@ -117,7 +123,6 @@ public class Recording implements Serializable {
|
||||||
|
|
||||||
public File getAbsoluteFile() {
|
public File getAbsoluteFile() {
|
||||||
if (absoluteFile == null) {
|
if (absoluteFile == null) {
|
||||||
String recordingsDir = Config.getInstance().getSettings().recordingsDir;
|
|
||||||
File recordingsFile = new File(recordingsDir, path);
|
File recordingsFile = new File(recordingsDir, path);
|
||||||
absoluteFile = recordingsFile;
|
absoluteFile = recordingsFile;
|
||||||
}
|
}
|
||||||
|
@ -140,6 +145,9 @@ public class Recording implements Serializable {
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getSizeInByte() {
|
public long getSizeInByte() {
|
||||||
|
if (sizeInByte == -1) {
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
return sizeInByte;
|
return sizeInByte;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -211,6 +219,10 @@ public class Recording implements Serializable {
|
||||||
return selectedResolution;
|
return selectedResolution;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setSelectedResolution(int selectedResolution) {
|
||||||
|
this.selectedResolution = selectedResolution;
|
||||||
|
}
|
||||||
|
|
||||||
public Duration getLength() {
|
public Duration getLength() {
|
||||||
File ppFile = getPostProcessedFile();
|
File ppFile = getPostProcessedFile();
|
||||||
if (ppFile.isDirectory()) {
|
if (ppFile.isDirectory()) {
|
||||||
|
|
|
@ -7,6 +7,8 @@ import java.io.IOException;
|
||||||
import java.nio.file.*;
|
import java.nio.file.*;
|
||||||
import java.nio.file.attribute.BasicFileAttributes;
|
import java.nio.file.attribute.BasicFileAttributes;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.concurrent.locks.Lock;
|
||||||
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
|
||||||
import static java.nio.file.LinkOption.NOFOLLOW_LINKS;
|
import static java.nio.file.LinkOption.NOFOLLOW_LINKS;
|
||||||
import static java.nio.file.StandardWatchEventKinds.*;
|
import static java.nio.file.StandardWatchEventKinds.*;
|
||||||
|
@ -19,6 +21,7 @@ public class RecordingSizeMonitor {
|
||||||
protected final Map<WatchKey, Recording> recordingByKey;
|
protected final Map<WatchKey, Recording> recordingByKey;
|
||||||
protected final Map<Recording, List<WatchKey>> keysByRecording;
|
protected final Map<Recording, List<WatchKey>> keysByRecording;
|
||||||
protected final Set<Path> registeredPaths;
|
protected final Set<Path> registeredPaths;
|
||||||
|
private final Lock lock = new ReentrantLock();
|
||||||
|
|
||||||
public RecordingSizeMonitor() throws IOException {
|
public RecordingSizeMonitor() throws IOException {
|
||||||
this.service = FileSystems.getDefault().newWatchService();
|
this.service = FileSystems.getDefault().newWatchService();
|
||||||
|
@ -54,13 +57,18 @@ public class RecordingSizeMonitor {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void register(Path path, Recording rec) throws IOException {
|
private synchronized void register(Path path, Recording rec) throws IOException {
|
||||||
if (!registeredPaths.contains(path)) {
|
lock.lock();
|
||||||
WatchKey key = path.register(service, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
|
try {
|
||||||
keys.put(key, path);
|
if (!registeredPaths.contains(path)) {
|
||||||
recordingByKey.put(key, rec);
|
WatchKey key = path.register(service, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
|
||||||
keysByRecording.computeIfAbsent(rec, r -> new ArrayList<>()).add(key);
|
keys.put(key, path);
|
||||||
registeredPaths.add(path);
|
recordingByKey.put(key, rec);
|
||||||
|
keysByRecording.computeIfAbsent(rec, r -> new ArrayList<>()).add(key);
|
||||||
|
registeredPaths.add(path);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
lock.unlock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,15 +83,20 @@ public class RecordingSizeMonitor {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void uninstall(Recording rec) {
|
public void uninstall(Recording rec) {
|
||||||
List<WatchKey> keysForRecording = this.keysByRecording.getOrDefault(rec, Collections.emptyList());
|
lock.lock();
|
||||||
keysForRecording.forEach(key -> {
|
try {
|
||||||
Path path = keys.get(key);
|
List<WatchKey> keysForRecording = this.keysByRecording.getOrDefault(rec, Collections.emptyList());
|
||||||
key.cancel();
|
keysForRecording.forEach(key -> {
|
||||||
keys.remove(key);
|
Path path = keys.get(key);
|
||||||
recordingByKey.remove(key);
|
key.cancel();
|
||||||
registeredPaths.remove(path);
|
keys.remove(key);
|
||||||
});
|
recordingByKey.remove(key);
|
||||||
this.keysByRecording.remove(rec);
|
registeredPaths.remove(path);
|
||||||
|
});
|
||||||
|
this.keysByRecording.remove(rec);
|
||||||
|
} finally {
|
||||||
|
lock.unlock();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void processEvents() {
|
public void processEvents() {
|
||||||
|
@ -97,6 +110,7 @@ public class RecordingSizeMonitor {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lock.lock();
|
||||||
Path dir = keys.get(key);
|
Path dir = keys.get(key);
|
||||||
if (dir == null) {
|
if (dir == null) {
|
||||||
log.error("WatchKey not recognized");
|
log.error("WatchKey not recognized");
|
||||||
|
@ -136,6 +150,7 @@ public class RecordingSizeMonitor {
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("Error while processing file system events", e);
|
log.error("Error while processing file system events", e);
|
||||||
} finally {
|
} finally {
|
||||||
|
lock.unlock();
|
||||||
if (key != null) {
|
if (key != null) {
|
||||||
key.reset();
|
key.reset();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
package ctbrec;
|
package ctbrec;
|
||||||
|
|
||||||
import ctbrec.event.EventHandlerConfiguration;
|
import ctbrec.event.EventHandlerConfiguration;
|
||||||
import ctbrec.recorder.postprocessing.PostProcessor;
|
import ctbrec.io.json.dto.ModelDto;
|
||||||
|
import ctbrec.io.json.dto.PostProcessorDto;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.time.LocalTime;
|
import java.time.LocalTime;
|
||||||
|
@ -115,12 +116,12 @@ public class Settings {
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public int minimumLengthInSeconds = 0;
|
public int minimumLengthInSeconds = 0;
|
||||||
public long minimumSpaceLeftInBytes = 0;
|
public long minimumSpaceLeftInBytes = 0;
|
||||||
public List<Model> models = new ArrayList<>();
|
public List<ModelDto> models = new ArrayList<>();
|
||||||
public Set<ModelGroup> modelGroups = new HashSet<>();
|
public Set<ModelGroup> modelGroups = new HashSet<>();
|
||||||
public Map<String, String> modelNotes = new HashMap<>();
|
public Map<String, String> modelNotes = new HashMap<>();
|
||||||
public Map<String, String> modelPortraits = new HashMap<>();
|
public Map<String, String> modelPortraits = new HashMap<>();
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public List<Model> modelsIgnored = new ArrayList<>();
|
public List<ModelDto> modelsIgnored = new ArrayList<>();
|
||||||
public boolean monitorClipboard = false;
|
public boolean monitorClipboard = false;
|
||||||
public int onlineCheckIntervalInSecs = 60;
|
public int onlineCheckIntervalInSecs = 60;
|
||||||
public boolean onlineCheckSkipsPausedModels = false;
|
public boolean onlineCheckSkipsPausedModels = false;
|
||||||
|
@ -131,7 +132,7 @@ public class Settings {
|
||||||
public String postProcessing = "";
|
public String postProcessing = "";
|
||||||
public int playlistRequestTimeout = 2000;
|
public int playlistRequestTimeout = 2000;
|
||||||
public int postProcessingThreads = 2;
|
public int postProcessingThreads = 2;
|
||||||
public List<PostProcessor> postProcessors = new ArrayList<>();
|
public List<PostProcessorDto> postProcessors = new ArrayList<>();
|
||||||
public String proxyHost;
|
public String proxyHost;
|
||||||
public String proxyPassword;
|
public String proxyPassword;
|
||||||
public String proxyPort;
|
public String proxyPort;
|
||||||
|
@ -156,7 +157,7 @@ public class Settings {
|
||||||
public String recordingsTableSortType = "";
|
public String recordingsTableSortType = "";
|
||||||
public String recordingsDir = System.getProperty("user.home") + File.separator + "ctbrec";
|
public String recordingsDir = System.getProperty("user.home") + File.separator + "ctbrec";
|
||||||
public DirectoryStructure recordingsDirStructure = DirectoryStructure.FLAT;
|
public DirectoryStructure recordingsDirStructure = DirectoryStructure.FLAT;
|
||||||
public List<Model> recordLater = new ArrayList<>();
|
public List<ModelDto> recordLater = new ArrayList<>();
|
||||||
public boolean recordSingleFile = false;
|
public boolean recordSingleFile = false;
|
||||||
public long recordUntilDefaultDurationInMinutes = 24 * 60L;
|
public long recordUntilDefaultDurationInMinutes = 24 * 60L;
|
||||||
public boolean removeRecordingAfterPostProcessing = false;
|
public boolean removeRecordingAfterPostProcessing = false;
|
||||||
|
|
|
@ -1,64 +0,0 @@
|
||||||
package ctbrec.io;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map.Entry;
|
|
||||||
|
|
||||||
import com.squareup.moshi.JsonAdapter;
|
|
||||||
import com.squareup.moshi.JsonReader;
|
|
||||||
import com.squareup.moshi.JsonReader.Token;
|
|
||||||
import com.squareup.moshi.JsonWriter;
|
|
||||||
|
|
||||||
import ctbrec.io.HttpClient.CookieContainer;
|
|
||||||
import okhttp3.Cookie;
|
|
||||||
|
|
||||||
public class CookieContainerJsonAdapter extends JsonAdapter<CookieContainer> {
|
|
||||||
|
|
||||||
private CookieJsonAdapter cookieAdapter = new CookieJsonAdapter();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public CookieContainer fromJson(JsonReader reader) throws IOException {
|
|
||||||
CookieContainer cookies = new CookieContainer();
|
|
||||||
reader.beginArray();
|
|
||||||
while(reader.hasNext()) {
|
|
||||||
reader.beginObject();
|
|
||||||
reader.nextName(); // "domain"
|
|
||||||
String domain = reader.nextString();
|
|
||||||
reader.nextName(); // "cookies"
|
|
||||||
reader.beginArray();
|
|
||||||
List<Cookie> cookieList = new ArrayList<>();
|
|
||||||
while(reader.hasNext()) {
|
|
||||||
Token token = reader.peek();
|
|
||||||
if(token == Token.END_ARRAY) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
Cookie cookie = cookieAdapter.fromJson(reader);
|
|
||||||
cookieList.add(cookie);
|
|
||||||
}
|
|
||||||
reader.endArray();
|
|
||||||
reader.endObject();
|
|
||||||
cookies.put(domain, cookieList);
|
|
||||||
}
|
|
||||||
reader.endArray();
|
|
||||||
return cookies;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void toJson(JsonWriter writer, CookieContainer cookieContainer) throws IOException {
|
|
||||||
writer.beginArray();
|
|
||||||
for (Entry<String, List<Cookie>> entry : cookieContainer.entrySet()) {
|
|
||||||
writer.beginObject();
|
|
||||||
writer.name("domain").value(entry.getKey());
|
|
||||||
writer.name("cookies");
|
|
||||||
writer.beginArray();
|
|
||||||
for (Cookie cookie : entry.getValue()) {
|
|
||||||
cookieAdapter.toJson(writer, cookie);
|
|
||||||
}
|
|
||||||
writer.endArray();
|
|
||||||
writer.endObject();
|
|
||||||
}
|
|
||||||
writer.endArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,81 +0,0 @@
|
||||||
package ctbrec.io;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import com.squareup.moshi.JsonAdapter;
|
|
||||||
import com.squareup.moshi.JsonReader;
|
|
||||||
import com.squareup.moshi.JsonWriter;
|
|
||||||
|
|
||||||
import okhttp3.Cookie;
|
|
||||||
import okhttp3.Cookie.Builder;
|
|
||||||
|
|
||||||
public class CookieJsonAdapter extends JsonAdapter<Cookie> {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Cookie fromJson(JsonReader reader) throws IOException {
|
|
||||||
reader.beginObject();
|
|
||||||
Builder builder = new Cookie.Builder();
|
|
||||||
// domain
|
|
||||||
reader.nextName();
|
|
||||||
String domain = reader.nextString();
|
|
||||||
builder.domain(domain);
|
|
||||||
|
|
||||||
// expiresAt
|
|
||||||
reader.nextName();
|
|
||||||
builder.expiresAt(reader.nextLong());
|
|
||||||
|
|
||||||
// host only
|
|
||||||
reader.nextName();
|
|
||||||
if(reader.nextBoolean()) {
|
|
||||||
builder.hostOnlyDomain(domain);
|
|
||||||
}
|
|
||||||
|
|
||||||
// http only
|
|
||||||
reader.nextName();
|
|
||||||
if(reader.nextBoolean()) {
|
|
||||||
builder.httpOnly();
|
|
||||||
}
|
|
||||||
|
|
||||||
// name
|
|
||||||
reader.nextName();
|
|
||||||
builder.name(reader.nextString());
|
|
||||||
|
|
||||||
// path
|
|
||||||
reader.nextName();
|
|
||||||
builder.path(reader.nextString());
|
|
||||||
|
|
||||||
// persistent
|
|
||||||
reader.nextName();
|
|
||||||
if(reader.nextBoolean()) {
|
|
||||||
// noop
|
|
||||||
}
|
|
||||||
|
|
||||||
// secure
|
|
||||||
reader.nextName();
|
|
||||||
if(reader.nextBoolean()) {
|
|
||||||
builder.secure();
|
|
||||||
}
|
|
||||||
|
|
||||||
// value
|
|
||||||
reader.nextName();
|
|
||||||
builder.value(reader.nextString());
|
|
||||||
|
|
||||||
reader.endObject();
|
|
||||||
return builder.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void toJson(JsonWriter writer, Cookie cookie) throws IOException {
|
|
||||||
writer.beginObject();
|
|
||||||
writer.name("domain").value(cookie.domain());
|
|
||||||
writer.name("expiresAt").value(cookie.expiresAt());
|
|
||||||
writer.name("hostOnly").value(cookie.hostOnly());
|
|
||||||
writer.name("httpOnly").value(cookie.httpOnly());
|
|
||||||
writer.name("name").value(cookie.name());
|
|
||||||
writer.name("path").value(cookie.path());
|
|
||||||
writer.name("persistent").value(cookie.persistent());
|
|
||||||
writer.name("secure").value(cookie.secure());
|
|
||||||
writer.name("value").value(cookie.value());
|
|
||||||
writer.endObject();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,30 +0,0 @@
|
||||||
package ctbrec.io;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import com.squareup.moshi.JsonAdapter;
|
|
||||||
import com.squareup.moshi.JsonReader;
|
|
||||||
import com.squareup.moshi.JsonWriter;
|
|
||||||
|
|
||||||
public class FileJsonAdapter extends JsonAdapter<File> {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public File fromJson(JsonReader reader) throws IOException {
|
|
||||||
String path = reader.nextString();
|
|
||||||
if (path != null) {
|
|
||||||
return new File(path);
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void toJson(JsonWriter writer, File value) throws IOException {
|
|
||||||
if (value != null) {
|
|
||||||
writer.value(value.getCanonicalPath());
|
|
||||||
} else {
|
|
||||||
writer.nullValue();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,30 +1,31 @@
|
||||||
package ctbrec.io;
|
package ctbrec.io;
|
||||||
|
|
||||||
import com.squareup.moshi.JsonAdapter;
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
import com.squareup.moshi.Moshi;
|
|
||||||
import ctbrec.Config;
|
import ctbrec.Config;
|
||||||
import ctbrec.LoggingInterceptor;
|
import ctbrec.LoggingInterceptor;
|
||||||
import ctbrec.Settings.ProxyType;
|
import ctbrec.Settings.ProxyType;
|
||||||
|
import ctbrec.io.json.ObjectMapperFactory;
|
||||||
|
import ctbrec.io.json.dto.CookieDto;
|
||||||
|
import ctbrec.io.json.mapper.CookieMapper;
|
||||||
|
import lombok.Data;
|
||||||
import okhttp3.*;
|
import okhttp3.*;
|
||||||
import okhttp3.OkHttpClient.Builder;
|
import okhttp3.OkHttpClient.Builder;
|
||||||
|
import org.mapstruct.factory.Mappers;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.net.ssl.*;
|
import javax.net.ssl.*;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.Authenticator;
|
import java.net.Authenticator;
|
||||||
import java.net.PasswordAuthentication;
|
import java.net.PasswordAuthentication;
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.security.KeyManagementException;
|
import java.security.KeyManagementException;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.Map.Entry;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.zip.GZIPInputStream;
|
import java.util.zip.GZIPInputStream;
|
||||||
|
@ -59,47 +60,47 @@ public abstract class HttpClient {
|
||||||
private void loadProxySettings() {
|
private void loadProxySettings() {
|
||||||
ProxyType proxyType = config.getSettings().proxyType;
|
ProxyType proxyType = config.getSettings().proxyType;
|
||||||
switch (proxyType) {
|
switch (proxyType) {
|
||||||
case HTTP:
|
case HTTP:
|
||||||
System.setProperty("http.proxyHost", config.getSettings().proxyHost);
|
System.setProperty("http.proxyHost", config.getSettings().proxyHost);
|
||||||
System.setProperty("http.proxyPort", config.getSettings().proxyPort);
|
System.setProperty("http.proxyPort", config.getSettings().proxyPort);
|
||||||
System.setProperty("https.proxyHost", config.getSettings().proxyHost);
|
System.setProperty("https.proxyHost", config.getSettings().proxyHost);
|
||||||
System.setProperty("https.proxyPort", config.getSettings().proxyPort);
|
System.setProperty("https.proxyPort", config.getSettings().proxyPort);
|
||||||
if(config.getSettings().proxyUser != null && !config.getSettings().proxyUser.isEmpty()) {
|
if (config.getSettings().proxyUser != null && !config.getSettings().proxyUser.isEmpty()) {
|
||||||
String username = config.getSettings().proxyUser;
|
String username = config.getSettings().proxyUser;
|
||||||
String password = config.getSettings().proxyPassword;
|
String password = config.getSettings().proxyPassword;
|
||||||
System.setProperty("http.proxyUser", username);
|
System.setProperty("http.proxyUser", username);
|
||||||
System.setProperty("http.proxyPassword", password);
|
System.setProperty("http.proxyPassword", password);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case SOCKS4:
|
case SOCKS4:
|
||||||
System.setProperty("socksProxyVersion", "4");
|
System.setProperty("socksProxyVersion", "4");
|
||||||
System.setProperty("socksProxyHost", config.getSettings().proxyHost);
|
System.setProperty("socksProxyHost", config.getSettings().proxyHost);
|
||||||
System.setProperty("socksProxyPort", config.getSettings().proxyPort);
|
System.setProperty("socksProxyPort", config.getSettings().proxyPort);
|
||||||
break;
|
break;
|
||||||
case SOCKS5:
|
case SOCKS5:
|
||||||
System.setProperty("socksProxyVersion", "5");
|
System.setProperty("socksProxyVersion", "5");
|
||||||
System.setProperty("socksProxyHost", config.getSettings().proxyHost);
|
System.setProperty("socksProxyHost", config.getSettings().proxyHost);
|
||||||
System.setProperty("socksProxyPort", config.getSettings().proxyPort);
|
System.setProperty("socksProxyPort", config.getSettings().proxyPort);
|
||||||
if(config.getSettings().proxyUser != null && !config.getSettings().proxyUser.isEmpty()) {
|
if (config.getSettings().proxyUser != null && !config.getSettings().proxyUser.isEmpty()) {
|
||||||
String username = config.getSettings().proxyUser;
|
String username = config.getSettings().proxyUser;
|
||||||
String password = config.getSettings().proxyPassword;
|
String password = config.getSettings().proxyPassword;
|
||||||
Authenticator.setDefault(new SocksProxyAuth(username, password));
|
Authenticator.setDefault(new SocksProxyAuth(username, password));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case DIRECT:
|
case DIRECT:
|
||||||
default:
|
default:
|
||||||
System.clearProperty("http.proxyHost");
|
System.clearProperty("http.proxyHost");
|
||||||
System.clearProperty("http.proxyPort");
|
System.clearProperty("http.proxyPort");
|
||||||
System.clearProperty("https.proxyHost");
|
System.clearProperty("https.proxyHost");
|
||||||
System.clearProperty("https.proxyPort");
|
System.clearProperty("https.proxyPort");
|
||||||
System.clearProperty("socksProxyVersion");
|
System.clearProperty("socksProxyVersion");
|
||||||
System.clearProperty("socksProxyHost");
|
System.clearProperty("socksProxyHost");
|
||||||
System.clearProperty("socksProxyPort");
|
System.clearProperty("socksProxyPort");
|
||||||
System.clearProperty("java.net.socks.username");
|
System.clearProperty("java.net.socks.username");
|
||||||
System.clearProperty("java.net.socks.password");
|
System.clearProperty("java.net.socks.password");
|
||||||
System.clearProperty("http.proxyUser");
|
System.clearProperty("http.proxyUser");
|
||||||
System.clearProperty("http.proxyPassword");
|
System.clearProperty("http.proxyPassword");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -156,12 +157,16 @@ public abstract class HttpClient {
|
||||||
X509Certificate[] x509Certificates = new X509Certificate[0];
|
X509Certificate[] x509Certificates = new X509Certificate[0];
|
||||||
return x509Certificates;
|
return x509Certificates;
|
||||||
}
|
}
|
||||||
@Override public void checkServerTrusted(final X509Certificate[] chain, final String authType) { /* noop*/ }
|
|
||||||
@Override public void checkClientTrusted(final X509Certificate[] chain, final String authType) { /* noop*/ }
|
@Override
|
||||||
|
public void checkServerTrusted(final X509Certificate[] chain, final String authType) { /* noop*/ }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void checkClientTrusted(final X509Certificate[] chain, final String authType) { /* noop*/ }
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final TrustManager[] trustManagers = new TrustManager[] { x509TrustManager };
|
final TrustManager[] trustManagers = new TrustManager[]{x509TrustManager};
|
||||||
final String PROTOCOL = "TLSv1.2";
|
final String PROTOCOL = "TLSv1.2";
|
||||||
SSLContext sslContext = SSLContext.getInstance(PROTOCOL);
|
SSLContext sslContext = SSLContext.getInstance(PROTOCOL);
|
||||||
KeyManager[] keyManagers = null;
|
KeyManager[] keyManagers = null;
|
||||||
|
@ -183,18 +188,17 @@ public abstract class HttpClient {
|
||||||
|
|
||||||
private void persistCookies() {
|
private void persistCookies() {
|
||||||
try {
|
try {
|
||||||
CookieContainer cookies = new CookieContainer();
|
List<CookieContainer> containers = new ArrayList<>();
|
||||||
cookies.putAll(cookieJar.getCookies());
|
cookieJar.getCookies().forEach((domain, cookieList) -> {
|
||||||
Moshi moshi = new Moshi.Builder()
|
CookieContainer cookies = new CookieContainer();
|
||||||
.add(CookieContainer.class, new CookieContainerJsonAdapter())
|
cookies.setDomain(domain);
|
||||||
.build();
|
List<CookieDto> dtos = cookieList.stream().map(Mappers.getMapper(CookieMapper.class)::toDto).toList();
|
||||||
JsonAdapter<CookieContainer> adapter = moshi.adapter(CookieContainer.class).indent(" ");
|
cookies.setCookies(dtos);
|
||||||
String json = adapter.toJson(cookies);
|
containers.add(cookies);
|
||||||
|
});
|
||||||
|
String json = ObjectMapperFactory.getMapper().writeValueAsString(containers);
|
||||||
File cookieFile = new File(config.getConfigDir(), "cookies-" + name + ".json");
|
File cookieFile = new File(config.getConfigDir(), "cookies-" + name + ".json");
|
||||||
try(FileOutputStream fout = new FileOutputStream(cookieFile)) {
|
Files.writeString(cookieFile.toPath(), json);
|
||||||
fout.write(json.getBytes(UTF_8));
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOG.error("Couldn't persist cookies for {}", name, e);
|
LOG.error("Couldn't persist cookies for {}", name, e);
|
||||||
}
|
}
|
||||||
|
@ -203,33 +207,29 @@ public abstract class HttpClient {
|
||||||
private void loadCookies() {
|
private void loadCookies() {
|
||||||
try {
|
try {
|
||||||
File cookieFile = new File(config.getConfigDir(), "cookies-" + name + ".json");
|
File cookieFile = new File(config.getConfigDir(), "cookies-" + name + ".json");
|
||||||
if(!cookieFile.exists()) {
|
if (!cookieFile.exists()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
byte[] jsonBytes = Files.readAllBytes(cookieFile.toPath());
|
String json = Files.readString(cookieFile.toPath());
|
||||||
String json = new String(jsonBytes, UTF_8);
|
|
||||||
|
|
||||||
Map<String, List<Cookie>> cookies = cookieJar.getCookies();
|
Map<String, List<Cookie>> cookies = cookieJar.getCookies();
|
||||||
Moshi moshi = new Moshi.Builder()
|
List<CookieContainer> fromJson = ObjectMapperFactory.getMapper().readValue(json, new TypeReference<>() {
|
||||||
.add(CookieContainer.class, new CookieContainerJsonAdapter())
|
});
|
||||||
.build();
|
for (CookieContainer container : fromJson) {
|
||||||
JsonAdapter<CookieContainer> adapter = moshi.adapter(CookieContainer.class).indent(" ");
|
List<Cookie> filteredCookies = container.getCookies().stream()
|
||||||
CookieContainer fromJson = adapter.fromJson(json);
|
.filter(c -> !Objects.equals("deleted", c.getValue()))
|
||||||
Set<Entry<String, List<Cookie>>> entries = fromJson.entrySet();
|
.map(Mappers.getMapper(CookieMapper.class)::toCookie)
|
||||||
for (Entry<String, List<Cookie>> entry : entries) {
|
|
||||||
List<Cookie> filteredCookies = entry.getValue().stream()
|
|
||||||
.filter(c -> !Objects.equals("deleted", c.value()))
|
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
cookies.put(entry.getKey(), filteredCookies);
|
cookies.put(container.getDomain(), filteredCookies);
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOG.error("Couldn't load cookies for {}", name, e);
|
LOG.error("Couldn't load cookies for {}", name, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class CookieContainer extends HashMap<String, List<Cookie>> {
|
@Data
|
||||||
|
public static class CookieContainer {
|
||||||
|
private String domain;
|
||||||
|
private List<CookieDto> cookies;
|
||||||
}
|
}
|
||||||
|
|
||||||
private okhttp3.Authenticator createHttpProxyAuthenticator(String username, String password) {
|
private okhttp3.Authenticator createHttpProxyAuthenticator(String username, String password) {
|
||||||
|
@ -310,7 +310,7 @@ public abstract class HttpClient {
|
||||||
while ((len = gzipIn.read(b)) >= 0) {
|
while ((len = gzipIn.read(b)) >= 0) {
|
||||||
bos.write(b, 0, len);
|
bos.write(b, 0, len);
|
||||||
}
|
}
|
||||||
return bos.toString(StandardCharsets.UTF_8.toString());
|
return bos.toString(UTF_8.toString());
|
||||||
} else {
|
} else {
|
||||||
return Objects.requireNonNull(response.body()).string();
|
return Objects.requireNonNull(response.body()).string();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,21 +0,0 @@
|
||||||
package ctbrec.io;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.time.Instant;
|
|
||||||
|
|
||||||
import com.squareup.moshi.JsonAdapter;
|
|
||||||
import com.squareup.moshi.JsonReader;
|
|
||||||
import com.squareup.moshi.JsonWriter;
|
|
||||||
|
|
||||||
public class InstantJsonAdapter extends JsonAdapter<Instant> {
|
|
||||||
@Override
|
|
||||||
public Instant fromJson(JsonReader reader) throws IOException {
|
|
||||||
long timeInEpochMillis = reader.nextLong();
|
|
||||||
return Instant.ofEpochMilli(timeInEpochMillis);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void toJson(JsonWriter writer, Instant time) throws IOException {
|
|
||||||
writer.value(time.toEpochMilli());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,21 +0,0 @@
|
||||||
package ctbrec.io;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.time.LocalTime;
|
|
||||||
|
|
||||||
import com.squareup.moshi.JsonAdapter;
|
|
||||||
import com.squareup.moshi.JsonReader;
|
|
||||||
import com.squareup.moshi.JsonWriter;
|
|
||||||
|
|
||||||
public class LocalTimeJsonAdapter extends JsonAdapter<LocalTime> {
|
|
||||||
@Override
|
|
||||||
public LocalTime fromJson(JsonReader reader) throws IOException {
|
|
||||||
String timeString = reader.nextString();
|
|
||||||
return LocalTime.parse(timeString);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void toJson(JsonWriter writer, LocalTime time) throws IOException {
|
|
||||||
writer.value(time.toString());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,134 +0,0 @@
|
||||||
package ctbrec.io;
|
|
||||||
|
|
||||||
import com.squareup.moshi.JsonAdapter;
|
|
||||||
import com.squareup.moshi.JsonReader;
|
|
||||||
import com.squareup.moshi.JsonReader.Token;
|
|
||||||
import com.squareup.moshi.JsonWriter;
|
|
||||||
import ctbrec.Model;
|
|
||||||
import ctbrec.SubsequentAction;
|
|
||||||
import ctbrec.sites.Site;
|
|
||||||
import ctbrec.sites.chaturbate.ChaturbateModel;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.lang.reflect.InvocationTargetException;
|
|
||||||
import java.time.Instant;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
public class ModelJsonAdapter extends JsonAdapter<Model> {
|
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(ModelJsonAdapter.class);
|
|
||||||
|
|
||||||
private List<Site> sites;
|
|
||||||
|
|
||||||
public ModelJsonAdapter() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public ModelJsonAdapter(List<Site> sites) {
|
|
||||||
this.sites = sites;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Model fromJson(JsonReader reader) throws IOException {
|
|
||||||
reader.beginObject();
|
|
||||||
Object type = null;
|
|
||||||
Model model = null;
|
|
||||||
|
|
||||||
while (reader.hasNext()) {
|
|
||||||
try {
|
|
||||||
Token token = reader.peek();
|
|
||||||
if (token == Token.NAME) {
|
|
||||||
String key = reader.nextName();
|
|
||||||
if (key.equals("type")) {
|
|
||||||
type = reader.readJsonValue();
|
|
||||||
Class<?> modelClass = Class.forName(Optional.ofNullable(type).orElse(ChaturbateModel.class.getName()).toString());
|
|
||||||
model = (Model) modelClass.getDeclaredConstructor().newInstance();
|
|
||||||
} else if (key.equals("name")) {
|
|
||||||
model.setName(reader.nextString());
|
|
||||||
} else if (key.equals("displayName")) {
|
|
||||||
model.setDisplayName(reader.nextString());
|
|
||||||
} else if (key.equals("description")) {
|
|
||||||
model.setDescription(reader.nextString());
|
|
||||||
} else if (key.equals("url")) {
|
|
||||||
model.setUrl(reader.nextString());
|
|
||||||
} else if (key.equals("priority")) {
|
|
||||||
model.setPriority(reader.nextInt());
|
|
||||||
} else if (key.equals("streamUrlIndex")) {
|
|
||||||
model.setStreamUrlIndex(reader.nextInt());
|
|
||||||
} else if (key.equals("suspended")) {
|
|
||||||
model.setSuspended(reader.nextBoolean());
|
|
||||||
} else if (key.equals("markedForLater")) {
|
|
||||||
model.setMarkedForLaterRecording(reader.nextBoolean());
|
|
||||||
} 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("addedTimestamp")) {
|
|
||||||
model.setAddedTimestamp(Instant.ofEpochMilli(reader.nextLong()));
|
|
||||||
} else if (key.equals("recordUntil")) {
|
|
||||||
model.setRecordUntil(Instant.ofEpochMilli(reader.nextLong()));
|
|
||||||
} else if (key.equals("recordUntilSubsequentAction")) {
|
|
||||||
model.setRecordUntilSubsequentAction(SubsequentAction.valueOf(reader.nextString()));
|
|
||||||
} else if (key.equals("siteSpecific")) {
|
|
||||||
reader.beginObject();
|
|
||||||
try {
|
|
||||||
model.readSiteSpecificData(reader);
|
|
||||||
} catch (Exception e) {
|
|
||||||
LOG.error("Couldn't read site specific data for model {}", Optional.ofNullable(model).map(Model::getName).orElse("n/a"));
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
reader.endObject();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
reader.skipValue();
|
|
||||||
}
|
|
||||||
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException
|
|
||||||
| NoSuchMethodException | SecurityException e) {
|
|
||||||
throw new IOException("Couldn't instantiate model class [" + type + "]", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
reader.endObject();
|
|
||||||
|
|
||||||
if (sites != null) {
|
|
||||||
for (Site site : sites) {
|
|
||||||
if (site.isSiteForModel(model)) {
|
|
||||||
model.setSite(site);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return model;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void toJson(JsonWriter writer, Model model) throws IOException {
|
|
||||||
writer.beginObject();
|
|
||||||
writer.name("type").value(model.getClass().getName());
|
|
||||||
writeValueIfSet(writer, "name", model.getName());
|
|
||||||
writeValueIfSet(writer, "displayName", model.getDisplayName());
|
|
||||||
writeValueIfSet(writer, "description", model.getDescription());
|
|
||||||
writeValueIfSet(writer, "url", model.getUrl());
|
|
||||||
writer.name("priority").value(model.getPriority());
|
|
||||||
writer.name("streamUrlIndex").value(model.getStreamUrlIndex());
|
|
||||||
writer.name("suspended").value(model.isSuspended());
|
|
||||||
writer.name("markedForLater").value(model.isMarkedForLaterRecording());
|
|
||||||
writer.name("lastSeen").value(model.getLastSeen().toEpochMilli());
|
|
||||||
writer.name("lastRecorded").value(model.getLastRecorded().toEpochMilli());
|
|
||||||
writer.name("addedTimestamp").value(model.getAddedTimestamp().toEpochMilli());
|
|
||||||
writer.name("recordUntil").value(model.getRecordUntil().toEpochMilli());
|
|
||||||
writer.name("recordUntilSubsequentAction").value(model.getRecordUntilSubsequentAction().name());
|
|
||||||
writer.name("siteSpecific");
|
|
||||||
writer.beginObject();
|
|
||||||
model.writeSiteSpecificData(writer);
|
|
||||||
writer.endObject();
|
|
||||||
writer.endObject();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void writeValueIfSet(JsonWriter writer, String name, String value) throws IOException {
|
|
||||||
if (value != null) {
|
|
||||||
writer.name(name).value(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,65 +0,0 @@
|
||||||
package ctbrec.io;
|
|
||||||
|
|
||||||
import com.squareup.moshi.JsonAdapter;
|
|
||||||
import com.squareup.moshi.JsonReader;
|
|
||||||
import com.squareup.moshi.JsonReader.Token;
|
|
||||||
import com.squareup.moshi.JsonWriter;
|
|
||||||
import ctbrec.recorder.postprocessing.PostProcessor;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.lang.reflect.InvocationTargetException;
|
|
||||||
import java.util.Map.Entry;
|
|
||||||
|
|
||||||
public class PostProcessorJsonAdapter extends JsonAdapter<PostProcessor> {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PostProcessor fromJson(JsonReader reader) throws IOException {
|
|
||||||
reader.beginObject();
|
|
||||||
Object type = null;
|
|
||||||
PostProcessor postProcessor = null;
|
|
||||||
while (reader.hasNext()) {
|
|
||||||
try {
|
|
||||||
Token token = reader.peek();
|
|
||||||
if (token == Token.NAME) {
|
|
||||||
String key = reader.nextName();
|
|
||||||
if (key.equals("type")) {
|
|
||||||
type = reader.readJsonValue();
|
|
||||||
Class<?> modelClass = Class.forName(type.toString());
|
|
||||||
postProcessor = (PostProcessor) modelClass.getDeclaredConstructor().newInstance();
|
|
||||||
} else if (key.equals("enabled")) {
|
|
||||||
postProcessor.setEnabled(reader.nextBoolean());
|
|
||||||
} else if (key.equals("config")) {
|
|
||||||
reader.beginObject();
|
|
||||||
} else {
|
|
||||||
String value = reader.nextString();
|
|
||||||
postProcessor.getConfig().put(key, value);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
reader.skipValue();
|
|
||||||
}
|
|
||||||
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException |
|
|
||||||
NoSuchMethodException | SecurityException e) {
|
|
||||||
throw new IOException("Couldn't instantiate post-processor class [" + type + "]", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
reader.endObject();
|
|
||||||
reader.endObject();
|
|
||||||
|
|
||||||
return postProcessor;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void toJson(JsonWriter writer, PostProcessor pp) throws IOException {
|
|
||||||
writer.beginObject();
|
|
||||||
writer.name("type").value(pp.getClass().getName());
|
|
||||||
writer.name("enabled").value(pp.isEnabled());
|
|
||||||
writer.name("config");
|
|
||||||
writer.beginObject();
|
|
||||||
for (Entry<String, String> entry : pp.getConfig().entrySet()) {
|
|
||||||
writer.name(entry.getKey()).value(entry.getValue());
|
|
||||||
}
|
|
||||||
writer.endObject();
|
|
||||||
writer.endObject();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
package ctbrec.io;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
import com.squareup.moshi.JsonAdapter;
|
|
||||||
import com.squareup.moshi.JsonReader;
|
|
||||||
import com.squareup.moshi.JsonWriter;
|
|
||||||
|
|
||||||
public class UuidJSonAdapter extends JsonAdapter<UUID> {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public UUID fromJson(JsonReader reader) throws IOException {
|
|
||||||
return UUID.fromString(reader.nextString());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void toJson(JsonWriter writer, UUID value) throws IOException {
|
|
||||||
writer.value(value.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
package ctbrec.io.json;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||||
|
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||||
|
import lombok.experimental.UtilityClass;
|
||||||
|
|
||||||
|
@UtilityClass
|
||||||
|
public class ObjectMapperFactory {
|
||||||
|
|
||||||
|
private static final ObjectMapper mapper;
|
||||||
|
|
||||||
|
static {
|
||||||
|
mapper = new ObjectMapper();
|
||||||
|
mapper.registerModule(new JavaTimeModule());
|
||||||
|
mapper.enable(SerializationFeature.INDENT_OUTPUT);
|
||||||
|
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
|
||||||
|
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ObjectMapper getMapper() {
|
||||||
|
return mapper;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
package ctbrec.io.json.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class CookieDto {
|
||||||
|
private String domain;
|
||||||
|
private long expiresAt;
|
||||||
|
private boolean hostOnly;
|
||||||
|
private boolean httpOnly;
|
||||||
|
private String name;
|
||||||
|
private String path;
|
||||||
|
private boolean persistent;
|
||||||
|
private boolean secure;
|
||||||
|
private String value;
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
package ctbrec.io.json.dto;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||||
|
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||||
|
import ctbrec.SubsequentAction;
|
||||||
|
import ctbrec.io.json.dto.converter.InstantToMillisConverter;
|
||||||
|
import ctbrec.io.json.dto.converter.MillisToInstantConverter;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class ModelDto {
|
||||||
|
private String type;
|
||||||
|
private String name;
|
||||||
|
private String displayName;
|
||||||
|
private String description;
|
||||||
|
private URI url;
|
||||||
|
private URI preview;
|
||||||
|
private int priority = -1;
|
||||||
|
private int streamUrlIndex = -1;
|
||||||
|
private boolean suspended = false;
|
||||||
|
private boolean bookmarked = false;
|
||||||
|
@JsonSerialize(converter = InstantToMillisConverter.class)
|
||||||
|
@JsonDeserialize(converter = MillisToInstantConverter.class)
|
||||||
|
private Instant lastSeen;
|
||||||
|
@JsonSerialize(converter = InstantToMillisConverter.class)
|
||||||
|
@JsonDeserialize(converter = MillisToInstantConverter.class)
|
||||||
|
private Instant lastRecorded;
|
||||||
|
@JsonSerialize(converter = InstantToMillisConverter.class)
|
||||||
|
@JsonDeserialize(converter = MillisToInstantConverter.class)
|
||||||
|
private Instant addedAt;
|
||||||
|
@JsonSerialize(converter = InstantToMillisConverter.class)
|
||||||
|
@JsonDeserialize(converter = MillisToInstantConverter.class)
|
||||||
|
private Instant recordUntil;
|
||||||
|
private SubsequentAction recordUntilSubsequentAction;
|
||||||
|
private Map<String, String> siteSpecific = new HashMap<>();
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
package ctbrec.io.json.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class PostProcessorDto {
|
||||||
|
private String type;
|
||||||
|
private boolean enabled;
|
||||||
|
private Map<String, String> config;
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
package ctbrec.io.json.dto;
|
||||||
|
|
||||||
|
import ctbrec.Recording;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class RecordingDto {
|
||||||
|
private String id;
|
||||||
|
private ModelDto model;
|
||||||
|
private Instant startDate;
|
||||||
|
private Recording.State status = Recording.State.UNKNOWN;
|
||||||
|
private int progress = -1;
|
||||||
|
private long sizeInByte = -1;
|
||||||
|
private String metaDataFile;
|
||||||
|
private boolean singleFile = false;
|
||||||
|
private boolean pinned = false;
|
||||||
|
private String note;
|
||||||
|
private Set<String> associatedFiles = new HashSet<>();
|
||||||
|
private File absoluteFile = null;
|
||||||
|
private File postProcessedFile = null;
|
||||||
|
private int selectedResolution = -1;
|
||||||
|
private long lastSizeUpdate = 0;
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
package ctbrec.io.json.dto.converter;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.JavaType;
|
||||||
|
import com.fasterxml.jackson.databind.type.TypeFactory;
|
||||||
|
import com.fasterxml.jackson.databind.util.Converter;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
|
||||||
|
public class InstantToMillisConverter implements Converter<Instant, Long> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Long convert(Instant instant) {
|
||||||
|
return instant.toEpochMilli();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JavaType getInputType(TypeFactory typeFactory) {
|
||||||
|
return typeFactory.constructType(Instant.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JavaType getOutputType(TypeFactory typeFactory) {
|
||||||
|
return typeFactory.constructType(long.class);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
package ctbrec.io.json.dto.converter;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.JavaType;
|
||||||
|
import com.fasterxml.jackson.databind.type.TypeFactory;
|
||||||
|
import com.fasterxml.jackson.databind.util.Converter;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
|
||||||
|
public class MillisToInstantConverter implements Converter<Long, Instant> {
|
||||||
|
@Override
|
||||||
|
public Instant convert(Long aLong) {
|
||||||
|
return Instant.ofEpochMilli(aLong);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JavaType getInputType(TypeFactory typeFactory) {
|
||||||
|
return typeFactory.constructType(long.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JavaType getOutputType(TypeFactory typeFactory) {
|
||||||
|
return typeFactory.constructType(Instant.class);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
package ctbrec.io.json.mapper;
|
||||||
|
|
||||||
|
import ctbrec.io.json.dto.CookieDto;
|
||||||
|
import okhttp3.Cookie;
|
||||||
|
import org.mapstruct.Mapper;
|
||||||
|
import org.mapstruct.Mapping;
|
||||||
|
import org.mapstruct.ObjectFactory;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface CookieMapper {
|
||||||
|
default Cookie toCookie(CookieDto dto) {
|
||||||
|
var builder = new Cookie.Builder()
|
||||||
|
.name(dto.getName())
|
||||||
|
.domain(dto.getDomain())
|
||||||
|
.path(dto.getPath())
|
||||||
|
.value(dto.getValue())
|
||||||
|
.expiresAt(dto.getExpiresAt());
|
||||||
|
if (dto.isHostOnly()) builder.hostOnlyDomain(dto.getDomain());
|
||||||
|
if (dto.isHttpOnly()) builder.httpOnly();
|
||||||
|
if (dto.isSecure()) builder.secure();
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Mapping(target = "name", expression = "java(cookie.name())")
|
||||||
|
@Mapping(target = "domain", expression = "java(cookie.domain())")
|
||||||
|
@Mapping(target = "path", expression = "java(cookie.path())")
|
||||||
|
@Mapping(target = "value", expression = "java(cookie.value())")
|
||||||
|
@Mapping(target = "expiresAt", expression = "java(cookie.expiresAt())")
|
||||||
|
@Mapping(target = "hostOnly", expression = "java(cookie.hostOnly())")
|
||||||
|
@Mapping(target = "httpOnly", expression = "java(cookie.httpOnly())")
|
||||||
|
@Mapping(target = "persistent", expression = "java(cookie.persistent())")
|
||||||
|
@Mapping(target = "secure", expression = "java(cookie.secure())")
|
||||||
|
CookieDto toDto(Cookie cookie);
|
||||||
|
|
||||||
|
@ObjectFactory
|
||||||
|
default Cookie.Builder builder() {
|
||||||
|
return new Cookie.Builder();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
package ctbrec.io.json.mapper;
|
||||||
|
|
||||||
|
public class MappingException extends RuntimeException {
|
||||||
|
public MappingException(Exception cause) {
|
||||||
|
super(cause);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
package ctbrec.io.json.mapper;
|
||||||
|
|
||||||
|
import ctbrec.Model;
|
||||||
|
import ctbrec.io.json.dto.ModelDto;
|
||||||
|
import org.mapstruct.ObjectFactory;
|
||||||
|
|
||||||
|
public class ModelFactory {
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@ObjectFactory
|
||||||
|
Model toModel(ModelDto dto) {
|
||||||
|
try {
|
||||||
|
Class<? extends Model> modelClass = (Class<? extends Model>) Class.forName(dto.getType());
|
||||||
|
return modelClass.getDeclaredConstructor().newInstance();
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new MappingException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
package ctbrec.io.json.mapper;
|
||||||
|
|
||||||
|
|
||||||
|
import ctbrec.Model;
|
||||||
|
import ctbrec.io.json.dto.ModelDto;
|
||||||
|
import org.mapstruct.AfterMapping;
|
||||||
|
import org.mapstruct.Mapper;
|
||||||
|
import org.mapstruct.Mapping;
|
||||||
|
import org.mapstruct.MappingTarget;
|
||||||
|
|
||||||
|
@Mapper(uses = {ModelFactory.class, UriMapper.class})
|
||||||
|
public interface ModelMapper {
|
||||||
|
|
||||||
|
@Mapping(target = "addedAt", source = "addedTimestamp")
|
||||||
|
@Mapping(target = "bookmarked", source = "markedForLaterRecording")
|
||||||
|
@Mapping(target = "type", expression = "java(model.getClass().getName())")
|
||||||
|
ModelDto toDto(Model model);
|
||||||
|
|
||||||
|
@Mapping(target = "addedTimestamp", source = "addedAt")
|
||||||
|
@Mapping(target = "markedForLaterRecording", source = "bookmarked")
|
||||||
|
Model toModel(ModelDto dto) throws MappingException;
|
||||||
|
|
||||||
|
@AfterMapping
|
||||||
|
default void afterToDto(Model model, @MappingTarget ModelDto dto) {
|
||||||
|
model.writeSiteSpecificData(dto.getSiteSpecific());
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterMapping
|
||||||
|
default void afterToModel(ModelDto dto, @MappingTarget Model model) {
|
||||||
|
model.readSiteSpecificData(dto.getSiteSpecific());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
package ctbrec.io.json.mapper;
|
||||||
|
|
||||||
|
import ctbrec.io.json.dto.PostProcessorDto;
|
||||||
|
import ctbrec.recorder.postprocessing.PostProcessor;
|
||||||
|
import org.mapstruct.ObjectFactory;
|
||||||
|
|
||||||
|
public class PostProcessorFactory {
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@ObjectFactory
|
||||||
|
PostProcessor toPostProcessor(PostProcessorDto dto) {
|
||||||
|
try {
|
||||||
|
Class<? extends PostProcessor> ppClass = (Class<? extends PostProcessor>) Class.forName(dto.getType());
|
||||||
|
return ppClass.getDeclaredConstructor().newInstance();
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new MappingException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
package ctbrec.io.json.mapper;
|
||||||
|
|
||||||
|
import ctbrec.io.json.dto.PostProcessorDto;
|
||||||
|
import ctbrec.recorder.postprocessing.PostProcessor;
|
||||||
|
import org.mapstruct.Mapper;
|
||||||
|
import org.mapstruct.Mapping;
|
||||||
|
|
||||||
|
@Mapper(uses = {PostProcessorFactory.class})
|
||||||
|
public interface PostProcessorMapper {
|
||||||
|
@Mapping(target = "type", expression = "java(model.getClass().getName())")
|
||||||
|
PostProcessorDto toDto(PostProcessor model);
|
||||||
|
|
||||||
|
PostProcessor toPostProcessor(PostProcessorDto dto) throws MappingException;
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
package ctbrec.io.json.mapper;
|
||||||
|
|
||||||
|
import ctbrec.Recording;
|
||||||
|
import ctbrec.io.json.dto.RecordingDto;
|
||||||
|
import org.mapstruct.Mapper;
|
||||||
|
|
||||||
|
@Mapper(uses = ModelMapper.class)
|
||||||
|
public interface RecordingMapper {
|
||||||
|
|
||||||
|
RecordingDto toDto(Recording recording);
|
||||||
|
|
||||||
|
Recording toRecording(RecordingDto dto);
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
package ctbrec.io.json.mapper;
|
||||||
|
|
||||||
|
import org.mapstruct.Mapper;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface UriMapper {
|
||||||
|
default URI map(String uri) {
|
||||||
|
return Optional.ofNullable(uri).map(URI::create).orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
default String map(URI uri) {
|
||||||
|
return Optional.ofNullable(uri).map(Object::toString).orElse(null);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,23 +1,21 @@
|
||||||
package ctbrec.recorder;
|
package ctbrec.recorder;
|
||||||
|
|
||||||
import com.squareup.moshi.JsonAdapter;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.squareup.moshi.Moshi;
|
|
||||||
import ctbrec.Config;
|
import ctbrec.Config;
|
||||||
import ctbrec.Model;
|
|
||||||
import ctbrec.Recording;
|
import ctbrec.Recording;
|
||||||
import ctbrec.Recording.State;
|
import ctbrec.Recording.State;
|
||||||
import ctbrec.RecordingSizeMonitor;
|
import ctbrec.RecordingSizeMonitor;
|
||||||
import ctbrec.io.FileJsonAdapter;
|
import ctbrec.io.json.ObjectMapperFactory;
|
||||||
import ctbrec.io.InstantJsonAdapter;
|
import ctbrec.io.json.dto.RecordingDto;
|
||||||
import ctbrec.io.ModelJsonAdapter;
|
import ctbrec.io.json.mapper.RecordingMapper;
|
||||||
import ctbrec.sites.Site;
|
import ctbrec.sites.Site;
|
||||||
import org.slf4j.Logger;
|
import ctbrec.sites.SiteUtil;
|
||||||
import org.slf4j.LoggerFactory;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.mapstruct.factory.Mappers;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.time.Instant;
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
@ -27,11 +25,12 @@ import static ctbrec.io.IoUtils.deleteDirectory;
|
||||||
import static ctbrec.io.IoUtils.deleteEmptyParents;
|
import static ctbrec.io.IoUtils.deleteEmptyParents;
|
||||||
import static java.nio.file.StandardOpenOption.*;
|
import static java.nio.file.StandardOpenOption.*;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
public class RecordingManager {
|
public class RecordingManager {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(RecordingManager.class);
|
|
||||||
|
|
||||||
|
private static final ObjectMapper mapper = ObjectMapperFactory.getMapper();
|
||||||
private final Config config;
|
private final Config config;
|
||||||
private final JsonAdapter<Recording> adapter;
|
private final List<Site> sites;
|
||||||
private final List<Recording> recordings = new ArrayList<>();
|
private final List<Recording> recordings = new ArrayList<>();
|
||||||
private final ReentrantLock recordingsLock = new ReentrantLock();
|
private final ReentrantLock recordingsLock = new ReentrantLock();
|
||||||
|
|
||||||
|
@ -39,12 +38,7 @@ public class RecordingManager {
|
||||||
|
|
||||||
public RecordingManager(Config config, List<Site> sites) throws IOException {
|
public RecordingManager(Config config, List<Site> sites) throws IOException {
|
||||||
this.config = config;
|
this.config = config;
|
||||||
Moshi moshi = new Moshi.Builder()
|
this.sites = sites;
|
||||||
.add(Model.class, new ModelJsonAdapter(sites))
|
|
||||||
.add(Instant.class, new InstantJsonAdapter())
|
|
||||||
.add(File.class, new FileJsonAdapter())
|
|
||||||
.build();
|
|
||||||
adapter = moshi.adapter(Recording.class).indent(" ");
|
|
||||||
|
|
||||||
sizeMonitor = new RecordingSizeMonitor();
|
sizeMonitor = new RecordingSizeMonitor();
|
||||||
Executors.newSingleThreadExecutor().submit(sizeMonitor::processEvents);
|
Executors.newSingleThreadExecutor().submit(sizeMonitor::processEvents);
|
||||||
|
@ -61,16 +55,16 @@ public class RecordingManager {
|
||||||
recordingsLock.lock();
|
recordingsLock.lock();
|
||||||
try {
|
try {
|
||||||
recordings.add(rec);
|
recordings.add(rec);
|
||||||
|
sizeMonitor.monitor(rec);
|
||||||
} finally {
|
} finally {
|
||||||
recordingsLock.unlock();
|
recordingsLock.unlock();
|
||||||
}
|
}
|
||||||
sizeMonitor.monitor(rec);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void saveRecording(Recording rec) throws IOException {
|
public void saveRecording(Recording rec) throws IOException {
|
||||||
if (rec.getMetaDataFile() != null) {
|
if (rec.getMetaDataFile() != null) {
|
||||||
File recordingMetaData = new File(rec.getMetaDataFile());
|
File recordingMetaData = new File(rec.getMetaDataFile());
|
||||||
String json = adapter.toJson(rec);
|
String json = mapper.writeValueAsString(Mappers.getMapper(RecordingMapper.class).toDto(rec));
|
||||||
rec.setMetaDataFile(recordingMetaData.getAbsolutePath());
|
rec.setMetaDataFile(recordingMetaData.getAbsolutePath());
|
||||||
Files.createDirectories(recordingMetaData.getParentFile().toPath());
|
Files.createDirectories(recordingMetaData.getParentFile().toPath());
|
||||||
Files.writeString(recordingMetaData.toPath(), json, CREATE, WRITE, TRUNCATE_EXISTING);
|
Files.writeString(recordingMetaData.toPath(), json, CREATE, WRITE, TRUNCATE_EXISTING);
|
||||||
|
@ -84,11 +78,12 @@ public class RecordingManager {
|
||||||
for (File file : metaFiles) {
|
for (File file : metaFiles) {
|
||||||
String json = Files.readString(file.toPath());
|
String json = Files.readString(file.toPath());
|
||||||
try {
|
try {
|
||||||
Recording recording = adapter.fromJson(json);
|
Recording recording = Mappers.getMapper(RecordingMapper.class).toRecording(mapper.readValue(json, RecordingDto.class));
|
||||||
recording.setMetaDataFile(file.getCanonicalPath());
|
recording.setMetaDataFile(file.getCanonicalPath());
|
||||||
|
SiteUtil.getSiteForModel(sites, recording.getModel()).ifPresent(s -> recording.getModel().setSite(s));
|
||||||
loadRecording(recording);
|
loadRecording(recording);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOG.error("Couldn't load recording {}", file, e);
|
log.error("Couldn't load recording {}", file, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -106,7 +101,7 @@ public class RecordingManager {
|
||||||
recordings.add(recording);
|
recordings.add(recording);
|
||||||
sizeMonitor.monitor(recording);
|
sizeMonitor.monitor(recording);
|
||||||
} else {
|
} else {
|
||||||
LOG.info("Recording {} does not exist anymore -> ignoring recording", recording);
|
log.info("Recording {} does not exist anymore -> ignoring recording", recording);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -139,7 +134,10 @@ public class RecordingManager {
|
||||||
recording.setStatus(State.DELETING);
|
recording.setStatus(State.DELETING);
|
||||||
File path = recording.getAbsoluteFile();
|
File path = recording.getAbsoluteFile();
|
||||||
boolean isFile = path.isFile();
|
boolean isFile = path.isFile();
|
||||||
LOG.debug("Deleting {}", path);
|
log.debug("Deleting {}", path);
|
||||||
|
|
||||||
|
// uninstall file monitor
|
||||||
|
sizeMonitor.uninstall(recording);
|
||||||
|
|
||||||
// delete the video files
|
// delete the video files
|
||||||
if (isFile) {
|
if (isFile) {
|
||||||
|
@ -177,7 +175,6 @@ public class RecordingManager {
|
||||||
} finally {
|
} finally {
|
||||||
recordingsLock.unlock();
|
recordingsLock.unlock();
|
||||||
}
|
}
|
||||||
sizeMonitor.uninstall(recording);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -191,6 +188,8 @@ public class RecordingManager {
|
||||||
try {
|
try {
|
||||||
int idx = recordings.indexOf(recording);
|
int idx = recordings.indexOf(recording);
|
||||||
recording = recordings.get(idx);
|
recording = recordings.get(idx);
|
||||||
|
// uninstall file monitor
|
||||||
|
sizeMonitor.uninstall(recording);
|
||||||
// delete the meta data
|
// delete the meta data
|
||||||
Files.deleteIfExists(new File(recording.getMetaDataFile()).toPath());
|
Files.deleteIfExists(new File(recording.getMetaDataFile()).toPath());
|
||||||
// remove from data structure
|
// remove from data structure
|
||||||
|
@ -198,7 +197,6 @@ public class RecordingManager {
|
||||||
} finally {
|
} finally {
|
||||||
recordingsLock.unlock();
|
recordingsLock.unlock();
|
||||||
}
|
}
|
||||||
sizeMonitor.uninstall(recording);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Recording> getAll() {
|
public List<Recording> getAll() {
|
||||||
|
|
|
@ -1,19 +1,29 @@
|
||||||
package ctbrec.recorder;
|
package ctbrec.recorder;
|
||||||
|
|
||||||
import com.squareup.moshi.JsonAdapter;
|
|
||||||
import com.squareup.moshi.Moshi;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import ctbrec.*;
|
import ctbrec.*;
|
||||||
import ctbrec.event.EventBusHolder;
|
import ctbrec.event.EventBusHolder;
|
||||||
import ctbrec.event.NoSpaceLeftEvent;
|
import ctbrec.event.NoSpaceLeftEvent;
|
||||||
import ctbrec.event.RecordingStateChangedEvent;
|
import ctbrec.event.RecordingStateChangedEvent;
|
||||||
import ctbrec.io.*;
|
import ctbrec.io.BandwidthMeter;
|
||||||
|
import ctbrec.io.HttpClient;
|
||||||
|
import ctbrec.io.HttpException;
|
||||||
|
import ctbrec.io.json.ObjectMapperFactory;
|
||||||
|
import ctbrec.io.json.dto.ModelDto;
|
||||||
|
import ctbrec.io.json.dto.RecordingDto;
|
||||||
|
import ctbrec.io.json.mapper.ModelMapper;
|
||||||
|
import ctbrec.io.json.mapper.RecordingMapper;
|
||||||
import ctbrec.sites.Site;
|
import ctbrec.sites.Site;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
import okhttp3.MediaType;
|
import okhttp3.MediaType;
|
||||||
import okhttp3.Request;
|
import okhttp3.Request;
|
||||||
import okhttp3.Request.Builder;
|
import okhttp3.Request.Builder;
|
||||||
import okhttp3.RequestBody;
|
import okhttp3.RequestBody;
|
||||||
import okhttp3.Response;
|
import okhttp3.Response;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
|
import org.mapstruct.factory.Mappers;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
@ -25,6 +35,7 @@ import java.time.Duration;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public class RemoteRecorder implements Recorder {
|
public class RemoteRecorder implements Recorder {
|
||||||
|
|
||||||
|
@ -35,25 +46,14 @@ public class RemoteRecorder implements Recorder {
|
||||||
|
|
||||||
public static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
|
public static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
|
||||||
private static final String LOG_MSG_SENDING_REQUERST = "Sending request to recording server: {}";
|
private static final String LOG_MSG_SENDING_REQUERST = "Sending request to recording server: {}";
|
||||||
private final Moshi moshi = new Moshi.Builder()
|
|
||||||
.add(Instant.class, new InstantJsonAdapter())
|
private final ObjectMapper mapper = ObjectMapperFactory.getMapper();
|
||||||
.add(Model.class, new ModelJsonAdapter())
|
|
||||||
.add(File.class, new FileJsonAdapter())
|
|
||||||
.add(UUID.class, new UuidJSonAdapter())
|
|
||||||
.build();
|
|
||||||
private final JsonAdapter<ModelListResponse> modelListResponseAdapter = moshi.adapter(ModelListResponse.class);
|
|
||||||
private final JsonAdapter<RecordingListResponse> recordingListResponseAdapter = moshi.adapter(RecordingListResponse.class);
|
|
||||||
private final JsonAdapter<ModelRequest> modelRequestAdapter = moshi.adapter(ModelRequest.class);
|
|
||||||
private final JsonAdapter<ModelGroupRequest> modelGroupRequestAdapter = moshi.adapter(ModelGroupRequest.class);
|
|
||||||
private final JsonAdapter<ModelGroupListResponse> modelGroupListResponseAdapter = moshi.adapter(ModelGroupListResponse.class);
|
|
||||||
private final JsonAdapter<RecordingRequest> recordingRequestAdapter = moshi.adapter(RecordingRequest.class);
|
|
||||||
private final JsonAdapter<SimpleResponse> simpleResponseAdapter = moshi.adapter(SimpleResponse.class);
|
|
||||||
|
|
||||||
private List<Model> models = Collections.emptyList();
|
private List<Model> models = Collections.emptyList();
|
||||||
private List<Model> onlineModels = Collections.emptyList();
|
private List<Model> onlineModels = Collections.emptyList();
|
||||||
private List<Recording> recordings = Collections.emptyList();
|
private List<Recording> recordings = Collections.emptyList();
|
||||||
private final ReentrantLock modelGroupLock = new ReentrantLock();
|
|
||||||
private final Set<ModelGroup> modelGroups = new HashSet<>();
|
private final Set<ModelGroup> modelGroups = new HashSet<>();
|
||||||
|
private final ReentrantLock modelGroupLock = new ReentrantLock();
|
||||||
private final List<Site> sites;
|
private final List<Site> sites;
|
||||||
private long spaceTotal = -1;
|
private long spaceTotal = -1;
|
||||||
private long spaceFree = -1;
|
private long spaceFree = -1;
|
||||||
|
@ -123,7 +123,7 @@ public class RemoteRecorder implements Recorder {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendRequest(String action, Model model) throws IOException, InvalidKeyException, NoSuchAlgorithmException {
|
private void sendRequest(String action, Model model) throws IOException, InvalidKeyException, NoSuchAlgorithmException {
|
||||||
String payload = modelRequestAdapter.toJson(new ModelRequest(action, model));
|
String payload = mapper.writeValueAsString(new ModelRequest(action, model));
|
||||||
LOG.trace(LOG_MSG_SENDING_REQUERST, payload);
|
LOG.trace(LOG_MSG_SENDING_REQUERST, payload);
|
||||||
RequestBody body = RequestBody.Companion.create(payload, JSON);
|
RequestBody body = RequestBody.Companion.create(payload, JSON);
|
||||||
Request.Builder builder = new Request.Builder().url(getRecordingEndpoint()).post(body);
|
Request.Builder builder = new Request.Builder().url(getRecordingEndpoint()).post(body);
|
||||||
|
@ -132,7 +132,7 @@ public class RemoteRecorder implements Recorder {
|
||||||
try (Response response = client.execute(request)) {
|
try (Response response = client.execute(request)) {
|
||||||
String json = response.body().string();
|
String json = response.body().string();
|
||||||
if (response.isSuccessful()) {
|
if (response.isSuccessful()) {
|
||||||
ModelListResponse resp = modelListResponseAdapter.fromJson(json);
|
ModelListResponse resp = mapper.readValue(json, ModelListResponse.class);
|
||||||
if (!resp.status.equals(SUCCESS)) {
|
if (!resp.status.equals(SUCCESS)) {
|
||||||
throw new IOException("Server returned error " + resp.status + " " + resp.msg);
|
throw new IOException("Server returned error " + resp.status + " " + resp.msg);
|
||||||
}
|
}
|
||||||
|
@ -144,7 +144,7 @@ public class RemoteRecorder implements Recorder {
|
||||||
|
|
||||||
private void sendRequest(String action, Recording recording, Runnable... onSuccess) throws InvalidKeyException, NoSuchAlgorithmException, IOException {
|
private void sendRequest(String action, Recording recording, Runnable... onSuccess) throws InvalidKeyException, NoSuchAlgorithmException, IOException {
|
||||||
RecordingRequest recReq = new RecordingRequest(action, recording);
|
RecordingRequest recReq = new RecordingRequest(action, recording);
|
||||||
String msg = recordingRequestAdapter.toJson(recReq);
|
String msg = mapper.writeValueAsString(recReq);
|
||||||
RequestBody body = RequestBody.Companion.create(msg, JSON);
|
RequestBody body = RequestBody.Companion.create(msg, JSON);
|
||||||
Request.Builder builder = new Request.Builder().url(getRecordingEndpoint()).post(body);
|
Request.Builder builder = new Request.Builder().url(getRecordingEndpoint()).post(body);
|
||||||
LOG.trace(LOG_MSG_SENDING_REQUERST, msg);
|
LOG.trace(LOG_MSG_SENDING_REQUERST, msg);
|
||||||
|
@ -152,7 +152,7 @@ public class RemoteRecorder implements Recorder {
|
||||||
Request request = builder.build();
|
Request request = builder.build();
|
||||||
try (Response response = client.execute(request)) {
|
try (Response response = client.execute(request)) {
|
||||||
String json = response.body().string();
|
String json = response.body().string();
|
||||||
RecordingListResponse resp = recordingListResponseAdapter.fromJson(json);
|
RecordingListResponse resp = mapper.readValue(json, RecordingListResponse.class);
|
||||||
if (response.isSuccessful()) {
|
if (response.isSuccessful()) {
|
||||||
if (!resp.status.equals(SUCCESS)) {
|
if (!resp.status.equals(SUCCESS)) {
|
||||||
throw new IOException("Request failed: " + resp.msg);
|
throw new IOException("Request failed: " + resp.msg);
|
||||||
|
@ -167,8 +167,8 @@ public class RemoteRecorder implements Recorder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendRequest(String action, ModelGroup model) throws IOException, InvalidKeyException, NoSuchAlgorithmException {
|
private void sendRequest(String action, ModelGroup modelGroup) throws IOException, InvalidKeyException, NoSuchAlgorithmException {
|
||||||
String payload = modelGroupRequestAdapter.toJson(new ModelGroupRequest(action, model));
|
String payload = mapper.writeValueAsString(new ModelGroupRequest(action, modelGroup));
|
||||||
LOG.trace(LOG_MSG_SENDING_REQUERST, payload);
|
LOG.trace(LOG_MSG_SENDING_REQUERST, payload);
|
||||||
RequestBody body = RequestBody.Companion.create(payload, JSON);
|
RequestBody body = RequestBody.Companion.create(payload, JSON);
|
||||||
Request.Builder builder = new Request.Builder().url(getRecordingEndpoint()).post(body);
|
Request.Builder builder = new Request.Builder().url(getRecordingEndpoint()).post(body);
|
||||||
|
@ -185,7 +185,7 @@ public class RemoteRecorder implements Recorder {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateModelGroups(String responseBody) throws IOException {
|
private void updateModelGroups(String responseBody) throws IOException {
|
||||||
ModelGroupListResponse resp = modelGroupListResponseAdapter.fromJson(responseBody);
|
ModelGroupListResponse resp = mapper.readValue(responseBody, ModelGroupListResponse.class);
|
||||||
if (!resp.status.equals(SUCCESS)) {
|
if (!resp.status.equals(SUCCESS)) {
|
||||||
throw new IOException("Server returned error " + resp.status + " " + resp.msg);
|
throw new IOException("Server returned error " + resp.status + " " + resp.msg);
|
||||||
}
|
}
|
||||||
|
@ -330,9 +330,9 @@ public class RemoteRecorder implements Recorder {
|
||||||
try (Response response = client.execute(request)) {
|
try (Response response = client.execute(request)) {
|
||||||
String json = response.body().string();
|
String json = response.body().string();
|
||||||
if (response.isSuccessful()) {
|
if (response.isSuccessful()) {
|
||||||
ModelListResponse resp = modelListResponseAdapter.fromJson(json);
|
ModelListResponse resp = mapper.readValue(json, ModelListResponse.class);
|
||||||
if (resp.status.equals(SUCCESS)) {
|
if (resp.status.equals(SUCCESS)) {
|
||||||
models = resp.models;
|
models = resp.models.stream().map(Mappers.getMapper(ModelMapper.class)::toModel).collect(Collectors.toList());
|
||||||
for (Model model : models) {
|
for (Model model : models) {
|
||||||
for (Site site : sites) {
|
for (Site site : sites) {
|
||||||
if (site.isSiteForModel(model)) {
|
if (site.isSiteForModel(model)) {
|
||||||
|
@ -363,9 +363,9 @@ public class RemoteRecorder implements Recorder {
|
||||||
try (Response response = client.execute(request)) {
|
try (Response response = client.execute(request)) {
|
||||||
String json = response.body().string();
|
String json = response.body().string();
|
||||||
if (response.isSuccessful()) {
|
if (response.isSuccessful()) {
|
||||||
ModelListResponse resp = modelListResponseAdapter.fromJson(json);
|
ModelListResponse resp = mapper.readValue(json, ModelListResponse.class);
|
||||||
if (resp.status.equals(SUCCESS)) {
|
if (resp.status.equals(SUCCESS)) {
|
||||||
onlineModels = resp.models;
|
onlineModels = resp.models.stream().map(Mappers.getMapper(ModelMapper.class)::toModel).collect(Collectors.toList());
|
||||||
for (Model model : models) {
|
for (Model model : models) {
|
||||||
for (Site site : sites) {
|
for (Site site : sites) {
|
||||||
if (site.isSiteForModel(model)) {
|
if (site.isSiteForModel(model)) {
|
||||||
|
@ -395,12 +395,11 @@ public class RemoteRecorder implements Recorder {
|
||||||
try (Response response = client.execute(request)) {
|
try (Response response = client.execute(request)) {
|
||||||
String json = response.body().string();
|
String json = response.body().string();
|
||||||
if (response.isSuccessful()) {
|
if (response.isSuccessful()) {
|
||||||
RecordingListResponse resp = recordingListResponseAdapter.fromJson(json);
|
RecordingListResponse resp = mapper.readValue(json, RecordingListResponse.class);
|
||||||
if (resp.status.equals(SUCCESS)) {
|
if (resp.status.equals(SUCCESS)) {
|
||||||
List<Recording> newRecordings = resp.recordings;
|
List<Recording> newRecordings = resp.recordings.stream().map(Mappers.getMapper(RecordingMapper.class)::toRecording).collect(Collectors.toList());
|
||||||
// fire changed events
|
// fire changed events
|
||||||
for (Iterator<Recording> iterator = recordings.iterator(); iterator.hasNext(); ) {
|
for (Recording recording : recordings) {
|
||||||
Recording recording = iterator.next();
|
|
||||||
if (newRecordings.contains(recording)) {
|
if (newRecordings.contains(recording)) {
|
||||||
int idx = newRecordings.indexOf(recording);
|
int idx = newRecordings.indexOf(recording);
|
||||||
Recording newRecording = newRecordings.get(idx);
|
Recording newRecording = newRecordings.get(idx);
|
||||||
|
@ -462,27 +461,31 @@ public class RemoteRecorder implements Recorder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
private static class ModelListResponse {
|
private static class ModelListResponse {
|
||||||
public String status;
|
String status;
|
||||||
public String msg;
|
String msg;
|
||||||
public List<Model> models;
|
List<ModelDto> models;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
private static class ModelGroupListResponse {
|
private static class ModelGroupListResponse {
|
||||||
public String status;
|
String status;
|
||||||
public String msg;
|
String msg;
|
||||||
public List<ModelGroup> groups;
|
List<ModelGroup> groups;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
private static class SimpleResponse {
|
private static class SimpleResponse {
|
||||||
public String status;
|
String status;
|
||||||
public String msg;
|
String msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
private static class RecordingListResponse {
|
private static class RecordingListResponse {
|
||||||
public String status;
|
String status;
|
||||||
public String msg;
|
String msg;
|
||||||
public List<Recording> recordings;
|
List<RecordingDto> recordings;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -495,80 +498,34 @@ public class RemoteRecorder implements Recorder {
|
||||||
sendRequest("delete", recording, () -> recordings.remove(recording));
|
sendRequest("delete", recording, () -> recordings.remove(recording));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
public static class ModelRequest {
|
public static class ModelRequest {
|
||||||
private String action;
|
private String action;
|
||||||
private Model model;
|
private ModelDto model;
|
||||||
|
|
||||||
public ModelRequest(String action, Model model) {
|
public ModelRequest(String action, Model model) {
|
||||||
super();
|
super();
|
||||||
this.action = action;
|
this.action = action;
|
||||||
this.model = model;
|
this.model = Mappers.getMapper(ModelMapper.class).toDto(model);
|
||||||
}
|
|
||||||
|
|
||||||
public String getAction() {
|
|
||||||
return action;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setAction(String action) {
|
|
||||||
this.action = action;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Model getModel() {
|
|
||||||
return model;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setModel(Model model) {
|
|
||||||
this.model = model;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
public static class ModelGroupRequest {
|
public static class ModelGroupRequest {
|
||||||
private String action;
|
private final String action;
|
||||||
private final ModelGroup modelGroup;
|
private final ModelGroup modelGroup;
|
||||||
|
|
||||||
public ModelGroupRequest(String action, ModelGroup modelGroup) {
|
|
||||||
super();
|
|
||||||
this.action = action;
|
|
||||||
this.modelGroup = modelGroup;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getAction() {
|
|
||||||
return action;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setAction(String action) {
|
|
||||||
this.action = action;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ModelGroup getModelGroup() {
|
|
||||||
return modelGroup;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
public static class RecordingRequest {
|
public static class RecordingRequest {
|
||||||
private String action;
|
private String action;
|
||||||
private Recording recording;
|
private RecordingDto recording;
|
||||||
|
|
||||||
public RecordingRequest(String action, Recording recording) {
|
public RecordingRequest(String action, Recording recording) {
|
||||||
super();
|
super();
|
||||||
this.action = action;
|
this.action = action;
|
||||||
this.recording = recording;
|
this.recording = Mappers.getMapper(RecordingMapper.class).toDto(recording);
|
||||||
}
|
|
||||||
|
|
||||||
public String getAction() {
|
|
||||||
return action;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setAction(String action) {
|
|
||||||
this.action = action;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Recording getRecording() {
|
|
||||||
return recording;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setRecording(Recording recording) {
|
|
||||||
this.recording = recording;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -612,7 +569,7 @@ public class RemoteRecorder implements Recorder {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getTotalSpaceBytes() throws IOException {
|
public long getTotalSpaceBytes() {
|
||||||
return spaceTotal;
|
return spaceTotal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -634,7 +591,7 @@ public class RemoteRecorder implements Recorder {
|
||||||
@Override
|
@Override
|
||||||
public void rerunPostProcessing(Recording recording) throws IOException, InvalidKeyException, NoSuchAlgorithmException {
|
public void rerunPostProcessing(Recording recording) throws IOException, InvalidKeyException, NoSuchAlgorithmException {
|
||||||
RecordingRequest recReq = new RecordingRequest("rerunPostProcessing", recording);
|
RecordingRequest recReq = new RecordingRequest("rerunPostProcessing", recording);
|
||||||
String msg = recordingRequestAdapter.toJson(recReq);
|
String msg = mapper.writeValueAsString(recReq);
|
||||||
LOG.debug(msg);
|
LOG.debug(msg);
|
||||||
RequestBody body = RequestBody.Companion.create(msg, JSON);
|
RequestBody body = RequestBody.Companion.create(msg, JSON);
|
||||||
Request.Builder builder = new Request.Builder().url(getRecordingEndpoint()).post(body);
|
Request.Builder builder = new Request.Builder().url(getRecordingEndpoint()).post(body);
|
||||||
|
@ -642,7 +599,7 @@ public class RemoteRecorder implements Recorder {
|
||||||
Request request = builder.build();
|
Request request = builder.build();
|
||||||
try (Response response = client.execute(request)) {
|
try (Response response = client.execute(request)) {
|
||||||
String json = response.body().string();
|
String json = response.body().string();
|
||||||
SimpleResponse resp = simpleResponseAdapter.fromJson(json);
|
SimpleResponse resp = mapper.readValue(json, SimpleResponse.class);
|
||||||
if (response.isSuccessful()) {
|
if (response.isSuccessful()) {
|
||||||
if (!resp.status.equals(SUCCESS)) {
|
if (!resp.status.equals(SUCCESS)) {
|
||||||
throw new IOException("Couldn't start post-processing for recording: " + resp.msg);
|
throw new IOException("Couldn't start post-processing for recording: " + resp.msg);
|
||||||
|
|
|
@ -5,12 +5,16 @@ import ctbrec.*;
|
||||||
import ctbrec.Recording.State;
|
import ctbrec.Recording.State;
|
||||||
import ctbrec.event.*;
|
import ctbrec.event.*;
|
||||||
import ctbrec.io.HttpClient;
|
import ctbrec.io.HttpClient;
|
||||||
|
import ctbrec.io.json.mapper.ModelMapper;
|
||||||
|
import ctbrec.io.json.mapper.PostProcessorMapper;
|
||||||
import ctbrec.notes.LocalModelNotesService;
|
import ctbrec.notes.LocalModelNotesService;
|
||||||
import ctbrec.recorder.download.RecordingProcess;
|
import ctbrec.recorder.download.RecordingProcess;
|
||||||
import ctbrec.recorder.postprocessing.PostProcessingContext;
|
import ctbrec.recorder.postprocessing.PostProcessingContext;
|
||||||
import ctbrec.recorder.postprocessing.PostProcessor;
|
import ctbrec.recorder.postprocessing.PostProcessor;
|
||||||
import ctbrec.sites.Site;
|
import ctbrec.sites.Site;
|
||||||
|
import ctbrec.sites.SiteUtil;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.mapstruct.factory.Mappers;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -24,6 +28,7 @@ import java.time.ZoneId;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.*;
|
import java.util.concurrent.*;
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static ctbrec.Recording.State.WAITING;
|
import static ctbrec.Recording.State.WAITING;
|
||||||
import static ctbrec.SubsequentAction.*;
|
import static ctbrec.SubsequentAction.*;
|
||||||
|
@ -35,7 +40,7 @@ import static java.lang.Thread.MIN_PRIORITY;
|
||||||
public class SimplifiedLocalRecorder implements Recorder {
|
public class SimplifiedLocalRecorder implements Recorder {
|
||||||
|
|
||||||
public static final boolean IGNORE_CACHE = true;
|
public static final boolean IGNORE_CACHE = true;
|
||||||
private final List<Model> models = Collections.synchronizedList(new ArrayList<>());
|
private List<Model> models = Collections.synchronizedList(new ArrayList<>());
|
||||||
private final Config config;
|
private final Config config;
|
||||||
private volatile boolean running;
|
private volatile boolean running;
|
||||||
private final ReentrantLock recorderLock = new ReentrantLock();
|
private final ReentrantLock recorderLock = new ReentrantLock();
|
||||||
|
@ -62,7 +67,7 @@ public class SimplifiedLocalRecorder implements Recorder {
|
||||||
scheduler = Executors.newScheduledThreadPool(5, createThreadFactory("Download", MAX_PRIORITY));
|
scheduler = Executors.newScheduledThreadPool(5, createThreadFactory("Download", MAX_PRIORITY));
|
||||||
threadPoolScaler = new ThreadPoolScaler((ThreadPoolExecutor) scheduler, 5);
|
threadPoolScaler = new ThreadPoolScaler((ThreadPoolExecutor) scheduler, 5);
|
||||||
recordingManager = new RecordingManager(config, sites);
|
recordingManager = new RecordingManager(config, sites);
|
||||||
loadModels();
|
loadModels(sites);
|
||||||
int ppThreads = config.getSettings().postProcessingThreads;
|
int ppThreads = config.getSettings().postProcessingThreads;
|
||||||
BlockingQueue<Runnable> ppQueue = new LinkedBlockingQueue<>();
|
BlockingQueue<Runnable> ppQueue = new LinkedBlockingQueue<>();
|
||||||
postProcessing = new ThreadPoolExecutor(ppThreads, ppThreads, 5, TimeUnit.MINUTES, ppQueue, createThreadFactory("PP", MIN_PRIORITY));
|
postProcessing = new ThreadPoolExecutor(ppThreads, ppThreads, 5, TimeUnit.MINUTES, ppQueue, createThreadFactory("PP", MIN_PRIORITY));
|
||||||
|
@ -178,18 +183,22 @@ public class SimplifiedLocalRecorder implements Recorder {
|
||||||
recordings.add(recording);
|
recordings.add(recording);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadModels() {
|
private void loadModels(List<Site> sites) {
|
||||||
config.getSettings().models.forEach(m -> {
|
config.getSettings().models
|
||||||
if (m.getSite() != null) {
|
.stream()
|
||||||
if (m.getSite().isEnabled()) {
|
.map(Mappers.getMapper(ModelMapper.class)::toModel)
|
||||||
models.add(m);
|
.forEach(m -> {
|
||||||
} else {
|
SiteUtil.getSiteForModel(sites, m).ifPresent(m::setSite);
|
||||||
log.info("{} disabled -> ignoring {}", m.getSite().getName(), m.getName());
|
if (m.getSite() != null) {
|
||||||
}
|
if (m.getSite().isEnabled()) {
|
||||||
} else {
|
models.add(m);
|
||||||
log.info("Site for model {} is unknown -> ignoring", m.getName());
|
} else {
|
||||||
}
|
log.info("{} disabled -> ignoring {}", m.getSite().getName(), m.getName());
|
||||||
});
|
}
|
||||||
|
} else {
|
||||||
|
log.info("Site for model {} is unknown -> ignoring", m.getName());
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void shutdownPool(String name, ExecutorService executorService, int secondsToWaitForTermination) throws InterruptedException {
|
private void shutdownPool(String name, ExecutorService executorService, int secondsToWaitForTermination) throws InterruptedException {
|
||||||
|
@ -238,7 +247,10 @@ public class SimplifiedLocalRecorder implements Recorder {
|
||||||
recording.refresh();
|
recording.refresh();
|
||||||
recordingManager.saveRecording(recording);
|
recordingManager.saveRecording(recording);
|
||||||
recording.postprocess();
|
recording.postprocess();
|
||||||
List<PostProcessor> postProcessors = config.getSettings().postProcessors;
|
List<PostProcessor> postProcessors = config.getSettings().postProcessors
|
||||||
|
.stream()
|
||||||
|
.map(Mappers.getMapper(PostProcessorMapper.class)::toPostProcessor)
|
||||||
|
.toList();
|
||||||
PostProcessingContext ctx = createPostProcessingContext(recording);
|
PostProcessingContext ctx = createPostProcessingContext(recording);
|
||||||
for (PostProcessor postProcessor : postProcessors) {
|
for (PostProcessor postProcessor : postProcessors) {
|
||||||
if (postProcessor.isEnabled()) {
|
if (postProcessor.isEnabled()) {
|
||||||
|
@ -281,7 +293,7 @@ public class SimplifiedLocalRecorder implements Recorder {
|
||||||
ctx.setRecorder(this);
|
ctx.setRecorder(this);
|
||||||
ctx.setRecording(recording);
|
ctx.setRecording(recording);
|
||||||
ctx.setRecordingManager(recordingManager);
|
ctx.setRecordingManager(recordingManager);
|
||||||
ctx.setModelNotesService(new LocalModelNotesService(config)); // TODO
|
ctx.setModelNotesService(new LocalModelNotesService(config));
|
||||||
return ctx;
|
return ctx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -307,7 +319,7 @@ public class SimplifiedLocalRecorder implements Recorder {
|
||||||
if (Objects.equals(model.getAddedTimestamp(), Instant.EPOCH)) {
|
if (Objects.equals(model.getAddedTimestamp(), Instant.EPOCH)) {
|
||||||
model.setAddedTimestamp(Instant.now());
|
model.setAddedTimestamp(Instant.now());
|
||||||
}
|
}
|
||||||
config.getSettings().models.add(model);
|
config.getSettings().models.add(Mappers.getMapper(ModelMapper.class).toDto(model));
|
||||||
config.save();
|
config.save();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
log.error("Couldn't save config", e);
|
log.error("Couldn't save config", e);
|
||||||
|
@ -364,7 +376,7 @@ public class SimplifiedLocalRecorder implements Recorder {
|
||||||
|
|
||||||
private Recording createRecording(RecordingProcess download) throws IOException {
|
private Recording createRecording(RecordingProcess download) throws IOException {
|
||||||
Model model = download.getModel();
|
Model model = download.getModel();
|
||||||
Recording rec = new Recording();
|
Recording rec = new Recording(config.getSettings().recordingsDir);
|
||||||
rec.setId(UUID.randomUUID().toString());
|
rec.setId(UUID.randomUUID().toString());
|
||||||
rec.setRecordingProcess(download);
|
rec.setRecordingProcess(download);
|
||||||
String recordingFile = download.getPath(model).replace('\\', '/');
|
String recordingFile = download.getPath(model).replace('\\', '/');
|
||||||
|
@ -405,9 +417,8 @@ public class SimplifiedLocalRecorder implements Recorder {
|
||||||
try {
|
try {
|
||||||
if (models.contains(model)) {
|
if (models.contains(model)) {
|
||||||
models.remove(model);
|
models.remove(model);
|
||||||
config.getSettings().models.remove(model);
|
saveConfig();
|
||||||
log.info("Model {} removed", model);
|
log.info("Model {} removed", model);
|
||||||
config.save();
|
|
||||||
} else {
|
} else {
|
||||||
throw new NoSuchElementException("Model " + model.getName() + " [" + model.getUrl() + "] not found in list of recorded models");
|
throw new NoSuchElementException("Model " + model.getName() + " [" + model.getUrl() + "] not found in list of recorded models");
|
||||||
}
|
}
|
||||||
|
@ -433,7 +444,7 @@ public class SimplifiedLocalRecorder implements Recorder {
|
||||||
if (models.contains(model)) {
|
if (models.contains(model)) {
|
||||||
int index = models.indexOf(model);
|
int index = models.indexOf(model);
|
||||||
models.get(index).setStreamUrlIndex(model.getStreamUrlIndex());
|
models.get(index).setStreamUrlIndex(model.getStreamUrlIndex());
|
||||||
config.save();
|
saveConfig();
|
||||||
log.debug("Switching stream source to index {} for model {}", model.getStreamUrlIndex(), model.getName());
|
log.debug("Switching stream source to index {} for model {}", model.getStreamUrlIndex(), model.getName());
|
||||||
recorderLock.lock();
|
recorderLock.lock();
|
||||||
try {
|
try {
|
||||||
|
@ -517,7 +528,7 @@ public class SimplifiedLocalRecorder implements Recorder {
|
||||||
int index = models.indexOf(model);
|
int index = models.indexOf(model);
|
||||||
models.get(index).setSuspended(true);
|
models.get(index).setSuspended(true);
|
||||||
model.setSuspended(true);
|
model.setSuspended(true);
|
||||||
config.save();
|
saveConfig();
|
||||||
} else {
|
} else {
|
||||||
log.warn("Couldn't suspend model {}. Not found in list", model.getName());
|
log.warn("Couldn't suspend model {}. Not found in list", model.getName());
|
||||||
return;
|
return;
|
||||||
|
@ -531,6 +542,11 @@ public class SimplifiedLocalRecorder implements Recorder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void saveConfig() throws IOException {
|
||||||
|
config.getSettings().models = models.stream().map(Mappers.getMapper(ModelMapper.class)::toDto).collect(Collectors.toList());
|
||||||
|
config.save();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void resumeRecording(Model model) throws IOException {
|
public void resumeRecording(Model model) throws IOException {
|
||||||
recorderLock.lock();
|
recorderLock.lock();
|
||||||
|
@ -542,7 +558,7 @@ public class SimplifiedLocalRecorder implements Recorder {
|
||||||
m.setMarkedForLaterRecording(false);
|
m.setMarkedForLaterRecording(false);
|
||||||
model.setSuspended(false);
|
model.setSuspended(false);
|
||||||
model.setMarkedForLaterRecording(false);
|
model.setMarkedForLaterRecording(false);
|
||||||
config.save();
|
saveConfig();
|
||||||
startRecordingProcess(m);
|
startRecordingProcess(m);
|
||||||
} else {
|
} else {
|
||||||
log.warn("Couldn't resume model {}. Not found in list", model.getName());
|
log.warn("Couldn't resume model {}. Not found in list", model.getName());
|
||||||
|
@ -591,6 +607,7 @@ public class SimplifiedLocalRecorder implements Recorder {
|
||||||
addModel(model);
|
addModel(model);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
saveConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Optional<Model> findModel(Model m) {
|
private Optional<Model> findModel(Model m) {
|
||||||
|
@ -758,7 +775,7 @@ public class SimplifiedLocalRecorder implements Recorder {
|
||||||
if (models.contains(model)) {
|
if (models.contains(model)) {
|
||||||
int index = models.indexOf(model);
|
int index = models.indexOf(model);
|
||||||
models.get(index).setPriority(model.getPriority());
|
models.get(index).setPriority(model.getPriority());
|
||||||
config.save();
|
saveConfig();
|
||||||
} else {
|
} else {
|
||||||
log.warn("Couldn't change priority for model {}. Not found in list", model.getName());
|
log.warn("Couldn't change priority for model {}. Not found in list", model.getName());
|
||||||
}
|
}
|
||||||
|
@ -794,7 +811,7 @@ public class SimplifiedLocalRecorder implements Recorder {
|
||||||
m.setRecordUntil(model.getRecordUntil());
|
m.setRecordUntil(model.getRecordUntil());
|
||||||
m.setRecordUntilSubsequentAction(model.getRecordUntilSubsequentAction());
|
m.setRecordUntilSubsequentAction(model.getRecordUntilSubsequentAction());
|
||||||
log.debug("Stopping recording of model {} at {} and then {}", m, model.getRecordUntil(), m.getRecordUntilSubsequentAction());
|
log.debug("Stopping recording of model {} at {} and then {}", m, model.getRecordUntil(), m.getRecordUntilSubsequentAction());
|
||||||
config.save();
|
saveConfig();
|
||||||
} else {
|
} else {
|
||||||
throw new NoSuchElementException("Model " + model.getName() + " [" + model.getUrl() + "] not found in list of recorded models");
|
throw new NoSuchElementException("Model " + model.getName() + " [" + model.getUrl() + "] not found in list of recorded models");
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,15 @@
|
||||||
package ctbrec.recorder.postprocessing;
|
package ctbrec.recorder.postprocessing;
|
||||||
|
|
||||||
|
import ctbrec.Recording;
|
||||||
|
import ctbrec.recorder.RecordingManager;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
@Slf4j
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import ctbrec.Recording;
|
|
||||||
import ctbrec.recorder.RecordingManager;
|
|
||||||
|
|
||||||
public class DeleteTooShort extends AbstractPostProcessor {
|
public class DeleteTooShort extends AbstractPostProcessor {
|
||||||
|
|
||||||
private static final transient Logger LOG = LoggerFactory.getLogger(DeleteTooShort.class);
|
|
||||||
public static final String MIN_LEN_IN_SECS = "minimumLengthInSeconds";
|
public static final String MIN_LEN_IN_SECS = "minimumLengthInSeconds";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -27,10 +25,10 @@ public class DeleteTooShort extends AbstractPostProcessor {
|
||||||
if (minimumLengthInSeconds.getSeconds() > 0) {
|
if (minimumLengthInSeconds.getSeconds() > 0) {
|
||||||
Duration recordingLength = rec.getLength();
|
Duration recordingLength = rec.getLength();
|
||||||
if (recordingLength.isNegative()) {
|
if (recordingLength.isNegative()) {
|
||||||
LOG.info("Video length couldn't be determined. Keeping the file!");
|
log.info("Video length couldn't be determined. Keeping the file!");
|
||||||
} else {
|
} else {
|
||||||
if (!recordingLength.isZero() && recordingLength.compareTo(minimumLengthInSeconds) < 0) {
|
if (!recordingLength.isZero() && recordingLength.compareTo(minimumLengthInSeconds) < 0) {
|
||||||
LOG.info("Deleting too short recording {} [{} < {}]", rec, recordingLength, minimumLengthInSeconds);
|
log.info("Deleting too short recording {} [{} < {}]", rec, recordingLength, minimumLengthInSeconds);
|
||||||
recordingManager.delete(rec);
|
recordingManager.delete(rec);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
package ctbrec.sites;
|
||||||
|
|
||||||
|
import ctbrec.Model;
|
||||||
|
import lombok.experimental.UtilityClass;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@UtilityClass
|
||||||
|
public class SiteUtil {
|
||||||
|
public static Optional<Site> getSiteForModel(List<Site> sites, Model model) {
|
||||||
|
for (Site site : sites) {
|
||||||
|
if (site.isSiteForModel(model)) {
|
||||||
|
return Optional.of(site);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,14 +1,14 @@
|
||||||
package ctbrec.sites.chaturbate;
|
package ctbrec.sites.chaturbate;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.iheartradio.m3u8.*;
|
import com.iheartradio.m3u8.*;
|
||||||
import com.iheartradio.m3u8.data.MasterPlaylist;
|
import com.iheartradio.m3u8.data.MasterPlaylist;
|
||||||
import com.iheartradio.m3u8.data.Playlist;
|
import com.iheartradio.m3u8.data.Playlist;
|
||||||
import com.iheartradio.m3u8.data.PlaylistData;
|
import com.iheartradio.m3u8.data.PlaylistData;
|
||||||
import com.squareup.moshi.JsonAdapter;
|
|
||||||
import com.squareup.moshi.Moshi;
|
|
||||||
import ctbrec.AbstractModel;
|
import ctbrec.AbstractModel;
|
||||||
import ctbrec.Config;
|
import ctbrec.Config;
|
||||||
import ctbrec.io.HttpException;
|
import ctbrec.io.HttpException;
|
||||||
|
import ctbrec.io.json.ObjectMapperFactory;
|
||||||
import ctbrec.recorder.download.StreamSource;
|
import ctbrec.recorder.download.StreamSource;
|
||||||
import okhttp3.FormBody;
|
import okhttp3.FormBody;
|
||||||
import okhttp3.Request;
|
import okhttp3.Request;
|
||||||
|
@ -38,7 +38,7 @@ public class ChaturbateModel extends AbstractModel { // NOSONAR
|
||||||
private int[] resolution = new int[2];
|
private int[] resolution = new int[2];
|
||||||
private transient StreamInfo streamInfo;
|
private transient StreamInfo streamInfo;
|
||||||
private transient Instant lastStreamInfoRequest = Instant.EPOCH;
|
private transient Instant lastStreamInfoRequest = Instant.EPOCH;
|
||||||
|
private final transient ObjectMapper mapper = ObjectMapperFactory.getMapper();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This constructor exists only for deserialization. Please don't call it directly
|
* This constructor exists only for deserialization. Please don't call it directly
|
||||||
|
@ -251,9 +251,7 @@ public class ChaturbateModel extends AbstractModel { // NOSONAR
|
||||||
if (response.isSuccessful()) {
|
if (response.isSuccessful()) {
|
||||||
String content = response.body().string();
|
String content = response.body().string();
|
||||||
LOG.trace("Raw stream info: {}", content);
|
LOG.trace("Raw stream info: {}", content);
|
||||||
Moshi moshi = new Moshi.Builder().build();
|
streamInfo = mapper.readValue(content, StreamInfo.class);
|
||||||
JsonAdapter<StreamInfo> adapter = moshi.adapter(StreamInfo.class);
|
|
||||||
streamInfo = adapter.fromJson(content);
|
|
||||||
return streamInfo;
|
return streamInfo;
|
||||||
} else {
|
} else {
|
||||||
int code = response.code();
|
int code = response.code();
|
||||||
|
|
|
@ -4,8 +4,6 @@ import com.iheartradio.m3u8.*;
|
||||||
import com.iheartradio.m3u8.data.MasterPlaylist;
|
import com.iheartradio.m3u8.data.MasterPlaylist;
|
||||||
import com.iheartradio.m3u8.data.Playlist;
|
import com.iheartradio.m3u8.data.Playlist;
|
||||||
import com.iheartradio.m3u8.data.PlaylistData;
|
import com.iheartradio.m3u8.data.PlaylistData;
|
||||||
import com.squareup.moshi.JsonReader;
|
|
||||||
import com.squareup.moshi.JsonWriter;
|
|
||||||
import ctbrec.*;
|
import ctbrec.*;
|
||||||
import ctbrec.io.HttpException;
|
import ctbrec.io.HttpException;
|
||||||
import ctbrec.recorder.download.StreamSource;
|
import ctbrec.recorder.download.StreamSource;
|
||||||
|
@ -287,15 +285,12 @@ public class CherryTvModel extends AbstractModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void readSiteSpecificData(JsonReader reader) throws IOException {
|
public void readSiteSpecificData(Map<String, String> data) {
|
||||||
if (reader.hasNext()) {
|
id = data.get("id");
|
||||||
reader.nextName();
|
|
||||||
id = reader.nextString();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void writeSiteSpecificData(JsonWriter writer) throws IOException {
|
public void writeSiteSpecificData(Map<String, String> data) {
|
||||||
writer.name("id").value(id);
|
data.put("id", id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,8 +5,6 @@ import com.iheartradio.m3u8.data.MasterPlaylist;
|
||||||
import com.iheartradio.m3u8.data.Playlist;
|
import com.iheartradio.m3u8.data.Playlist;
|
||||||
import com.iheartradio.m3u8.data.PlaylistData;
|
import com.iheartradio.m3u8.data.PlaylistData;
|
||||||
import com.iheartradio.m3u8.data.StreamInfo;
|
import com.iheartradio.m3u8.data.StreamInfo;
|
||||||
import com.squareup.moshi.JsonReader;
|
|
||||||
import com.squareup.moshi.JsonWriter;
|
|
||||||
import ctbrec.AbstractModel;
|
import ctbrec.AbstractModel;
|
||||||
import ctbrec.Config;
|
import ctbrec.Config;
|
||||||
import ctbrec.io.HttpException;
|
import ctbrec.io.HttpException;
|
||||||
|
@ -21,10 +19,7 @@ import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
@ -382,14 +377,13 @@ public class Fc2Model extends AbstractModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void readSiteSpecificData(JsonReader reader) throws IOException {
|
public void readSiteSpecificData(Map<String, String> data) {
|
||||||
reader.nextName();
|
id = data.get("id");
|
||||||
id = reader.nextString();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void writeSiteSpecificData(JsonWriter writer) throws IOException {
|
public void writeSiteSpecificData(Map<String, String> data) {
|
||||||
writer.name("id").value(id);
|
data.put("id", id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -4,8 +4,6 @@ import com.iheartradio.m3u8.*;
|
||||||
import com.iheartradio.m3u8.data.MasterPlaylist;
|
import com.iheartradio.m3u8.data.MasterPlaylist;
|
||||||
import com.iheartradio.m3u8.data.Playlist;
|
import com.iheartradio.m3u8.data.Playlist;
|
||||||
import com.iheartradio.m3u8.data.PlaylistData;
|
import com.iheartradio.m3u8.data.PlaylistData;
|
||||||
import com.squareup.moshi.JsonReader;
|
|
||||||
import com.squareup.moshi.JsonWriter;
|
|
||||||
import ctbrec.AbstractModel;
|
import ctbrec.AbstractModel;
|
||||||
import ctbrec.Config;
|
import ctbrec.Config;
|
||||||
import ctbrec.Model;
|
import ctbrec.Model;
|
||||||
|
@ -499,16 +497,13 @@ public class Flirt4FreeModel extends AbstractModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void readSiteSpecificData(JsonReader reader) throws IOException {
|
public void readSiteSpecificData(Map<String, String> data) {
|
||||||
if (reader.hasNext()) {
|
id = data.get("id");
|
||||||
reader.nextName();
|
|
||||||
id = reader.nextString();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void writeSiteSpecificData(JsonWriter writer) throws IOException {
|
public void writeSiteSpecificData(Map<String, String> data) {
|
||||||
writer.name("id").value(id);
|
data.put("id", id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setStreamUrl(String streamUrl) {
|
public void setStreamUrl(String streamUrl) {
|
||||||
|
|
|
@ -2,8 +2,6 @@ package ctbrec.sites.jasmin;
|
||||||
|
|
||||||
import com.iheartradio.m3u8.ParseException;
|
import com.iheartradio.m3u8.ParseException;
|
||||||
import com.iheartradio.m3u8.PlaylistException;
|
import com.iheartradio.m3u8.PlaylistException;
|
||||||
import com.squareup.moshi.JsonReader;
|
|
||||||
import com.squareup.moshi.JsonWriter;
|
|
||||||
import ctbrec.AbstractModel;
|
import ctbrec.AbstractModel;
|
||||||
import ctbrec.Config;
|
import ctbrec.Config;
|
||||||
import ctbrec.StringUtil;
|
import ctbrec.StringUtil;
|
||||||
|
@ -229,13 +227,12 @@ public class LiveJasminModel extends AbstractModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void readSiteSpecificData(JsonReader reader) throws IOException {
|
public void readSiteSpecificData(Map<String, String> data) {
|
||||||
reader.nextName();
|
id = data.get("id");
|
||||||
id = reader.nextString();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void writeSiteSpecificData(JsonWriter writer) throws IOException {
|
public void writeSiteSpecificData(Map<String, String> data) {
|
||||||
if (id == null) {
|
if (id == null) {
|
||||||
try {
|
try {
|
||||||
loadModelInfo();
|
loadModelInfo();
|
||||||
|
@ -243,7 +240,7 @@ public class LiveJasminModel extends AbstractModel {
|
||||||
LOG.error("Couldn't load model ID for {}. This can cause problems with saving / loading the model", getName());
|
LOG.error("Couldn't load model ID for {}. This can cause problems with saving / loading the model", getName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
writer.name("id").value(id);
|
data.put("id", id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setOnline(boolean online) {
|
public void setOnline(boolean online) {
|
||||||
|
|
|
@ -4,8 +4,6 @@ import com.iheartradio.m3u8.*;
|
||||||
import com.iheartradio.m3u8.data.MasterPlaylist;
|
import com.iheartradio.m3u8.data.MasterPlaylist;
|
||||||
import com.iheartradio.m3u8.data.Playlist;
|
import com.iheartradio.m3u8.data.Playlist;
|
||||||
import com.iheartradio.m3u8.data.PlaylistData;
|
import com.iheartradio.m3u8.data.PlaylistData;
|
||||||
import com.squareup.moshi.JsonReader;
|
|
||||||
import com.squareup.moshi.JsonWriter;
|
|
||||||
import ctbrec.*;
|
import ctbrec.*;
|
||||||
import ctbrec.io.HttpException;
|
import ctbrec.io.HttpException;
|
||||||
import ctbrec.recorder.download.RecordingProcess;
|
import ctbrec.recorder.download.RecordingProcess;
|
||||||
|
@ -240,17 +238,13 @@ public class MVLiveModel extends AbstractModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void writeSiteSpecificData(JsonWriter writer) throws IOException {
|
public void writeSiteSpecificData(Map<String, String> data) {
|
||||||
writer.name("id").value(id);
|
data.put("id", id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void readSiteSpecificData(JsonReader reader) throws IOException {
|
public void readSiteSpecificData(Map<String, String> data) {
|
||||||
if (reader.hasNext()) {
|
id = data.get("id");
|
||||||
//noinspection ResultOfMethodCallIgnored
|
|
||||||
reader.nextName();
|
|
||||||
id = reader.nextString();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setId(String id) {
|
public void setId(String id) {
|
||||||
|
|
|
@ -2,8 +2,6 @@ package ctbrec.sites.mfc;
|
||||||
|
|
||||||
import com.google.common.cache.Cache;
|
import com.google.common.cache.Cache;
|
||||||
import com.google.common.cache.CacheBuilder;
|
import com.google.common.cache.CacheBuilder;
|
||||||
import com.squareup.moshi.JsonAdapter;
|
|
||||||
import com.squareup.moshi.Moshi;
|
|
||||||
import ctbrec.Config;
|
import ctbrec.Config;
|
||||||
import ctbrec.StringUtil;
|
import ctbrec.StringUtil;
|
||||||
import ctbrec.io.HttpException;
|
import ctbrec.io.HttpException;
|
||||||
|
@ -39,7 +37,6 @@ public class MyFreeCamsClient {
|
||||||
private MyFreeCams mfc;
|
private MyFreeCams mfc;
|
||||||
private WebSocket ws;
|
private WebSocket ws;
|
||||||
private Thread keepAlive;
|
private Thread keepAlive;
|
||||||
private final Moshi moshi;
|
|
||||||
private volatile boolean running = false;
|
private volatile boolean running = false;
|
||||||
|
|
||||||
private final Cache<Integer, SessionState> sessionStates = CacheBuilder.newBuilder().maximumSize(4000).build();
|
private final Cache<Integer, SessionState> sessionStates = CacheBuilder.newBuilder().maximumSize(4000).build();
|
||||||
|
@ -61,7 +58,6 @@ public class MyFreeCamsClient {
|
||||||
private final Queue<String> receivedTextHistory = new LinkedList<>();
|
private final Queue<String> receivedTextHistory = new LinkedList<>();
|
||||||
|
|
||||||
private MyFreeCamsClient() {
|
private MyFreeCamsClient() {
|
||||||
moshi = new Moshi.Builder().build();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static synchronized MyFreeCamsClient getInstance() {
|
public static synchronized MyFreeCamsClient getInstance() {
|
||||||
|
@ -228,16 +224,16 @@ public class MyFreeCamsClient {
|
||||||
case MYWEBCAM:
|
case MYWEBCAM:
|
||||||
case JOINCHAN:
|
case JOINCHAN:
|
||||||
case SESSIONSTATE:
|
case SESSIONSTATE:
|
||||||
if (!message.getMessage().isEmpty()) {
|
// if (!message.getMessage().isEmpty()) {
|
||||||
//LOG.debug("SessionState: {}", message.getMessage());
|
// //LOG.debug("SessionState: {}", message.getMessage());
|
||||||
JsonAdapter<SessionState> adapter = moshi.adapter(SessionState.class);
|
// JsonAdapter<SessionState> adapter = moshi.adapter(SessionState.class);
|
||||||
try {
|
// try {
|
||||||
SessionState sessionState = adapter.fromJson(message.getMessage());
|
// SessionState sessionState = adapter.fromJson(message.getMessage());
|
||||||
updateSessionState(sessionState);
|
// updateSessionState(sessionState);
|
||||||
} catch (IOException e) {
|
// } catch (IOException e) {
|
||||||
LOG.error("Couldn't parse session state message {}", message, e);
|
// LOG.error("Couldn't parse session state message {}", message, e);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
break;
|
break;
|
||||||
case USERNAMELOOKUP:
|
case USERNAMELOOKUP:
|
||||||
// LOG.debug("{}", message.getType());
|
// LOG.debug("{}", message.getType());
|
||||||
|
@ -680,13 +676,13 @@ public class MyFreeCamsClient {
|
||||||
if (StringUtil.isNotBlank(msg.getMessage()) && !Objects.equals(msg.getMessage(), q)) {
|
if (StringUtil.isNotBlank(msg.getMessage()) && !Objects.equals(msg.getMessage(), q)) {
|
||||||
JSONObject json = new JSONObject(msg.getMessage());
|
JSONObject json = new JSONObject(msg.getMessage());
|
||||||
|
|
||||||
JsonAdapter<SessionState> adapter = moshi.adapter(SessionState.class);
|
// JsonAdapter<SessionState> adapter = moshi.adapter(SessionState.class);
|
||||||
try {
|
// try {
|
||||||
SessionState sessionState = Objects.requireNonNull(adapter.fromJson(msg.getMessage()));
|
// SessionState sessionState = Objects.requireNonNull(adapter.fromJson(msg.getMessage()));
|
||||||
updateSessionState(sessionState);
|
// updateSessionState(sessionState);
|
||||||
} catch (Exception e) {
|
// } catch (Exception e) {
|
||||||
LOG.error("Couldn't parse session state message {}", msg, e);
|
// LOG.error("Couldn't parse session state message {}", msg, e);
|
||||||
}
|
// }
|
||||||
|
|
||||||
String name = json.getString("nm");
|
String name = json.getString("nm");
|
||||||
MyFreeCamsModel model = mfc.createModel(name);
|
MyFreeCamsModel model = mfc.createModel(name);
|
||||||
|
|
|
@ -2,8 +2,6 @@ package ctbrec.sites.mfc;
|
||||||
|
|
||||||
import com.iheartradio.m3u8.ParseException;
|
import com.iheartradio.m3u8.ParseException;
|
||||||
import com.iheartradio.m3u8.PlaylistException;
|
import com.iheartradio.m3u8.PlaylistException;
|
||||||
import com.squareup.moshi.JsonReader;
|
|
||||||
import com.squareup.moshi.JsonWriter;
|
|
||||||
import ctbrec.AbstractModel;
|
import ctbrec.AbstractModel;
|
||||||
import ctbrec.Config;
|
import ctbrec.Config;
|
||||||
import ctbrec.io.HtmlParser;
|
import ctbrec.io.HtmlParser;
|
||||||
|
@ -324,14 +322,13 @@ public class MyFreeCamsModel extends AbstractModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void readSiteSpecificData(JsonReader reader) throws IOException {
|
public void readSiteSpecificData(Map<String, String> data) {
|
||||||
reader.nextName();
|
uid = Integer.parseInt(data.get("uid"));
|
||||||
uid = reader.nextInt();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void writeSiteSpecificData(JsonWriter writer) throws IOException {
|
public void writeSiteSpecificData(Map<String, String> data) {
|
||||||
writer.name("uid").value(uid);
|
data.put("uid", Integer.toString(uid));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -2,8 +2,6 @@ package ctbrec.sites.streamate;
|
||||||
|
|
||||||
import com.iheartradio.m3u8.ParseException;
|
import com.iheartradio.m3u8.ParseException;
|
||||||
import com.iheartradio.m3u8.PlaylistException;
|
import com.iheartradio.m3u8.PlaylistException;
|
||||||
import com.squareup.moshi.JsonReader;
|
|
||||||
import com.squareup.moshi.JsonWriter;
|
|
||||||
import ctbrec.AbstractModel;
|
import ctbrec.AbstractModel;
|
||||||
import ctbrec.Config;
|
import ctbrec.Config;
|
||||||
import ctbrec.NotImplementedExcetion;
|
import ctbrec.NotImplementedExcetion;
|
||||||
|
@ -224,13 +222,12 @@ public class StreamateModel extends AbstractModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void readSiteSpecificData(JsonReader reader) throws IOException {
|
public void readSiteSpecificData(Map<String, String> data) {
|
||||||
reader.nextName();
|
id = Long.parseLong(data.get("id"));
|
||||||
id = reader.nextLong();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void writeSiteSpecificData(JsonWriter writer) throws IOException {
|
public void writeSiteSpecificData(Map<String, String> data) {
|
||||||
if (id == null || Objects.equals(id, MODEL_ID_UNDEFINED)) {
|
if (id == null || Objects.equals(id, MODEL_ID_UNDEFINED)) {
|
||||||
try {
|
try {
|
||||||
loadModelId();
|
loadModelId();
|
||||||
|
@ -238,7 +235,7 @@ public class StreamateModel extends AbstractModel {
|
||||||
LOG.error("Couldn't load model ID for {}. This can cause problems with saving / loading the model", getName(), e);
|
LOG.error("Couldn't load model ID for {}. This can cause problems with saving / loading the model", getName(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
writer.name("id").value(id);
|
data.put("id", Long.toString(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -0,0 +1,85 @@
|
||||||
|
package ctbrec.io.json.mapper;
|
||||||
|
|
||||||
|
import ctbrec.SubsequentAction;
|
||||||
|
import ctbrec.io.json.dto.ModelDto;
|
||||||
|
import ctbrec.sites.chaturbate.Chaturbate;
|
||||||
|
import ctbrec.sites.chaturbate.ChaturbateModel;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.mapstruct.factory.Mappers;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.temporal.ChronoUnit;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
class ModelMapperTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void mapsAllFieldsFromModelToDto() {
|
||||||
|
var model = new Chaturbate().createModel("foobarina");
|
||||||
|
model.setDescription("description");
|
||||||
|
model.setAddedTimestamp(Instant.now());
|
||||||
|
model.setRecordUntil(Instant.now().plus(1, ChronoUnit.DAYS));
|
||||||
|
model.setLastRecorded(Instant.now().minusSeconds(3600));
|
||||||
|
model.setLastSeen(Instant.now().minusSeconds(60));
|
||||||
|
model.setPriority(51);
|
||||||
|
model.setSuspended(true);
|
||||||
|
model.setMarkedForLaterRecording(true);
|
||||||
|
model.setRecordUntilSubsequentAction(SubsequentAction.REMOVE);
|
||||||
|
model.setDisplayName("whatever");
|
||||||
|
|
||||||
|
var mapper = Mappers.getMapper(ModelMapper.class);
|
||||||
|
var mapped = mapper.toDto(model);
|
||||||
|
|
||||||
|
assertEquals(model.getName(), mapped.getName());
|
||||||
|
assertEquals(model.getUrl(), mapped.getUrl().toString());
|
||||||
|
assertEquals(model.getDescription(), mapped.getDescription());
|
||||||
|
assertEquals(model.getAddedTimestamp(), mapped.getAddedAt());
|
||||||
|
assertEquals(model.getLastSeen(), mapped.getLastSeen());
|
||||||
|
assertEquals(model.getLastRecorded(), mapped.getLastRecorded());
|
||||||
|
assertEquals(model.getRecordUntil(), mapped.getRecordUntil());
|
||||||
|
assertEquals(model.getRecordUntilSubsequentAction(), mapped.getRecordUntilSubsequentAction());
|
||||||
|
assertEquals(model.getDisplayName(), mapped.getDisplayName());
|
||||||
|
assertEquals(model.getPriority(), mapped.getPriority());
|
||||||
|
assertEquals(model.getPreview(), mapped.getPreview().toString());
|
||||||
|
assertEquals(model.isMarkedForLaterRecording(), mapped.isBookmarked());
|
||||||
|
assertEquals(model.isSuspended(), mapped.isSuspended());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void mapsAllFieldsFromDtoToModel() {
|
||||||
|
var dto = new ModelDto();
|
||||||
|
dto.setType(ChaturbateModel.class.getName());
|
||||||
|
dto.setName("foobarina");
|
||||||
|
dto.setUrl(URI.create("https://foobarina.com"));
|
||||||
|
dto.setPreview(URI.create("https://foobarina.com/portrait.jpg"));
|
||||||
|
dto.setDescription("description");
|
||||||
|
dto.setAddedAt(Instant.now());
|
||||||
|
dto.setRecordUntil(Instant.now().plus(1, ChronoUnit.DAYS));
|
||||||
|
dto.setLastRecorded(Instant.now().minusSeconds(3600));
|
||||||
|
dto.setLastSeen(Instant.now().minusSeconds(60));
|
||||||
|
dto.setPriority(51);
|
||||||
|
dto.setSuspended(true);
|
||||||
|
dto.setBookmarked(true);
|
||||||
|
dto.setRecordUntilSubsequentAction(SubsequentAction.REMOVE);
|
||||||
|
dto.setDisplayName("whatever");
|
||||||
|
|
||||||
|
var mapper = Mappers.getMapper(ModelMapper.class);
|
||||||
|
var mapped = mapper.toModel(dto);
|
||||||
|
|
||||||
|
assertEquals(dto.getName(), mapped.getName());
|
||||||
|
assertEquals(dto.getUrl().toString(), mapped.getUrl());
|
||||||
|
assertEquals(dto.getDescription(), mapped.getDescription());
|
||||||
|
assertEquals(dto.getAddedAt(), mapped.getAddedTimestamp());
|
||||||
|
assertEquals(dto.getLastSeen(), mapped.getLastSeen());
|
||||||
|
assertEquals(dto.getLastRecorded(), mapped.getLastRecorded());
|
||||||
|
assertEquals(dto.getRecordUntil(), mapped.getRecordUntil());
|
||||||
|
assertEquals(dto.getRecordUntilSubsequentAction(), mapped.getRecordUntilSubsequentAction());
|
||||||
|
assertEquals(dto.getDisplayName(), mapped.getDisplayName());
|
||||||
|
assertEquals(dto.getPriority(), mapped.getPriority());
|
||||||
|
assertEquals(dto.getPreview().toString(), mapped.getPreview());
|
||||||
|
assertEquals(dto.isBookmarked(), mapped.isMarkedForLaterRecording());
|
||||||
|
assertEquals(dto.isSuspended(), mapped.isSuspended());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,106 @@
|
||||||
|
package ctbrec.io.json.mapper;
|
||||||
|
|
||||||
|
import ctbrec.Recording;
|
||||||
|
import ctbrec.UnknownModel;
|
||||||
|
import ctbrec.io.json.dto.ModelDto;
|
||||||
|
import ctbrec.io.json.dto.RecordingDto;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.mapstruct.factory.Mappers;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
class RecordingMapperTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void mapsAllFieldsFromRecordingToDto() {
|
||||||
|
var model = new UnknownModel();
|
||||||
|
model.setName("unknown");
|
||||||
|
model.setUrl("https://site/model/unknown");
|
||||||
|
|
||||||
|
var recording = new Recording();
|
||||||
|
recording.setModel(model);
|
||||||
|
recording.setId(UUID.randomUUID().toString());
|
||||||
|
recording.setAbsoluteFile(new File("/tmp/recording"));
|
||||||
|
recording.setStatus(Recording.State.RECORDING);
|
||||||
|
recording.setSelectedResolution(1080);
|
||||||
|
recording.setProgress(23);
|
||||||
|
recording.setStartDate(Instant.now());
|
||||||
|
recording.setNote("note");
|
||||||
|
recording.setPinned(true);
|
||||||
|
recording.setSingleFile(true);
|
||||||
|
recording.setSizeInByte(87346587);
|
||||||
|
recording.setPostProcessedFile(new File("/tmp/pp"));
|
||||||
|
recording.setMetaDataFile("/tmp/meta");
|
||||||
|
recording.setAssociatedFiles(Set.of("a", "b", "c"));
|
||||||
|
|
||||||
|
var mapper = Mappers.getMapper(RecordingMapper.class);
|
||||||
|
var mapped = mapper.toDto(recording);
|
||||||
|
|
||||||
|
assertEquals(recording.getId(), mapped.getId());
|
||||||
|
assertEquals(recording.getAbsoluteFile(), mapped.getAbsoluteFile());
|
||||||
|
assertEquals(recording.getStatus(), mapped.getStatus());
|
||||||
|
assertEquals(recording.getSelectedResolution(), mapped.getSelectedResolution());
|
||||||
|
assertEquals(recording.getProgress(), mapped.getProgress());
|
||||||
|
assertEquals(recording.getStartDate(), mapped.getStartDate());
|
||||||
|
assertEquals(recording.getNote(), mapped.getNote());
|
||||||
|
assertEquals(recording.isPinned(), mapped.isPinned());
|
||||||
|
assertEquals(recording.isSingleFile(), mapped.isSingleFile());
|
||||||
|
assertEquals(recording.getSizeInByte(), mapped.getSizeInByte());
|
||||||
|
assertEquals(recording.getPostProcessedFile(), mapped.getPostProcessedFile());
|
||||||
|
assertEquals(recording.getMetaDataFile(), mapped.getMetaDataFile());
|
||||||
|
assertEquals(recording.getAssociatedFiles(), mapped.getAssociatedFiles());
|
||||||
|
assertEquals(recording.getModel().getName(), mapped.getModel().getName());
|
||||||
|
assertEquals(recording.getModel().getUrl(), mapped.getModel().getUrl().toString());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void mapsAllFieldsFromDtoToRecording() {
|
||||||
|
ModelDto model = new ModelDto();
|
||||||
|
model.setType(UnknownModel.class.getName());
|
||||||
|
model.setName("unknown");
|
||||||
|
model.setUrl(URI.create("http://site/model/unknown"));
|
||||||
|
|
||||||
|
var dto = new RecordingDto();
|
||||||
|
dto.setId(UUID.randomUUID().toString());
|
||||||
|
dto.setModel(model);
|
||||||
|
dto.setAbsoluteFile(new File("/tmp/recording"));
|
||||||
|
dto.setStatus(Recording.State.RECORDING);
|
||||||
|
dto.setSelectedResolution(1080);
|
||||||
|
dto.setProgress(23);
|
||||||
|
dto.setStartDate(Instant.now());
|
||||||
|
dto.setNote("note");
|
||||||
|
dto.setPinned(true);
|
||||||
|
dto.setSingleFile(true);
|
||||||
|
dto.setSizeInByte(87346587);
|
||||||
|
dto.setPostProcessedFile(new File("/tmp/pp"));
|
||||||
|
dto.setMetaDataFile("/tmp/meta");
|
||||||
|
dto.setAssociatedFiles(Set.of("a", "b", "c"));
|
||||||
|
|
||||||
|
var mapper = Mappers.getMapper(RecordingMapper.class);
|
||||||
|
var mapped = mapper.toRecording(dto);
|
||||||
|
|
||||||
|
assertEquals(dto.getId(), mapped.getId());
|
||||||
|
assertEquals(dto.getAbsoluteFile(), mapped.getAbsoluteFile());
|
||||||
|
assertEquals(dto.getStatus(), mapped.getStatus());
|
||||||
|
assertEquals(dto.getSelectedResolution(), mapped.getSelectedResolution());
|
||||||
|
assertEquals(dto.getProgress(), mapped.getProgress());
|
||||||
|
assertEquals(dto.getStartDate(), mapped.getStartDate());
|
||||||
|
assertEquals(dto.getNote(), mapped.getNote());
|
||||||
|
assertEquals(dto.isPinned(), mapped.isPinned());
|
||||||
|
assertEquals(dto.isSingleFile(), mapped.isSingleFile());
|
||||||
|
assertEquals(dto.getSizeInByte(), mapped.getSizeInByte());
|
||||||
|
assertEquals(dto.getPostProcessedFile(), mapped.getPostProcessedFile());
|
||||||
|
assertEquals(dto.getMetaDataFile(), mapped.getMetaDataFile());
|
||||||
|
assertEquals(dto.getAssociatedFiles(), mapped.getAssociatedFiles());
|
||||||
|
assertEquals(dto.getModel().getName(), mapped.getModel().getName());
|
||||||
|
assertEquals(dto.getModel().getUrl().toString(), mapped.getModel().getUrl());
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,9 +1,11 @@
|
||||||
package ctbrec.recorder;
|
package ctbrec.recorder;
|
||||||
|
|
||||||
import ctbrec.*;
|
import ctbrec.*;
|
||||||
|
import ctbrec.io.json.mapper.ModelMapper;
|
||||||
import ctbrec.recorder.download.RecordingProcess;
|
import ctbrec.recorder.download.RecordingProcess;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.mapstruct.factory.Mappers;
|
||||||
import org.mockito.MockedStatic;
|
import org.mockito.MockedStatic;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -15,6 +17,7 @@ import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static java.time.temporal.ChronoUnit.HOURS;
|
import static java.time.temporal.ChronoUnit.HOURS;
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
@ -169,9 +172,9 @@ class RecordingPreconditionsTest {
|
||||||
|
|
||||||
var recorder = mock(SimplifiedLocalRecorder.class);
|
var recorder = mock(SimplifiedLocalRecorder.class);
|
||||||
List<Model> modelsToRecord = new LinkedList<>();
|
List<Model> modelsToRecord = new LinkedList<>();
|
||||||
settings.models = modelsToRecord;
|
|
||||||
modelsToRecord.add(theOtherOne);
|
modelsToRecord.add(theOtherOne);
|
||||||
modelsToRecord.add(mockita);
|
modelsToRecord.add(mockita);
|
||||||
|
settings.models = modelsToRecord.stream().map(Mappers.getMapper(ModelMapper.class)::toDto).collect(Collectors.toList());
|
||||||
when(recorder.isRunning()).thenReturn(true);
|
when(recorder.isRunning()).thenReturn(true);
|
||||||
when(recorder.getModels()).thenReturn(modelsToRecord);
|
when(recorder.getModels()).thenReturn(modelsToRecord);
|
||||||
when(recorder.notEnoughSpaceForRecording()).thenReturn(false);
|
when(recorder.notEnoughSpaceForRecording()).thenReturn(false);
|
||||||
|
@ -197,8 +200,8 @@ class RecordingPreconditionsTest {
|
||||||
|
|
||||||
var recorder = mock(SimplifiedLocalRecorder.class);
|
var recorder = mock(SimplifiedLocalRecorder.class);
|
||||||
List<Model> modelsToRecord = new LinkedList<>();
|
List<Model> modelsToRecord = new LinkedList<>();
|
||||||
settings.models = modelsToRecord;
|
|
||||||
modelsToRecord.add(mockita);
|
modelsToRecord.add(mockita);
|
||||||
|
settings.models = modelsToRecord.stream().map(Mappers.getMapper(ModelMapper.class)::toDto).collect(Collectors.toList());
|
||||||
when(recorder.isRunning()).thenReturn(true);
|
when(recorder.isRunning()).thenReturn(true);
|
||||||
when(recorder.getModels()).thenReturn(modelsToRecord);
|
when(recorder.getModels()).thenReturn(modelsToRecord);
|
||||||
when(recorder.notEnoughSpaceForRecording()).thenReturn(false);
|
when(recorder.notEnoughSpaceForRecording()).thenReturn(false);
|
||||||
|
@ -217,8 +220,8 @@ class RecordingPreconditionsTest {
|
||||||
|
|
||||||
var recorder = mock(SimplifiedLocalRecorder.class);
|
var recorder = mock(SimplifiedLocalRecorder.class);
|
||||||
List<Model> modelsToRecord = new LinkedList<>();
|
List<Model> modelsToRecord = new LinkedList<>();
|
||||||
settings.models = modelsToRecord;
|
|
||||||
modelsToRecord.add(mockita);
|
modelsToRecord.add(mockita);
|
||||||
|
settings.models = modelsToRecord.stream().map(Mappers.getMapper(ModelMapper.class)::toDto).collect(Collectors.toList());
|
||||||
when(recorder.isRunning()).thenReturn(true);
|
when(recorder.isRunning()).thenReturn(true);
|
||||||
when(recorder.getModels()).thenReturn(modelsToRecord);
|
when(recorder.getModels()).thenReturn(modelsToRecord);
|
||||||
when(recorder.notEnoughSpaceForRecording()).thenReturn(false);
|
when(recorder.notEnoughSpaceForRecording()).thenReturn(false);
|
||||||
|
@ -252,8 +255,8 @@ class RecordingPreconditionsTest {
|
||||||
|
|
||||||
var recorder = mock(SimplifiedLocalRecorder.class);
|
var recorder = mock(SimplifiedLocalRecorder.class);
|
||||||
List<Model> modelsToRecord = new LinkedList<>();
|
List<Model> modelsToRecord = new LinkedList<>();
|
||||||
settings.models = modelsToRecord;
|
|
||||||
modelsToRecord.add(mockita);
|
modelsToRecord.add(mockita);
|
||||||
|
settings.models = modelsToRecord.stream().map(Mappers.getMapper(ModelMapper.class)::toDto).collect(Collectors.toList());
|
||||||
when(recorder.isRunning()).thenReturn(true);
|
when(recorder.isRunning()).thenReturn(true);
|
||||||
when(recorder.getModels()).thenReturn(modelsToRecord);
|
when(recorder.getModels()).thenReturn(modelsToRecord);
|
||||||
when(recorder.notEnoughSpaceForRecording()).thenReturn(false);
|
when(recorder.notEnoughSpaceForRecording()).thenReturn(false);
|
||||||
|
@ -291,8 +294,8 @@ class RecordingPreconditionsTest {
|
||||||
|
|
||||||
var recorder = mock(SimplifiedLocalRecorder.class);
|
var recorder = mock(SimplifiedLocalRecorder.class);
|
||||||
List<Model> modelsToRecord = new LinkedList<>();
|
List<Model> modelsToRecord = new LinkedList<>();
|
||||||
settings.models = modelsToRecord;
|
|
||||||
modelsToRecord.add(mockita);
|
modelsToRecord.add(mockita);
|
||||||
|
settings.models = modelsToRecord.stream().map(Mappers.getMapper(ModelMapper.class)::toDto).collect(Collectors.toList());
|
||||||
when(recorder.isRunning()).thenReturn(true);
|
when(recorder.isRunning()).thenReturn(true);
|
||||||
when(recorder.getModels()).thenReturn(modelsToRecord);
|
when(recorder.getModels()).thenReturn(modelsToRecord);
|
||||||
when(recorder.notEnoughSpaceForRecording()).thenReturn(false);
|
when(recorder.notEnoughSpaceForRecording()).thenReturn(false);
|
||||||
|
@ -334,10 +337,10 @@ class RecordingPreconditionsTest {
|
||||||
when(mockita.getPriority()).thenReturn(100);
|
when(mockita.getPriority()).thenReturn(100);
|
||||||
var recorder = mock(SimplifiedLocalRecorder.class);
|
var recorder = mock(SimplifiedLocalRecorder.class);
|
||||||
List<Model> modelsToRecord = new LinkedList<>();
|
List<Model> modelsToRecord = new LinkedList<>();
|
||||||
settings.models = modelsToRecord;
|
|
||||||
settings.timeoutRecordingStartingAt = LocalTime.now().minusHours(1).truncatedTo(ChronoUnit.MINUTES);
|
settings.timeoutRecordingStartingAt = LocalTime.now().minusHours(1).truncatedTo(ChronoUnit.MINUTES);
|
||||||
settings.timeoutRecordingEndingAt = LocalTime.now().plusHours(1).truncatedTo(ChronoUnit.MINUTES);
|
settings.timeoutRecordingEndingAt = LocalTime.now().plusHours(1).truncatedTo(ChronoUnit.MINUTES);
|
||||||
modelsToRecord.add(mockita);
|
modelsToRecord.add(mockita);
|
||||||
|
settings.models = modelsToRecord.stream().map(Mappers.getMapper(ModelMapper.class)::toDto).collect(Collectors.toList());
|
||||||
when(recorder.isRunning()).thenReturn(true);
|
when(recorder.isRunning()).thenReturn(true);
|
||||||
when(recorder.getModels()).thenReturn(modelsToRecord);
|
when(recorder.getModels()).thenReturn(modelsToRecord);
|
||||||
when(recorder.notEnoughSpaceForRecording()).thenReturn(false);
|
when(recorder.notEnoughSpaceForRecording()).thenReturn(false);
|
||||||
|
|
|
@ -46,7 +46,7 @@ class AbstractPlaceholderAwarePostProcessorTest extends AbstractPpTest {
|
||||||
@Test
|
@Test
|
||||||
void testModelNameReplacement() {
|
void testModelNameReplacement() {
|
||||||
String input = "asdf_${modelName}_asdf";
|
String input = "asdf_${modelName}_asdf";
|
||||||
assertEquals("asdf_Mockita Boobilicious_asdf", placeHolderAwarePp.fillInPlaceHolders(input, createPostProcessingContext(rec, null, config)));
|
assertEquals("asdf_Mockita_Boobilicious_asdf", placeHolderAwarePp.fillInPlaceHolders(input, createPostProcessingContext(rec, null, config)));
|
||||||
input = "asdf_${modelDisplayName}_asdf";
|
input = "asdf_${modelDisplayName}_asdf";
|
||||||
assertEquals("asdf_Mockita Boobilicious_asdf", placeHolderAwarePp.fillInPlaceHolders(input, createPostProcessingContext(rec, null, config)));
|
assertEquals("asdf_Mockita Boobilicious_asdf", placeHolderAwarePp.fillInPlaceHolders(input, createPostProcessingContext(rec, null, config)));
|
||||||
input = "asdf_$sanitize(${modelName})_asdf";
|
input = "asdf_$sanitize(${modelName})_asdf";
|
||||||
|
@ -170,7 +170,7 @@ class AbstractPlaceholderAwarePostProcessorTest extends AbstractPpTest {
|
||||||
@Test
|
@Test
|
||||||
void testFunctionCalls() {
|
void testFunctionCalls() {
|
||||||
String input = "$upper(${modelName})";
|
String input = "$upper(${modelName})";
|
||||||
assertEquals("MOCKITA BOOBILICIOUS", placeHolderAwarePp.fillInPlaceHolders(input, createPostProcessingContext(rec, null, config)));
|
assertEquals("MOCKITA_BOOBILICIOUS", placeHolderAwarePp.fillInPlaceHolders(input, createPostProcessingContext(rec, null, config)));
|
||||||
|
|
||||||
input = "$upper($orElse(${doesNotExist},mockita boobilicious))";
|
input = "$upper($orElse(${doesNotExist},mockita boobilicious))";
|
||||||
assertEquals("MOCKITA BOOBILICIOUS", placeHolderAwarePp.fillInPlaceHolders(input, createPostProcessingContext(rec, null, config)));
|
assertEquals("MOCKITA BOOBILICIOUS", placeHolderAwarePp.fillInPlaceHolders(input, createPostProcessingContext(rec, null, config)));
|
||||||
|
|
|
@ -76,7 +76,7 @@ public abstract class AbstractPpTest {
|
||||||
|
|
||||||
Model mockModel() {
|
Model mockModel() {
|
||||||
Site site = new Stripchat();
|
Site site = new Stripchat();
|
||||||
Model model = site.createModel("Mockita Boobilicious");
|
Model model = site.createModel("Mockita_Boobilicious");
|
||||||
model.setDisplayName("Mockita Boobilicious");
|
model.setDisplayName("Mockita Boobilicious");
|
||||||
return model;
|
return model;
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,12 +20,12 @@ import static org.mockito.Mockito.*;
|
||||||
class DeleteTooShortTest extends AbstractPpTest {
|
class DeleteTooShortTest extends AbstractPpTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void tooShortSingleFileRecShouldBeDeleted() throws IOException, InterruptedException {
|
void tooShortSingleFileRecShouldBeDeleted() throws IOException {
|
||||||
testProcess(original);
|
testProcess(original);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void tooShortDirectoryRecShouldBeDeleted() throws IOException, InterruptedException {
|
void tooShortDirectoryRecShouldBeDeleted() throws IOException {
|
||||||
testProcess(originalDir);
|
testProcess(originalDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,7 +56,7 @@ class DeleteTooShortTest extends AbstractPpTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testDisabledWithSingleFile() throws IOException, InterruptedException {
|
void testDisabledWithSingleFile() throws IOException {
|
||||||
Recording rec = createRec(original);
|
Recording rec = createRec(original);
|
||||||
Config config = mockConfig();
|
Config config = mockConfig();
|
||||||
RecordingManager recordingManager = new RecordingManager(config, Collections.emptyList());
|
RecordingManager recordingManager = new RecordingManager(config, Collections.emptyList());
|
||||||
|
@ -73,7 +73,7 @@ class DeleteTooShortTest extends AbstractPpTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void longEnoughVideoShouldStay() throws IOException, InterruptedException {
|
void longEnoughVideoShouldStay() throws IOException {
|
||||||
Recording rec = createRec(original);
|
Recording rec = createRec(original);
|
||||||
Config config = mockConfig();
|
Config config = mockConfig();
|
||||||
RecordingManager recordingManager = new RecordingManager(config, Collections.emptyList());
|
RecordingManager recordingManager = new RecordingManager(config, Collections.emptyList());
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
package ctbrec.recorder.postprocessing;
|
package ctbrec.recorder.postprocessing;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Collections;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
import ctbrec.Config;
|
import ctbrec.Config;
|
||||||
import ctbrec.Recording;
|
import ctbrec.Recording;
|
||||||
import ctbrec.recorder.RecordingManager;
|
import ctbrec.recorder.RecordingManager;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
class RemoveKeepFileTest extends AbstractPpTest {
|
class RemoveKeepFileTest extends AbstractPpTest {
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,9 @@
|
||||||
<maven.compiler.target>17</maven.compiler.target>
|
<maven.compiler.target>17</maven.compiler.target>
|
||||||
<version.javafx>20.0.1</version.javafx>
|
<version.javafx>20.0.1</version.javafx>
|
||||||
<version.junit>5.7.2</version.junit>
|
<version.junit>5.7.2</version.junit>
|
||||||
|
<jackson.version>2.15.1</jackson.version>
|
||||||
|
<org.mapstruct.version>1.5.3.Final</org.mapstruct.version>
|
||||||
|
<lombok.version>1.18.24</lombok.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
@ -67,11 +70,6 @@
|
||||||
<artifactId>okhttp</artifactId>
|
<artifactId>okhttp</artifactId>
|
||||||
<version>4.9.3</version>
|
<version>4.9.3</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>com.squareup.moshi</groupId>
|
|
||||||
<artifactId>moshi</artifactId>
|
|
||||||
<version>1.13.0</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.json</groupId>
|
<groupId>org.json</groupId>
|
||||||
<artifactId>json</artifactId>
|
<artifactId>json</artifactId>
|
||||||
|
@ -123,6 +121,21 @@
|
||||||
<artifactId>commons-io</artifactId>
|
<artifactId>commons-io</artifactId>
|
||||||
<version>2.11.0</version>
|
<version>2.11.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.fasterxml.jackson.core</groupId>
|
||||||
|
<artifactId>jackson-databind</artifactId>
|
||||||
|
<version>${jackson.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||||
|
<artifactId>jackson-datatype-jsr310</artifactId>
|
||||||
|
<version>${jackson.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.mapstruct</groupId>
|
||||||
|
<artifactId>mapstruct</artifactId>
|
||||||
|
<version>${org.mapstruct.version}</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.junit.jupiter</groupId>
|
<groupId>org.junit.jupiter</groupId>
|
||||||
<artifactId>junit-jupiter-api</artifactId>
|
<artifactId>junit-jupiter-api</artifactId>
|
||||||
|
@ -159,7 +172,7 @@
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.projectlombok</groupId>
|
<groupId>org.projectlombok</groupId>
|
||||||
<artifactId>lombok</artifactId>
|
<artifactId>lombok</artifactId>
|
||||||
<version>1.18.24</version>
|
<version>${lombok.version}</version>
|
||||||
<optional>true</optional>
|
<optional>true</optional>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
@ -186,6 +199,12 @@
|
||||||
<artifactId>lombok</artifactId>
|
<artifactId>lombok</artifactId>
|
||||||
<optional>true</optional>
|
<optional>true</optional>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.mapstruct</groupId>
|
||||||
|
<artifactId>mapstruct-processor</artifactId>
|
||||||
|
<version>${org.mapstruct.version}</version>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
|
|
|
@ -31,6 +31,7 @@ import ctbrec.sites.stripchat.Stripchat;
|
||||||
import ctbrec.sites.xlovecam.XloveCam;
|
import ctbrec.sites.xlovecam.XloveCam;
|
||||||
import org.eclipse.jetty.security.*;
|
import org.eclipse.jetty.security.*;
|
||||||
import org.eclipse.jetty.security.authentication.BasicAuthenticator;
|
import org.eclipse.jetty.security.authentication.BasicAuthenticator;
|
||||||
|
import org.eclipse.jetty.server.Request;
|
||||||
import org.eclipse.jetty.server.*;
|
import org.eclipse.jetty.server.*;
|
||||||
import org.eclipse.jetty.server.handler.ErrorHandler;
|
import org.eclipse.jetty.server.handler.ErrorHandler;
|
||||||
import org.eclipse.jetty.server.handler.HandlerList;
|
import org.eclipse.jetty.server.handler.HandlerList;
|
||||||
|
|
|
@ -1,50 +1,42 @@
|
||||||
package ctbrec.recorder.server;
|
package ctbrec.recorder.server;
|
||||||
|
|
||||||
import static javax.servlet.http.HttpServletResponse.*;
|
|
||||||
|
|
||||||
import java.io.File;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import ctbrec.*;
|
||||||
|
import ctbrec.io.BandwidthMeter;
|
||||||
|
import ctbrec.io.json.ObjectMapperFactory;
|
||||||
|
import ctbrec.io.json.mapper.ModelMapper;
|
||||||
|
import ctbrec.io.json.mapper.RecordingMapper;
|
||||||
|
import ctbrec.recorder.Recorder;
|
||||||
|
import ctbrec.recorder.server.io.json.dto.RequestDto;
|
||||||
|
import ctbrec.recorder.server.io.json.mapper.RequestMapper;
|
||||||
|
import ctbrec.sites.Site;
|
||||||
|
import ctbrec.sites.SiteUtil;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
import org.mapstruct.factory.Mappers;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
import java.security.InvalidKeyException;
|
import java.security.InvalidKeyException;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.Iterator;
|
import java.util.*;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
import javax.servlet.ServletException;
|
import static javax.servlet.http.HttpServletResponse.*;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
|
|
||||||
import org.json.JSONObject;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import com.squareup.moshi.JsonAdapter;
|
|
||||||
import com.squareup.moshi.Moshi;
|
|
||||||
|
|
||||||
import ctbrec.Config;
|
|
||||||
import ctbrec.GlobalThreadPool;
|
|
||||||
import ctbrec.Model;
|
|
||||||
import ctbrec.ModelGroup;
|
|
||||||
import ctbrec.Recording;
|
|
||||||
import ctbrec.io.BandwidthMeter;
|
|
||||||
import ctbrec.io.FileJsonAdapter;
|
|
||||||
import ctbrec.io.InstantJsonAdapter;
|
|
||||||
import ctbrec.io.ModelJsonAdapter;
|
|
||||||
import ctbrec.io.UuidJSonAdapter;
|
|
||||||
import ctbrec.recorder.Recorder;
|
|
||||||
import ctbrec.sites.Site;
|
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
public class RecorderServlet extends AbstractCtbrecServlet {
|
public class RecorderServlet extends AbstractCtbrecServlet {
|
||||||
|
|
||||||
private static final transient Logger LOG = LoggerFactory.getLogger(RecorderServlet.class);
|
private final ObjectMapper mapper = ObjectMapperFactory.getMapper();
|
||||||
|
private final ModelMapper modelMapper = Mappers.getMapper(ModelMapper.class);
|
||||||
|
private final RecordingMapper recordingMapper = Mappers.getMapper(RecordingMapper.class);
|
||||||
|
|
||||||
private Recorder recorder;
|
private final Recorder recorder;
|
||||||
|
private final List<Site> sites;
|
||||||
|
|
||||||
private List<Site> sites;
|
|
||||||
|
|
||||||
public RecorderServlet(Recorder recorder, List<Site> sites) {
|
public RecorderServlet(Recorder recorder, List<Site> sites) {
|
||||||
this.recorder = recorder;
|
this.recorder = recorder;
|
||||||
|
@ -52,7 +44,7 @@ public class RecorderServlet extends AbstractCtbrecServlet {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
|
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
|
||||||
resp.setStatus(SC_OK);
|
resp.setStatus(SC_OK);
|
||||||
resp.setContentType("application/json");
|
resp.setContentType("application/json");
|
||||||
|
|
||||||
|
@ -66,221 +58,208 @@ public class RecorderServlet extends AbstractCtbrecServlet {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG.debug("Request: {}", json);
|
log.debug("Request: {}", json);
|
||||||
Moshi moshi = new Moshi.Builder()
|
Request request = Mappers.getMapper(RequestMapper.class).toRequest(mapper.readValue(json, RequestDto.class));
|
||||||
.add(Instant.class, new InstantJsonAdapter())
|
Model model = request.getModel();
|
||||||
.add(Model.class, new ModelJsonAdapter(sites))
|
Optional.ofNullable(model).ifPresent(m -> SiteUtil.getSiteForModel(sites, m).ifPresent(m::setSite));
|
||||||
.add(File.class, new FileJsonAdapter())
|
Recording recording = request.getRecording();
|
||||||
.add(UUID.class, new UuidJSonAdapter())
|
if (request.getAction() == null) {
|
||||||
.build();
|
|
||||||
JsonAdapter<Request> requestAdapter = moshi.adapter(Request.class);
|
|
||||||
Request request = requestAdapter.fromJson(json);
|
|
||||||
if (request.action == null) {
|
|
||||||
sendError(resp, SC_BAD_REQUEST, "{\"status\": \"error\", \"msg\": \"action is missing\"}");
|
sendError(resp, SC_BAD_REQUEST, "{\"status\": \"error\", \"msg\": \"action is missing\"}");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
switch (request.action) {
|
switch (request.getAction()) {
|
||||||
case "start":
|
case "start":
|
||||||
LOG.debug("Starting recording for model {} - {}", request.model.getName(), request.model.getUrl());
|
log.debug("Starting recording for model {} - {}", model.getName(), model.getUrl());
|
||||||
LOG.trace("Model marked: {}", request.model.isMarkedForLaterRecording());
|
log.trace("Model marked: {}", model.isMarkedForLaterRecording());
|
||||||
LOG.trace("Model paused: {}", request.model.isSuspended());
|
log.trace("Model paused: {}", model.isSuspended());
|
||||||
LOG.trace("Model until: {}", request.model.getRecordUntil().equals(Instant.ofEpochMilli(Model.RECORD_INDEFINITELY)) ? "no limit" : request.model.getRecordUntil());
|
log.trace("Model until: {}", model.getRecordUntil().equals(Instant.ofEpochMilli(Model.RECORD_INDEFINITELY)) ? "no limit" : model.getRecordUntil());
|
||||||
LOG.trace("Model after: {}", request.model.getRecordUntilSubsequentAction());
|
log.trace("Model after: {}", model.getRecordUntilSubsequentAction());
|
||||||
recorder.addModel(request.model);
|
recorder.addModel(model);
|
||||||
String response = "{\"status\": \"success\", \"msg\": \"Recording started\"}";
|
String response = "{\"status\": \"success\", \"msg\": \"Recording started\"}";
|
||||||
responseWriter.write(response);
|
responseWriter.write(response);
|
||||||
break;
|
break;
|
||||||
case "startByUrl":
|
case "startByUrl":
|
||||||
LOG.debug("Starting recording for model {}", request.model.getUrl());
|
log.debug("Starting recording for model {}", model.getUrl());
|
||||||
startByUrl(request);
|
startByUrl(request);
|
||||||
response = "{\"status\": \"success\", \"msg\": \"Recording started\"}";
|
response = "{\"status\": \"success\", \"msg\": \"Recording started\"}";
|
||||||
responseWriter.write(response);
|
responseWriter.write(response);
|
||||||
break;
|
break;
|
||||||
case "startByName":
|
case "startByName":
|
||||||
LOG.debug("Starting recording for model {}", request.model.getUrl());
|
log.debug("Starting recording for model {}", model.getUrl());
|
||||||
startByName(request);
|
startByName(request);
|
||||||
response = "{\"status\": \"success\", \"msg\": \"Recording started\"}";
|
response = "{\"status\": \"success\", \"msg\": \"Recording started\"}";
|
||||||
responseWriter.write(response);
|
responseWriter.write(response);
|
||||||
break;
|
break;
|
||||||
case "stop":
|
case "stop":
|
||||||
GlobalThreadPool.submit(() -> {
|
GlobalThreadPool.submit(() -> {
|
||||||
try {
|
try {
|
||||||
recorder.stopRecording(request.model);
|
recorder.stopRecording(model);
|
||||||
} catch (InvalidKeyException | NoSuchAlgorithmException | IllegalStateException | IOException e) {
|
} catch (InvalidKeyException | NoSuchAlgorithmException | IllegalStateException | IOException e) {
|
||||||
LOG.error("Couldn't stop recording for model {}", request.model, e);
|
log.error("Couldn't stop recording for model {}", model, e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
response = "{\"status\": \"success\", \"msg\": \"Stopping recording\"}";
|
||||||
|
responseWriter.write(response);
|
||||||
|
break;
|
||||||
|
case "stopAt":
|
||||||
|
GlobalThreadPool.submit(() -> {
|
||||||
|
try {
|
||||||
|
recorder.stopRecordingAt(model);
|
||||||
|
} catch (InvalidKeyException | NoSuchAlgorithmException | IllegalStateException | IOException e) {
|
||||||
|
log.error("Couldn't stop recording for model {}", model, e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
response = "{\"status\": \"success\", \"msg\": \"Stopping recording\"}";
|
||||||
|
responseWriter.write(response);
|
||||||
|
break;
|
||||||
|
case "list":
|
||||||
|
responseWriter.write("{\"status\": \"success\", \"msg\": \"List of models\", \"models\": [");
|
||||||
|
for (Iterator<Model> iterator = recorder.getModels().iterator(); iterator.hasNext(); ) {
|
||||||
|
Model m = iterator.next();
|
||||||
|
responseWriter.write(mapper.writeValueAsString(modelMapper.toDto(m)));
|
||||||
|
if (iterator.hasNext()) {
|
||||||
|
responseWriter.write(',');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
responseWriter.write("]}");
|
||||||
response = "{\"status\": \"success\", \"msg\": \"Stopping recording\"}";
|
break;
|
||||||
responseWriter.write(response);
|
case "listOnline":
|
||||||
break;
|
responseWriter.write("{\"status\": \"success\", \"msg\": \"List of online models\", \"models\": [");
|
||||||
case "stopAt":
|
for (Iterator<Model> iterator = recorder.getOnlineModels().iterator(); iterator.hasNext(); ) {
|
||||||
GlobalThreadPool.submit(() -> {
|
Model m = iterator.next();
|
||||||
try {
|
responseWriter.write(mapper.writeValueAsString(modelMapper.toDto(m)));
|
||||||
recorder.stopRecordingAt(request.model);
|
if (iterator.hasNext()) {
|
||||||
} catch (InvalidKeyException | NoSuchAlgorithmException | IllegalStateException | IOException e) {
|
responseWriter.write(',');
|
||||||
LOG.error("Couldn't stop recording for model {}", request.model, e);
|
}
|
||||||
}
|
}
|
||||||
});
|
responseWriter.write("]}");
|
||||||
response = "{\"status\": \"success\", \"msg\": \"Stopping recording\"}";
|
break;
|
||||||
responseWriter.write(response);
|
case "recordings":
|
||||||
break;
|
responseWriter.write("{\"status\": \"success\", \"msg\": \"List of recordings\", \"recordings\": [");
|
||||||
case "list":
|
List<Recording> recordings = recorder.getRecordings();
|
||||||
responseWriter.write("{\"status\": \"success\", \"msg\": \"List of models\", \"models\": [");
|
for (Iterator<Recording> iterator = recordings.iterator(); iterator.hasNext(); ) {
|
||||||
JsonAdapter<Model> modelAdapter = new ModelJsonAdapter();
|
Recording rec = iterator.next();
|
||||||
List<Model> models = recorder.getModels();
|
String recJSON = mapper.writeValueAsString(recordingMapper.toDto(rec));
|
||||||
for (Iterator<Model> iterator = models.iterator(); iterator.hasNext();) {
|
log.debug("Rec: {}", recJSON);
|
||||||
Model model = iterator.next();
|
responseWriter.write(recJSON);
|
||||||
responseWriter.write(modelAdapter.toJson(model));
|
if (iterator.hasNext()) {
|
||||||
if (iterator.hasNext()) {
|
responseWriter.write(',');
|
||||||
responseWriter.write(',');
|
}
|
||||||
}
|
}
|
||||||
}
|
responseWriter.write("]}");
|
||||||
responseWriter.write("]}");
|
break;
|
||||||
break;
|
case "delete":
|
||||||
case "listOnline":
|
recorder.delete(recording);
|
||||||
responseWriter.write("{\"status\": \"success\", \"msg\": \"List of online models\", \"models\": [");
|
responseWriter.write("{\"status\": \"success\", \"msg\": \"List of recordings\", \"recordings\": [");
|
||||||
modelAdapter = new ModelJsonAdapter();
|
responseWriter.write(mapper.writeValueAsString(recordingMapper.toDto(recording)));
|
||||||
models = recorder.getOnlineModels();
|
responseWriter.write("]}");
|
||||||
for (Iterator<Model> iterator = models.iterator(); iterator.hasNext();) {
|
break;
|
||||||
Model model = iterator.next();
|
case "pin":
|
||||||
responseWriter.write(modelAdapter.toJson(model));
|
recorder.pin(recording);
|
||||||
if (iterator.hasNext()) {
|
responseWriter.write("{\"status\": \"success\", \"msg\": \"List of recordings\", \"recordings\": [");
|
||||||
responseWriter.write(',');
|
responseWriter.write(mapper.writeValueAsString(recordingMapper.toDto(recording)));
|
||||||
}
|
responseWriter.write("]}");
|
||||||
}
|
break;
|
||||||
responseWriter.write("]}");
|
case "unpin":
|
||||||
break;
|
recorder.unpin(recording);
|
||||||
case "recordings":
|
responseWriter.write("{\"status\": \"success\", \"msg\": \"Note saved\", \"recordings\": [");
|
||||||
responseWriter.write("{\"status\": \"success\", \"msg\": \"List of recordings\", \"recordings\": [");
|
responseWriter.write(mapper.writeValueAsString(recordingMapper.toDto(recording)));
|
||||||
JsonAdapter<Recording> recAdapter = moshi.adapter(Recording.class);
|
responseWriter.write("]}");
|
||||||
List<Recording> recordings = recorder.getRecordings();
|
break;
|
||||||
for (Iterator<Recording> iterator = recordings.iterator(); iterator.hasNext();) {
|
case "setNote":
|
||||||
Recording recording = iterator.next();
|
recorder.setNote(recording, recording.getNote());
|
||||||
String recJSON = recAdapter.toJson(recording);
|
responseWriter.write("{\"status\": \"success\", \"msg\": \"List of recordings\", \"recordings\": [");
|
||||||
LOG.debug("Rec: {}", recJSON);
|
responseWriter.write(mapper.writeValueAsString(recordingMapper.toDto(recording)));
|
||||||
responseWriter.write(recJSON);
|
responseWriter.write("]}");
|
||||||
if (iterator.hasNext()) {
|
break;
|
||||||
responseWriter.write(',');
|
case "rerunPostProcessing":
|
||||||
}
|
recorder.rerunPostProcessing(recording);
|
||||||
}
|
responseWriter.write("{\"status\": \"success\", \"msg\": \"Post-Processing triggered\"}");
|
||||||
responseWriter.write("]}");
|
break;
|
||||||
break;
|
case "switch":
|
||||||
case "delete":
|
recorder.switchStreamSource(model);
|
||||||
recorder.delete(request.recording);
|
response = "{\"status\": \"success\", \"msg\": \"Resolution switched\"}";
|
||||||
recAdapter = moshi.adapter(Recording.class);
|
responseWriter.write(response);
|
||||||
responseWriter.write("{\"status\": \"success\", \"msg\": \"List of recordings\", \"recordings\": [");
|
break;
|
||||||
responseWriter.write(recAdapter.toJson(request.recording));
|
case "suspend":
|
||||||
responseWriter.write("]}");
|
log.debug("Suspend recording for model {} - {}", model.getName(), model.getUrl());
|
||||||
break;
|
GlobalThreadPool.submit(() -> {
|
||||||
case "pin":
|
try {
|
||||||
recorder.pin(request.recording);
|
recorder.suspendRecording(model);
|
||||||
recAdapter = moshi.adapter(Recording.class);
|
} catch (InvalidKeyException | NoSuchAlgorithmException | IllegalStateException | IOException e) {
|
||||||
responseWriter.write("{\"status\": \"success\", \"msg\": \"List of recordings\", \"recordings\": [");
|
log.error("Couldn't suspend recording for model {}", model, e);
|
||||||
responseWriter.write(recAdapter.toJson(request.recording));
|
}
|
||||||
responseWriter.write("]}");
|
});
|
||||||
break;
|
response = "{\"status\": \"success\", \"msg\": \"Suspending recording\"}";
|
||||||
case "unpin":
|
responseWriter.write(response);
|
||||||
recorder.unpin(request.recording);
|
break;
|
||||||
recAdapter = moshi.adapter(Recording.class);
|
case "resume":
|
||||||
responseWriter.write("{\"status\": \"success\", \"msg\": \"Note saved\", \"recordings\": [");
|
log.debug("Resume recording for model {} - {}", model.getName(), model.getUrl());
|
||||||
responseWriter.write(recAdapter.toJson(request.recording));
|
recorder.resumeRecording(model);
|
||||||
responseWriter.write("]}");
|
response = "{\"status\": \"success\", \"msg\": \"Recording resumed\"}";
|
||||||
break;
|
responseWriter.write(response);
|
||||||
case "setNote":
|
break;
|
||||||
recorder.setNote(request.recording, request.recording.getNote());
|
case "space":
|
||||||
recAdapter = moshi.adapter(Recording.class);
|
JSONObject jsonResponse = new JSONObject();
|
||||||
responseWriter.write("{\"status\": \"success\", \"msg\": \"List of recordings\", \"recordings\": [");
|
jsonResponse.put("status", "success");
|
||||||
responseWriter.write(recAdapter.toJson(request.recording));
|
jsonResponse.put("spaceTotal", recorder.getTotalSpaceBytes());
|
||||||
responseWriter.write("]}");
|
jsonResponse.put("spaceFree", recorder.getFreeSpaceBytes());
|
||||||
break;
|
jsonResponse.put("throughput", BandwidthMeter.getThroughput());
|
||||||
case "rerunPostProcessing":
|
jsonResponse.put("throughputTimeframe", BandwidthMeter.getTimeframe().toMillis());
|
||||||
recorder.rerunPostProcessing(request.recording);
|
jsonResponse.put("minimumSpaceLeftInBytes", Config.getInstance().getSettings().minimumSpaceLeftInBytes);
|
||||||
responseWriter.write("{\"status\": \"success\", \"msg\": \"Post-Processing triggered\"}");
|
responseWriter.write(jsonResponse.toString());
|
||||||
break;
|
break;
|
||||||
case "switch":
|
case "changePriority":
|
||||||
recorder.switchStreamSource(request.model);
|
recorder.priorityChanged(model);
|
||||||
response = "{\"status\": \"success\", \"msg\": \"Resolution switched\"}";
|
response = "{\"status\": \"success\"}";
|
||||||
responseWriter.write(response);
|
responseWriter.write(response);
|
||||||
break;
|
break;
|
||||||
case "suspend":
|
case "pauseRecorder":
|
||||||
LOG.debug("Suspend recording for model {} - {}", request.model.getName(), request.model.getUrl());
|
recorder.pause();
|
||||||
GlobalThreadPool.submit(() -> {
|
response = "{\"status\": \"success\"}";
|
||||||
try {
|
responseWriter.write(response);
|
||||||
recorder.suspendRecording(request.model);
|
break;
|
||||||
} catch (InvalidKeyException | NoSuchAlgorithmException | IllegalStateException | IOException e) {
|
case "resumeRecorder":
|
||||||
LOG.error("Couldn't suspend recording for model {}", request.model, e);
|
recorder.resume();
|
||||||
}
|
response = "{\"status\": \"success\"}";
|
||||||
});
|
responseWriter.write(response);
|
||||||
response = "{\"status\": \"success\", \"msg\": \"Suspending recording\"}";
|
break;
|
||||||
responseWriter.write(response);
|
case "saveModelGroup":
|
||||||
break;
|
recorder.saveModelGroup(request.getModelGroup());
|
||||||
case "resume":
|
sendModelGroups(resp, recorder.getModelGroups());
|
||||||
LOG.debug("Resume recording for model {} - {}", request.model.getName(), request.model.getUrl());
|
break;
|
||||||
recorder.resumeRecording(request.model);
|
case "deleteModelGroup":
|
||||||
response = "{\"status\": \"success\", \"msg\": \"Recording resumed\"}";
|
recorder.deleteModelGroup(request.getModelGroup());
|
||||||
responseWriter.write(response);
|
sendModelGroups(resp, recorder.getModelGroups());
|
||||||
break;
|
break;
|
||||||
case "space":
|
case "listModelGroups":
|
||||||
JSONObject jsonResponse = new JSONObject();
|
sendModelGroups(resp, recorder.getModelGroups());
|
||||||
jsonResponse.put("status", "success");
|
break;
|
||||||
jsonResponse.put("spaceTotal", recorder.getTotalSpaceBytes());
|
case "markForLater":
|
||||||
jsonResponse.put("spaceFree", recorder.getFreeSpaceBytes());
|
log.debug("Mark model {} for later recording", model.getName());
|
||||||
jsonResponse.put("throughput", BandwidthMeter.getThroughput());
|
response = "{\"status\": \"success\", \"msg\": \"Model marked for later recording\"}";
|
||||||
jsonResponse.put("throughputTimeframe", BandwidthMeter.getTimeframe().toMillis());
|
responseWriter.write(response);
|
||||||
jsonResponse.put("minimumSpaceLeftInBytes", Config.getInstance().getSettings().minimumSpaceLeftInBytes);
|
recorder.markForLaterRecording(model, true);
|
||||||
responseWriter.write(jsonResponse.toString());
|
break;
|
||||||
break;
|
case "unmarkForLater":
|
||||||
case "changePriority":
|
log.debug("Unmark model {} for later recording", model.getName());
|
||||||
recorder.priorityChanged(request.model);
|
response = "{\"status\": \"success\", \"msg\": \"Model has been unmarked\"}";
|
||||||
response = "{\"status\": \"success\"}";
|
responseWriter.write(response);
|
||||||
responseWriter.write(response);
|
recorder.markForLaterRecording(model, false);
|
||||||
break;
|
break;
|
||||||
case "pauseRecorder":
|
default:
|
||||||
recorder.pause();
|
sendError(resp, SC_BAD_REQUEST, "{\"status\": \"error\", \"msg\": \"Unknown action [" + request.getAction() + "]\"}");
|
||||||
response = "{\"status\": \"success\"}";
|
break;
|
||||||
responseWriter.write(response);
|
|
||||||
break;
|
|
||||||
case "resumeRecorder":
|
|
||||||
recorder.resume();
|
|
||||||
response = "{\"status\": \"success\"}";
|
|
||||||
responseWriter.write(response);
|
|
||||||
break;
|
|
||||||
case "saveModelGroup":
|
|
||||||
recorder.saveModelGroup(request.modelGroup);
|
|
||||||
sendModelGroups(resp, recorder.getModelGroups());
|
|
||||||
break;
|
|
||||||
case "deleteModelGroup":
|
|
||||||
recorder.deleteModelGroup(request.modelGroup);
|
|
||||||
sendModelGroups(resp, recorder.getModelGroups());
|
|
||||||
break;
|
|
||||||
case "listModelGroups":
|
|
||||||
sendModelGroups(resp, recorder.getModelGroups());
|
|
||||||
break;
|
|
||||||
case "markForLater":
|
|
||||||
LOG.debug("Mark model {} for later recording", request.model.getName());
|
|
||||||
response = "{\"status\": \"success\", \"msg\": \"Model marked for later recording\"}";
|
|
||||||
responseWriter.write(response);
|
|
||||||
recorder.markForLaterRecording(request.model, true);
|
|
||||||
break;
|
|
||||||
case "unmarkForLater":
|
|
||||||
LOG.debug("Unmark model {} for later recording", request.model.getName());
|
|
||||||
response = "{\"status\": \"success\", \"msg\": \"Model has been unmarked\"}";
|
|
||||||
responseWriter.write(response);
|
|
||||||
recorder.markForLaterRecording(request.model, false);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
sendError(resp, SC_BAD_REQUEST, "{\"status\": \"error\", \"msg\": \"Unknown action [" + request.action + "]\"}");
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
} catch(Exception e) {
|
} catch (Exception e) {
|
||||||
resp.setStatus(SC_INTERNAL_SERVER_ERROR);
|
resp.setStatus(SC_INTERNAL_SERVER_ERROR);
|
||||||
JSONObject response = new JSONObject();
|
JSONObject response = new JSONObject();
|
||||||
response.put("status", "error");
|
response.put("status", "error");
|
||||||
response.put("msg", e.getMessage());
|
response.put("msg", e.getMessage());
|
||||||
resp.getWriter().write(response.toString());
|
resp.getWriter().write(response.toString());
|
||||||
LOG.error("Unexpected error", e);
|
log.error("Unexpected error", e);
|
||||||
if (json != null) {
|
if (json != null) {
|
||||||
LOG.debug("Request: {}", json);
|
log.debug("Request: {}", json);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -298,7 +277,7 @@ public class RecorderServlet extends AbstractCtbrecServlet {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void startByUrl(Request request) throws InvalidKeyException, NoSuchAlgorithmException, IOException {
|
private void startByUrl(Request request) throws InvalidKeyException, NoSuchAlgorithmException, IOException {
|
||||||
String url = request.model.getUrl();
|
String url = request.getModel().getUrl();
|
||||||
for (Site site : sites) {
|
for (Site site : sites) {
|
||||||
Model model = site.createModelFromUrl(url);
|
Model model = site.createModelFromUrl(url);
|
||||||
if (model != null) {
|
if (model != null) {
|
||||||
|
@ -310,7 +289,7 @@ public class RecorderServlet extends AbstractCtbrecServlet {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void startByName(Request request) throws InvalidKeyException, NoSuchAlgorithmException, IOException {
|
private void startByName(Request request) throws InvalidKeyException, NoSuchAlgorithmException, IOException {
|
||||||
String[] input = request.model.getUrl().split(":");
|
String[] input = request.getModel().getUrl().split(":");
|
||||||
if (input.length != 2) {
|
if (input.length != 2) {
|
||||||
throw new IllegalArgumentException("Invalid input. Should be site:model");
|
throw new IllegalArgumentException("Invalid input. Should be site:model");
|
||||||
}
|
}
|
||||||
|
@ -323,13 +302,6 @@ public class RecorderServlet extends AbstractCtbrecServlet {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw new IllegalArgumentException("No site found to record " + request.model.getUrl());
|
throw new IllegalArgumentException("No site found to record " + request.getModel().getUrl());
|
||||||
}
|
|
||||||
|
|
||||||
private static class Request {
|
|
||||||
public String action;
|
|
||||||
public Model model;
|
|
||||||
public Recording recording;
|
|
||||||
public ModelGroup modelGroup;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
package ctbrec.recorder.server;
|
||||||
|
|
||||||
|
import ctbrec.Model;
|
||||||
|
import ctbrec.ModelGroup;
|
||||||
|
import ctbrec.Recording;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class Request {
|
||||||
|
private String action;
|
||||||
|
private Model model;
|
||||||
|
private Recording recording;
|
||||||
|
private ModelGroup modelGroup;
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
package ctbrec.recorder.server.io.json.dto;
|
||||||
|
|
||||||
|
import ctbrec.ModelGroup;
|
||||||
|
import ctbrec.io.json.dto.ModelDto;
|
||||||
|
import ctbrec.io.json.dto.RecordingDto;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class RequestDto {
|
||||||
|
private String action;
|
||||||
|
private ModelDto model;
|
||||||
|
private RecordingDto recording;
|
||||||
|
private ModelGroup modelGroup;
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
package ctbrec.recorder.server.io.json.mapper;
|
||||||
|
|
||||||
|
import ctbrec.io.json.mapper.ModelMapper;
|
||||||
|
import ctbrec.io.json.mapper.RecordingMapper;
|
||||||
|
import ctbrec.recorder.server.Request;
|
||||||
|
import ctbrec.recorder.server.io.json.dto.RequestDto;
|
||||||
|
import org.mapstruct.Mapper;
|
||||||
|
|
||||||
|
@Mapper(uses = {ModelMapper.class, RecordingMapper.class})
|
||||||
|
public interface RequestMapper {
|
||||||
|
RequestDto toDto(Request recording);
|
||||||
|
|
||||||
|
Request toRequest(RequestDto dto);
|
||||||
|
}
|
Loading…
Reference in New Issue