Compare commits

..
6 Commits
Author SHA1 Message Date
death-claw b4eaf0d1c4 v2.10.7 2020-01-25 14:45:54 +01:00
death-claw 9d2975faaa Drop SockJs and switch to WebSocket
Autoreconnect front to back
2020-01-25 14:39:50 +01:00
death-claw 0c0fdf9d78 v2.10.6 2020-01-19 13:01:03 +01:00
death-claw e17afa0ef3 Limit cache size 2020-01-19 12:59:47 +01:00
death-claw 3d3eb6aa36 v2.10.5 2020-01-18 22:28:27 +01:00
death-claw e22132255e Bug fixes 2020-01-18 22:24:53 +01:00
33 changed files with 1261 additions and 1027 deletions
+13
View File
@@ -1,5 +1,18 @@
# Changelog
## [2.10.7] - 2020-01-25
### Changed
- Drop SockJs and switch to WebSocket
- Autoreconnect front to back
## [2.10.6] - 2020-01-19
### Changed
- Limit cache size
## [2.10.5] - 2020-01-18
### Changed
- Bug fixes
## [2.10.4] - 2020-01-18
### Changed
- Zero value for total max download will disable the limit
+1 -1
View File
@@ -4,7 +4,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>tn.mnlr</groupId>
<artifactId>vripper</artifactId>
<version>2.10.4</version>
<version>2.10.7</version>
<packaging>pom</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
+5 -1
View File
@@ -23,6 +23,11 @@ process.on("uncaughtException", err => {
});
function createWindow() {
if (process.platform === 'win32') {
app.setAppUserModelId("tn.mnlr.vripper");
}
let icon;
if(process.platform === "win32") {
icon = __dirname + '/icon.ico';
@@ -64,7 +69,6 @@ const gotTheLock = app.requestSingleInstanceLock();
if (!gotTheLock) {
app.quit();
} else {
app.setAppUserModelId(process.execPath)
getPort().then(port => {
serverPort = port;
ipcMain.on("get-port", event => {
+683 -528
View File
File diff suppressed because it is too large Load Diff
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "vripper-electron",
"version": "2.10.4",
"version": "2.10.7",
"description": "A ripper for vipergirls.to built using web technolgies",
"main": "main.js",
"author": "death-claw <53543762+death-claw@users.noreply.github.com>",
@@ -70,8 +70,8 @@
"dist": "node pre-build.js && electron-builder"
},
"devDependencies": {
"electron": "^7.1.7",
"electron-builder": "^21.2.0"
"electron": "^7.1.9",
"electron-builder": "^22.2.0"
},
"dependencies": {
"axios": "^0.19.0",
+1 -1
View File
@@ -5,7 +5,7 @@
<parent>
<groupId>tn.mnlr</groupId>
<artifactId>vripper</artifactId>
<version>2.10.4</version>
<version>2.10.7</version>
</parent>
<artifactId>vripper-electron</artifactId>
<name>vripper-electron</name>
+1 -1
View File
@@ -5,7 +5,7 @@
<parent>
<groupId>tn.mnlr</groupId>
<artifactId>vripper</artifactId>
<version>2.10.4</version>
<version>2.10.7</version>
</parent>
<artifactId>vripper-server</artifactId>
<name>vripper-server</name>
@@ -18,6 +18,10 @@ import java.util.stream.Collectors;
@NoArgsConstructor
public class Post {
public enum METADATA {
PREVIEWS, RESOLVED_NAME, POSTED_BY, THANKED
}
private AppStateService appStateService;
private Status status;
@@ -40,6 +40,12 @@ public class PostParser {
@Autowired
private List<Host> supportedHosts;
@Autowired
private HtmlProcessorService htmlProcessorService;
@Autowired
private XpathService xpathService;
@PostConstruct
private void init() {
@@ -52,7 +58,7 @@ public class PostParser {
return;
}
VRPostParser vrPostParser = new VRPostParser(threadId, postId, cm, vipergirlsAuthService, supportedHosts);
VRPostParser vrPostParser = new VRPostParser(threadId, postId, cm, vipergirlsAuthService, supportedHosts, htmlProcessorService, xpathService);
Post post = vrPostParser.parse();
post.setAppStateService(appStateService);
@@ -3,6 +3,7 @@ package tn.mnlr.vripper.services;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.cache.Weigher;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@@ -22,6 +23,7 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.time.Duration;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicLong;
@@ -59,7 +61,9 @@ public class ThumbnailGenerator {
throw new Exception(String.format("%s could not be created", cacheFolder.toString()));
}
thumbnails = CacheBuilder.newBuilder()
.maximumSize(20000)
.expireAfterAccess(Duration.ofMinutes(30))
.weigher((Weigher<CacheKey, byte[]>) (k, v) -> v.length)
.maximumWeight(104_857_600)
.build(loader);
}
@@ -10,6 +10,8 @@ import org.apache.http.client.utils.URIBuilder;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.xml.sax.Attributes;
import org.xml.sax.helpers.DefaultHandler;
import tn.mnlr.vripper.entities.Image;
@@ -51,13 +53,17 @@ class VRPostParser {
private final ConnectionManager cm;
private final VipergirlsAuthService vipergirlsAuthService;
private final List<Host> supportedHosts;
private final HtmlProcessorService htmlProcessorService;
private final XpathService xpathService;
VRPostParser(String threadId, String postId, ConnectionManager cm, VipergirlsAuthService vipergirlsAuthService, List<Host> supportedHosts) {
VRPostParser(String threadId, String postId, ConnectionManager cm, VipergirlsAuthService vipergirlsAuthService, List<Host> supportedHosts, HtmlProcessorService htmlProcessorService, XpathService xpathService) {
this.threadId = threadId;
this.postId = postId;
this.cm = cm;
this.vipergirlsAuthService = vipergirlsAuthService;
this.supportedHosts = supportedHosts;
this.htmlProcessorService = htmlProcessorService;
this.xpathService = xpathService;
}
public Post parse() throws PostParseException {
@@ -74,7 +80,16 @@ class VRPostParser {
VRPostHandler handler = new VRPostHandler(threadId, postId, supportedHosts);
AtomicReference<Throwable> thr = new AtomicReference<>();
logger.debug(String.format("Requesting %s", httpGet));
Post post = Failsafe.with(retryPolicy)
Post post = getPost(httpGet, handler, thr);
if (thr.get() != null || post == null) {
logger.error(String.format("parsing failed for thread %s, post %s", threadId, postId), thr.get());
throw new PostParseException(thr.get());
}
return post;
}
private Post getPost(HttpGet httpGet, VRPostHandler handler, AtomicReference<Throwable> thr) {
return Failsafe.with(retryPolicy)
.onFailure(e -> thr.set(e.getFailure()))
.get(() -> {
HttpClient connection = cm.getClient().build();
@@ -93,11 +108,34 @@ class VRPostParser {
}
}
});
if (thr.get() != null || post == null) {
logger.error(String.format("parsing failed for thread %s, post %s", threadId, postId), thr.get());
throw new PostParseException(thr.get());
}
return post;
}
private void getPostExtraMetadata(Post post, AtomicReference<Throwable> thr) {
HttpGet httpGet = cm.buildHttpGet(post.getUrl());
Failsafe.with(retryPolicy)
.onFailure(e -> thr.set(e.getFailure()))
.get(() -> {
HttpClient connection = cm.getClient().build();
try (CloseableHttpResponse response = (CloseableHttpResponse) connection.execute(httpGet, vipergirlsAuthService.getContext())) {
if (response.getStatusLine().getStatusCode() / 100 != 2) {
throw new DownloadException(String.format("Unexpected response code '%d' for %s", response.getStatusLine().getStatusCode(), httpGet));
}
try {
Document document = htmlProcessorService.clean(EntityUtils.toString(response.getEntity()));
Node postNode = xpathService.getAsNode(document, String.format("//li[@id='post_%s']/div[contains(@class, 'postdetails')]", post.getPostId()));
String postedBy = xpathService.getAsNode(postNode, "./div[contains(@class, 'userinfo')]//a[contains(@class, 'username')]//font").getTextContent().trim();
HashMap<String, Object> stringObjectHashMap = new HashMap<>();
response.getEntity().getContent();
return stringObjectHashMap;
} catch (Exception e) {
throw new PostParseException(String.format("Failed to parse thread %s, post %s", threadId, postId), e);
} finally {
EntityUtils.consumeQuietly(response.getEntity());
}
}
});
}
}
@@ -169,7 +207,7 @@ class VRPostHandler extends DefaultHandler {
if ("post".equals(qName.toLowerCase())) {
if (imageCount != 0) {
HashMap<String, Object> metadata = new HashMap<>();
metadata.put("PREVIEWS", previews);
metadata.put(Post.METADATA.PREVIEWS.name(), previews);
parsedPost = new Post(
postTitle,
String.format("https://vipergirls.to/threads/%s/?p=%s&viewfull=1#post%s", threadId, postId, postId),
@@ -1,10 +1,14 @@
package tn.mnlr.vripper.web;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
import org.springframework.web.socket.server.standard.ServletServerContainerFactoryBean;
import tn.mnlr.vripper.web.wsendpoints.WebSocketHandler;
@Configuration
@@ -16,6 +20,24 @@ public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(handler, "/endpoint").setAllowedOrigins("*").withSockJS().setClientLibraryUrl("../../assets/sockjs.min.js");
registry.addHandler(handler, "/endpoint").setAllowedOrigins("*");
}
@Bean
public TaskScheduler taskScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(2);
scheduler.setThreadNamePrefix("scheduled-task-");
scheduler.setDaemon(true);
return scheduler;
}
@Bean
public ServletServerContainerFactoryBean createWebSocketContainer() {
ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean();
container.setMaxSessionIdleTimeout(0L);
return container;
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "vripper-ui",
"version": "2.10.4",
"version": "2.10.7",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "vripper-ui",
"version": "2.10.4",
"version": "2.10.7",
"scripts": {
"ng": "ng",
"start": "ng serve",
+1 -1
View File
@@ -5,7 +5,7 @@
<parent>
<groupId>tn.mnlr</groupId>
<artifactId>vripper</artifactId>
<version>2.10.4</version>
<version>2.10.7</version>
</parent>
<artifactId>vripper-ui</artifactId>
<name>vripper-ui</name>
+13 -16
View File
@@ -1,5 +1,15 @@
<ng-container *ngIf="appState | async as state">
<div *ngIf="state === 'CONNECTING'" class="overlay loading" fxLayout="column" fxLayoutAlign="center center">
<div *ngIf="loaded | async; else loading" [ngClass]="{ electron: electron | async }" fxLayout="column"
id="app-container">
<div fxFlex="nogrow" id="app-header">
<app-toolbar></app-toolbar>
</div>
<div fxFlex="grow" id="app-body" style="margin-bottom: 20px;">
<router-outlet></router-outlet>
</div>
<app-status-bar style="width: 100%"></app-status-bar>
</div>
<ng-template #loading>
<div class="overlay loading" fxLayout="column" fxLayoutAlign="center center">
<div class="sk-chase">
<div class="sk-chase-dot"></div>
<div class="sk-chase-dot"></div>
@@ -9,17 +19,4 @@
<div class="sk-chase-dot"></div>
</div>
</div>
<div *ngIf="state === 'DISCONNECTED'" class="overlay no-connection" fxLayout="column" fxLayoutAlign="center center">
<mat-icon class="overlay-icon">error</mat-icon>
<h2>Error connecting to daemon, try restarting the application</h2>
</div>
<div *ngIf="state === 'CONNECTED'" [ngClass]="{ electron: electron | async }" fxLayout="column" id="app-container">
<div fxFlex="nogrow" id="app-header">
<app-toolbar></app-toolbar>
</div>
<div fxFlex="grow" id="app-body" style="margin-bottom: 20px;">
<router-outlet></router-outlet>
</div>
<app-status-bar style="width: 100%"></app-status-bar>
</div>
</ng-container>
</ng-template>
+23 -18
View File
@@ -4,7 +4,7 @@ import { ElectronService } from 'ngx-electron';
import { Component, OnDestroy, AfterViewInit, Renderer2, ChangeDetectionStrategy, NgZone } from '@angular/core';
import { MatDialog } from '@angular/material';
import { Subscription, BehaviorSubject, Subject, merge } from 'rxjs';
import { WsConnectionService, WSState } from './ws-connection.service';
import { WsConnectionService } from './ws-connection.service';
@Component({
selector: 'app-root',
@@ -15,7 +15,7 @@ import { WsConnectionService, WSState } from './ws-connection.service';
export class AppComponent implements OnDestroy, AfterViewInit {
constructor(
private dialog: MatDialog,
private ws: WsConnectionService,
public ws: WsConnectionService,
public electronService: ElectronService,
private clipboardService: ClipboardService,
private appService: AppService,
@@ -26,28 +26,33 @@ export class AppComponent implements OnDestroy, AfterViewInit {
}
private subscriptions: Subscription[] = [];
appState: Subject<string> = new BehaviorSubject('CONNECTING');
electron: Subject<boolean>;
loaded: Subject<boolean> = new BehaviorSubject(false);
ngAfterViewInit() {
this.appService.renderer = this.renderer;
this.subscriptions.push(this.ws.state.subscribe(wsState => {
if (wsState === WSState.CLOSE || wsState === WSState.ERROR) {
setTimeout(() => this.ngZone.run(() => {
this.dialog.closeAll();
this.appState.next('DISCONNECTED');
}), 500);
} else if (wsState === WSState.OPEN) {
this.clipboardService.init();
merge(this.appService.loadTheme(), this.appService.loadSettings())
.subscribe(() => this.ngZone.run(() => this.appState.next('CONNECTED')));
}
}));
this.subscriptions.push(
this.ws.state.subscribe(online => {
if (!online) {
this.ngZone.run(() => this.loaded.next(false));
setTimeout(
() =>
this.ngZone.run(() => {
this.dialog.closeAll();
}),
500
);
} else {
this.ngZone.run(() => this.loaded.next(true));
this.clipboardService.init();
this.subscriptions.push(merge(this.appService.loadTheme(), this.appService.loadSettings()).subscribe());
}
})
);
}
ngOnDestroy() {
this.subscriptions.forEach(e => {
e.unsubscribe();
});
this.subscriptions.forEach(e => e.unsubscribe());
}
}
@@ -1,3 +1,4 @@
import { flatMap, filter } from 'rxjs/operators';
import { GrabQueueState } from './grab-queue.model';
import { CMD } from './../common/cmd.enum';
import { WSMessage } from './../common/ws-message.model';
@@ -5,65 +6,66 @@ import { Subscription } from 'rxjs';
import { WsConnectionService } from '../ws-connection.service';
import { NotificationService } from '../notification.service';
import { GridOptions } from 'ag-grid-community';
import { WsHandler } from '../ws-handler';
import { NgZone } from '@angular/core';
export class GrabQueueDataSource {
constructor(
private wsConnectionService: WsConnectionService,
private ws: WsConnectionService,
private gridOptions: GridOptions,
private zone: NgZone,
private notificationService: NotificationService
) {
this.websocketHandlerPromise = this.wsConnectionService.getConnection();
}
) {}
websocketHandlerPromise: Promise<WsHandler>;
subscriptions: Subscription[] = [];
grabQueueSub: Subscription;
connect() {
this.websocketHandlerPromise.then((handler: WsHandler) => {
console.log('Connecting to grab queue datasource');
this.subscriptions.push(
handler.subscribeForGrabQueue((e: GrabQueueState[]) => {
this.zone.run(() => {
const toAdd = [];
const toUpdate = [];
const toRemove = [];
e.forEach(v => {
if (v.removed) {
if (this.gridOptions.api.getRowNode(v.link) != null) {
toRemove.push(this.gridOptions.api.getRowNode(v.link).data);
console.log('Connecting to grab queue datasource');
this.subscriptions.push(
this.ws.state.subscribe(state => {
if (state) {
this.grabQueueSub = this.ws.subscribeForGrabQueue().subscribe((e: GrabQueueState[]) => {
this.zone.run(() => {
const toAdd = [];
const toUpdate = [];
const toRemove = [];
e.forEach(v => {
if (v.removed) {
if (this.gridOptions.api.getRowNode(v.link) != null) {
toRemove.push(this.gridOptions.api.getRowNode(v.link).data);
}
return;
}
return;
}
if (this.gridOptions.api.getRowNode(v.link) == null) {
toAdd.push(v);
} else {
toUpdate.push(v);
if (this.gridOptions.api.getRowNode(v.link) == null) {
toAdd.push(v);
} else {
toUpdate.push(v);
}
});
this.gridOptions.api.updateRowData({ update: toUpdate, add: toAdd, remove: toRemove });
const count = this.gridOptions.api.getDisplayedRowCount();
if (count > 0 && toAdd.length > 0) {
this.notificationService.notifyFromGrabQueue(
'Link Collector',
`You have ${count} ${count > 1 ? 'threads' : 'thread'} waiting in the link collector`
);
}
});
this.gridOptions.api.updateRowData({ update: toUpdate, add: toAdd, remove: toRemove });
const count = this.gridOptions.api.getDisplayedRowCount();
if (count > 0 && toAdd.length > 0) {
this.notificationService.notifyFromGrabQueue(
'Link Collector',
`${count} ${count > 1 ? 'threads are' : 'thread is'} in the link collector`
);
}
});
})
);
handler.send(new WSMessage(CMD.GRAB_QUEUE_SUB.toString()));
});
this.ws.send(new WSMessage(CMD.GRAB_QUEUE_SUB.toString()));
} else if (this.grabQueueSub != null) {
this.grabQueueSub.unsubscribe();
}
})
);
}
disconnect() {
console.log('Disconnecting from grab queue datasource');
this.subscriptions.forEach(e => e.unsubscribe());
this.websocketHandlerPromise.then((handler: WsHandler) => {
handler.send(new WSMessage(CMD.GRAB_QUEUE_UNSUB.toString()));
});
this.ws.send(new WSMessage(CMD.GRAB_QUEUE_UNSUB.toString()));
if (this.grabQueueSub != null) {
this.grabQueueSub.unsubscribe();
}
}
}
+9 -2
View File
@@ -1,3 +1,4 @@
import { Subscription } from 'rxjs';
import { LinkCollectorService } from './../link-collector.service';
import { ServerService } from './../server-service';
import { ElectronService } from 'ngx-electron';
@@ -24,8 +25,10 @@ export class HomeComponent implements OnInit, OnDestroy {
public linkCollectorService: LinkCollectorService
) {}
clipboardSub: Subscription;
ngOnInit() {
this.clipboardService.links.subscribe(e => {
this.clipboardSub = this.clipboardService.links.subscribe(e => {
this.ngZone.run(() => {
this.httpClient
.post<{ threadId: string; postId: string }>(this.serverService.baseUrl + '/post', { url: e })
@@ -45,5 +48,9 @@ export class HomeComponent implements OnInit, OnDestroy {
});
}
ngOnDestroy() {}
ngOnDestroy() {
if (this.clipboardSub != null) {
this.clipboardSub.unsubscribe();
}
}
}
@@ -22,13 +22,7 @@ import { ElectronService } from 'ngx-electron';
changeDetection: ChangeDetectionStrategy.OnPush
})
export class PostDetailsProgressRendererComponent implements AgRendererComponent, OnInit, OnDestroy, AfterViewInit {
constructor(
private wsConnectionService: WsConnectionService,
private zone: NgZone,
public electronService: ElectronService
) {
this.websocketHandlerPromise = this.wsConnectionService.getConnection();
}
constructor(private ws: WsConnectionService, private zone: NgZone, public electronService: ElectronService) {}
websocketHandlerPromise: Promise<WsHandler>;
subscription: Subscription;
@@ -37,22 +31,29 @@ export class PostDetailsProgressRendererComponent implements AgRendererComponent
loaded: Subject<boolean> = new BehaviorSubject(false);
loading;
stateSub: Subscription;
postDetailsSub: Subscription;
trunc(value: number): number {
return Math.trunc(value);
}
ngOnInit(): void {
this.websocketHandlerPromise.then((handler: WsHandler) => {
this.subscription = handler.subscribeForPostDetails(e => {
this.zone.run(() => {
e.forEach(v => {
if (this.postDetails.url === v.url) {
this.postDetails = v;
this.postDetails$.emit(this.postDetails);
}
this.stateSub = this.ws.state.subscribe(state => {
if (state) {
this.postDetailsSub = this.ws.subscribeForPostDetails().subscribe(e => {
this.zone.run(() => {
e.forEach(v => {
if (this.postDetails.url === v.url) {
this.postDetails = v;
this.postDetails$.emit(this.postDetails);
}
});
});
});
});
} else if (this.postDetailsSub != null) {
this.postDetailsSub.unsubscribe();
}
});
}
@@ -73,6 +74,12 @@ export class PostDetailsProgressRendererComponent implements AgRendererComponent
if (this.subscription != null) {
this.subscription.unsubscribe();
}
if (this.postDetailsSub != null) {
this.postDetailsSub.unsubscribe();
}
if (this.stateSub != null) {
this.stateSub.unsubscribe();
}
clearTimeout(this.loading);
}
@@ -1,54 +1,57 @@
import { WsHandler } from './../ws-handler';
import { GridOptions } from 'ag-grid-community';
import { Subscription } from 'rxjs';
import { WsConnectionService, WSState } from '../ws-connection.service';
import { WsConnectionService } from '../ws-connection.service';
import { WSMessage } from '../common/ws-message.model';
import { CMD } from '../common/cmd.enum';
import { NgZone } from '@angular/core';
export class PostDetailsDataSource {
constructor(
private wsConnectionService: WsConnectionService,
private ws: WsConnectionService,
private gridOptions: GridOptions,
private postId: string,
private zone: NgZone
) {
this.websocketHandlerPromise = this.wsConnectionService.getConnection();
}
) {}
websocketHandlerPromise: Promise<WsHandler>;
subscriptions: Subscription[] = [];
postDetailsSub: Subscription;
connect() {
console.log('Connecting to post details datasource');
this.websocketHandlerPromise.then((handler: WsHandler) => {
this.subscriptions.push(
handler.subscribeForPostDetails(e => {
this.zone.run(() => {
const toAdd = [];
const toUpdate = [];
e.forEach(v => {
if (this.gridOptions.api.getRowNode(v.url) == null) {
toAdd.push(v);
} else {
toUpdate.push(v);
}
this.subscriptions.push(
this.ws.state.subscribe(state => {
if (state) {
this.postDetailsSub = this.ws.subscribeForPostDetails().subscribe(e => {
this.zone.run(() => {
const toAdd = [];
const toUpdate = [];
e.forEach(v => {
if (this.gridOptions.api.getRowNode(v.url) == null) {
toAdd.push(v);
} else {
toUpdate.push(v);
}
});
this.gridOptions.api.updateRowData({ update: toUpdate, add: toAdd });
});
this.gridOptions.api.updateRowData({ update: toUpdate, add: toAdd });
});
})
);
handler.send(new WSMessage(CMD.POST_DETAILS_SUB.toString(), this.postId));
});
this.ws.send(new WSMessage(CMD.POST_DETAILS_SUB.toString(), this.postId));
} else if (this.postDetailsSub != null) {
this.postDetailsSub.unsubscribe();
}
})
);
}
disconnect() {
console.log('Disconnecting from post details datasource');
this.subscriptions.forEach(e => e.unsubscribe());
this.websocketHandlerPromise.then((handler: WsHandler) => {
handler.send(new WSMessage(CMD.POST_DETAILS_UNSUB.toString()));
});
this.ws.send(new WSMessage(CMD.POST_DETAILS_UNSUB.toString()));
if (this.postDetailsSub != null) {
this.postDetailsSub.unsubscribe();
}
}
}
@@ -7,7 +7,7 @@ import {
NgZone,
ChangeDetectionStrategy,
AfterViewInit,
EventEmitter,
EventEmitter
} from '@angular/core';
import { AgRendererComponent } from 'ag-grid-angular';
import { Subscription, BehaviorSubject, Subject } from 'rxjs';
@@ -25,13 +25,12 @@ import { ContextMenuService } from '../ctxt-menu.service';
})
export class PostProgressRendererComponent implements AgRendererComponent, OnInit, OnDestroy, AfterViewInit {
constructor(
private wsConnectionService: WsConnectionService,
private ws: WsConnectionService,
private zone: NgZone,
public electronService: ElectronService,
public dialog: MatDialog,
private contextMenuService: ContextMenuService) {
this.websocketHandlerPromise = this.wsConnectionService.getConnection();
}
private contextMenuService: ContextMenuService
) {}
websocketHandlerPromise: Promise<WsHandler>;
postState$: EventEmitter<PostState> = new EventEmitter();
@@ -43,22 +42,29 @@ export class PostProgressRendererComponent implements AgRendererComponent, OnIni
node: RowNode;
gridApi: GridApi;
postsSub: Subscription;
stateSub: Subscription;
trunc(value: number): number {
return Math.trunc(value);
}
ngOnInit(): void {
this.websocketHandlerPromise.then((handler: WsHandler) => {
this.updatesSubscription = handler.subscribeForPosts(e => {
this.zone.run(() => {
e.forEach(v => {
if (this.postState.postId === v.postId) {
this.postState = v;
this.postState$.emit(this.postState);
}
this.stateSub = this.ws.state.subscribe(state => {
if (state) {
this.postsSub = this.ws.subscribeForPosts().subscribe(e => {
this.zone.run(() => {
e.forEach(v => {
if (this.postState.postId === v.postId) {
this.postState = v;
this.postState$.emit(this.postState);
}
});
});
});
});
} else if (this.postsSub != null) {
this.postsSub.unsubscribe();
}
});
}
@@ -71,6 +77,12 @@ export class PostProgressRendererComponent implements AgRendererComponent, OnIni
if (this.updatesSubscription != null) {
this.updatesSubscription.unsubscribe();
}
if (this.postsSub != null) {
this.postsSub.unsubscribe();
}
if (this.stateSub != null) {
this.stateSub.unsubscribe();
}
clearTimeout(this.loading);
}
+36 -36
View File
@@ -8,53 +8,53 @@ import { WsHandler } from '../ws-handler';
import { NgZone } from '@angular/core';
export class PostsDataSource {
constructor(
private wsConnectionService: WsConnectionService,
private gridOptions: GridOptions,
private zone: NgZone
) {
this.websocketHandlerPromise = this.wsConnectionService.getConnection();
}
constructor(private ws: WsConnectionService, private gridOptions: GridOptions, private zone: NgZone) {}
websocketHandlerPromise: Promise<WsHandler>;
subscriptions: Subscription[] = [];
connect() {
this.websocketHandlerPromise.then((handler: WsHandler) => {
console.log('Connecting to posts datasource');
this.subscriptions.push(
handler.subscribeForPosts((e: PostState[]) => {
this.zone.run(() => {
const toAdd = [];
const toUpdate = [];
const toRemove = [];
e.forEach(v => {
if (v.removed) {
if (this.gridOptions.api.getRowNode(v.postId) != null) {
toRemove.push(this.gridOptions.api.getRowNode(v.postId).data);
}
return;
}
postsSub: Subscription;
if (this.gridOptions.api.getRowNode(v.postId) == null) {
toAdd.push(v);
} else {
toUpdate.push(v);
}
connect() {
console.log('Connecting to posts datasource');
this.subscriptions.push(
this.ws.state.subscribe(state => {
if (state) {
this.postsSub = this.ws.subscribeForPosts().subscribe((e: PostState[]) => {
this.zone.run(() => {
const toAdd = [];
const toUpdate = [];
const toRemove = [];
e.forEach(v => {
if (v.removed) {
if (this.gridOptions.api.getRowNode(v.postId) != null) {
toRemove.push(this.gridOptions.api.getRowNode(v.postId).data);
}
return;
}
if (this.gridOptions.api.getRowNode(v.postId) == null) {
toAdd.push(v);
} else {
toUpdate.push(v);
}
});
this.gridOptions.api.updateRowData({ update: toUpdate, add: toAdd, remove: toRemove });
});
this.gridOptions.api.updateRowData({ update: toUpdate, add: toAdd, remove: toRemove });
});
})
);
handler.send(new WSMessage(CMD.POSTS_SUB.toString()));
});
this.ws.send(new WSMessage(CMD.POSTS_SUB.toString()));
} else if (this.postsSub != null) {
this.postsSub.unsubscribe();
}
})
);
}
disconnect() {
console.log('Disconnecting from posts datasource');
this.subscriptions.forEach(e => e.unsubscribe());
this.websocketHandlerPromise.then((handler: WsHandler) => {
handler.send(new WSMessage(CMD.POSTS_UNSUB.toString()));
});
this.ws.send(new WSMessage(CMD.POSTS_UNSUB.toString()));
if (this.postsSub != null) {
this.postsSub.unsubscribe();
}
}
}
+9
View File
@@ -4,6 +4,7 @@ import { Injectable } from '@angular/core';
export class ServerService {
private _baseUrl: string;
private _wsBaseUrl: string;
constructor() {}
@@ -14,4 +15,12 @@ export class ServerService {
get baseUrl(): string {
return this._baseUrl;
}
set wsBaseUrl(wsBaseUrl: string) {
this._wsBaseUrl = wsBaseUrl;
}
get wsBaseUrl(): string {
return this._wsBaseUrl;
}
}
@@ -9,7 +9,7 @@ import { ServerService } from '../server-service';
import { ElectronService } from 'ngx-electron';
import { Settings } from '../common/settings.model';
import { OpenDialogReturnValue } from 'electron';
import { forkJoin, Subject, BehaviorSubject } from 'rxjs';
import { Subject, BehaviorSubject } from 'rxjs';
interface CacheSize {
size: string;
@@ -68,13 +68,18 @@ export class SettingsComponent implements OnInit {
ngOnInit() {
this.darkTheme = this.appService.darkTheme;
forkJoin([
this.httpClient.get<Settings>(this.serverService.baseUrl + '/settings'),
this.httpClient.get<CacheSize>(this.serverService.baseUrl + '/gallery/cache')
]).subscribe(data => {
this.generalSettingsForm.reset(data[0]);
this.desktopSettingsForm.reset(data[0]);
this.cacheSize.next(data[1]);
this.httpClient.get<Settings>(this.serverService.baseUrl + '/settings')
.subscribe(data => {
this.generalSettingsForm.reset(data);
this.desktopSettingsForm.reset(data);
}, error => {
this._snackBar.open(error.error || 'Unexpected error, check log file', null, {
duration: 5000
});
});
this.httpClient.get<CacheSize>(this.serverService.baseUrl + '/gallery/cache')
.subscribe(data => {
this.cacheSize.next(data);
}, error => {
this._snackBar.open(error.error || 'Unexpected error, check log file', null, {
duration: 5000
@@ -1,3 +1,4 @@
import { flatMap, filter } from 'rxjs/operators';
import { SelectionService } from './../selection-service';
import {
Component,
@@ -23,13 +24,7 @@ import { CMD } from '../common/cmd.enum';
changeDetection: ChangeDetectionStrategy.OnPush
})
export class StatusBarComponent implements OnInit, OnDestroy, AfterViewInit {
constructor(
private wsConnectionService: WsConnectionService,
private ngZone: NgZone,
private selectionService: SelectionService
) {
this.websocketHandlerPromise = this.wsConnectionService.getConnection();
}
constructor(private ws: WsConnectionService, private ngZone: NgZone, private selectionService: SelectionService) {}
websocketHandlerPromise: Promise<WsHandler>;
downloadSpeed: EventEmitter<DownloadSpeed> = new EventEmitter();
@@ -37,6 +32,9 @@ export class StatusBarComponent implements OnInit, OnDestroy, AfterViewInit {
globalState: EventEmitter<GlobalState> = new EventEmitter();
selected: EventEmitter<number> = new EventEmitter();
globalStateSub: Subscription;
speedSub: Subscription;
ngAfterViewInit(): void {
this.ngZone.run(() => {
this.selected.emit(0);
@@ -46,25 +44,34 @@ export class StatusBarComponent implements OnInit, OnDestroy, AfterViewInit {
}
ngOnInit() {
this.websocketHandlerPromise.then((handler: WsHandler) => {
console.log('Connecting to global state and download speed');
this.subscriptions.push(
handler.subscribeForGlobalState((e: GlobalState[]) => {
this.ngZone.run(() => {
this.globalState.emit(e[0]);
console.log('Connecting to global state and download speed');
this.subscriptions.push(
this.ws.state.subscribe(state => {
if (state) {
this.globalStateSub = this.ws.subscribeForGlobalState().subscribe((e: GlobalState[]) => {
this.ngZone.run(() => {
this.globalState.emit(e[0]);
});
});
})
);
this.subscriptions.push(
handler.subscribeForSpeed((e: DownloadSpeed[]) => {
this.ngZone.run(() => {
this.downloadSpeed.emit(e[0]);
this.speedSub = this.ws.subscribeForSpeed().subscribe((e: DownloadSpeed[]) => {
this.ngZone.run(() => {
this.downloadSpeed.emit(e[0]);
});
});
})
);
handler.send(new WSMessage(CMD.GLOBAL_STATE_SUB.toString()));
handler.send(new WSMessage(CMD.SPEED_SUB.toString()));
});
this.ws.send(new WSMessage(CMD.GLOBAL_STATE_SUB.toString()));
this.ws.send(new WSMessage(CMD.SPEED_SUB.toString()));
} else {
if (this.globalStateSub != null) {
this.globalStateSub.unsubscribe();
}
if (this.speedSub != null) {
this.speedSub.unsubscribe();
}
}
})
);
this.subscriptions.push(
this.selectionService.selected$.subscribe(selected => this.ngZone.run(() => this.selected.emit(selected.length)))
);
@@ -72,9 +79,13 @@ export class StatusBarComponent implements OnInit, OnDestroy, AfterViewInit {
ngOnDestroy(): void {
this.subscriptions.forEach(e => e.unsubscribe());
this.websocketHandlerPromise.then((handler: WsHandler) => {
handler.send(new WSMessage(CMD.GLOBAL_STATE_UNSUB.toString()));
handler.send(new WSMessage(CMD.SPEED_UNSUB.toString()));
});
this.ws.send(new WSMessage(CMD.GLOBAL_STATE_UNSUB.toString()));
this.ws.send(new WSMessage(CMD.SPEED_UNSUB.toString()));
if (this.globalStateSub != null) {
this.globalStateSub.unsubscribe();
}
if (this.speedSub != null) {
this.speedSub.unsubscribe();
}
}
}
+35 -22
View File
@@ -1,4 +1,12 @@
import { Component, OnInit, NgZone, OnDestroy, ChangeDetectionStrategy, EventEmitter, AfterViewInit } from '@angular/core';
import {
Component,
OnInit,
NgZone,
OnDestroy,
ChangeDetectionStrategy,
EventEmitter,
AfterViewInit
} from '@angular/core';
import { RemoveAllResponse } from '../common/remove-all-response.model';
import { ServerService } from '../server-service';
import { AppService } from '../app.service';
@@ -26,7 +34,6 @@ import { PostsDataService } from '../posts-data.service';
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
constructor(
private serverService: ServerService,
private appService: AppService,
@@ -38,9 +45,7 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
private ws: WsConnectionService,
private selectionService: SelectionService,
private postsDataService: PostsDataService
) {
this.websocketHandlerPromise = this.ws.getConnection();
}
) {}
loggedUser: EventEmitter<LoggedUser> = new EventEmitter();
isExtraSmall: Observable<BreakpointState> = this.breakpointObserver.observe(Breakpoints.XSmall);
@@ -48,6 +53,7 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
subscriptions: Subscription[] = [];
selected: RowNode[] = [];
disableSelection: EventEmitter<boolean> = new EventEmitter();
userSub: Subscription;
openSettings(): void {
const dialogRef = this.dialog.open(SettingsComponent, {
@@ -92,9 +98,7 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
.afterClosed()
.pipe(
filter(e => e === 'yes'),
flatMap(e =>
this.httpClient.post<RemoveResponse[]>(this.serverService.baseUrl + '/post/remove', toRemove)
)
flatMap(e => this.httpClient.post<RemoveResponse[]>(this.serverService.baseUrl + '/post/remove', toRemove))
)
.subscribe(
data => {
@@ -223,25 +227,34 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
}
ngOnInit() {
this.websocketHandlerPromise.then((handler: WsHandler) => {
console.log('Connecting to user stream');
this.subscriptions.push(
handler.subscribeForUser((e: LoggedUser[]) => {
this.ngZone.run(() => {
this.loggedUser.emit(e[0]);
console.log('Connecting to user stream');
this.subscriptions.push(
this.ws.state.subscribe(state => {
if (state) {
this.userSub = this.ws.subscribeForUser().subscribe((e: LoggedUser[]) => {
this.ngZone.run(() => {
this.loggedUser.emit(e[0]);
});
});
})
);
handler.send(new WSMessage(CMD.USER_SUB.toString()));
});
this.ws.send(new WSMessage(CMD.USER_SUB.toString()));
} else if (this.userSub != null) {
this.userSub.unsubscribe();
}
})
);
this.selectionService.selected$.subscribe(selected => {
this.selected = selected;
this.ngZone.run(() => this.disableSelection.next(this.selected.length === 0));
});
this.subscriptions.push(
this.selectionService.selected$.subscribe(selected => {
this.selected = selected;
this.ngZone.run(() => this.disableSelection.next(this.selected.length === 0));
})
);
}
ngOnDestroy() {
this.subscriptions.forEach(e => e.unsubscribe());
if (this.userSub != null) {
this.userSub.unsubscribe();
}
}
}
+164 -109
View File
@@ -1,136 +1,191 @@
import { Injectable } from '@angular/core';
import { Observer, Subject, BehaviorSubject, ConnectableObservable, Observable, Subscription } from 'rxjs';
import { Subject, BehaviorSubject, Observable, Subscription } from 'rxjs';
import { WebSocketSubject } from 'rxjs/webSocket';
import { environment } from 'src/environments/environment';
import { multicast } from 'rxjs/operators';
import { ElectronService } from 'ngx-electron';
import { WsHandler } from './ws-handler';
import { ServerService } from './server-service';
declare var SockJS;
export enum WSState {
INIT,
CONNECTING,
ERROR,
CLOSE,
OPEN
}
const maxAttemps = 15;
import { GrabQueueState } from './grab-queue/grab-queue.model';
import { WSMessage } from './common/ws-message.model';
import { PostDetails } from './post-detail/post-details.model';
import { PostState } from './posts/post-state.model';
import { LoggedUser } from './common/logged-user.model';
import { DownloadSpeed } from './common/download-speed.model';
import { GlobalState } from './common/global-state.model';
import { map, filter } from 'rxjs/operators';
@Injectable()
export class WsConnectionService {
private websocket: Subject<any>;
private state$: BehaviorSubject<WSState> = new BehaviorSubject(WSState.INIT);
private sock;
private _state: WSState = WSState.INIT;
private wsHandlerPromise: Promise<WsHandler>;
private wsHandler: WsHandler;
private websocket: WebSocketSubject<any>;
private online: Subject<boolean> = new BehaviorSubject(false);
private open = false;
constructor(private electronService: ElectronService, private serverService: ServerService) {
this.init();
}
init() {
this.wsHandlerPromise = new Promise((resolve, reject) => {
if (this.wsHandler != null) {
return this.wsHandler;
}
if (this.electronService.isElectronApp) {
const portRequest = setInterval(() => {
this.electronService.ipcRenderer.send('get-port');
}, 1000);
if (this.electronService.isElectronApp) {
const portRequest = setInterval(() => {
this.electronService.ipcRenderer.send('get-port');
}, 1000);
// wait for MainIPC
this.electronService.ipcRenderer.once('port', (event, port) => {
clearInterval(portRequest);
console.log('server running on port', port);
this.serverService.baseUrl = 'http://localhost:' + port;
this.tryConnect(resolve, reject);
});
} else {
this.serverService.baseUrl = environment.localhost;
this.tryConnect(resolve, reject);
}
});
}
public get state(): Observable<WSState> {
return this.state$.asObservable();
}
tryConnect(resolve, reject) {
let attempts = 0;
this.connect();
const subscription: Subscription = this.state$.subscribe(e => {
if (e === WSState.OPEN) {
this.wsHandler = new WsHandler(this.websocket);
resolve(this.wsHandler);
}
});
const interval = setInterval(() => {
if (this._state !== WSState.OPEN && attempts >= maxAttemps) {
clearInterval(interval);
this._state = WSState.ERROR;
this.state$.next(this._state);
reject('Failed to connect to server');
if (subscription != null) {
subscription.unsubscribe();
}
return;
} else if (this._state === WSState.OPEN) {
clearInterval(interval);
if (subscription != null) {
subscription.unsubscribe();
}
return;
}
// wait for MainIPC
this.electronService.ipcRenderer.once('port', (event, port) => {
clearInterval(portRequest);
console.log('server running on port', port);
this.serverService.baseUrl = 'http://localhost:' + port;
this.serverService.wsBaseUrl = 'ws://localhost:' + port;
this.connect();
});
} else {
this.serverService.baseUrl = environment.localhost;
this.serverService.wsBaseUrl = environment.ws;
this.connect();
attempts++;
}, 5000);
}
}
connect() {
this._state = WSState.CONNECTING;
this.state$.next(this._state);
this.sock = new SockJS(this.serverService.baseUrl + '/endpoint');
const observable = Observable.create((obs: Observer<string>) => {
this.sock.onmessage = message => obs.next(message.data);
this.sock.onerror = error => {
console.log('Sockjs error', error);
obs.error(error);
this._state = WSState.ERROR;
this.state$.next(this._state);
};
this.sock.onclose = () => {
console.log('Sockjs disconnected');
obs.complete();
this._state = WSState.CLOSE;
this.state$.next(this._state);
};
});
this.sock.onopen = () => {
console.log('Sockjs connection established');
const observer = {
next: (data: Object) => {
if (this.sock.readyState === WebSocket.OPEN) {
this.sock.send(JSON.stringify(data));
console.log('Connecting...');
if (this.websocket != null) {
this.websocket.unsubscribe();
}
this.websocket = new WebSocketSubject({
url: this.serverService.wsBaseUrl + '/endpoint',
openObserver: {
next: () => {
console.log('Connection established');
if (this.open !== true) {
this.open = true;
this.online.next(true);
}
}
};
this.websocket = Subject.create(observer, observable).pipe(multicast(() => new Subject()));
(<ConnectableObservable<any>>(<unknown>this.websocket)).connect();
this._state = WSState.OPEN;
this.state$.next(this._state);
};
},
closeObserver: {
next: () => {
if (this.open !== false) {
this.open = false;
this.online.next(false);
}
setTimeout(() => this.connect(), 1000);
}
}
});
this.websocket.subscribe();
}
getConnection(): Promise<WsHandler> {
return this.wsHandlerPromise;
public get state(): Observable<boolean> {
return this.online.asObservable();
}
disconnect() {
console.log('Sockjs disconnecting');
this.sock.close();
this.websocket.unsubscribe();
}
subscribeForGlobalState(): Observable<GlobalState[]> {
return this.websocket.pipe(
filter(e => e.length > 0 && e.filter(v => v.type === 'globalState').length > 0),
map(e => {
const state: Array<GlobalState> = [];
(<Array<any>>e).forEach(element => {
state.push(new GlobalState(element.running, element.queued, element.remaining, element.error));
});
return state;
})
);
}
subscribeForSpeed(): Observable<DownloadSpeed[]> {
return this.websocket.pipe(
filter(e => e.length > 0 && e.filter(v => v.type === 'downSpeed').length > 0),
map(e => {
const speed: Array<DownloadSpeed> = [];
(<Array<any>>e).forEach(element => {
speed.push(new DownloadSpeed(element.speed));
});
return speed;
})
);
}
subscribeForUser(): Observable<LoggedUser[]> {
return this.websocket.pipe(
filter(e => e.length > 0 && e.filter(v => v.type === 'user').length > 0),
map(e => {
const user: Array<LoggedUser> = [];
(<Array<any>>e).forEach(element => {
user.push(new LoggedUser(element.user));
});
return user;
})
);
}
subscribeForPosts(): Observable<PostState[]> {
return this.websocket.pipe(
filter(e => e.length > 0 && e.filter(v => v.type === 'post').length > 0),
map(e => {
const posts: Array<PostState> = [];
(<Array<any>>e).forEach(element => {
posts.push(
new PostState(
element.type,
element.postId,
element.title,
element.done === 0 && element.total === 0 ? 0 : (element.done / element.total) * 100,
element.status,
element.removed,
element.url,
element.done,
element.total,
element.hosts,
element.metadata.PREVIEWS
)
);
});
return posts;
})
);
}
subscribeForPostDetails(): Observable<PostDetails[]> {
return this.websocket.pipe(
filter(e => e.length > 0 && e[0].type === 'img'),
map(e => {
const values = [];
(<Array<any>>e).forEach(element => {
values.push(
new PostDetails(
element.postId,
element.postName,
element.url,
element.current === 0 && element.total === 0 ? 0 : (element.current / element.total) * 100,
element.status,
element.index
)
);
});
return values;
})
);
}
subscribeForGrabQueue(): Observable<GrabQueueState[]> {
return this.websocket.pipe(
filter(e => e.length > 0 && e.filter(v => v.type === 'grabQueue').length > 0),
map(e => {
const grabQueue: Array<GrabQueueState> = [];
(<Array<any>>e).forEach(element => {
grabQueue.push(
new GrabQueueState(element.type, element.link, element.threadId, element.postId, element.removed)
);
});
return grabQueue;
})
);
}
send(wsMessage: WSMessage) {
this.websocket.next(wsMessage);
}
}
-135
View File
@@ -10,140 +10,5 @@ import { GlobalState } from './common/global-state.model';
import { DownloadSpeed } from './common/download-speed.model';
export class WsHandler {
constructor(private websocket: Subject<any>) {}
subscribeForGlobalState(callback: (stateStream: Array<GlobalState>) => void): Subscription {
return this.websocket
.pipe(
map(e => JSON.parse(e)),
filter(e => e.length > 0 && e.filter(v => v.type === 'globalState').length > 0),
map(e => {
const state: Array<GlobalState> = [];
(<Array<any>>e).forEach(element => {
state.push(new GlobalState(element.running, element.queued, element.remaining, element.error));
});
return state;
})
)
.subscribe(e => {
callback(e);
});
}
subscribeForSpeed(callback: (speedStream: Array<DownloadSpeed>) => void): Subscription {
return this.websocket
.pipe(
map(e => JSON.parse(e)),
filter(e => e.length > 0 && e.filter(v => v.type === 'downSpeed').length > 0),
map(e => {
const speed: Array<DownloadSpeed> = [];
(<Array<any>>e).forEach(element => {
speed.push(new DownloadSpeed(element.speed));
});
return speed;
})
)
.subscribe(e => {
callback(e);
});
}
subscribeForUser(callback: (userStream: Array<LoggedUser>) => void): Subscription {
return this.websocket
.pipe(
map(e => JSON.parse(e)),
filter(e => e.length > 0 && e.filter(v => v.type === 'user').length > 0),
map(e => {
const user: Array<LoggedUser> = [];
(<Array<any>>e).forEach(element => {
user.push(new LoggedUser(element.user));
});
return user;
})
)
.subscribe(e => {
callback(e);
});
}
subscribeForPosts(callback: (postStream: Array<PostState>) => void): Subscription {
return this.websocket
.pipe(
map(e => JSON.parse(e)),
filter(e => e.length > 0 && e.filter(v => v.type === 'post').length > 0),
map(e => {
const posts: Array<PostState> = [];
(<Array<any>>e).forEach(element => {
posts.push(
new PostState(
element.type,
element.postId,
element.title,
element.done === 0 && element.total === 0 ? 0 : (element.done / element.total) * 100,
element.status,
element.removed,
element.url,
element.done,
element.total,
element.hosts,
element.metadata.PREVIEWS
)
);
});
return posts;
})
)
.subscribe(e => {
callback(e);
});
}
subscribeForPostDetails(callback: (stream: Array<PostDetails>) => void): Subscription {
return this.websocket
.pipe(
map(e => JSON.parse(e)),
filter(e => e.length > 0 && e[0].type === 'img'),
map(e => {
const values = [];
(<Array<any>>e).forEach(element => {
values.push(
new PostDetails(
element.postId,
element.postName,
element.url,
element.current === 0 && element.total === 0 ? 0 : (element.current / element.total) * 100,
element.status,
element.index
)
);
});
return values;
})
)
.subscribe(e => callback(e));
}
subscribeForGrabQueue(callback: (grabQueueStream: Array<GrabQueueState>) => void): Subscription {
return this.websocket
.pipe(
map(e => JSON.parse(e)),
filter(e => e.length > 0 && e.filter(v => v.type === 'grabQueue').length > 0),
map(e => {
const grabQueue: Array<GrabQueueState> = [];
(<Array<any>>e).forEach(element => {
grabQueue.push(
new GrabQueueState(element.type, element.link, element.threadId, element.postId, element.removed)
);
});
return grabQueue;
})
)
.subscribe(e => {
callback(e);
});
}
send(wsMessage: WSMessage) {
this.websocket.next(wsMessage);
}
}
File diff suppressed because one or more lines are too long
@@ -1,5 +1,6 @@
export const environment = {
production: true,
localhost: '',
version: '2.10.4'
localhost: `${window.location.protocol}://${window.location.host}`,
ws: `${window.location.protocol === 'https' ? 'wss' : 'ws'}://${window.location.host}`,
version: '2.10.7'
};
+2 -1
View File
@@ -5,7 +5,8 @@
export const environment = {
production: false,
localhost: 'http://localhost:8080',
version: '2.10.4'
ws: 'ws://localhost:8080',
version: '2.10.7'
};
/*
-1
View File
@@ -7,7 +7,6 @@
<meta content="width=device-width, initial-scale=1" name="viewport"/>
<link href="favicon.ico" rel="icon" type="image/x-icon"/>
<script src="assets/sockjs.min.js"></script>
</head>
<body class="mat-app-background">
<app-root></app-root>