forked from j62/ctbrec
Get rid of moshi
This commit is contained in:
parent
b53be222fb
commit
619d888bfa
|
@ -1,9 +1,8 @@
|
|||
package ctbrec.ui;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
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.Model;
|
||||
import ctbrec.StringUtil;
|
||||
|
@ -19,6 +18,7 @@ import ctbrec.io.BandwidthMeter;
|
|||
import ctbrec.io.ByteUnitFormatter;
|
||||
import ctbrec.io.HttpClient;
|
||||
import ctbrec.io.HttpException;
|
||||
import ctbrec.io.json.ObjectMapperFactory;
|
||||
import ctbrec.notes.LocalModelNotesService;
|
||||
import ctbrec.notes.ModelNotesService;
|
||||
import ctbrec.notes.RemoteModelNotesService;
|
||||
|
@ -63,6 +63,7 @@ import javafx.scene.layout.HBox;
|
|||
import javafx.scene.paint.Color;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.WindowEvent;
|
||||
import lombok.Data;
|
||||
import okhttp3.Request;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
@ -72,7 +73,6 @@ import java.io.File;
|
|||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.Type;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
@ -549,7 +549,6 @@ public class CamrecApplication extends Application {
|
|||
private void createRecorder() {
|
||||
if (config.getSettings().localRecording) {
|
||||
try {
|
||||
//recorder = new NextGenLocalRecorder(config, sites);
|
||||
recorder = new SimplifiedLocalRecorder(config, sites);
|
||||
} catch (IOException e) {
|
||||
LOG.error("Couldn't initialize recorder", e);
|
||||
|
@ -597,10 +596,8 @@ public class CamrecApplication extends Application {
|
|||
var body = response.body().string();
|
||||
LOG.trace("Version check respone: {}", body);
|
||||
if (response.isSuccessful()) {
|
||||
var moshi = new Moshi.Builder().build();
|
||||
Type type = Types.newParameterizedType(List.class, Release.class);
|
||||
JsonAdapter<List<Release>> adapter = moshi.adapter(type);
|
||||
List<Release> releases = adapter.fromJson(body);
|
||||
List<Release> releases = ObjectMapperFactory.getMapper().readValue(body, new TypeReference<>() {
|
||||
});
|
||||
var latest = releases.get(0);
|
||||
var latestVersion = latest.getVersion();
|
||||
var ctbrecVersion = Version.getVersion();
|
||||
|
@ -622,38 +619,16 @@ public class CamrecApplication extends Application {
|
|||
updateCheck.start();
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class Release {
|
||||
private String name;
|
||||
private String tag_name; // NOSONAR - name pattern is needed by moshi
|
||||
private String html_url; // NOSONAR - name pattern is needed by moshi
|
||||
|
||||
public String getName() {
|
||||
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;
|
||||
}
|
||||
@JsonProperty("tag_name")
|
||||
private String tagName;
|
||||
@JsonProperty("html_url")
|
||||
private String htmlUrl;
|
||||
|
||||
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.PlaylistException;
|
||||
import com.squareup.moshi.JsonReader;
|
||||
import com.squareup.moshi.JsonWriter;
|
||||
import ctbrec.Model;
|
||||
import ctbrec.SubsequentAction;
|
||||
import ctbrec.recorder.download.HttpHeaderFactory;
|
||||
|
@ -19,20 +17,21 @@ import javax.xml.bind.JAXBException;
|
|||
import java.io.IOException;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
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
|
||||
*/
|
||||
public class JavaFxModel implements Model {
|
||||
private transient BooleanProperty onlineProperty = new SimpleBooleanProperty();
|
||||
private transient BooleanProperty recordingProperty = new SimpleBooleanProperty();
|
||||
private transient BooleanProperty pausedProperty = new SimpleBooleanProperty();
|
||||
private transient SimpleIntegerProperty priorityProperty = new SimpleIntegerProperty();
|
||||
private transient SimpleObjectProperty<Instant> lastSeenProperty = new SimpleObjectProperty<>();
|
||||
private transient SimpleObjectProperty<Instant> lastRecordedProperty = new SimpleObjectProperty<>();
|
||||
private final transient BooleanProperty onlineProperty = new SimpleBooleanProperty();
|
||||
private final transient BooleanProperty recordingProperty = new SimpleBooleanProperty();
|
||||
private final transient BooleanProperty pausedProperty = new SimpleBooleanProperty();
|
||||
private final transient SimpleIntegerProperty priorityProperty = new SimpleIntegerProperty();
|
||||
private final transient SimpleObjectProperty<Instant> lastSeenProperty = new SimpleObjectProperty<>();
|
||||
private final transient SimpleObjectProperty<Instant> lastRecordedProperty = new SimpleObjectProperty<>();
|
||||
|
||||
private Model delegate;
|
||||
private final Model delegate;
|
||||
|
||||
public JavaFxModel(Model delegate) {
|
||||
this.delegate = delegate;
|
||||
|
@ -188,13 +187,13 @@ public class JavaFxModel implements Model {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void readSiteSpecificData(JsonReader reader) throws IOException {
|
||||
delegate.readSiteSpecificData(reader);
|
||||
public void readSiteSpecificData(Map<String, String> data) {
|
||||
delegate.readSiteSpecificData(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeSiteSpecificData(JsonWriter writer) throws IOException {
|
||||
delegate.writeSiteSpecificData(writer);
|
||||
public void writeSiteSpecificData(Map<String, String> data) {
|
||||
delegate.writeSiteSpecificData(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -1,11 +1,16 @@
|
|||
package ctbrec.ui.event;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Objects;
|
||||
|
||||
import ctbrec.Model;
|
||||
import ctbrec.ui.JavaFxModel;
|
||||
import lombok.*;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@EqualsAndHashCode(of = "timestamp")
|
||||
@ToString
|
||||
@NoArgsConstructor
|
||||
public class PlayerStartedEvent {
|
||||
|
||||
private Model model;
|
||||
|
@ -20,41 +25,16 @@ public class PlayerStartedEvent {
|
|||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public Model getModel() {
|
||||
return 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 + "]";
|
||||
public void setModel(Model model) {
|
||||
this.model = unwrap(model);
|
||||
}
|
||||
|
||||
private Model unwrap(Model model) {
|
||||
if (model instanceof JavaFxModel) {
|
||||
return ((JavaFxModel) model).getDelegate();
|
||||
if (model instanceof JavaFxModel fxModel) {
|
||||
return fxModel.getDelegate();
|
||||
} else {
|
||||
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;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.squareup.moshi.Json;
|
||||
|
||||
@Data
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class Account {
|
||||
|
||||
@Json(name = "emojis")
|
||||
@JsonProperty("emojis")
|
||||
private List<Object> emojis = null;
|
||||
@Json(name = "note")
|
||||
@JsonProperty("note")
|
||||
private String note;
|
||||
@Json(name = "bot")
|
||||
@JsonProperty("bot")
|
||||
private Boolean bot;
|
||||
@Json(name = "created_at")
|
||||
@JsonProperty("created_at")
|
||||
private String createdAt;
|
||||
@Json(name = "avatar")
|
||||
@JsonProperty("avatar")
|
||||
private String avatar;
|
||||
@Json(name = "display_name")
|
||||
@JsonProperty("display_name")
|
||||
private String displayName;
|
||||
@Json(name = "header_static")
|
||||
@JsonProperty("header_static")
|
||||
private String headerStatic;
|
||||
@Json(name = "url")
|
||||
@JsonProperty("url")
|
||||
private String url;
|
||||
@Json(name = "following_count")
|
||||
@JsonProperty("following_count")
|
||||
private Integer followingCount;
|
||||
@Json(name = "statuses_count")
|
||||
@JsonProperty("statuses_count")
|
||||
private Integer statusesCount;
|
||||
@Json(name = "followers_count")
|
||||
@JsonProperty("followers_count")
|
||||
private Integer followersCount;
|
||||
@Json(name = "header")
|
||||
@JsonProperty("header")
|
||||
private String header;
|
||||
@Json(name = "id")
|
||||
@JsonProperty("id")
|
||||
private String id;
|
||||
@Json(name = "locked")
|
||||
@JsonProperty("locked")
|
||||
private Boolean locked;
|
||||
@Json(name = "avatar_static")
|
||||
@JsonProperty("avatar_static")
|
||||
private String avatarStatic;
|
||||
@Json(name = "fields")
|
||||
@JsonProperty("fields")
|
||||
private List<Object> fields = null;
|
||||
@Json(name = "acct")
|
||||
@JsonProperty("acct")
|
||||
private String acct;
|
||||
@Json(name = "username")
|
||||
@JsonProperty("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;
|
||||
|
||||
import com.squareup.moshi.JsonAdapter;
|
||||
import com.squareup.moshi.Moshi;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import ctbrec.Config;
|
||||
import ctbrec.GlobalThreadPool;
|
||||
import ctbrec.Version;
|
||||
import ctbrec.io.HttpException;
|
||||
import ctbrec.io.json.ObjectMapperFactory;
|
||||
import ctbrec.ui.CamrecApplication;
|
||||
import ctbrec.ui.controls.Dialogs;
|
||||
import ctbrec.ui.tabs.TabSelectionListener;
|
||||
|
@ -15,6 +15,7 @@ import javafx.geometry.Pos;
|
|||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.control.Tab;
|
||||
import javafx.scene.layout.VBox;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import okhttp3.Request;
|
||||
import org.json.JSONObject;
|
||||
|
||||
|
@ -24,12 +25,15 @@ import java.util.Objects;
|
|||
import static ctbrec.ErrorMessages.HTTP_RESPONSE_BODY_IS_NULL;
|
||||
import static ctbrec.io.HttpConstants.USER_AGENT;
|
||||
|
||||
@Slf4j
|
||||
public class NewsTab extends Tab implements TabSelectionListener {
|
||||
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 final Config config;
|
||||
private final VBox layout = new VBox();
|
||||
|
||||
private final ObjectMapper mapper = ObjectMapperFactory.getMapper();
|
||||
|
||||
public NewsTab(Config config) {
|
||||
this.config = config;
|
||||
setText("News");
|
||||
|
@ -53,6 +57,7 @@ public class NewsTab extends Tab implements TabSelectionListener {
|
|||
try (var response = CamrecApplication.httpClient.execute(request)) {
|
||||
if (response.isSuccessful()) {
|
||||
var body = Objects.requireNonNull(response.body(), HTTP_RESPONSE_BODY_IS_NULL).string();
|
||||
log.debug(body);
|
||||
if (body.startsWith("[")) {
|
||||
onSuccess(body);
|
||||
} else if (body.startsWith("{")) {
|
||||
|
@ -65,6 +70,7 @@ public class NewsTab extends Tab implements TabSelectionListener {
|
|||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.info("Error while loading news", 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 {
|
||||
var moshi = new Moshi.Builder().build();
|
||||
JsonAdapter<Status[]> statusListAdapter = moshi.adapter(Status[].class);
|
||||
Status[] statusArray = Objects.requireNonNull(statusListAdapter.fromJson(body));
|
||||
Status[] statusArray = mapper.readValue(body, Status[].class);
|
||||
Platform.runLater(() -> {
|
||||
layout.getChildren().clear();
|
||||
for (Status status : statusArray) {
|
||||
|
|
|
@ -1,275 +1,71 @@
|
|||
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.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.List;
|
||||
|
||||
import com.squareup.moshi.Json;
|
||||
|
||||
@Data
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class Status {
|
||||
|
||||
@Json(name = "pinned")
|
||||
@JsonProperty("pinned")
|
||||
private Boolean pinned;
|
||||
@Json(name = "in_reply_to_id")
|
||||
@JsonProperty("in_reply_to_id")
|
||||
private Object inReplyToId;
|
||||
@Json(name = "favourites_count")
|
||||
@JsonProperty("favourites_count")
|
||||
private Integer favouritesCount;
|
||||
@Json(name = "media_attachments")
|
||||
@JsonProperty("media_attachments")
|
||||
private List<Object> mediaAttachments = null;
|
||||
@Json(name = "created_at")
|
||||
@JsonProperty("created_at")
|
||||
private String createdAt;
|
||||
@Json(name = "replies_count")
|
||||
@JsonProperty("replies_count")
|
||||
private Integer repliesCount;
|
||||
@Json(name = "language")
|
||||
@JsonProperty("language")
|
||||
private String language;
|
||||
@Json(name = "in_reply_to_account_id")
|
||||
@JsonProperty("in_reply_to_account_id")
|
||||
private Object inReplyToAccountId;
|
||||
@Json(name = "content")
|
||||
@JsonProperty("content")
|
||||
private String content;
|
||||
@Json(name = "reblog")
|
||||
@JsonProperty("reblog")
|
||||
private Object reblog;
|
||||
@Json(name = "spoiler_text")
|
||||
@JsonProperty("spoiler_text")
|
||||
private String spoilerText;
|
||||
@Json(name = "id")
|
||||
@JsonProperty("id")
|
||||
private String id;
|
||||
@Json(name = "reblogged")
|
||||
@JsonProperty("reblogged")
|
||||
private Boolean reblogged;
|
||||
@Json(name = "muted")
|
||||
@JsonProperty("muted")
|
||||
private Boolean muted;
|
||||
@Json(name = "emojis")
|
||||
@JsonProperty("emojis")
|
||||
private List<Object> emojis = null;
|
||||
@Json(name = "reblogs_count")
|
||||
@JsonProperty("reblogs_count")
|
||||
private Integer reblogsCount;
|
||||
@Json(name = "visibility")
|
||||
@JsonProperty("visibility")
|
||||
private String visibility;
|
||||
@Json(name = "sensitive")
|
||||
@JsonProperty("sensitive")
|
||||
private Boolean sensitive;
|
||||
@Json(name = "uri")
|
||||
@JsonProperty("uri")
|
||||
private String uri;
|
||||
@Json(name = "url")
|
||||
@JsonProperty("url")
|
||||
private String url;
|
||||
@Json(name = "tags")
|
||||
@JsonProperty("tags")
|
||||
private List<Object> tags = null;
|
||||
@Json(name = "application")
|
||||
@JsonProperty("application")
|
||||
private Object application;
|
||||
@Json(name = "favourited")
|
||||
@JsonProperty("favourited")
|
||||
private Boolean favourited;
|
||||
@Json(name = "mentions")
|
||||
@JsonProperty("mentions")
|
||||
private List<Object> mentions = null;
|
||||
@Json(name = "account")
|
||||
@JsonProperty("account")
|
||||
private Account account;
|
||||
@Json(name = "card")
|
||||
@JsonProperty("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() {
|
||||
String timestamp = getCreatedAt();
|
||||
var instant = Instant.parse(timestamp);
|
||||
|
|
|
@ -1,52 +1,40 @@
|
|||
package ctbrec.ui.settings;
|
||||
|
||||
import static javafx.scene.control.ButtonType.*;
|
||||
|
||||
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 com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import ctbrec.Config;
|
||||
import ctbrec.Model;
|
||||
import ctbrec.io.ModelJsonAdapter;
|
||||
import ctbrec.sites.Site;
|
||||
import ctbrec.io.json.ObjectMapperFactory;
|
||||
import ctbrec.ui.AutosizeAlert;
|
||||
import ctbrec.ui.controls.Dialogs;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.control.Alert.AlertType;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ButtonType;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ListView;
|
||||
import javafx.scene.control.SelectionMode;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
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 {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(IgnoreList.class);
|
||||
|
||||
private ListView<String> ignoreListView;
|
||||
|
||||
private List<Site> sites;
|
||||
private final ObjectMapper mapper = ObjectMapperFactory.getMapper();
|
||||
|
||||
public IgnoreList(List<Site> sites) {
|
||||
this.sites = sites;
|
||||
public IgnoreList() {
|
||||
createGui();
|
||||
loadIgnoredModels();
|
||||
}
|
||||
|
@ -83,16 +71,14 @@ public class IgnoreList extends GridPane {
|
|||
|
||||
private void removeSelectedModels() {
|
||||
List<String> selectedModels = ignoreListView.getSelectionModel().getSelectedItems();
|
||||
if (selectedModels.isEmpty()) {
|
||||
return; // NOSONAR
|
||||
} else {
|
||||
if (!selectedModels.isEmpty()) {
|
||||
Config.getInstance().getSettings().ignoredModels.removeAll(selectedModels);
|
||||
ignoreListView.getItems().removeAll(selectedModels);
|
||||
LOG.debug(Config.getInstance().getSettings().ignoredModels.toString());
|
||||
log.debug(Config.getInstance().getSettings().ignoredModels.toString());
|
||||
try {
|
||||
Config.getInstance().save();
|
||||
} 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");
|
||||
var file = chooser.showSaveDialog(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)) {
|
||||
String json = adapter.toJson(Config.getInstance().getSettings().ignoredModels);
|
||||
String json = mapper.writeValueAsString(Config.getInstance().getSettings().ignoredModels);
|
||||
out.write(json.getBytes(StandardCharsets.UTF_8));
|
||||
} catch (IOException 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");
|
||||
var file = chooser.showOpenDialog(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 {
|
||||
byte[] fileContent = Files.readAllBytes(file.toPath());
|
||||
List<String> ignoredModels = adapter.fromJson(new String(fileContent, StandardCharsets.UTF_8));
|
||||
String fileContent = Files.readString(file.toPath());
|
||||
List<String> ignoredModels = mapper.readValue(fileContent, new TypeReference<>() {
|
||||
});
|
||||
var confirmed = true;
|
||||
if (!Config.getInstance().getSettings().ignoredModels.isEmpty()) {
|
||||
var msg = "This will replace the existing ignore list! Continue?";
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package ctbrec.ui.settings;
|
||||
|
||||
import ctbrec.Config;
|
||||
import ctbrec.io.json.mapper.PostProcessorMapper;
|
||||
import ctbrec.recorder.postprocessing.*;
|
||||
import ctbrec.ui.controls.Dialogs;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
|
@ -16,11 +17,14 @@ import javafx.scene.layout.GridPane;
|
|||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.stage.Stage;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class PostProcessingStepPanel extends GridPane {
|
||||
|
||||
|
@ -64,7 +68,11 @@ public class PostProcessingStepPanel extends GridPane {
|
|||
edit = createEditButton();
|
||||
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());
|
||||
stepView = new TableView<>(stepList);
|
||||
stepView.setEditable(true);
|
||||
|
@ -107,6 +115,9 @@ public class PostProcessingStepPanel extends GridPane {
|
|||
|
||||
private void safelySaveConfig() {
|
||||
try {
|
||||
config.getSettings().postProcessors = stepList.stream()
|
||||
.map(Mappers.getMapper(PostProcessorMapper.class)::toDto)
|
||||
.collect(Collectors.toList()); // NOSONAR - toList returns an unmodifiable list
|
||||
config.save();
|
||||
} catch (IOException 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() {
|
||||
var button = createButton("\u25B4", "Move step up");
|
||||
var button = createButton("▴", "Move step up");
|
||||
button.setOnAction(evt -> {
|
||||
int idx = stepView.getSelectionModel().getSelectedIndex();
|
||||
PostProcessor selectedItem = stepView.getSelectionModel().getSelectedItem();
|
||||
|
@ -126,7 +137,7 @@ public class PostProcessingStepPanel extends GridPane {
|
|||
}
|
||||
|
||||
private Button createDownButton() {
|
||||
var button = createButton("\u25BE", "Move step down");
|
||||
var button = createButton("▾", "Move step down");
|
||||
button.setOnAction(evt -> {
|
||||
int idx = stepView.getSelectionModel().getSelectedIndex();
|
||||
PostProcessor selectedItem = stepView.getSelectionModel().getSelectedItem();
|
||||
|
@ -197,7 +208,7 @@ public class PostProcessingStepPanel extends GridPane {
|
|||
}
|
||||
|
||||
private Button createEditButton() {
|
||||
var button = createButton("\u270E", "Edit selected step");
|
||||
var button = createButton("✎", "Edit selected step");
|
||||
button.setOnAction(evt -> {
|
||||
PostProcessor selectedItem = stepView.getSelectionModel().getSelectedItem();
|
||||
PostProcessingDialogFactory.openEditDialog(selectedItem, getScene(), stepList);
|
||||
|
|
|
@ -203,7 +203,7 @@ public class SettingsTab extends Tab implements TabSelectionListener {
|
|||
private void createGui() {
|
||||
var postProcessingStepPanel = new PostProcessingStepPanel(config);
|
||||
var variablesHelpButton = createHelpButton("Variables", "http://localhost:5689/docs/PostProcessing.md#variables");
|
||||
ignoreList = new IgnoreList(sites);
|
||||
ignoreList = new IgnoreList();
|
||||
List<Category> siteCategories = new ArrayList<>();
|
||||
for (Site site : sites) {
|
||||
ofNullable(SiteUiFactory.getUi(site)).map(SiteUI::getConfigUI).map(ConfigUI::createConfigPanel)
|
||||
|
|
|
@ -1,40 +1,24 @@
|
|||
package ctbrec.ui.tabs;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.*;
|
||||
import static java.nio.file.StandardOpenOption.*;
|
||||
|
||||
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.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
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.Model;
|
||||
import ctbrec.StringUtil;
|
||||
import ctbrec.event.EventBusHolder;
|
||||
import ctbrec.io.InstantJsonAdapter;
|
||||
import ctbrec.io.ModelJsonAdapter;
|
||||
import ctbrec.io.json.ObjectMapperFactory;
|
||||
import ctbrec.recorder.Recorder;
|
||||
import ctbrec.sites.Site;
|
||||
import ctbrec.sites.SiteUtil;
|
||||
import ctbrec.ui.ShutdownListener;
|
||||
import ctbrec.ui.action.PlayAction;
|
||||
import ctbrec.ui.controls.CustomMouseBehaviorContextMenu;
|
||||
import ctbrec.ui.controls.DateTimeCellFactory;
|
||||
import ctbrec.ui.controls.SearchBox;
|
||||
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 javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
|
@ -42,36 +26,38 @@ import javafx.collections.FXCollections;
|
|||
import javafx.collections.ObservableList;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.Cursor;
|
||||
import javafx.scene.control.ContextMenu;
|
||||
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.*;
|
||||
import javafx.scene.control.TableColumn.SortType;
|
||||
import javafx.scene.control.TableView;
|
||||
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.input.*;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
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 {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(RecentlyWatchedTab.class);
|
||||
|
||||
private ObservableList<PlayerStartedEvent> filteredModels = FXCollections.observableArrayList();
|
||||
private ObservableList<PlayerStartedEvent> observableModels = FXCollections.observableArrayList();
|
||||
private TableView<PlayerStartedEvent> table = new TableView<>();
|
||||
private final ObservableList<PlayerStartedEvent> filteredModels = FXCollections.observableArrayList();
|
||||
private final ObservableList<PlayerStartedEvent> observableModels = FXCollections.observableArrayList();
|
||||
private final TableView<PlayerStartedEvent> table = new TableView<>();
|
||||
private final ReentrantLock lock = new ReentrantLock();
|
||||
private final Recorder recorder;
|
||||
private final ObjectMapper mapper = ObjectMapperFactory.getMapper();
|
||||
private ContextMenu popup;
|
||||
private ReentrantLock lock = new ReentrantLock();
|
||||
private Recorder recorder;
|
||||
private 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);
|
||||
filterInput.setPromptText("Filter");
|
||||
filterInput.textProperty().addListener( (observableValue, oldValue, newValue) -> {
|
||||
filterInput.textProperty().addListener((observableValue, oldValue, newValue) -> {
|
||||
String filter = filterInput.getText();
|
||||
lock.lock();
|
||||
try {
|
||||
|
@ -186,7 +172,7 @@ public class RecentlyWatchedTab extends Tab implements ShutdownListener {
|
|||
var sb = new StringBuilder();
|
||||
for (TableColumn<PlayerStartedEvent, ?> tc : table.getColumns()) {
|
||||
Object cellData = tc.getCellData(i);
|
||||
if(cellData != null) {
|
||||
if (cellData != null) {
|
||||
var content = cellData.toString();
|
||||
sb.append(content).append(' ');
|
||||
}
|
||||
|
@ -195,12 +181,12 @@ public class RecentlyWatchedTab extends Tab implements ShutdownListener {
|
|||
|
||||
var tokensMissing = false;
|
||||
for (String token : tokens) {
|
||||
if(!searchText.toLowerCase().contains(token.toLowerCase())) {
|
||||
if (!searchText.toLowerCase().contains(token.toLowerCase())) {
|
||||
tokensMissing = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(tokensMissing) {
|
||||
if (tokensMissing) {
|
||||
PlayerStartedEvent sessionState = table.getItems().get(i);
|
||||
filteredModels.add(sessionState);
|
||||
}
|
||||
|
@ -221,9 +207,9 @@ public class RecentlyWatchedTab extends Tab implements ShutdownListener {
|
|||
ContextMenu menu = new CustomMouseBehaviorContextMenu();
|
||||
|
||||
ModelMenuContributor.newContributor(getTabPane(), Config.getInstance(), recorder) //
|
||||
.withStartStopCallback(m -> getTabPane().setCursor(Cursor.DEFAULT)) //
|
||||
.afterwards(table::refresh)
|
||||
.contributeToMenu(selectedModels, menu);
|
||||
.withStartStopCallback(m -> getTabPane().setCursor(Cursor.DEFAULT)) //
|
||||
.afterwards(table::refresh)
|
||||
.contributeToMenu(selectedModels, menu);
|
||||
|
||||
menu.getItems().add(new SeparatorMenuItem());
|
||||
var delete = new MenuItem("Delete");
|
||||
|
@ -267,7 +253,7 @@ public class RecentlyWatchedTab extends Tab implements ShutdownListener {
|
|||
cell.addEventFilter(MouseEvent.MOUSE_CLICKED, event -> {
|
||||
if (event.getButton() == MouseButton.PRIMARY && event.getClickCount() == 2) {
|
||||
var selectedModel = table.getSelectionModel().getSelectedItem().getModel();
|
||||
if(selectedModel != null) {
|
||||
if (selectedModel != null) {
|
||||
new PlayAction(table, selectedModel).execute();
|
||||
}
|
||||
}
|
||||
|
@ -277,37 +263,29 @@ public class RecentlyWatchedTab extends Tab implements ShutdownListener {
|
|||
}
|
||||
|
||||
private void saveHistory() throws IOException {
|
||||
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);
|
||||
String json = adapter.indent(" ").toJson(observableModels);
|
||||
String json = mapper.writeValueAsString(observableModels.stream().map(Mappers.getMapper(PlayerStartedEventMapper.class)::toDto).toList());
|
||||
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.write(recentlyWatched.toPath(), json.getBytes(UTF_8), CREATE, WRITE, TRUNCATE_EXISTING);
|
||||
Files.writeString(recentlyWatched.toPath(), json, CREATE, WRITE, TRUNCATE_EXISTING);
|
||||
}
|
||||
|
||||
private void loadHistory() {
|
||||
var recentlyWatched = new File(Config.getInstance().getConfigDir(), "recently_watched.json");
|
||||
if(!recentlyWatched.exists()) {
|
||||
if (!recentlyWatched.exists()) {
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
log.debug("Loading recently watched models from {}", recentlyWatched.getAbsolutePath());
|
||||
try {
|
||||
List<PlayerStartedEvent> fromJson = adapter.fromJson(Files.readString(recentlyWatched.toPath(), UTF_8));
|
||||
observableModels.addAll(fromJson);
|
||||
List<PlayerStartedEventDto> fromJson = mapper.readValue(Files.readString(recentlyWatched.toPath(), UTF_8), new TypeReference<List<PlayerStartedEventDto>>() {
|
||||
});
|
||||
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) {
|
||||
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 {
|
||||
saveHistory();
|
||||
} 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.recorder.ProgressListener;
|
||||
import ctbrec.recorder.Recorder;
|
||||
import ctbrec.recorder.RecordingPinnedException;
|
||||
import ctbrec.recorder.download.StreamSource;
|
||||
import ctbrec.recorder.postprocessing.PostProcessingContext;
|
||||
import ctbrec.ui.*;
|
||||
|
@ -844,7 +843,7 @@ public class RecordingsTab extends Tab implements TabSelectionListener, Shutdown
|
|||
try {
|
||||
recorder.delete(r.getDelegate());
|
||||
deleted.add(r);
|
||||
} catch (RecordingPinnedException | IOException | InvalidKeyException | NoSuchAlgorithmException | IllegalStateException e1) {
|
||||
} catch (Exception e1) {
|
||||
exceptions.add(e1);
|
||||
LOG.error("Error while deleting recording", e1);
|
||||
}
|
||||
|
|
|
@ -273,6 +273,7 @@ public abstract class AbstractRecordedModelsTab extends Tab implements TabSelect
|
|||
try {
|
||||
List<Model> models = ModelImportExport.importFrom(target, sites, config);
|
||||
importModelList(models);
|
||||
portraitCache.invalidateAll();
|
||||
} catch (IOException e) {
|
||||
String msg = "An error occurred while importing the model list";
|
||||
Dialogs.showError(getTabPane().getScene(), "Import models", msg, e);
|
||||
|
|
|
@ -1,32 +1,36 @@
|
|||
package ctbrec.ui.tabs.recorded;
|
||||
|
||||
import com.squareup.moshi.*;
|
||||
import com.squareup.moshi.JsonReader.Token;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import ctbrec.Config;
|
||||
import ctbrec.Model;
|
||||
import ctbrec.ModelGroup;
|
||||
import ctbrec.image.LocalPortraitStore;
|
||||
import ctbrec.image.PortraitStore;
|
||||
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 okio.Buffer;
|
||||
import okio.Okio;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import ctbrec.sites.SiteUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
import org.json.JSONWriter;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.time.LocalTime;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.squareup.moshi.JsonReader.Token.*;
|
||||
|
||||
@Slf4j
|
||||
public class ModelImportExport {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ModelImportExport.class);
|
||||
|
||||
enum ExportIncludes {
|
||||
NOTES,
|
||||
|
@ -34,6 +38,8 @@ public class ModelImportExport {
|
|||
PORTRAITS
|
||||
}
|
||||
|
||||
private static final ObjectMapper mapper = ObjectMapperFactory.getMapper();
|
||||
|
||||
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 {
|
||||
Moshi moshi = new Moshi.Builder()
|
||||
.add(Model.class, new ModelJsonAdapter())
|
||||
.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));
|
||||
StringBuilder sb = new StringBuilder();
|
||||
JSONWriter writer = new JSONWriter(sb);
|
||||
|
||||
try (JsonWriter writer = JsonWriter.of(Okio.buffer(Okio.sink(exportOptions.targetFile())))) {
|
||||
writer.setIndent(" ");
|
||||
writer.beginObject();
|
||||
writer.name("models");
|
||||
modelListAdapter.toJson(writer, models);
|
||||
if (exportOptions.includes().contains(ExportIncludes.NOTES)) {
|
||||
writer.name("notes");
|
||||
notesAdapter.toJson(writer, config.getSettings().modelNotes);
|
||||
}
|
||||
if (exportOptions.includes().contains(ExportIncludes.GROUPS)) {
|
||||
writer.name("groups");
|
||||
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.object();
|
||||
writer.key("models");
|
||||
writer.array();
|
||||
var modelArray = models.stream()
|
||||
.map(Mappers.getMapper(ModelMapper.class)::toDto)
|
||||
.map(dto -> {
|
||||
try {
|
||||
return mapper.writeValueAsString(dto);
|
||||
} catch (JsonProcessingException e) {
|
||||
log.error("Error while serializing model {}", dto);
|
||||
throw new MappingException(e);
|
||||
}
|
||||
writer.endArray();
|
||||
}
|
||||
}
|
||||
writer.endObject();
|
||||
})
|
||||
.collect(Collectors.joining(","));
|
||||
sb.append(modelArray);
|
||||
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 {
|
||||
Moshi moshi = new Moshi.Builder()
|
||||
.add(Model.class, new ModelJsonAdapter(sites))
|
||||
.add(File.class, new FileJsonAdapter())
|
||||
.add(UUID.class, new UuidJSonAdapter())
|
||||
.add(LocalTime.class, new LocalTimeJsonAdapter())
|
||||
.build();
|
||||
JsonAdapter<Model> modelAdapter = moshi.adapter(Model.class);
|
||||
JsonAdapter<Map<String, String>> notesAdapter = moshi.adapter(Types.newParameterizedType(Map.class, String.class, String.class));
|
||||
JsonAdapter<Set<ModelGroup>> modelGroupAdapter = moshi.adapter(Types.newParameterizedType(Set.class, ModelGroup.class));
|
||||
|
||||
List<Model> models = null;
|
||||
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();
|
||||
JSONObject json = new JSONObject(Files.readString(target.toPath(), StandardCharsets.UTF_8));
|
||||
List<Model> models = readModels(json.getJSONArray("models"));
|
||||
models.forEach(m -> SiteUtil.getSiteForModel(sites, m).ifPresent(m::setSite));
|
||||
if (json.has("notes")) {
|
||||
importNotes(json.getJSONObject("notes"), config);
|
||||
}
|
||||
if (json.has("groups")) {
|
||||
importGroups(json.getJSONArray("groups"), config);
|
||||
}
|
||||
if (json.has("portraits")) {
|
||||
importPortraits(json.getJSONArray("portraits"), config);
|
||||
}
|
||||
return models;
|
||||
}
|
||||
|
||||
private static void importPortraits(JsonReader reader, Config config) throws IOException {
|
||||
private static void importPortraits(JSONArray portraits, Config config) throws IOException {
|
||||
PortraitStore portraitStore;
|
||||
if (config.getSettings().localRecording) {
|
||||
portraitStore = new LocalPortraitStore(config);
|
||||
|
@ -145,89 +151,50 @@ public class ModelImportExport {
|
|||
};
|
||||
portraitStore = new RemotePortraitStore(httpClient, config);
|
||||
}
|
||||
reader.beginArray();
|
||||
while (reader.hasNext()) {
|
||||
reader.beginObject();
|
||||
String url = null;
|
||||
String id = null;
|
||||
String dataBase64 = null;
|
||||
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);
|
||||
for (int i = 0; i < portraits.length(); i++) {
|
||||
JSONObject portrait = portraits.getJSONObject(i);
|
||||
String url = portrait.getString("url");
|
||||
String id = portrait.getString("id");
|
||||
String dataBase64 = portrait.getString("data");
|
||||
portraitStore.writePortrait(url, Base64.getDecoder().decode(dataBase64));
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
List<Model> result = new LinkedList<>();
|
||||
reader.beginArray();
|
||||
while (reader.hasNext()) {
|
||||
private static void importGroups(JSONArray groups, Config config) {
|
||||
for (int i = 0; i < groups.length(); i++) {
|
||||
JSONObject group = groups.getJSONObject(i);
|
||||
try {
|
||||
Token token = reader.peek();
|
||||
if (token == BEGIN_OBJECT) {
|
||||
Model model = modelAdapter.fromJson(reader);
|
||||
result.add(model);
|
||||
} else {
|
||||
skipToNextModel(reader);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.error("Couldn't parse model json", e);
|
||||
ModelGroup modelGroup = mapper.readValue(group.toString(), ModelGroup.class);
|
||||
config.getSettings().modelGroups.add(modelGroup);
|
||||
} catch (JsonProcessingException e) {
|
||||
log.error("Error while deserializing model group {}", group);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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>
|
||||
<artifactId>okhttp</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.squareup.moshi</groupId>
|
||||
<artifactId>moshi</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.json</groupId>
|
||||
<artifactId>json</artifactId>
|
||||
</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>
|
||||
<groupId>com.iheartradio.m3u8</groupId>
|
||||
<artifactId>open-m3u8</artifactId>
|
||||
|
@ -96,6 +104,32 @@
|
|||
</execution>
|
||||
</executions>
|
||||
</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>
|
||||
</build>
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package ctbrec;
|
||||
|
||||
import com.squareup.moshi.JsonReader;
|
||||
import com.squareup.moshi.JsonWriter;
|
||||
|
||||
import ctbrec.recorder.download.HttpHeaderFactory;
|
||||
import ctbrec.recorder.download.HttpHeaderFactoryImpl;
|
||||
import ctbrec.recorder.download.RecordingProcess;
|
||||
|
@ -14,10 +13,7 @@ import okhttp3.Response;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
import static ctbrec.io.HttpConstants.USER_AGENT;
|
||||
|
@ -32,7 +28,7 @@ public abstract class AbstractModel implements Model {
|
|||
private String description;
|
||||
private List<String> tags = new ArrayList<>();
|
||||
private int streamUrlIndex = -1;
|
||||
private int priority = -1;
|
||||
private int priority = new Settings().defaultPriority;
|
||||
private boolean suspended = false;
|
||||
private boolean markedForLaterRecording = false;
|
||||
protected transient Site site;
|
||||
|
@ -129,12 +125,12 @@ public abstract class AbstractModel implements Model {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void readSiteSpecificData(JsonReader reader) throws IOException {
|
||||
public void readSiteSpecificData(Map<String, String> data) {
|
||||
// noop default implementation, can be overriden by concrete models
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeSiteSpecificData(JsonWriter writer) throws IOException {
|
||||
public void writeSiteSpecificData(Map<String, String> data) {
|
||||
// noop default implementation, can be overriden by concrete models
|
||||
}
|
||||
|
||||
|
@ -222,9 +218,6 @@ public abstract class AbstractModel implements Model {
|
|||
|
||||
@Override
|
||||
public int getPriority() {
|
||||
if (priority == -1) {
|
||||
priority = Config.getInstance().getSettings().defaultPriority;
|
||||
}
|
||||
return priority;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,34 +1,30 @@
|
|||
package ctbrec;
|
||||
|
||||
import com.squareup.moshi.JsonAdapter;
|
||||
import com.squareup.moshi.Moshi;
|
||||
import ctbrec.io.*;
|
||||
import ctbrec.recorder.postprocessing.PostProcessor;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import ctbrec.io.IoUtils;
|
||||
import ctbrec.io.json.ObjectMapperFactory;
|
||||
import ctbrec.sites.Site;
|
||||
import ctbrec.sites.chaturbate.ChaturbateModel;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.FormatStyle;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static java.nio.file.StandardOpenOption.*;
|
||||
|
||||
// TODO don't use singleton pattern
|
||||
@Slf4j
|
||||
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 V_4_7_5 = "4.7.5";
|
||||
|
||||
|
@ -45,6 +41,8 @@ public class Config {
|
|||
private boolean savingDisabled = false;
|
||||
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 {
|
||||
this.sites = sites;
|
||||
|
||||
|
@ -70,7 +68,7 @@ public class Config {
|
|||
File src = currentConfigDir;
|
||||
if (src.exists()) {
|
||||
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);
|
||||
deleteOldBackups(currentConfigDir);
|
||||
}
|
||||
|
@ -83,10 +81,10 @@ public class Config {
|
|||
for (int i = 0; i < backupDirectories.length - 5; i++) {
|
||||
File dirToDelete = backupDirectories[i];
|
||||
try {
|
||||
LOG.info("Delete old config backup {}", dirToDelete);
|
||||
log.info("Delete old config backup {}", dirToDelete);
|
||||
IoUtils.deleteDirectory(dirToDelete);
|
||||
} 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;
|
||||
}
|
||||
if (!Objects.equals(previousVersion, currentVersion)) {
|
||||
LOG.debug("Version update {} -> {}", previousVersion, currentVersion);
|
||||
LOG.debug("Copying config from {} to {}", src, target);
|
||||
log.debug("Version update {} -> {}", previousVersion, currentVersion);
|
||||
log.debug("Copying config from {} to {}", src, target);
|
||||
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 {
|
||||
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);
|
||||
LOG.info("Loading config from {}", configFile.getAbsolutePath());
|
||||
log.info("Loading config from {}", configFile.getAbsolutePath());
|
||||
if (configFile.exists()) {
|
||||
try {
|
||||
byte[] fileContent = Files.readAllBytes(configFile.toPath());
|
||||
if (fileContent[0] == -17 && fileContent[1] == -69 && fileContent[2] == -65) {
|
||||
// found BOM (byte order mark)
|
||||
LOG.debug("Removing BOM from config file");
|
||||
log.debug("Removing BOM from config file");
|
||||
fileContent[0] = ' ';
|
||||
fileContent[1] = ' ';
|
||||
fileContent[2] = ' ';
|
||||
}
|
||||
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);
|
||||
if (settings.recordingsDir.endsWith("/")) {
|
||||
settings.recordingsDir = settings.recordingsDir.substring(0, settings.recordingsDir.length() - 1);
|
||||
|
@ -172,7 +162,7 @@ public class Config {
|
|||
throw e;
|
||||
}
|
||||
} 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();
|
||||
}
|
||||
for (Site site : sites) {
|
||||
|
@ -183,66 +173,6 @@ public class Config {
|
|||
}
|
||||
|
||||
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 {
|
||||
|
@ -267,17 +197,9 @@ public class Config {
|
|||
if (savingDisabled) {
|
||||
return;
|
||||
}
|
||||
Moshi moshi = new Moshi.Builder()
|
||||
.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);
|
||||
String json = mapper.writeValueAsString(settings);
|
||||
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.writeString(configFile.toPath(), json, CREATE, WRITE, TRUNCATE_EXISTING);
|
||||
}
|
||||
|
|
|
@ -2,8 +2,6 @@ package ctbrec;
|
|||
|
||||
import com.iheartradio.m3u8.ParseException;
|
||||
import com.iheartradio.m3u8.PlaylistException;
|
||||
import com.squareup.moshi.JsonReader;
|
||||
import com.squareup.moshi.JsonWriter;
|
||||
import ctbrec.recorder.download.HttpHeaderFactory;
|
||||
import ctbrec.recorder.download.RecordingProcess;
|
||||
import ctbrec.recorder.download.StreamSource;
|
||||
|
@ -14,6 +12,7 @@ import java.io.IOException;
|
|||
import java.io.Serializable;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
public interface Model extends Comparable<Model>, Serializable {
|
||||
|
@ -116,9 +115,9 @@ public interface Model extends Comparable<Model>, Serializable {
|
|||
|
||||
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();
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ import ctbrec.io.IoUtils;
|
|||
import ctbrec.recorder.download.RecordingProcess;
|
||||
import ctbrec.recorder.download.StreamSource;
|
||||
import ctbrec.recorder.download.VideoLengthDetector;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.io.File;
|
||||
|
@ -28,6 +29,7 @@ import static ctbrec.Recording.State.*;
|
|||
import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE;
|
||||
|
||||
@Slf4j
|
||||
@NoArgsConstructor
|
||||
public class Recording implements Serializable {
|
||||
|
||||
private String id;
|
||||
|
@ -48,7 +50,11 @@ public class Recording implements Serializable {
|
|||
private File postProcessedFile = null;
|
||||
private int selectedResolution = -1;
|
||||
private long lastSizeUpdate = 0;
|
||||
private String recordingsDir;
|
||||
|
||||
public Recording(String recordingsDir) {
|
||||
this.recordingsDir = recordingsDir;
|
||||
}
|
||||
|
||||
public enum State {
|
||||
RECORDING("recording"),
|
||||
|
@ -117,7 +123,6 @@ public class Recording implements Serializable {
|
|||
|
||||
public File getAbsoluteFile() {
|
||||
if (absoluteFile == null) {
|
||||
String recordingsDir = Config.getInstance().getSettings().recordingsDir;
|
||||
File recordingsFile = new File(recordingsDir, path);
|
||||
absoluteFile = recordingsFile;
|
||||
}
|
||||
|
@ -140,6 +145,9 @@ public class Recording implements Serializable {
|
|||
}
|
||||
|
||||
public long getSizeInByte() {
|
||||
if (sizeInByte == -1) {
|
||||
refresh();
|
||||
}
|
||||
return sizeInByte;
|
||||
}
|
||||
|
||||
|
@ -211,6 +219,10 @@ public class Recording implements Serializable {
|
|||
return selectedResolution;
|
||||
}
|
||||
|
||||
public void setSelectedResolution(int selectedResolution) {
|
||||
this.selectedResolution = selectedResolution;
|
||||
}
|
||||
|
||||
public Duration getLength() {
|
||||
File ppFile = getPostProcessedFile();
|
||||
if (ppFile.isDirectory()) {
|
||||
|
|
|
@ -7,6 +7,8 @@ import java.io.IOException;
|
|||
import java.nio.file.*;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
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.StandardWatchEventKinds.*;
|
||||
|
@ -19,6 +21,7 @@ public class RecordingSizeMonitor {
|
|||
protected final Map<WatchKey, Recording> recordingByKey;
|
||||
protected final Map<Recording, List<WatchKey>> keysByRecording;
|
||||
protected final Set<Path> registeredPaths;
|
||||
private final Lock lock = new ReentrantLock();
|
||||
|
||||
public RecordingSizeMonitor() throws IOException {
|
||||
this.service = FileSystems.getDefault().newWatchService();
|
||||
|
@ -54,13 +57,18 @@ public class RecordingSizeMonitor {
|
|||
});
|
||||
}
|
||||
|
||||
private void register(Path path, Recording rec) throws IOException {
|
||||
if (!registeredPaths.contains(path)) {
|
||||
WatchKey key = path.register(service, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
|
||||
keys.put(key, path);
|
||||
recordingByKey.put(key, rec);
|
||||
keysByRecording.computeIfAbsent(rec, r -> new ArrayList<>()).add(key);
|
||||
registeredPaths.add(path);
|
||||
private synchronized void register(Path path, Recording rec) throws IOException {
|
||||
lock.lock();
|
||||
try {
|
||||
if (!registeredPaths.contains(path)) {
|
||||
WatchKey key = path.register(service, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
|
||||
keys.put(key, 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) {
|
||||
List<WatchKey> keysForRecording = this.keysByRecording.getOrDefault(rec, Collections.emptyList());
|
||||
keysForRecording.forEach(key -> {
|
||||
Path path = keys.get(key);
|
||||
key.cancel();
|
||||
keys.remove(key);
|
||||
recordingByKey.remove(key);
|
||||
registeredPaths.remove(path);
|
||||
});
|
||||
this.keysByRecording.remove(rec);
|
||||
lock.lock();
|
||||
try {
|
||||
List<WatchKey> keysForRecording = this.keysByRecording.getOrDefault(rec, Collections.emptyList());
|
||||
keysForRecording.forEach(key -> {
|
||||
Path path = keys.get(key);
|
||||
key.cancel();
|
||||
keys.remove(key);
|
||||
recordingByKey.remove(key);
|
||||
registeredPaths.remove(path);
|
||||
});
|
||||
this.keysByRecording.remove(rec);
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public void processEvents() {
|
||||
|
@ -97,6 +110,7 @@ public class RecordingSizeMonitor {
|
|||
continue;
|
||||
}
|
||||
|
||||
lock.lock();
|
||||
Path dir = keys.get(key);
|
||||
if (dir == null) {
|
||||
log.error("WatchKey not recognized");
|
||||
|
@ -136,6 +150,7 @@ public class RecordingSizeMonitor {
|
|||
} catch (Exception e) {
|
||||
log.error("Error while processing file system events", e);
|
||||
} finally {
|
||||
lock.unlock();
|
||||
if (key != null) {
|
||||
key.reset();
|
||||
}
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
package ctbrec;
|
||||
|
||||
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.time.LocalTime;
|
||||
|
@ -115,12 +116,12 @@ public class Settings {
|
|||
@Deprecated
|
||||
public int minimumLengthInSeconds = 0;
|
||||
public long minimumSpaceLeftInBytes = 0;
|
||||
public List<Model> models = new ArrayList<>();
|
||||
public List<ModelDto> models = new ArrayList<>();
|
||||
public Set<ModelGroup> modelGroups = new HashSet<>();
|
||||
public Map<String, String> modelNotes = new HashMap<>();
|
||||
public Map<String, String> modelPortraits = new HashMap<>();
|
||||
@Deprecated
|
||||
public List<Model> modelsIgnored = new ArrayList<>();
|
||||
public List<ModelDto> modelsIgnored = new ArrayList<>();
|
||||
public boolean monitorClipboard = false;
|
||||
public int onlineCheckIntervalInSecs = 60;
|
||||
public boolean onlineCheckSkipsPausedModels = false;
|
||||
|
@ -131,7 +132,7 @@ public class Settings {
|
|||
public String postProcessing = "";
|
||||
public int playlistRequestTimeout = 2000;
|
||||
public int postProcessingThreads = 2;
|
||||
public List<PostProcessor> postProcessors = new ArrayList<>();
|
||||
public List<PostProcessorDto> postProcessors = new ArrayList<>();
|
||||
public String proxyHost;
|
||||
public String proxyPassword;
|
||||
public String proxyPort;
|
||||
|
@ -156,7 +157,7 @@ public class Settings {
|
|||
public String recordingsTableSortType = "";
|
||||
public String recordingsDir = System.getProperty("user.home") + File.separator + "ctbrec";
|
||||
public DirectoryStructure recordingsDirStructure = DirectoryStructure.FLAT;
|
||||
public List<Model> recordLater = new ArrayList<>();
|
||||
public List<ModelDto> recordLater = new ArrayList<>();
|
||||
public boolean recordSingleFile = false;
|
||||
public long recordUntilDefaultDurationInMinutes = 24 * 60L;
|
||||
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;
|
||||
|
||||
import com.squareup.moshi.JsonAdapter;
|
||||
import com.squareup.moshi.Moshi;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import ctbrec.Config;
|
||||
import ctbrec.LoggingInterceptor;
|
||||
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.OkHttpClient.Builder;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.net.ssl.*;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.Authenticator;
|
||||
import java.net.PasswordAuthentication;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
@ -59,47 +60,47 @@ public abstract class HttpClient {
|
|||
private void loadProxySettings() {
|
||||
ProxyType proxyType = config.getSettings().proxyType;
|
||||
switch (proxyType) {
|
||||
case HTTP:
|
||||
System.setProperty("http.proxyHost", config.getSettings().proxyHost);
|
||||
System.setProperty("http.proxyPort", config.getSettings().proxyPort);
|
||||
System.setProperty("https.proxyHost", config.getSettings().proxyHost);
|
||||
System.setProperty("https.proxyPort", config.getSettings().proxyPort);
|
||||
if(config.getSettings().proxyUser != null && !config.getSettings().proxyUser.isEmpty()) {
|
||||
String username = config.getSettings().proxyUser;
|
||||
String password = config.getSettings().proxyPassword;
|
||||
System.setProperty("http.proxyUser", username);
|
||||
System.setProperty("http.proxyPassword", password);
|
||||
}
|
||||
break;
|
||||
case SOCKS4:
|
||||
System.setProperty("socksProxyVersion", "4");
|
||||
System.setProperty("socksProxyHost", config.getSettings().proxyHost);
|
||||
System.setProperty("socksProxyPort", config.getSettings().proxyPort);
|
||||
break;
|
||||
case SOCKS5:
|
||||
System.setProperty("socksProxyVersion", "5");
|
||||
System.setProperty("socksProxyHost", config.getSettings().proxyHost);
|
||||
System.setProperty("socksProxyPort", config.getSettings().proxyPort);
|
||||
if(config.getSettings().proxyUser != null && !config.getSettings().proxyUser.isEmpty()) {
|
||||
String username = config.getSettings().proxyUser;
|
||||
String password = config.getSettings().proxyPassword;
|
||||
Authenticator.setDefault(new SocksProxyAuth(username, password));
|
||||
}
|
||||
break;
|
||||
case DIRECT:
|
||||
default:
|
||||
System.clearProperty("http.proxyHost");
|
||||
System.clearProperty("http.proxyPort");
|
||||
System.clearProperty("https.proxyHost");
|
||||
System.clearProperty("https.proxyPort");
|
||||
System.clearProperty("socksProxyVersion");
|
||||
System.clearProperty("socksProxyHost");
|
||||
System.clearProperty("socksProxyPort");
|
||||
System.clearProperty("java.net.socks.username");
|
||||
System.clearProperty("java.net.socks.password");
|
||||
System.clearProperty("http.proxyUser");
|
||||
System.clearProperty("http.proxyPassword");
|
||||
break;
|
||||
case HTTP:
|
||||
System.setProperty("http.proxyHost", config.getSettings().proxyHost);
|
||||
System.setProperty("http.proxyPort", config.getSettings().proxyPort);
|
||||
System.setProperty("https.proxyHost", config.getSettings().proxyHost);
|
||||
System.setProperty("https.proxyPort", config.getSettings().proxyPort);
|
||||
if (config.getSettings().proxyUser != null && !config.getSettings().proxyUser.isEmpty()) {
|
||||
String username = config.getSettings().proxyUser;
|
||||
String password = config.getSettings().proxyPassword;
|
||||
System.setProperty("http.proxyUser", username);
|
||||
System.setProperty("http.proxyPassword", password);
|
||||
}
|
||||
break;
|
||||
case SOCKS4:
|
||||
System.setProperty("socksProxyVersion", "4");
|
||||
System.setProperty("socksProxyHost", config.getSettings().proxyHost);
|
||||
System.setProperty("socksProxyPort", config.getSettings().proxyPort);
|
||||
break;
|
||||
case SOCKS5:
|
||||
System.setProperty("socksProxyVersion", "5");
|
||||
System.setProperty("socksProxyHost", config.getSettings().proxyHost);
|
||||
System.setProperty("socksProxyPort", config.getSettings().proxyPort);
|
||||
if (config.getSettings().proxyUser != null && !config.getSettings().proxyUser.isEmpty()) {
|
||||
String username = config.getSettings().proxyUser;
|
||||
String password = config.getSettings().proxyPassword;
|
||||
Authenticator.setDefault(new SocksProxyAuth(username, password));
|
||||
}
|
||||
break;
|
||||
case DIRECT:
|
||||
default:
|
||||
System.clearProperty("http.proxyHost");
|
||||
System.clearProperty("http.proxyPort");
|
||||
System.clearProperty("https.proxyHost");
|
||||
System.clearProperty("https.proxyPort");
|
||||
System.clearProperty("socksProxyVersion");
|
||||
System.clearProperty("socksProxyHost");
|
||||
System.clearProperty("socksProxyPort");
|
||||
System.clearProperty("java.net.socks.username");
|
||||
System.clearProperty("java.net.socks.password");
|
||||
System.clearProperty("http.proxyUser");
|
||||
System.clearProperty("http.proxyPassword");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -156,12 +157,16 @@ public abstract class HttpClient {
|
|||
X509Certificate[] x509Certificates = new X509Certificate[0];
|
||||
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 {
|
||||
final TrustManager[] trustManagers = new TrustManager[] { x509TrustManager };
|
||||
final TrustManager[] trustManagers = new TrustManager[]{x509TrustManager};
|
||||
final String PROTOCOL = "TLSv1.2";
|
||||
SSLContext sslContext = SSLContext.getInstance(PROTOCOL);
|
||||
KeyManager[] keyManagers = null;
|
||||
|
@ -183,18 +188,17 @@ public abstract class HttpClient {
|
|||
|
||||
private void persistCookies() {
|
||||
try {
|
||||
CookieContainer cookies = new CookieContainer();
|
||||
cookies.putAll(cookieJar.getCookies());
|
||||
Moshi moshi = new Moshi.Builder()
|
||||
.add(CookieContainer.class, new CookieContainerJsonAdapter())
|
||||
.build();
|
||||
JsonAdapter<CookieContainer> adapter = moshi.adapter(CookieContainer.class).indent(" ");
|
||||
String json = adapter.toJson(cookies);
|
||||
|
||||
List<CookieContainer> containers = new ArrayList<>();
|
||||
cookieJar.getCookies().forEach((domain, cookieList) -> {
|
||||
CookieContainer cookies = new CookieContainer();
|
||||
cookies.setDomain(domain);
|
||||
List<CookieDto> dtos = cookieList.stream().map(Mappers.getMapper(CookieMapper.class)::toDto).toList();
|
||||
cookies.setCookies(dtos);
|
||||
containers.add(cookies);
|
||||
});
|
||||
String json = ObjectMapperFactory.getMapper().writeValueAsString(containers);
|
||||
File cookieFile = new File(config.getConfigDir(), "cookies-" + name + ".json");
|
||||
try(FileOutputStream fout = new FileOutputStream(cookieFile)) {
|
||||
fout.write(json.getBytes(UTF_8));
|
||||
}
|
||||
Files.writeString(cookieFile.toPath(), json);
|
||||
} catch (Exception e) {
|
||||
LOG.error("Couldn't persist cookies for {}", name, e);
|
||||
}
|
||||
|
@ -203,33 +207,29 @@ public abstract class HttpClient {
|
|||
private void loadCookies() {
|
||||
try {
|
||||
File cookieFile = new File(config.getConfigDir(), "cookies-" + name + ".json");
|
||||
if(!cookieFile.exists()) {
|
||||
if (!cookieFile.exists()) {
|
||||
return;
|
||||
}
|
||||
byte[] jsonBytes = Files.readAllBytes(cookieFile.toPath());
|
||||
String json = new String(jsonBytes, UTF_8);
|
||||
|
||||
String json = Files.readString(cookieFile.toPath());
|
||||
Map<String, List<Cookie>> cookies = cookieJar.getCookies();
|
||||
Moshi moshi = new Moshi.Builder()
|
||||
.add(CookieContainer.class, new CookieContainerJsonAdapter())
|
||||
.build();
|
||||
JsonAdapter<CookieContainer> adapter = moshi.adapter(CookieContainer.class).indent(" ");
|
||||
CookieContainer fromJson = adapter.fromJson(json);
|
||||
Set<Entry<String, List<Cookie>>> entries = fromJson.entrySet();
|
||||
for (Entry<String, List<Cookie>> entry : entries) {
|
||||
List<Cookie> filteredCookies = entry.getValue().stream()
|
||||
.filter(c -> !Objects.equals("deleted", c.value()))
|
||||
List<CookieContainer> fromJson = ObjectMapperFactory.getMapper().readValue(json, new TypeReference<>() {
|
||||
});
|
||||
for (CookieContainer container : fromJson) {
|
||||
List<Cookie> filteredCookies = container.getCookies().stream()
|
||||
.filter(c -> !Objects.equals("deleted", c.getValue()))
|
||||
.map(Mappers.getMapper(CookieMapper.class)::toCookie)
|
||||
.collect(Collectors.toList());
|
||||
cookies.put(entry.getKey(), filteredCookies);
|
||||
cookies.put(container.getDomain(), filteredCookies);
|
||||
}
|
||||
|
||||
} catch (Exception 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) {
|
||||
|
@ -310,7 +310,7 @@ public abstract class HttpClient {
|
|||
while ((len = gzipIn.read(b)) >= 0) {
|
||||
bos.write(b, 0, len);
|
||||
}
|
||||
return bos.toString(StandardCharsets.UTF_8.toString());
|
||||
return bos.toString(UTF_8.toString());
|
||||
} else {
|
||||
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;
|
||||
|
||||
import com.squareup.moshi.JsonAdapter;
|
||||
import com.squareup.moshi.Moshi;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import ctbrec.Config;
|
||||
import ctbrec.Model;
|
||||
import ctbrec.Recording;
|
||||
import ctbrec.Recording.State;
|
||||
import ctbrec.RecordingSizeMonitor;
|
||||
import ctbrec.io.FileJsonAdapter;
|
||||
import ctbrec.io.InstantJsonAdapter;
|
||||
import ctbrec.io.ModelJsonAdapter;
|
||||
import ctbrec.io.json.ObjectMapperFactory;
|
||||
import ctbrec.io.json.dto.RecordingDto;
|
||||
import ctbrec.io.json.mapper.RecordingMapper;
|
||||
import ctbrec.sites.Site;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import ctbrec.sites.SiteUtil;
|
||||
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.*;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
@ -27,11 +25,12 @@ import static ctbrec.io.IoUtils.deleteDirectory;
|
|||
import static ctbrec.io.IoUtils.deleteEmptyParents;
|
||||
import static java.nio.file.StandardOpenOption.*;
|
||||
|
||||
@Slf4j
|
||||
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 JsonAdapter<Recording> adapter;
|
||||
private final List<Site> sites;
|
||||
private final List<Recording> recordings = new ArrayList<>();
|
||||
private final ReentrantLock recordingsLock = new ReentrantLock();
|
||||
|
||||
|
@ -39,12 +38,7 @@ public class RecordingManager {
|
|||
|
||||
public RecordingManager(Config config, List<Site> sites) throws IOException {
|
||||
this.config = config;
|
||||
Moshi moshi = new Moshi.Builder()
|
||||
.add(Model.class, new ModelJsonAdapter(sites))
|
||||
.add(Instant.class, new InstantJsonAdapter())
|
||||
.add(File.class, new FileJsonAdapter())
|
||||
.build();
|
||||
adapter = moshi.adapter(Recording.class).indent(" ");
|
||||
this.sites = sites;
|
||||
|
||||
sizeMonitor = new RecordingSizeMonitor();
|
||||
Executors.newSingleThreadExecutor().submit(sizeMonitor::processEvents);
|
||||
|
@ -61,16 +55,16 @@ public class RecordingManager {
|
|||
recordingsLock.lock();
|
||||
try {
|
||||
recordings.add(rec);
|
||||
sizeMonitor.monitor(rec);
|
||||
} finally {
|
||||
recordingsLock.unlock();
|
||||
}
|
||||
sizeMonitor.monitor(rec);
|
||||
}
|
||||
|
||||
public void saveRecording(Recording rec) throws IOException {
|
||||
if (rec.getMetaDataFile() != null) {
|
||||
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());
|
||||
Files.createDirectories(recordingMetaData.getParentFile().toPath());
|
||||
Files.writeString(recordingMetaData.toPath(), json, CREATE, WRITE, TRUNCATE_EXISTING);
|
||||
|
@ -84,11 +78,12 @@ public class RecordingManager {
|
|||
for (File file : metaFiles) {
|
||||
String json = Files.readString(file.toPath());
|
||||
try {
|
||||
Recording recording = adapter.fromJson(json);
|
||||
Recording recording = Mappers.getMapper(RecordingMapper.class).toRecording(mapper.readValue(json, RecordingDto.class));
|
||||
recording.setMetaDataFile(file.getCanonicalPath());
|
||||
SiteUtil.getSiteForModel(sites, recording.getModel()).ifPresent(s -> recording.getModel().setSite(s));
|
||||
loadRecording(recording);
|
||||
} 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);
|
||||
sizeMonitor.monitor(recording);
|
||||
} 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);
|
||||
File path = recording.getAbsoluteFile();
|
||||
boolean isFile = path.isFile();
|
||||
LOG.debug("Deleting {}", path);
|
||||
log.debug("Deleting {}", path);
|
||||
|
||||
// uninstall file monitor
|
||||
sizeMonitor.uninstall(recording);
|
||||
|
||||
// delete the video files
|
||||
if (isFile) {
|
||||
|
@ -177,7 +175,6 @@ public class RecordingManager {
|
|||
} finally {
|
||||
recordingsLock.unlock();
|
||||
}
|
||||
sizeMonitor.uninstall(recording);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -191,6 +188,8 @@ public class RecordingManager {
|
|||
try {
|
||||
int idx = recordings.indexOf(recording);
|
||||
recording = recordings.get(idx);
|
||||
// uninstall file monitor
|
||||
sizeMonitor.uninstall(recording);
|
||||
// delete the meta data
|
||||
Files.deleteIfExists(new File(recording.getMetaDataFile()).toPath());
|
||||
// remove from data structure
|
||||
|
@ -198,7 +197,6 @@ public class RecordingManager {
|
|||
} finally {
|
||||
recordingsLock.unlock();
|
||||
}
|
||||
sizeMonitor.uninstall(recording);
|
||||
}
|
||||
|
||||
public List<Recording> getAll() {
|
||||
|
|
|
@ -1,19 +1,29 @@
|
|||
package ctbrec.recorder;
|
||||
|
||||
import com.squareup.moshi.JsonAdapter;
|
||||
import com.squareup.moshi.Moshi;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import ctbrec.*;
|
||||
import ctbrec.event.EventBusHolder;
|
||||
import ctbrec.event.NoSpaceLeftEvent;
|
||||
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 lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Request.Builder;
|
||||
import okhttp3.RequestBody;
|
||||
import okhttp3.Response;
|
||||
import org.json.JSONObject;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
@ -25,6 +35,7 @@ import java.time.Duration;
|
|||
import java.time.Instant;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
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");
|
||||
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())
|
||||
.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 final ObjectMapper mapper = ObjectMapperFactory.getMapper();
|
||||
|
||||
private List<Model> models = Collections.emptyList();
|
||||
private List<Model> onlineModels = Collections.emptyList();
|
||||
private List<Recording> recordings = Collections.emptyList();
|
||||
private final ReentrantLock modelGroupLock = new ReentrantLock();
|
||||
private final Set<ModelGroup> modelGroups = new HashSet<>();
|
||||
private final ReentrantLock modelGroupLock = new ReentrantLock();
|
||||
private final List<Site> sites;
|
||||
private long spaceTotal = -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 {
|
||||
String payload = modelRequestAdapter.toJson(new ModelRequest(action, model));
|
||||
String payload = mapper.writeValueAsString(new ModelRequest(action, model));
|
||||
LOG.trace(LOG_MSG_SENDING_REQUERST, payload);
|
||||
RequestBody body = RequestBody.Companion.create(payload, JSON);
|
||||
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)) {
|
||||
String json = response.body().string();
|
||||
if (response.isSuccessful()) {
|
||||
ModelListResponse resp = modelListResponseAdapter.fromJson(json);
|
||||
ModelListResponse resp = mapper.readValue(json, ModelListResponse.class);
|
||||
if (!resp.status.equals(SUCCESS)) {
|
||||
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 {
|
||||
RecordingRequest recReq = new RecordingRequest(action, recording);
|
||||
String msg = recordingRequestAdapter.toJson(recReq);
|
||||
String msg = mapper.writeValueAsString(recReq);
|
||||
RequestBody body = RequestBody.Companion.create(msg, JSON);
|
||||
Request.Builder builder = new Request.Builder().url(getRecordingEndpoint()).post(body);
|
||||
LOG.trace(LOG_MSG_SENDING_REQUERST, msg);
|
||||
|
@ -152,7 +152,7 @@ public class RemoteRecorder implements Recorder {
|
|||
Request request = builder.build();
|
||||
try (Response response = client.execute(request)) {
|
||||
String json = response.body().string();
|
||||
RecordingListResponse resp = recordingListResponseAdapter.fromJson(json);
|
||||
RecordingListResponse resp = mapper.readValue(json, RecordingListResponse.class);
|
||||
if (response.isSuccessful()) {
|
||||
if (!resp.status.equals(SUCCESS)) {
|
||||
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 {
|
||||
String payload = modelGroupRequestAdapter.toJson(new ModelGroupRequest(action, model));
|
||||
private void sendRequest(String action, ModelGroup modelGroup) throws IOException, InvalidKeyException, NoSuchAlgorithmException {
|
||||
String payload = mapper.writeValueAsString(new ModelGroupRequest(action, modelGroup));
|
||||
LOG.trace(LOG_MSG_SENDING_REQUERST, payload);
|
||||
RequestBody body = RequestBody.Companion.create(payload, JSON);
|
||||
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 {
|
||||
ModelGroupListResponse resp = modelGroupListResponseAdapter.fromJson(responseBody);
|
||||
ModelGroupListResponse resp = mapper.readValue(responseBody, ModelGroupListResponse.class);
|
||||
if (!resp.status.equals(SUCCESS)) {
|
||||
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)) {
|
||||
String json = response.body().string();
|
||||
if (response.isSuccessful()) {
|
||||
ModelListResponse resp = modelListResponseAdapter.fromJson(json);
|
||||
ModelListResponse resp = mapper.readValue(json, ModelListResponse.class);
|
||||
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 (Site site : sites) {
|
||||
if (site.isSiteForModel(model)) {
|
||||
|
@ -363,9 +363,9 @@ public class RemoteRecorder implements Recorder {
|
|||
try (Response response = client.execute(request)) {
|
||||
String json = response.body().string();
|
||||
if (response.isSuccessful()) {
|
||||
ModelListResponse resp = modelListResponseAdapter.fromJson(json);
|
||||
ModelListResponse resp = mapper.readValue(json, ModelListResponse.class);
|
||||
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 (Site site : sites) {
|
||||
if (site.isSiteForModel(model)) {
|
||||
|
@ -395,12 +395,11 @@ public class RemoteRecorder implements Recorder {
|
|||
try (Response response = client.execute(request)) {
|
||||
String json = response.body().string();
|
||||
if (response.isSuccessful()) {
|
||||
RecordingListResponse resp = recordingListResponseAdapter.fromJson(json);
|
||||
RecordingListResponse resp = mapper.readValue(json, RecordingListResponse.class);
|
||||
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
|
||||
for (Iterator<Recording> iterator = recordings.iterator(); iterator.hasNext(); ) {
|
||||
Recording recording = iterator.next();
|
||||
for (Recording recording : recordings) {
|
||||
if (newRecordings.contains(recording)) {
|
||||
int idx = newRecordings.indexOf(recording);
|
||||
Recording newRecording = newRecordings.get(idx);
|
||||
|
@ -462,27 +461,31 @@ public class RemoteRecorder implements Recorder {
|
|||
}
|
||||
}
|
||||
|
||||
@Data
|
||||
private static class ModelListResponse {
|
||||
public String status;
|
||||
public String msg;
|
||||
public List<Model> models;
|
||||
String status;
|
||||
String msg;
|
||||
List<ModelDto> models;
|
||||
}
|
||||
|
||||
@Data
|
||||
private static class ModelGroupListResponse {
|
||||
public String status;
|
||||
public String msg;
|
||||
public List<ModelGroup> groups;
|
||||
String status;
|
||||
String msg;
|
||||
List<ModelGroup> groups;
|
||||
}
|
||||
|
||||
@Data
|
||||
private static class SimpleResponse {
|
||||
public String status;
|
||||
public String msg;
|
||||
String status;
|
||||
String msg;
|
||||
}
|
||||
|
||||
@Data
|
||||
private static class RecordingListResponse {
|
||||
public String status;
|
||||
public String msg;
|
||||
public List<Recording> recordings;
|
||||
String status;
|
||||
String msg;
|
||||
List<RecordingDto> recordings;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -495,80 +498,34 @@ public class RemoteRecorder implements Recorder {
|
|||
sendRequest("delete", recording, () -> recordings.remove(recording));
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class ModelRequest {
|
||||
private String action;
|
||||
private Model model;
|
||||
private ModelDto model;
|
||||
|
||||
public ModelRequest(String action, Model model) {
|
||||
super();
|
||||
this.action = action;
|
||||
this.model = 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;
|
||||
this.model = Mappers.getMapper(ModelMapper.class).toDto(model);
|
||||
}
|
||||
}
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public static class ModelGroupRequest {
|
||||
private String action;
|
||||
private final String action;
|
||||
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 {
|
||||
private String action;
|
||||
private Recording recording;
|
||||
private RecordingDto recording;
|
||||
|
||||
public RecordingRequest(String action, Recording recording) {
|
||||
super();
|
||||
this.action = action;
|
||||
this.recording = 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;
|
||||
this.recording = Mappers.getMapper(RecordingMapper.class).toDto(recording);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -612,7 +569,7 @@ public class RemoteRecorder implements Recorder {
|
|||
}
|
||||
|
||||
@Override
|
||||
public long getTotalSpaceBytes() throws IOException {
|
||||
public long getTotalSpaceBytes() {
|
||||
return spaceTotal;
|
||||
}
|
||||
|
||||
|
@ -634,7 +591,7 @@ public class RemoteRecorder implements Recorder {
|
|||
@Override
|
||||
public void rerunPostProcessing(Recording recording) throws IOException, InvalidKeyException, NoSuchAlgorithmException {
|
||||
RecordingRequest recReq = new RecordingRequest("rerunPostProcessing", recording);
|
||||
String msg = recordingRequestAdapter.toJson(recReq);
|
||||
String msg = mapper.writeValueAsString(recReq);
|
||||
LOG.debug(msg);
|
||||
RequestBody body = RequestBody.Companion.create(msg, JSON);
|
||||
Request.Builder builder = new Request.Builder().url(getRecordingEndpoint()).post(body);
|
||||
|
@ -642,7 +599,7 @@ public class RemoteRecorder implements Recorder {
|
|||
Request request = builder.build();
|
||||
try (Response response = client.execute(request)) {
|
||||
String json = response.body().string();
|
||||
SimpleResponse resp = simpleResponseAdapter.fromJson(json);
|
||||
SimpleResponse resp = mapper.readValue(json, SimpleResponse.class);
|
||||
if (response.isSuccessful()) {
|
||||
if (!resp.status.equals(SUCCESS)) {
|
||||
throw new IOException("Couldn't start post-processing for recording: " + resp.msg);
|
||||
|
|
|
@ -5,12 +5,16 @@ import ctbrec.*;
|
|||
import ctbrec.Recording.State;
|
||||
import ctbrec.event.*;
|
||||
import ctbrec.io.HttpClient;
|
||||
import ctbrec.io.json.mapper.ModelMapper;
|
||||
import ctbrec.io.json.mapper.PostProcessorMapper;
|
||||
import ctbrec.notes.LocalModelNotesService;
|
||||
import ctbrec.recorder.download.RecordingProcess;
|
||||
import ctbrec.recorder.postprocessing.PostProcessingContext;
|
||||
import ctbrec.recorder.postprocessing.PostProcessor;
|
||||
import ctbrec.sites.Site;
|
||||
import ctbrec.sites.SiteUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
@ -24,6 +28,7 @@ import java.time.ZoneId;
|
|||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static ctbrec.Recording.State.WAITING;
|
||||
import static ctbrec.SubsequentAction.*;
|
||||
|
@ -35,7 +40,7 @@ import static java.lang.Thread.MIN_PRIORITY;
|
|||
public class SimplifiedLocalRecorder implements Recorder {
|
||||
|
||||
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 volatile boolean running;
|
||||
private final ReentrantLock recorderLock = new ReentrantLock();
|
||||
|
@ -62,7 +67,7 @@ public class SimplifiedLocalRecorder implements Recorder {
|
|||
scheduler = Executors.newScheduledThreadPool(5, createThreadFactory("Download", MAX_PRIORITY));
|
||||
threadPoolScaler = new ThreadPoolScaler((ThreadPoolExecutor) scheduler, 5);
|
||||
recordingManager = new RecordingManager(config, sites);
|
||||
loadModels();
|
||||
loadModels(sites);
|
||||
int ppThreads = config.getSettings().postProcessingThreads;
|
||||
BlockingQueue<Runnable> ppQueue = new LinkedBlockingQueue<>();
|
||||
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);
|
||||
}
|
||||
|
||||
private void loadModels() {
|
||||
config.getSettings().models.forEach(m -> {
|
||||
if (m.getSite() != null) {
|
||||
if (m.getSite().isEnabled()) {
|
||||
models.add(m);
|
||||
} else {
|
||||
log.info("{} disabled -> ignoring {}", m.getSite().getName(), m.getName());
|
||||
}
|
||||
} else {
|
||||
log.info("Site for model {} is unknown -> ignoring", m.getName());
|
||||
}
|
||||
});
|
||||
private void loadModels(List<Site> sites) {
|
||||
config.getSettings().models
|
||||
.stream()
|
||||
.map(Mappers.getMapper(ModelMapper.class)::toModel)
|
||||
.forEach(m -> {
|
||||
SiteUtil.getSiteForModel(sites, m).ifPresent(m::setSite);
|
||||
if (m.getSite() != null) {
|
||||
if (m.getSite().isEnabled()) {
|
||||
models.add(m);
|
||||
} 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 {
|
||||
|
@ -238,7 +247,10 @@ public class SimplifiedLocalRecorder implements Recorder {
|
|||
recording.refresh();
|
||||
recordingManager.saveRecording(recording);
|
||||
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);
|
||||
for (PostProcessor postProcessor : postProcessors) {
|
||||
if (postProcessor.isEnabled()) {
|
||||
|
@ -281,7 +293,7 @@ public class SimplifiedLocalRecorder implements Recorder {
|
|||
ctx.setRecorder(this);
|
||||
ctx.setRecording(recording);
|
||||
ctx.setRecordingManager(recordingManager);
|
||||
ctx.setModelNotesService(new LocalModelNotesService(config)); // TODO
|
||||
ctx.setModelNotesService(new LocalModelNotesService(config));
|
||||
return ctx;
|
||||
}
|
||||
|
||||
|
@ -307,7 +319,7 @@ public class SimplifiedLocalRecorder implements Recorder {
|
|||
if (Objects.equals(model.getAddedTimestamp(), Instant.EPOCH)) {
|
||||
model.setAddedTimestamp(Instant.now());
|
||||
}
|
||||
config.getSettings().models.add(model);
|
||||
config.getSettings().models.add(Mappers.getMapper(ModelMapper.class).toDto(model));
|
||||
config.save();
|
||||
} catch (IOException e) {
|
||||
log.error("Couldn't save config", e);
|
||||
|
@ -364,7 +376,7 @@ public class SimplifiedLocalRecorder implements Recorder {
|
|||
|
||||
private Recording createRecording(RecordingProcess download) throws IOException {
|
||||
Model model = download.getModel();
|
||||
Recording rec = new Recording();
|
||||
Recording rec = new Recording(config.getSettings().recordingsDir);
|
||||
rec.setId(UUID.randomUUID().toString());
|
||||
rec.setRecordingProcess(download);
|
||||
String recordingFile = download.getPath(model).replace('\\', '/');
|
||||
|
@ -405,9 +417,8 @@ public class SimplifiedLocalRecorder implements Recorder {
|
|||
try {
|
||||
if (models.contains(model)) {
|
||||
models.remove(model);
|
||||
config.getSettings().models.remove(model);
|
||||
saveConfig();
|
||||
log.info("Model {} removed", model);
|
||||
config.save();
|
||||
} else {
|
||||
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)) {
|
||||
int index = models.indexOf(model);
|
||||
models.get(index).setStreamUrlIndex(model.getStreamUrlIndex());
|
||||
config.save();
|
||||
saveConfig();
|
||||
log.debug("Switching stream source to index {} for model {}", model.getStreamUrlIndex(), model.getName());
|
||||
recorderLock.lock();
|
||||
try {
|
||||
|
@ -517,7 +528,7 @@ public class SimplifiedLocalRecorder implements Recorder {
|
|||
int index = models.indexOf(model);
|
||||
models.get(index).setSuspended(true);
|
||||
model.setSuspended(true);
|
||||
config.save();
|
||||
saveConfig();
|
||||
} else {
|
||||
log.warn("Couldn't suspend model {}. Not found in list", model.getName());
|
||||
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
|
||||
public void resumeRecording(Model model) throws IOException {
|
||||
recorderLock.lock();
|
||||
|
@ -542,7 +558,7 @@ public class SimplifiedLocalRecorder implements Recorder {
|
|||
m.setMarkedForLaterRecording(false);
|
||||
model.setSuspended(false);
|
||||
model.setMarkedForLaterRecording(false);
|
||||
config.save();
|
||||
saveConfig();
|
||||
startRecordingProcess(m);
|
||||
} else {
|
||||
log.warn("Couldn't resume model {}. Not found in list", model.getName());
|
||||
|
@ -591,6 +607,7 @@ public class SimplifiedLocalRecorder implements Recorder {
|
|||
addModel(model);
|
||||
}
|
||||
}
|
||||
saveConfig();
|
||||
}
|
||||
|
||||
private Optional<Model> findModel(Model m) {
|
||||
|
@ -758,7 +775,7 @@ public class SimplifiedLocalRecorder implements Recorder {
|
|||
if (models.contains(model)) {
|
||||
int index = models.indexOf(model);
|
||||
models.get(index).setPriority(model.getPriority());
|
||||
config.save();
|
||||
saveConfig();
|
||||
} else {
|
||||
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.setRecordUntilSubsequentAction(model.getRecordUntilSubsequentAction());
|
||||
log.debug("Stopping recording of model {} at {} and then {}", m, model.getRecordUntil(), m.getRecordUntilSubsequentAction());
|
||||
config.save();
|
||||
saveConfig();
|
||||
} else {
|
||||
throw new NoSuchElementException("Model " + model.getName() + " [" + model.getUrl() + "] not found in list of recorded models");
|
||||
}
|
||||
|
|
|
@ -1,17 +1,15 @@
|
|||
package ctbrec.recorder.postprocessing;
|
||||
|
||||
import ctbrec.Recording;
|
||||
import ctbrec.recorder.RecordingManager;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.Duration;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import ctbrec.Recording;
|
||||
import ctbrec.recorder.RecordingManager;
|
||||
|
||||
@Slf4j
|
||||
public class DeleteTooShort extends AbstractPostProcessor {
|
||||
|
||||
private static final transient Logger LOG = LoggerFactory.getLogger(DeleteTooShort.class);
|
||||
public static final String MIN_LEN_IN_SECS = "minimumLengthInSeconds";
|
||||
|
||||
@Override
|
||||
|
@ -27,10 +25,10 @@ public class DeleteTooShort extends AbstractPostProcessor {
|
|||
if (minimumLengthInSeconds.getSeconds() > 0) {
|
||||
Duration recordingLength = rec.getLength();
|
||||
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 {
|
||||
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);
|
||||
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;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.iheartradio.m3u8.*;
|
||||
import com.iheartradio.m3u8.data.MasterPlaylist;
|
||||
import com.iheartradio.m3u8.data.Playlist;
|
||||
import com.iheartradio.m3u8.data.PlaylistData;
|
||||
import com.squareup.moshi.JsonAdapter;
|
||||
import com.squareup.moshi.Moshi;
|
||||
import ctbrec.AbstractModel;
|
||||
import ctbrec.Config;
|
||||
import ctbrec.io.HttpException;
|
||||
import ctbrec.io.json.ObjectMapperFactory;
|
||||
import ctbrec.recorder.download.StreamSource;
|
||||
import okhttp3.FormBody;
|
||||
import okhttp3.Request;
|
||||
|
@ -38,7 +38,7 @@ public class ChaturbateModel extends AbstractModel { // NOSONAR
|
|||
private int[] resolution = new int[2];
|
||||
private transient StreamInfo streamInfo;
|
||||
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
|
||||
|
@ -251,9 +251,7 @@ public class ChaturbateModel extends AbstractModel { // NOSONAR
|
|||
if (response.isSuccessful()) {
|
||||
String content = response.body().string();
|
||||
LOG.trace("Raw stream info: {}", content);
|
||||
Moshi moshi = new Moshi.Builder().build();
|
||||
JsonAdapter<StreamInfo> adapter = moshi.adapter(StreamInfo.class);
|
||||
streamInfo = adapter.fromJson(content);
|
||||
streamInfo = mapper.readValue(content, StreamInfo.class);
|
||||
return streamInfo;
|
||||
} else {
|
||||
int code = response.code();
|
||||
|
|
|
@ -4,8 +4,6 @@ import com.iheartradio.m3u8.*;
|
|||
import com.iheartradio.m3u8.data.MasterPlaylist;
|
||||
import com.iheartradio.m3u8.data.Playlist;
|
||||
import com.iheartradio.m3u8.data.PlaylistData;
|
||||
import com.squareup.moshi.JsonReader;
|
||||
import com.squareup.moshi.JsonWriter;
|
||||
import ctbrec.*;
|
||||
import ctbrec.io.HttpException;
|
||||
import ctbrec.recorder.download.StreamSource;
|
||||
|
@ -287,15 +285,12 @@ public class CherryTvModel extends AbstractModel {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void readSiteSpecificData(JsonReader reader) throws IOException {
|
||||
if (reader.hasNext()) {
|
||||
reader.nextName();
|
||||
id = reader.nextString();
|
||||
}
|
||||
public void readSiteSpecificData(Map<String, String> data) {
|
||||
id = data.get("id");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeSiteSpecificData(JsonWriter writer) throws IOException {
|
||||
writer.name("id").value(id);
|
||||
public void writeSiteSpecificData(Map<String, String> data) {
|
||||
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.PlaylistData;
|
||||
import com.iheartradio.m3u8.data.StreamInfo;
|
||||
import com.squareup.moshi.JsonReader;
|
||||
import com.squareup.moshi.JsonWriter;
|
||||
import ctbrec.AbstractModel;
|
||||
import ctbrec.Config;
|
||||
import ctbrec.io.HttpException;
|
||||
|
@ -21,10 +19,7 @@ import org.slf4j.LoggerFactory;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
@ -382,14 +377,13 @@ public class Fc2Model extends AbstractModel {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void readSiteSpecificData(JsonReader reader) throws IOException {
|
||||
reader.nextName();
|
||||
id = reader.nextString();
|
||||
public void readSiteSpecificData(Map<String, String> data) {
|
||||
id = data.get("id");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeSiteSpecificData(JsonWriter writer) throws IOException {
|
||||
writer.name("id").value(id);
|
||||
public void writeSiteSpecificData(Map<String, String> data) {
|
||||
data.put("id", id);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -4,8 +4,6 @@ import com.iheartradio.m3u8.*;
|
|||
import com.iheartradio.m3u8.data.MasterPlaylist;
|
||||
import com.iheartradio.m3u8.data.Playlist;
|
||||
import com.iheartradio.m3u8.data.PlaylistData;
|
||||
import com.squareup.moshi.JsonReader;
|
||||
import com.squareup.moshi.JsonWriter;
|
||||
import ctbrec.AbstractModel;
|
||||
import ctbrec.Config;
|
||||
import ctbrec.Model;
|
||||
|
@ -499,16 +497,13 @@ public class Flirt4FreeModel extends AbstractModel {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void readSiteSpecificData(JsonReader reader) throws IOException {
|
||||
if (reader.hasNext()) {
|
||||
reader.nextName();
|
||||
id = reader.nextString();
|
||||
}
|
||||
public void readSiteSpecificData(Map<String, String> data) {
|
||||
id = data.get("id");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeSiteSpecificData(JsonWriter writer) throws IOException {
|
||||
writer.name("id").value(id);
|
||||
public void writeSiteSpecificData(Map<String, String> data) {
|
||||
data.put("id", id);
|
||||
}
|
||||
|
||||
public void setStreamUrl(String streamUrl) {
|
||||
|
|
|
@ -2,8 +2,6 @@ package ctbrec.sites.jasmin;
|
|||
|
||||
import com.iheartradio.m3u8.ParseException;
|
||||
import com.iheartradio.m3u8.PlaylistException;
|
||||
import com.squareup.moshi.JsonReader;
|
||||
import com.squareup.moshi.JsonWriter;
|
||||
import ctbrec.AbstractModel;
|
||||
import ctbrec.Config;
|
||||
import ctbrec.StringUtil;
|
||||
|
@ -229,13 +227,12 @@ public class LiveJasminModel extends AbstractModel {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void readSiteSpecificData(JsonReader reader) throws IOException {
|
||||
reader.nextName();
|
||||
id = reader.nextString();
|
||||
public void readSiteSpecificData(Map<String, String> data) {
|
||||
id = data.get("id");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeSiteSpecificData(JsonWriter writer) throws IOException {
|
||||
public void writeSiteSpecificData(Map<String, String> data) {
|
||||
if (id == null) {
|
||||
try {
|
||||
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());
|
||||
}
|
||||
}
|
||||
writer.name("id").value(id);
|
||||
data.put("id", id);
|
||||
}
|
||||
|
||||
public void setOnline(boolean online) {
|
||||
|
|
|
@ -4,8 +4,6 @@ import com.iheartradio.m3u8.*;
|
|||
import com.iheartradio.m3u8.data.MasterPlaylist;
|
||||
import com.iheartradio.m3u8.data.Playlist;
|
||||
import com.iheartradio.m3u8.data.PlaylistData;
|
||||
import com.squareup.moshi.JsonReader;
|
||||
import com.squareup.moshi.JsonWriter;
|
||||
import ctbrec.*;
|
||||
import ctbrec.io.HttpException;
|
||||
import ctbrec.recorder.download.RecordingProcess;
|
||||
|
@ -240,17 +238,13 @@ public class MVLiveModel extends AbstractModel {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void writeSiteSpecificData(JsonWriter writer) throws IOException {
|
||||
writer.name("id").value(id);
|
||||
public void writeSiteSpecificData(Map<String, String> data) {
|
||||
data.put("id", id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readSiteSpecificData(JsonReader reader) throws IOException {
|
||||
if (reader.hasNext()) {
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
reader.nextName();
|
||||
id = reader.nextString();
|
||||
}
|
||||
public void readSiteSpecificData(Map<String, String> data) {
|
||||
id = data.get("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.CacheBuilder;
|
||||
import com.squareup.moshi.JsonAdapter;
|
||||
import com.squareup.moshi.Moshi;
|
||||
import ctbrec.Config;
|
||||
import ctbrec.StringUtil;
|
||||
import ctbrec.io.HttpException;
|
||||
|
@ -39,7 +37,6 @@ public class MyFreeCamsClient {
|
|||
private MyFreeCams mfc;
|
||||
private WebSocket ws;
|
||||
private Thread keepAlive;
|
||||
private final Moshi moshi;
|
||||
private volatile boolean running = false;
|
||||
|
||||
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 MyFreeCamsClient() {
|
||||
moshi = new Moshi.Builder().build();
|
||||
}
|
||||
|
||||
public static synchronized MyFreeCamsClient getInstance() {
|
||||
|
@ -228,16 +224,16 @@ public class MyFreeCamsClient {
|
|||
case MYWEBCAM:
|
||||
case JOINCHAN:
|
||||
case SESSIONSTATE:
|
||||
if (!message.getMessage().isEmpty()) {
|
||||
//LOG.debug("SessionState: {}", message.getMessage());
|
||||
JsonAdapter<SessionState> adapter = moshi.adapter(SessionState.class);
|
||||
try {
|
||||
SessionState sessionState = adapter.fromJson(message.getMessage());
|
||||
updateSessionState(sessionState);
|
||||
} catch (IOException e) {
|
||||
LOG.error("Couldn't parse session state message {}", message, e);
|
||||
}
|
||||
}
|
||||
// if (!message.getMessage().isEmpty()) {
|
||||
// //LOG.debug("SessionState: {}", message.getMessage());
|
||||
// JsonAdapter<SessionState> adapter = moshi.adapter(SessionState.class);
|
||||
// try {
|
||||
// SessionState sessionState = adapter.fromJson(message.getMessage());
|
||||
// updateSessionState(sessionState);
|
||||
// } catch (IOException e) {
|
||||
// LOG.error("Couldn't parse session state message {}", message, e);
|
||||
// }
|
||||
// }
|
||||
break;
|
||||
case USERNAMELOOKUP:
|
||||
// LOG.debug("{}", message.getType());
|
||||
|
@ -680,13 +676,13 @@ public class MyFreeCamsClient {
|
|||
if (StringUtil.isNotBlank(msg.getMessage()) && !Objects.equals(msg.getMessage(), q)) {
|
||||
JSONObject json = new JSONObject(msg.getMessage());
|
||||
|
||||
JsonAdapter<SessionState> adapter = moshi.adapter(SessionState.class);
|
||||
try {
|
||||
SessionState sessionState = Objects.requireNonNull(adapter.fromJson(msg.getMessage()));
|
||||
updateSessionState(sessionState);
|
||||
} catch (Exception e) {
|
||||
LOG.error("Couldn't parse session state message {}", msg, e);
|
||||
}
|
||||
// JsonAdapter<SessionState> adapter = moshi.adapter(SessionState.class);
|
||||
// try {
|
||||
// SessionState sessionState = Objects.requireNonNull(adapter.fromJson(msg.getMessage()));
|
||||
// updateSessionState(sessionState);
|
||||
// } catch (Exception e) {
|
||||
// LOG.error("Couldn't parse session state message {}", msg, e);
|
||||
// }
|
||||
|
||||
String name = json.getString("nm");
|
||||
MyFreeCamsModel model = mfc.createModel(name);
|
||||
|
|
|
@ -2,8 +2,6 @@ package ctbrec.sites.mfc;
|
|||
|
||||
import com.iheartradio.m3u8.ParseException;
|
||||
import com.iheartradio.m3u8.PlaylistException;
|
||||
import com.squareup.moshi.JsonReader;
|
||||
import com.squareup.moshi.JsonWriter;
|
||||
import ctbrec.AbstractModel;
|
||||
import ctbrec.Config;
|
||||
import ctbrec.io.HtmlParser;
|
||||
|
@ -324,14 +322,13 @@ public class MyFreeCamsModel extends AbstractModel {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void readSiteSpecificData(JsonReader reader) throws IOException {
|
||||
reader.nextName();
|
||||
uid = reader.nextInt();
|
||||
public void readSiteSpecificData(Map<String, String> data) {
|
||||
uid = Integer.parseInt(data.get("uid"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeSiteSpecificData(JsonWriter writer) throws IOException {
|
||||
writer.name("uid").value(uid);
|
||||
public void writeSiteSpecificData(Map<String, String> data) {
|
||||
data.put("uid", Integer.toString(uid));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -2,8 +2,6 @@ package ctbrec.sites.streamate;
|
|||
|
||||
import com.iheartradio.m3u8.ParseException;
|
||||
import com.iheartradio.m3u8.PlaylistException;
|
||||
import com.squareup.moshi.JsonReader;
|
||||
import com.squareup.moshi.JsonWriter;
|
||||
import ctbrec.AbstractModel;
|
||||
import ctbrec.Config;
|
||||
import ctbrec.NotImplementedExcetion;
|
||||
|
@ -224,13 +222,12 @@ public class StreamateModel extends AbstractModel {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void readSiteSpecificData(JsonReader reader) throws IOException {
|
||||
reader.nextName();
|
||||
id = reader.nextLong();
|
||||
public void readSiteSpecificData(Map<String, String> data) {
|
||||
id = Long.parseLong(data.get("id"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeSiteSpecificData(JsonWriter writer) throws IOException {
|
||||
public void writeSiteSpecificData(Map<String, String> data) {
|
||||
if (id == null || Objects.equals(id, MODEL_ID_UNDEFINED)) {
|
||||
try {
|
||||
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);
|
||||
}
|
||||
}
|
||||
writer.name("id").value(id);
|
||||
data.put("id", Long.toString(id));
|
||||
}
|
||||
|
||||
@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;
|
||||
|
||||
import ctbrec.*;
|
||||
import ctbrec.io.json.mapper.ModelMapper;
|
||||
import ctbrec.recorder.download.RecordingProcess;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
import org.mockito.MockedStatic;
|
||||
|
||||
import java.io.IOException;
|
||||
|
@ -15,6 +17,7 @@ import java.util.LinkedList;
|
|||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static java.time.temporal.ChronoUnit.HOURS;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
@ -169,9 +172,9 @@ class RecordingPreconditionsTest {
|
|||
|
||||
var recorder = mock(SimplifiedLocalRecorder.class);
|
||||
List<Model> modelsToRecord = new LinkedList<>();
|
||||
settings.models = modelsToRecord;
|
||||
modelsToRecord.add(theOtherOne);
|
||||
modelsToRecord.add(mockita);
|
||||
settings.models = modelsToRecord.stream().map(Mappers.getMapper(ModelMapper.class)::toDto).collect(Collectors.toList());
|
||||
when(recorder.isRunning()).thenReturn(true);
|
||||
when(recorder.getModels()).thenReturn(modelsToRecord);
|
||||
when(recorder.notEnoughSpaceForRecording()).thenReturn(false);
|
||||
|
@ -197,8 +200,8 @@ class RecordingPreconditionsTest {
|
|||
|
||||
var recorder = mock(SimplifiedLocalRecorder.class);
|
||||
List<Model> modelsToRecord = new LinkedList<>();
|
||||
settings.models = modelsToRecord;
|
||||
modelsToRecord.add(mockita);
|
||||
settings.models = modelsToRecord.stream().map(Mappers.getMapper(ModelMapper.class)::toDto).collect(Collectors.toList());
|
||||
when(recorder.isRunning()).thenReturn(true);
|
||||
when(recorder.getModels()).thenReturn(modelsToRecord);
|
||||
when(recorder.notEnoughSpaceForRecording()).thenReturn(false);
|
||||
|
@ -217,8 +220,8 @@ class RecordingPreconditionsTest {
|
|||
|
||||
var recorder = mock(SimplifiedLocalRecorder.class);
|
||||
List<Model> modelsToRecord = new LinkedList<>();
|
||||
settings.models = modelsToRecord;
|
||||
modelsToRecord.add(mockita);
|
||||
settings.models = modelsToRecord.stream().map(Mappers.getMapper(ModelMapper.class)::toDto).collect(Collectors.toList());
|
||||
when(recorder.isRunning()).thenReturn(true);
|
||||
when(recorder.getModels()).thenReturn(modelsToRecord);
|
||||
when(recorder.notEnoughSpaceForRecording()).thenReturn(false);
|
||||
|
@ -252,8 +255,8 @@ class RecordingPreconditionsTest {
|
|||
|
||||
var recorder = mock(SimplifiedLocalRecorder.class);
|
||||
List<Model> modelsToRecord = new LinkedList<>();
|
||||
settings.models = modelsToRecord;
|
||||
modelsToRecord.add(mockita);
|
||||
settings.models = modelsToRecord.stream().map(Mappers.getMapper(ModelMapper.class)::toDto).collect(Collectors.toList());
|
||||
when(recorder.isRunning()).thenReturn(true);
|
||||
when(recorder.getModels()).thenReturn(modelsToRecord);
|
||||
when(recorder.notEnoughSpaceForRecording()).thenReturn(false);
|
||||
|
@ -291,8 +294,8 @@ class RecordingPreconditionsTest {
|
|||
|
||||
var recorder = mock(SimplifiedLocalRecorder.class);
|
||||
List<Model> modelsToRecord = new LinkedList<>();
|
||||
settings.models = modelsToRecord;
|
||||
modelsToRecord.add(mockita);
|
||||
settings.models = modelsToRecord.stream().map(Mappers.getMapper(ModelMapper.class)::toDto).collect(Collectors.toList());
|
||||
when(recorder.isRunning()).thenReturn(true);
|
||||
when(recorder.getModels()).thenReturn(modelsToRecord);
|
||||
when(recorder.notEnoughSpaceForRecording()).thenReturn(false);
|
||||
|
@ -334,10 +337,10 @@ class RecordingPreconditionsTest {
|
|||
when(mockita.getPriority()).thenReturn(100);
|
||||
var recorder = mock(SimplifiedLocalRecorder.class);
|
||||
List<Model> modelsToRecord = new LinkedList<>();
|
||||
settings.models = modelsToRecord;
|
||||
settings.timeoutRecordingStartingAt = LocalTime.now().minusHours(1).truncatedTo(ChronoUnit.MINUTES);
|
||||
settings.timeoutRecordingEndingAt = LocalTime.now().plusHours(1).truncatedTo(ChronoUnit.MINUTES);
|
||||
modelsToRecord.add(mockita);
|
||||
settings.models = modelsToRecord.stream().map(Mappers.getMapper(ModelMapper.class)::toDto).collect(Collectors.toList());
|
||||
when(recorder.isRunning()).thenReturn(true);
|
||||
when(recorder.getModels()).thenReturn(modelsToRecord);
|
||||
when(recorder.notEnoughSpaceForRecording()).thenReturn(false);
|
||||
|
|
|
@ -46,7 +46,7 @@ class AbstractPlaceholderAwarePostProcessorTest extends AbstractPpTest {
|
|||
@Test
|
||||
void testModelNameReplacement() {
|
||||
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";
|
||||
assertEquals("asdf_Mockita Boobilicious_asdf", placeHolderAwarePp.fillInPlaceHolders(input, createPostProcessingContext(rec, null, config)));
|
||||
input = "asdf_$sanitize(${modelName})_asdf";
|
||||
|
@ -170,7 +170,7 @@ class AbstractPlaceholderAwarePostProcessorTest extends AbstractPpTest {
|
|||
@Test
|
||||
void testFunctionCalls() {
|
||||
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))";
|
||||
assertEquals("MOCKITA BOOBILICIOUS", placeHolderAwarePp.fillInPlaceHolders(input, createPostProcessingContext(rec, null, config)));
|
||||
|
|
|
@ -76,7 +76,7 @@ public abstract class AbstractPpTest {
|
|||
|
||||
Model mockModel() {
|
||||
Site site = new Stripchat();
|
||||
Model model = site.createModel("Mockita Boobilicious");
|
||||
Model model = site.createModel("Mockita_Boobilicious");
|
||||
model.setDisplayName("Mockita Boobilicious");
|
||||
return model;
|
||||
}
|
||||
|
|
|
@ -20,12 +20,12 @@ import static org.mockito.Mockito.*;
|
|||
class DeleteTooShortTest extends AbstractPpTest {
|
||||
|
||||
@Test
|
||||
void tooShortSingleFileRecShouldBeDeleted() throws IOException, InterruptedException {
|
||||
void tooShortSingleFileRecShouldBeDeleted() throws IOException {
|
||||
testProcess(original);
|
||||
}
|
||||
|
||||
@Test
|
||||
void tooShortDirectoryRecShouldBeDeleted() throws IOException, InterruptedException {
|
||||
void tooShortDirectoryRecShouldBeDeleted() throws IOException {
|
||||
testProcess(originalDir);
|
||||
}
|
||||
|
||||
|
@ -56,7 +56,7 @@ class DeleteTooShortTest extends AbstractPpTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
void testDisabledWithSingleFile() throws IOException, InterruptedException {
|
||||
void testDisabledWithSingleFile() throws IOException {
|
||||
Recording rec = createRec(original);
|
||||
Config config = mockConfig();
|
||||
RecordingManager recordingManager = new RecordingManager(config, Collections.emptyList());
|
||||
|
@ -73,7 +73,7 @@ class DeleteTooShortTest extends AbstractPpTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
void longEnoughVideoShouldStay() throws IOException, InterruptedException {
|
||||
void longEnoughVideoShouldStay() throws IOException {
|
||||
Recording rec = createRec(original);
|
||||
Config config = mockConfig();
|
||||
RecordingManager recordingManager = new RecordingManager(config, Collections.emptyList());
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
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.Recording;
|
||||
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 {
|
||||
|
||||
|
|
|
@ -20,6 +20,9 @@
|
|||
<maven.compiler.target>17</maven.compiler.target>
|
||||
<version.javafx>20.0.1</version.javafx>
|
||||
<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>
|
||||
|
||||
<build>
|
||||
|
@ -67,11 +70,6 @@
|
|||
<artifactId>okhttp</artifactId>
|
||||
<version>4.9.3</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.squareup.moshi</groupId>
|
||||
<artifactId>moshi</artifactId>
|
||||
<version>1.13.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.json</groupId>
|
||||
<artifactId>json</artifactId>
|
||||
|
@ -123,6 +121,21 @@
|
|||
<artifactId>commons-io</artifactId>
|
||||
<version>2.11.0</version>
|
||||
</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>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-api</artifactId>
|
||||
|
@ -159,7 +172,7 @@
|
|||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.18.24</version>
|
||||
<version>${lombok.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
@ -186,6 +199,12 @@
|
|||
<artifactId>lombok</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mapstruct</groupId>
|
||||
<artifactId>mapstruct-processor</artifactId>
|
||||
<version>${org.mapstruct.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
|
|
@ -31,6 +31,7 @@ import ctbrec.sites.stripchat.Stripchat;
|
|||
import ctbrec.sites.xlovecam.XloveCam;
|
||||
import org.eclipse.jetty.security.*;
|
||||
import org.eclipse.jetty.security.authentication.BasicAuthenticator;
|
||||
import org.eclipse.jetty.server.Request;
|
||||
import org.eclipse.jetty.server.*;
|
||||
import org.eclipse.jetty.server.handler.ErrorHandler;
|
||||
import org.eclipse.jetty.server.handler.HandlerList;
|
||||
|
|
|
@ -1,50 +1,42 @@
|
|||
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.PrintWriter;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.time.Instant;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.*;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
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;
|
||||
import static javax.servlet.http.HttpServletResponse.*;
|
||||
|
||||
@Slf4j
|
||||
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) {
|
||||
this.recorder = recorder;
|
||||
|
@ -52,7 +44,7 @@ public class RecorderServlet extends AbstractCtbrecServlet {
|
|||
}
|
||||
|
||||
@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.setContentType("application/json");
|
||||
|
||||
|
@ -66,221 +58,208 @@ public class RecorderServlet extends AbstractCtbrecServlet {
|
|||
return;
|
||||
}
|
||||
|
||||
LOG.debug("Request: {}", json);
|
||||
Moshi moshi = new Moshi.Builder()
|
||||
.add(Instant.class, new InstantJsonAdapter())
|
||||
.add(Model.class, new ModelJsonAdapter(sites))
|
||||
.add(File.class, new FileJsonAdapter())
|
||||
.add(UUID.class, new UuidJSonAdapter())
|
||||
.build();
|
||||
JsonAdapter<Request> requestAdapter = moshi.adapter(Request.class);
|
||||
Request request = requestAdapter.fromJson(json);
|
||||
if (request.action == null) {
|
||||
log.debug("Request: {}", json);
|
||||
Request request = Mappers.getMapper(RequestMapper.class).toRequest(mapper.readValue(json, RequestDto.class));
|
||||
Model model = request.getModel();
|
||||
Optional.ofNullable(model).ifPresent(m -> SiteUtil.getSiteForModel(sites, m).ifPresent(m::setSite));
|
||||
Recording recording = request.getRecording();
|
||||
if (request.getAction() == null) {
|
||||
sendError(resp, SC_BAD_REQUEST, "{\"status\": \"error\", \"msg\": \"action is missing\"}");
|
||||
return;
|
||||
}
|
||||
switch (request.action) {
|
||||
case "start":
|
||||
LOG.debug("Starting recording for model {} - {}", request.model.getName(), request.model.getUrl());
|
||||
LOG.trace("Model marked: {}", request.model.isMarkedForLaterRecording());
|
||||
LOG.trace("Model paused: {}", request.model.isSuspended());
|
||||
LOG.trace("Model until: {}", request.model.getRecordUntil().equals(Instant.ofEpochMilli(Model.RECORD_INDEFINITELY)) ? "no limit" : request.model.getRecordUntil());
|
||||
LOG.trace("Model after: {}", request.model.getRecordUntilSubsequentAction());
|
||||
recorder.addModel(request.model);
|
||||
String response = "{\"status\": \"success\", \"msg\": \"Recording started\"}";
|
||||
responseWriter.write(response);
|
||||
break;
|
||||
case "startByUrl":
|
||||
LOG.debug("Starting recording for model {}", request.model.getUrl());
|
||||
startByUrl(request);
|
||||
response = "{\"status\": \"success\", \"msg\": \"Recording started\"}";
|
||||
responseWriter.write(response);
|
||||
break;
|
||||
case "startByName":
|
||||
LOG.debug("Starting recording for model {}", request.model.getUrl());
|
||||
startByName(request);
|
||||
response = "{\"status\": \"success\", \"msg\": \"Recording started\"}";
|
||||
responseWriter.write(response);
|
||||
break;
|
||||
case "stop":
|
||||
GlobalThreadPool.submit(() -> {
|
||||
try {
|
||||
recorder.stopRecording(request.model);
|
||||
} catch (InvalidKeyException | NoSuchAlgorithmException | IllegalStateException | IOException e) {
|
||||
LOG.error("Couldn't stop recording for model {}", request.model, e);
|
||||
switch (request.getAction()) {
|
||||
case "start":
|
||||
log.debug("Starting recording for model {} - {}", model.getName(), model.getUrl());
|
||||
log.trace("Model marked: {}", model.isMarkedForLaterRecording());
|
||||
log.trace("Model paused: {}", model.isSuspended());
|
||||
log.trace("Model until: {}", model.getRecordUntil().equals(Instant.ofEpochMilli(Model.RECORD_INDEFINITELY)) ? "no limit" : model.getRecordUntil());
|
||||
log.trace("Model after: {}", model.getRecordUntilSubsequentAction());
|
||||
recorder.addModel(model);
|
||||
String response = "{\"status\": \"success\", \"msg\": \"Recording started\"}";
|
||||
responseWriter.write(response);
|
||||
break;
|
||||
case "startByUrl":
|
||||
log.debug("Starting recording for model {}", model.getUrl());
|
||||
startByUrl(request);
|
||||
response = "{\"status\": \"success\", \"msg\": \"Recording started\"}";
|
||||
responseWriter.write(response);
|
||||
break;
|
||||
case "startByName":
|
||||
log.debug("Starting recording for model {}", model.getUrl());
|
||||
startByName(request);
|
||||
response = "{\"status\": \"success\", \"msg\": \"Recording started\"}";
|
||||
responseWriter.write(response);
|
||||
break;
|
||||
case "stop":
|
||||
GlobalThreadPool.submit(() -> {
|
||||
try {
|
||||
recorder.stopRecording(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 "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(',');
|
||||
}
|
||||
}
|
||||
});
|
||||
response = "{\"status\": \"success\", \"msg\": \"Stopping recording\"}";
|
||||
responseWriter.write(response);
|
||||
break;
|
||||
case "stopAt":
|
||||
GlobalThreadPool.submit(() -> {
|
||||
try {
|
||||
recorder.stopRecordingAt(request.model);
|
||||
} catch (InvalidKeyException | NoSuchAlgorithmException | IllegalStateException | IOException e) {
|
||||
LOG.error("Couldn't stop recording for model {}", request.model, e);
|
||||
responseWriter.write("]}");
|
||||
break;
|
||||
case "listOnline":
|
||||
responseWriter.write("{\"status\": \"success\", \"msg\": \"List of online models\", \"models\": [");
|
||||
for (Iterator<Model> iterator = recorder.getOnlineModels().iterator(); iterator.hasNext(); ) {
|
||||
Model m = iterator.next();
|
||||
responseWriter.write(mapper.writeValueAsString(modelMapper.toDto(m)));
|
||||
if (iterator.hasNext()) {
|
||||
responseWriter.write(',');
|
||||
}
|
||||
}
|
||||
});
|
||||
response = "{\"status\": \"success\", \"msg\": \"Stopping recording\"}";
|
||||
responseWriter.write(response);
|
||||
break;
|
||||
case "list":
|
||||
responseWriter.write("{\"status\": \"success\", \"msg\": \"List of models\", \"models\": [");
|
||||
JsonAdapter<Model> modelAdapter = new ModelJsonAdapter();
|
||||
List<Model> models = recorder.getModels();
|
||||
for (Iterator<Model> iterator = models.iterator(); iterator.hasNext();) {
|
||||
Model model = iterator.next();
|
||||
responseWriter.write(modelAdapter.toJson(model));
|
||||
if (iterator.hasNext()) {
|
||||
responseWriter.write(',');
|
||||
responseWriter.write("]}");
|
||||
break;
|
||||
case "recordings":
|
||||
responseWriter.write("{\"status\": \"success\", \"msg\": \"List of recordings\", \"recordings\": [");
|
||||
List<Recording> recordings = recorder.getRecordings();
|
||||
for (Iterator<Recording> iterator = recordings.iterator(); iterator.hasNext(); ) {
|
||||
Recording rec = iterator.next();
|
||||
String recJSON = mapper.writeValueAsString(recordingMapper.toDto(rec));
|
||||
log.debug("Rec: {}", recJSON);
|
||||
responseWriter.write(recJSON);
|
||||
if (iterator.hasNext()) {
|
||||
responseWriter.write(',');
|
||||
}
|
||||
}
|
||||
}
|
||||
responseWriter.write("]}");
|
||||
break;
|
||||
case "listOnline":
|
||||
responseWriter.write("{\"status\": \"success\", \"msg\": \"List of online models\", \"models\": [");
|
||||
modelAdapter = new ModelJsonAdapter();
|
||||
models = recorder.getOnlineModels();
|
||||
for (Iterator<Model> iterator = models.iterator(); iterator.hasNext();) {
|
||||
Model model = iterator.next();
|
||||
responseWriter.write(modelAdapter.toJson(model));
|
||||
if (iterator.hasNext()) {
|
||||
responseWriter.write(',');
|
||||
}
|
||||
}
|
||||
responseWriter.write("]}");
|
||||
break;
|
||||
case "recordings":
|
||||
responseWriter.write("{\"status\": \"success\", \"msg\": \"List of recordings\", \"recordings\": [");
|
||||
JsonAdapter<Recording> recAdapter = moshi.adapter(Recording.class);
|
||||
List<Recording> recordings = recorder.getRecordings();
|
||||
for (Iterator<Recording> iterator = recordings.iterator(); iterator.hasNext();) {
|
||||
Recording recording = iterator.next();
|
||||
String recJSON = recAdapter.toJson(recording);
|
||||
LOG.debug("Rec: {}", recJSON);
|
||||
responseWriter.write(recJSON);
|
||||
if (iterator.hasNext()) {
|
||||
responseWriter.write(',');
|
||||
}
|
||||
}
|
||||
responseWriter.write("]}");
|
||||
break;
|
||||
case "delete":
|
||||
recorder.delete(request.recording);
|
||||
recAdapter = moshi.adapter(Recording.class);
|
||||
responseWriter.write("{\"status\": \"success\", \"msg\": \"List of recordings\", \"recordings\": [");
|
||||
responseWriter.write(recAdapter.toJson(request.recording));
|
||||
responseWriter.write("]}");
|
||||
break;
|
||||
case "pin":
|
||||
recorder.pin(request.recording);
|
||||
recAdapter = moshi.adapter(Recording.class);
|
||||
responseWriter.write("{\"status\": \"success\", \"msg\": \"List of recordings\", \"recordings\": [");
|
||||
responseWriter.write(recAdapter.toJson(request.recording));
|
||||
responseWriter.write("]}");
|
||||
break;
|
||||
case "unpin":
|
||||
recorder.unpin(request.recording);
|
||||
recAdapter = moshi.adapter(Recording.class);
|
||||
responseWriter.write("{\"status\": \"success\", \"msg\": \"Note saved\", \"recordings\": [");
|
||||
responseWriter.write(recAdapter.toJson(request.recording));
|
||||
responseWriter.write("]}");
|
||||
break;
|
||||
case "setNote":
|
||||
recorder.setNote(request.recording, request.recording.getNote());
|
||||
recAdapter = moshi.adapter(Recording.class);
|
||||
responseWriter.write("{\"status\": \"success\", \"msg\": \"List of recordings\", \"recordings\": [");
|
||||
responseWriter.write(recAdapter.toJson(request.recording));
|
||||
responseWriter.write("]}");
|
||||
break;
|
||||
case "rerunPostProcessing":
|
||||
recorder.rerunPostProcessing(request.recording);
|
||||
responseWriter.write("{\"status\": \"success\", \"msg\": \"Post-Processing triggered\"}");
|
||||
break;
|
||||
case "switch":
|
||||
recorder.switchStreamSource(request.model);
|
||||
response = "{\"status\": \"success\", \"msg\": \"Resolution switched\"}";
|
||||
responseWriter.write(response);
|
||||
break;
|
||||
case "suspend":
|
||||
LOG.debug("Suspend recording for model {} - {}", request.model.getName(), request.model.getUrl());
|
||||
GlobalThreadPool.submit(() -> {
|
||||
try {
|
||||
recorder.suspendRecording(request.model);
|
||||
} catch (InvalidKeyException | NoSuchAlgorithmException | IllegalStateException | IOException e) {
|
||||
LOG.error("Couldn't suspend recording for model {}", request.model, e);
|
||||
}
|
||||
});
|
||||
response = "{\"status\": \"success\", \"msg\": \"Suspending recording\"}";
|
||||
responseWriter.write(response);
|
||||
break;
|
||||
case "resume":
|
||||
LOG.debug("Resume recording for model {} - {}", request.model.getName(), request.model.getUrl());
|
||||
recorder.resumeRecording(request.model);
|
||||
response = "{\"status\": \"success\", \"msg\": \"Recording resumed\"}";
|
||||
responseWriter.write(response);
|
||||
break;
|
||||
case "space":
|
||||
JSONObject jsonResponse = new JSONObject();
|
||||
jsonResponse.put("status", "success");
|
||||
jsonResponse.put("spaceTotal", recorder.getTotalSpaceBytes());
|
||||
jsonResponse.put("spaceFree", recorder.getFreeSpaceBytes());
|
||||
jsonResponse.put("throughput", BandwidthMeter.getThroughput());
|
||||
jsonResponse.put("throughputTimeframe", BandwidthMeter.getTimeframe().toMillis());
|
||||
jsonResponse.put("minimumSpaceLeftInBytes", Config.getInstance().getSettings().minimumSpaceLeftInBytes);
|
||||
responseWriter.write(jsonResponse.toString());
|
||||
break;
|
||||
case "changePriority":
|
||||
recorder.priorityChanged(request.model);
|
||||
response = "{\"status\": \"success\"}";
|
||||
responseWriter.write(response);
|
||||
break;
|
||||
case "pauseRecorder":
|
||||
recorder.pause();
|
||||
response = "{\"status\": \"success\"}";
|
||||
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;
|
||||
responseWriter.write("]}");
|
||||
break;
|
||||
case "delete":
|
||||
recorder.delete(recording);
|
||||
responseWriter.write("{\"status\": \"success\", \"msg\": \"List of recordings\", \"recordings\": [");
|
||||
responseWriter.write(mapper.writeValueAsString(recordingMapper.toDto(recording)));
|
||||
responseWriter.write("]}");
|
||||
break;
|
||||
case "pin":
|
||||
recorder.pin(recording);
|
||||
responseWriter.write("{\"status\": \"success\", \"msg\": \"List of recordings\", \"recordings\": [");
|
||||
responseWriter.write(mapper.writeValueAsString(recordingMapper.toDto(recording)));
|
||||
responseWriter.write("]}");
|
||||
break;
|
||||
case "unpin":
|
||||
recorder.unpin(recording);
|
||||
responseWriter.write("{\"status\": \"success\", \"msg\": \"Note saved\", \"recordings\": [");
|
||||
responseWriter.write(mapper.writeValueAsString(recordingMapper.toDto(recording)));
|
||||
responseWriter.write("]}");
|
||||
break;
|
||||
case "setNote":
|
||||
recorder.setNote(recording, recording.getNote());
|
||||
responseWriter.write("{\"status\": \"success\", \"msg\": \"List of recordings\", \"recordings\": [");
|
||||
responseWriter.write(mapper.writeValueAsString(recordingMapper.toDto(recording)));
|
||||
responseWriter.write("]}");
|
||||
break;
|
||||
case "rerunPostProcessing":
|
||||
recorder.rerunPostProcessing(recording);
|
||||
responseWriter.write("{\"status\": \"success\", \"msg\": \"Post-Processing triggered\"}");
|
||||
break;
|
||||
case "switch":
|
||||
recorder.switchStreamSource(model);
|
||||
response = "{\"status\": \"success\", \"msg\": \"Resolution switched\"}";
|
||||
responseWriter.write(response);
|
||||
break;
|
||||
case "suspend":
|
||||
log.debug("Suspend recording for model {} - {}", model.getName(), model.getUrl());
|
||||
GlobalThreadPool.submit(() -> {
|
||||
try {
|
||||
recorder.suspendRecording(model);
|
||||
} catch (InvalidKeyException | NoSuchAlgorithmException | IllegalStateException | IOException e) {
|
||||
log.error("Couldn't suspend recording for model {}", model, e);
|
||||
}
|
||||
});
|
||||
response = "{\"status\": \"success\", \"msg\": \"Suspending recording\"}";
|
||||
responseWriter.write(response);
|
||||
break;
|
||||
case "resume":
|
||||
log.debug("Resume recording for model {} - {}", model.getName(), model.getUrl());
|
||||
recorder.resumeRecording(model);
|
||||
response = "{\"status\": \"success\", \"msg\": \"Recording resumed\"}";
|
||||
responseWriter.write(response);
|
||||
break;
|
||||
case "space":
|
||||
JSONObject jsonResponse = new JSONObject();
|
||||
jsonResponse.put("status", "success");
|
||||
jsonResponse.put("spaceTotal", recorder.getTotalSpaceBytes());
|
||||
jsonResponse.put("spaceFree", recorder.getFreeSpaceBytes());
|
||||
jsonResponse.put("throughput", BandwidthMeter.getThroughput());
|
||||
jsonResponse.put("throughputTimeframe", BandwidthMeter.getTimeframe().toMillis());
|
||||
jsonResponse.put("minimumSpaceLeftInBytes", Config.getInstance().getSettings().minimumSpaceLeftInBytes);
|
||||
responseWriter.write(jsonResponse.toString());
|
||||
break;
|
||||
case "changePriority":
|
||||
recorder.priorityChanged(model);
|
||||
response = "{\"status\": \"success\"}";
|
||||
responseWriter.write(response);
|
||||
break;
|
||||
case "pauseRecorder":
|
||||
recorder.pause();
|
||||
response = "{\"status\": \"success\"}";
|
||||
responseWriter.write(response);
|
||||
break;
|
||||
case "resumeRecorder":
|
||||
recorder.resume();
|
||||
response = "{\"status\": \"success\"}";
|
||||
responseWriter.write(response);
|
||||
break;
|
||||
case "saveModelGroup":
|
||||
recorder.saveModelGroup(request.getModelGroup());
|
||||
sendModelGroups(resp, recorder.getModelGroups());
|
||||
break;
|
||||
case "deleteModelGroup":
|
||||
recorder.deleteModelGroup(request.getModelGroup());
|
||||
sendModelGroups(resp, recorder.getModelGroups());
|
||||
break;
|
||||
case "listModelGroups":
|
||||
sendModelGroups(resp, recorder.getModelGroups());
|
||||
break;
|
||||
case "markForLater":
|
||||
log.debug("Mark model {} for later recording", model.getName());
|
||||
response = "{\"status\": \"success\", \"msg\": \"Model marked for later recording\"}";
|
||||
responseWriter.write(response);
|
||||
recorder.markForLaterRecording(model, true);
|
||||
break;
|
||||
case "unmarkForLater":
|
||||
log.debug("Unmark model {} for later recording", model.getName());
|
||||
response = "{\"status\": \"success\", \"msg\": \"Model has been unmarked\"}";
|
||||
responseWriter.write(response);
|
||||
recorder.markForLaterRecording(model, false);
|
||||
break;
|
||||
default:
|
||||
sendError(resp, SC_BAD_REQUEST, "{\"status\": \"error\", \"msg\": \"Unknown action [" + request.getAction() + "]\"}");
|
||||
break;
|
||||
}
|
||||
} catch(Exception e) {
|
||||
} catch (Exception e) {
|
||||
resp.setStatus(SC_INTERNAL_SERVER_ERROR);
|
||||
JSONObject response = new JSONObject();
|
||||
response.put("status", "error");
|
||||
response.put("msg", e.getMessage());
|
||||
resp.getWriter().write(response.toString());
|
||||
LOG.error("Unexpected error", e);
|
||||
log.error("Unexpected error", e);
|
||||
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 {
|
||||
String url = request.model.getUrl();
|
||||
String url = request.getModel().getUrl();
|
||||
for (Site site : sites) {
|
||||
Model model = site.createModelFromUrl(url);
|
||||
if (model != null) {
|
||||
|
@ -310,7 +289,7 @@ public class RecorderServlet extends AbstractCtbrecServlet {
|
|||
}
|
||||
|
||||
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) {
|
||||
throw new IllegalArgumentException("Invalid input. Should be site:model");
|
||||
}
|
||||
|
@ -323,13 +302,6 @@ public class RecorderServlet extends AbstractCtbrecServlet {
|
|||
return;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("No site found to record " + request.model.getUrl());
|
||||
}
|
||||
|
||||
private static class Request {
|
||||
public String action;
|
||||
public Model model;
|
||||
public Recording recording;
|
||||
public ModelGroup modelGroup;
|
||||
throw new IllegalArgumentException("No site found to record " + request.getModel().getUrl());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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