mirror of
https://github.com/dev-claw/vripper-project.git
synced 2026-08-19 08:35:41 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bf2e8c4bb6 | ||
|
|
5d449fa4e5 | ||
|
|
c8b76b2dfb | ||
|
|
e3e9935e15 | ||
|
|
83b41763bd | ||
|
|
ec479cb920 | ||
|
|
4827a88ac4 | ||
|
|
d4980ad6a3 | ||
|
|
f9fc8de3a5 | ||
|
|
9a9df9c8e9 | ||
|
|
46083b6c91 | ||
|
|
8abd8dbf9e |
@@ -1,5 +1,29 @@
|
||||
# Changelog
|
||||
|
||||
## [1.4.7] - 2019-08-13
|
||||
### Changed
|
||||
- Fixed a bug in image filename extensions
|
||||
|
||||
## [1.4.6] - 2019-08-06
|
||||
### Changed
|
||||
- Fixed a bug when creating image file names
|
||||
- Remove unnecessary logs
|
||||
- Fix data.json init
|
||||
|
||||
## [1.4.5] - 2019-08-03
|
||||
### Changed
|
||||
- Add min width and height for electron app
|
||||
### Added
|
||||
- Add download speed to status bar
|
||||
|
||||
## [1.4.4] - 2019-08-01
|
||||
### Changed
|
||||
- Fix names for ImgboxHost
|
||||
- Enhance UI
|
||||
- Minor fixes
|
||||
### Added
|
||||
- Add start and stop button
|
||||
|
||||
## [1.4.3] - 2019-07-14
|
||||
### Changed
|
||||
- Update some messages
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
# Viper Ripper!
|
||||
|
||||
This is my spin for a gallery ripper app for [vipergirls](https://vipergirls.to) website.
|
||||
The purpose of this project is to build a robust and clean application to conveniently download photo galleries using modern web technologies, Java + Spring boot for the back end and angular + electron for the front end.
|
||||
|
||||
## How to build
|
||||
You need a recent version of maven 3.6.1+.
|
||||
|
||||
The application support 2 build modes:
|
||||
- Desktop app (*Partially cross platform): Self contained app, built with electron, depends on Java runtimes.
|
||||
- Server app (Fully cross platform): Depends on Java runtimes, you need a web browser to access the app UI.
|
||||
|
||||
Most people will be interested only on the desktop app, however if you have a nas or a server, the server app is there for you.
|
||||
|
||||
To build the server app:
|
||||
|
||||
mvn clean install
|
||||
To build the desktop app:
|
||||
|
||||
mvn clean install -Pelectron
|
||||
|
||||
Maven will automatically handle front end compilation. However, for development, you will need to install a recent version of nodejs on your system.
|
||||
|
||||
*Partially cross platform:
|
||||
Technically cross platform, but the Desktop app relies on electron builder to make appropriate binaries for each platform. mac binaries can only be generated on a mac, and i don't own one. So i can only provide binaries for windows and linux.
|
||||
The Server app however depends only on Java, so it will work just fine on a mac, you only need a browser to access it.
|
||||
@@ -4,7 +4,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>tn.mnlr</groupId>
|
||||
<artifactId>vripper</artifactId>
|
||||
<version>1.4.3</version>
|
||||
<version>1.4.7</version>
|
||||
<packaging>pom</packaging>
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
@@ -15,5 +15,6 @@
|
||||
<modules>
|
||||
<module>vripper-server</module>
|
||||
<module>vripper-ui</module>
|
||||
<module>vripper-electron</module>
|
||||
</modules>
|
||||
</project>
|
||||
@@ -6,13 +6,17 @@ const { spawn } = require("child_process");
|
||||
const { ipcMain } = require("electron");
|
||||
const commandExists = require("command-exists").sync;
|
||||
const { dialog } = require("electron");
|
||||
const axios = require('axios');
|
||||
const appDir = process.env.APPDIR;
|
||||
|
||||
console.log('App path', path.join(app.getPath('exe'), "../bin/vripper-server.jar"));
|
||||
console.log('App image path', appDir);
|
||||
|
||||
let win;
|
||||
let vripperServer;
|
||||
let serverPort;
|
||||
|
||||
const maxTerminationAttemps = 5;
|
||||
let terminationAttemps = 0;
|
||||
let terminationInteval;
|
||||
let terminated = false;
|
||||
|
||||
process.on("uncaughtException", err => {
|
||||
dialog.showErrorBox(err.message, err.stack);
|
||||
@@ -37,6 +41,8 @@ function createWindow() {
|
||||
win = new BrowserWindow({
|
||||
width: 1024,
|
||||
height: 768,
|
||||
minWidth: 640,
|
||||
minHeight: 480,
|
||||
frame: false,
|
||||
webPreferences: {
|
||||
nodeIntegration: true
|
||||
@@ -62,6 +68,7 @@ function createWindow() {
|
||||
}
|
||||
|
||||
getPort().then(port => {
|
||||
serverPort = port;
|
||||
ipcMain.on("get-port", event => {
|
||||
event.reply("port", port);
|
||||
});
|
||||
@@ -70,7 +77,14 @@ getPort().then(port => {
|
||||
"-jar",
|
||||
appDir !== undefined ? path.join(appDir, "bin/vripper-server.jar") :
|
||||
path.join(app.getPath('exe'), "../bin/vripper-server.jar")
|
||||
], {stdio: 'ignore'});
|
||||
], {
|
||||
stdio: 'ignore'
|
||||
});
|
||||
vripperServer.on('exit', (code, signal) => {
|
||||
console.log(`vripper server terminated, code = ${code}, signal = ${signal}`);
|
||||
terminated = true;
|
||||
app.quit();
|
||||
});
|
||||
});
|
||||
|
||||
const gotTheLock = app.requestSingleInstanceLock();
|
||||
@@ -89,10 +103,30 @@ if (!gotTheLock) {
|
||||
|
||||
app.on("window-all-closed", () => {
|
||||
if (process.platform !== "darwin") {
|
||||
if(vripperServer != null) {
|
||||
vripperServer.kill("SIGTERM");
|
||||
}
|
||||
app.quit();
|
||||
axios.post('http://localhost:' + serverPort + '/actuator/shutdown', {}, {
|
||||
headers: { 'content-type': 'application/json' },
|
||||
}).then((response) => {
|
||||
terminationInteval= setInterval(() => {
|
||||
terminationAttemps++;
|
||||
if(terminated) {
|
||||
console.log('viper server terminated');
|
||||
clearInterval(terminationInteval);
|
||||
app.quit();
|
||||
} else if(terminationAttemps > maxTerminationAttemps) {
|
||||
console.log('viper server is not terminated');
|
||||
console.log('Proceed to kill');
|
||||
vripperServer.kill('SIGKILL');
|
||||
clearInterval(terminationInteval);
|
||||
app.quit();
|
||||
}
|
||||
}, 1000);
|
||||
})
|
||||
.catch((error) => {
|
||||
// Terminate immediately
|
||||
vripperServer.kill('SIGKILL');
|
||||
terminated = true;
|
||||
app.quit();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Generated
+41
-4
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "vripper-electron",
|
||||
"version": "1.4.3",
|
||||
"version": "1.4.7",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
@@ -225,6 +225,15 @@
|
||||
"integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==",
|
||||
"dev": true
|
||||
},
|
||||
"axios": {
|
||||
"version": "0.19.0",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.19.0.tgz",
|
||||
"integrity": "sha512-1uvKqKQta3KBxIz14F2v06AEHZ/dIoeKfbTRkK1E5oqjDnuEerLmYTgJB5AiQZHJcljpg1TuRzdjDR06qNk0DQ==",
|
||||
"requires": {
|
||||
"follow-redirects": "1.5.10",
|
||||
"is-buffer": "^2.0.2"
|
||||
}
|
||||
},
|
||||
"balanced-match": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
|
||||
@@ -1199,6 +1208,29 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"follow-redirects": {
|
||||
"version": "1.5.10",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz",
|
||||
"integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==",
|
||||
"requires": {
|
||||
"debug": "=3.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
|
||||
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
|
||||
"requires": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
|
||||
}
|
||||
}
|
||||
},
|
||||
"forever-agent": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
|
||||
@@ -1494,6 +1526,11 @@
|
||||
"integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=",
|
||||
"dev": true
|
||||
},
|
||||
"is-buffer": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.3.tgz",
|
||||
"integrity": "sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw=="
|
||||
},
|
||||
"is-ci": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz",
|
||||
@@ -1732,9 +1769,9 @@
|
||||
}
|
||||
},
|
||||
"lodash": {
|
||||
"version": "4.17.11",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
|
||||
"integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg=="
|
||||
"version": "4.17.15",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
|
||||
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A=="
|
||||
},
|
||||
"loud-rejection": {
|
||||
"version": "1.6.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "vripper-electron",
|
||||
"version": "1.4.3",
|
||||
"version": "1.4.7",
|
||||
"description": "",
|
||||
"main": "main.js",
|
||||
"author": "",
|
||||
@@ -27,7 +27,7 @@
|
||||
],
|
||||
"extraFiles": [
|
||||
{
|
||||
"from": "../vripper-server/target/vripper-server-${version}.jar",
|
||||
"from": "../vripper-server/target/vripper-server-${version}-electron.jar",
|
||||
"to": "bin/vripper-server.jar"
|
||||
}
|
||||
],
|
||||
@@ -36,7 +36,7 @@
|
||||
},
|
||||
"linux": {
|
||||
"target": [
|
||||
"AppImage"
|
||||
"zip"
|
||||
]
|
||||
}
|
||||
},
|
||||
@@ -49,6 +49,7 @@
|
||||
"electron-builder": "^20.44.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^0.19.0",
|
||||
"cheerio": "^1.0.0-rc.3",
|
||||
"command-exists": "^1.2.8",
|
||||
"copy-dir": "^1.1.0",
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
<?xml version="1.0"?>
|
||||
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
|
||||
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>tn.mnlr</groupId>
|
||||
<artifactId>vripper</artifactId>
|
||||
<version>1.4.7</version>
|
||||
</parent>
|
||||
<artifactId>vripper-electron</artifactId>
|
||||
<name>vripper-electron</name>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<configuration>
|
||||
<classifier>${buildClassifier}</classifier>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>electron</id>
|
||||
<properties>
|
||||
<buildClassifier>electron</buildClassifier>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>tn.mnlr</groupId>
|
||||
<artifactId>vripper-server</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>runtime</scope>
|
||||
<classifier>${buildClassifier}</classifier>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>com.github.eirslett</groupId>
|
||||
<artifactId>frontend-maven-plugin</artifactId>
|
||||
<version>1.3</version>
|
||||
<configuration>
|
||||
<nodeVersion>v12.2.0</nodeVersion>
|
||||
<npmVersion>6.9.0</npmVersion>
|
||||
<workingDirectory>build-dir</workingDirectory>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>install node and npm</id>
|
||||
<goals>
|
||||
<goal>install-node-and-npm</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>npm install</id>
|
||||
<goals>
|
||||
<goal>npm</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>npm run build</id>
|
||||
<goals>
|
||||
<goal>npm</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<arguments>run dist</arguments>
|
||||
</configuration>
|
||||
<phase>generate-resources</phase>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
</profiles>
|
||||
</project>
|
||||
File diff suppressed because one or more lines are too long
+58
-16
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>tn.mnlr</groupId>
|
||||
<artifactId>vripper</artifactId>
|
||||
<version>1.4.3</version>
|
||||
<version>1.4.7</version>
|
||||
</parent>
|
||||
<artifactId>vripper-server</artifactId>
|
||||
<name>vripper-server</name>
|
||||
@@ -28,7 +28,10 @@
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-websocket</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
@@ -69,33 +72,29 @@
|
||||
<artifactId>vripper-ui</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>runtime</scope>
|
||||
<classifier>${buildClassifier}</classifier>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<finalName>${project.artifactId}-${project.version}</finalName>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<configuration>
|
||||
<classifier>${buildClassifier}</classifier>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-resources-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>copy-resources</id>
|
||||
<phase>validate</phase>
|
||||
<id>repackage</id>
|
||||
<goals>
|
||||
<goal>copy-resources</goal>
|
||||
<goal>repackage</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<outputDirectory>${project.build.directory}/classes/static</outputDirectory>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>${project.parent.basedir}/vripper-ui/dist/vripper-ui/
|
||||
</directory>
|
||||
</resource>
|
||||
</resources>
|
||||
<classifier>${buildClassifier}</classifier>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
@@ -149,4 +148,47 @@
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>web</id>
|
||||
<activation>
|
||||
<activeByDefault>true</activeByDefault>
|
||||
</activation>
|
||||
<properties>
|
||||
<buildClassifier>web</buildClassifier>
|
||||
</properties>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-resources-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>copy-resources</id>
|
||||
<phase>validate</phase>
|
||||
<goals>
|
||||
<goal>copy-resources</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<outputDirectory>${project.build.directory}/classes/static</outputDirectory>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>${project.parent.basedir}/vripper-ui/dist/vripper-ui/
|
||||
</directory>
|
||||
</resource>
|
||||
</resources>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
<profile>
|
||||
<id>electron</id>
|
||||
<properties>
|
||||
<buildClassifier>electron</buildClassifier>
|
||||
</properties>
|
||||
</profile>
|
||||
</profiles>
|
||||
</project>
|
||||
|
||||
@@ -7,7 +7,6 @@ import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.stereotype.Component;
|
||||
import tn.mnlr.vripper.services.PersistenceService;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@@ -18,8 +17,6 @@ public class SpringContext implements ApplicationContextAware {
|
||||
|
||||
private static ConfigurableApplicationContext context;
|
||||
|
||||
private PersistenceService persistenceService;
|
||||
|
||||
/**
|
||||
* Returns the Spring managed bean instance of the given class type (if it exists).
|
||||
* Returns null otherwise.
|
||||
|
||||
@@ -27,6 +27,8 @@ public class Post {
|
||||
|
||||
private String title;
|
||||
|
||||
private String postCounter;
|
||||
|
||||
private String url;
|
||||
|
||||
private List<Image> images;
|
||||
@@ -39,12 +41,13 @@ public class Post {
|
||||
|
||||
private boolean removed = false;
|
||||
|
||||
public Post(String title, String url, List<Image> images, Map<String, String> metadata, String postId, AppStateService appStateService) {
|
||||
public Post(String title, String url, List<Image> images, Map<String, String> metadata, String postId, String postCounter, AppStateService appStateService) {
|
||||
this.title = title;
|
||||
this.url = url;
|
||||
this.images = images;
|
||||
this.metadata = metadata;
|
||||
this.postId = postId;
|
||||
this.postCounter = postCounter;
|
||||
this.appStateService = appStateService;
|
||||
total = images.size();
|
||||
status = Status.PENDING;
|
||||
|
||||
@@ -11,20 +11,21 @@ import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.w3c.dom.Document;
|
||||
import tn.mnlr.vripper.services.AppSettingsService;
|
||||
import tn.mnlr.vripper.entities.Image;
|
||||
import tn.mnlr.vripper.exception.DownloadException;
|
||||
import tn.mnlr.vripper.exception.HostException;
|
||||
import tn.mnlr.vripper.exception.HtmlProcessorException;
|
||||
import tn.mnlr.vripper.q.ImageFileData;
|
||||
import tn.mnlr.vripper.services.ConnectionManager;
|
||||
import tn.mnlr.vripper.services.HtmlProcessorService;
|
||||
import tn.mnlr.vripper.services.XpathService;
|
||||
import tn.mnlr.vripper.services.*;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.imageio.ImageReader;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Iterator;
|
||||
import java.util.Random;
|
||||
|
||||
@Service
|
||||
@@ -46,6 +47,9 @@ abstract public class Host {
|
||||
@Autowired
|
||||
private ConnectionManager cm;
|
||||
|
||||
@Autowired
|
||||
private DownloadSpeedService downloadSpeedService;
|
||||
|
||||
abstract protected String getHost();
|
||||
|
||||
public boolean isSupported(String url) {
|
||||
@@ -72,17 +76,12 @@ abstract public class Host {
|
||||
* END HOST SPECIFIC
|
||||
*/
|
||||
|
||||
if(!imageFileData.getImageName().toLowerCase().endsWith(".jpg")) {
|
||||
imageFileData.setImageName(imageFileData.getImageName() + ".jpg");
|
||||
}
|
||||
|
||||
File destinationFolder = new File(appSettingsService.getDownloadPath(), sanitize(image.getPostName()));
|
||||
imageFileData.setImageName(formatImageFileName(imageFileData.getImageName()));
|
||||
File destinationFolder = new File(appSettingsService.getDownloadPath(), sanitize(image.getPostName() + "_" + image.getPostId()));
|
||||
logger.info(String.format("Saving to %s", destinationFolder.getPath()));
|
||||
if (!destinationFolder.exists()) {
|
||||
logger.info(String.format("Creating %s", destinationFolder.getPath()));
|
||||
destinationFolder.mkdirs();
|
||||
} else {
|
||||
logger.warn(String.format("%s already exists, the file will be overridden", destinationFolder.getPath()));
|
||||
}
|
||||
|
||||
HttpClient client = cm.getClient().build();
|
||||
@@ -95,9 +94,10 @@ abstract public class Host {
|
||||
throw new DownloadException(String.format("Server returned code %d", response.getStatusLine().getStatusCode()));
|
||||
}
|
||||
|
||||
File outputFile = new File(destinationFolder.getPath() + File.separator + imageFileData.getImageName() + ".tmp");
|
||||
try (
|
||||
InputStream downloadStream = response.getEntity().getContent();
|
||||
FileOutputStream fos = new FileOutputStream(destinationFolder.getPath() + File.separator + imageFileData.getImageName())
|
||||
FileOutputStream fos = new FileOutputStream(outputFile)
|
||||
) {
|
||||
|
||||
image.setTotal(response.getEntity().getContentLength());
|
||||
@@ -109,8 +109,10 @@ abstract public class Host {
|
||||
while ((read = downloadStream.read(buffer, 0, READ_BUFFER_SIZE)) != -1) {
|
||||
fos.write(buffer, 0, read);
|
||||
image.increase(read);
|
||||
downloadSpeedService.increase(read);
|
||||
}
|
||||
EntityUtils.consumeQuietly(response.getEntity());
|
||||
checkImageTypeAndRename(outputFile, imageFileData.getImageName());
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
@@ -121,6 +123,42 @@ abstract public class Host {
|
||||
}
|
||||
}
|
||||
|
||||
private void checkImageTypeAndRename(File outputFile, String imageName) throws HostException {
|
||||
try {
|
||||
ImageInputStream iis = ImageIO.createImageInputStream(outputFile);
|
||||
Iterator<ImageReader> it = ImageIO.getImageReaders(iis);
|
||||
if (!it.hasNext()) {
|
||||
throw new HostException("Image file is not recognized!");
|
||||
}
|
||||
ImageReader reader = it.next();
|
||||
String formatName = reader.getFormatName();
|
||||
if (formatName.toUpperCase().equals("JPEG")) {
|
||||
formatName = "JPG";
|
||||
}
|
||||
String outImageName = imageName + "." + formatName.toUpperCase();
|
||||
outputFile.renameTo(new File(outputFile.getParent(), outImageName));
|
||||
} catch (Exception e) {
|
||||
throw new HostException("Failed to rename output file", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Will sanitize the image name and remove extension
|
||||
*
|
||||
* @param imageName
|
||||
* @return
|
||||
*/
|
||||
protected String formatImageFileName(String imageName) {
|
||||
int extensionIndex = imageName.lastIndexOf('.');
|
||||
String fileName;
|
||||
if (extensionIndex != -1) {
|
||||
fileName = imageName.substring(0, extensionIndex);
|
||||
} else {
|
||||
fileName = imageName;
|
||||
}
|
||||
return sanitize(fileName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Just for testing, you may ignore
|
||||
* @throws Exception
|
||||
@@ -155,6 +193,9 @@ abstract public class Host {
|
||||
Header[] headers;
|
||||
logger.info(String.format("Requesting %s", url));
|
||||
try (CloseableHttpResponse response = (CloseableHttpResponse) client.execute(httpGet)) {
|
||||
if (response.getStatusLine().getStatusCode() / 100 != 2) {
|
||||
throw new HostException(String.format("Unexpected response code: %d", response.getStatusLine().getStatusCode()));
|
||||
}
|
||||
headers = response.getAllHeaders();
|
||||
basePage = EntityUtils.toString(response.getEntity());
|
||||
logger.debug(String.format("%s response: %n%s", url, basePage));
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
package tn.mnlr.vripper.host;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Node;
|
||||
import tn.mnlr.vripper.exception.HostException;
|
||||
import tn.mnlr.vripper.exception.XpathException;
|
||||
import tn.mnlr.vripper.q.ImageFileData;
|
||||
import tn.mnlr.vripper.services.ConnectionManager;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
@Service
|
||||
public class ImageTwistHost extends Host {
|
||||
|
||||
public static final String IMG_XPATH = "//img[contains(@class, 'img')]";
|
||||
private static final Logger logger = LoggerFactory.getLogger(ImageTwistHost.class);
|
||||
private static final String host = "imagetwist.com";
|
||||
@Autowired
|
||||
private ConnectionManager cm;
|
||||
|
||||
@Override
|
||||
protected String getHost() {
|
||||
return host;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setNameAndUrl(final String url, final ImageFileData imageFileData) throws HostException {
|
||||
|
||||
Document doc = getResponse(url).getDocument();
|
||||
|
||||
Node imgNode;
|
||||
try {
|
||||
logger.info(String.format("Looking for xpath expression %s in %s", IMG_XPATH, url));
|
||||
imgNode = xpathService.getAsNode(doc, IMG_XPATH);
|
||||
} catch (XpathException e) {
|
||||
throw new HostException(e);
|
||||
}
|
||||
|
||||
try {
|
||||
logger.info(String.format("Resolving name and image url for %s", url));
|
||||
String imgTitle = Optional.ofNullable(imgNode.getAttributes().getNamedItem("alt")).map(Node::getTextContent).map(String::trim).orElse(null);
|
||||
String imgUrl = imgNode.getAttributes().getNamedItem("src").getTextContent().trim();
|
||||
|
||||
imageFileData.setImageUrl(imgUrl);
|
||||
imageFileData.setImageName(imgTitle);
|
||||
} catch (Exception e) {
|
||||
throw new HostException("Unexpected error occurred", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -46,7 +46,7 @@ public class ImgboxHost extends Host {
|
||||
String imgUrl = imgNode.getAttributes().getNamedItem("src").getTextContent().trim();
|
||||
|
||||
imageFileData.setImageUrl(imgUrl);
|
||||
imageFileData.setImageName(imgTitle.substring(8));
|
||||
imageFileData.setImageName(imgTitle);
|
||||
} catch (Exception e) {
|
||||
throw new HostException("Unexpected error occurred", e);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import tn.mnlr.vripper.entities.Image;
|
||||
import tn.mnlr.vripper.entities.Post;
|
||||
import tn.mnlr.vripper.services.AppStateService;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
@@ -18,6 +19,8 @@ import java.util.stream.Collectors;
|
||||
@Service
|
||||
public class DownloadQ {
|
||||
|
||||
private static final List<Post.Status> FINISHED = Arrays.asList(Post.Status.ERROR, Post.Status.COMPLETE, Post.Status.STOPPED);
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(DownloadQ.class);
|
||||
|
||||
@Autowired
|
||||
@@ -102,6 +105,9 @@ public class DownloadQ {
|
||||
|
||||
public synchronized void stop(String postId) {
|
||||
try {
|
||||
if (FINISHED.contains(appStateService.getPost(postId).getStatus())) {
|
||||
return;
|
||||
}
|
||||
notPauseQ = false;
|
||||
appStateService.getPost(postId).setStatus(Post.Status.STOPPED);
|
||||
List<Image> images = appStateService.getPost(postId)
|
||||
@@ -119,4 +125,19 @@ public class DownloadQ {
|
||||
notPauseQ = true;
|
||||
}
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return downloadQ.size();
|
||||
}
|
||||
|
||||
public synchronized void stopAll() {
|
||||
appStateService.getCurrentPosts().values().stream().map(Post::getPostId).forEach(this::stop);
|
||||
}
|
||||
|
||||
public synchronized void restartAll() throws InterruptedException {
|
||||
for (Post post : appStateService.getCurrentPosts().values()) {
|
||||
String postId = post.getPostId();
|
||||
restart(postId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,8 +7,8 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import tn.mnlr.vripper.services.AppSettingsService;
|
||||
import tn.mnlr.vripper.entities.Image;
|
||||
import tn.mnlr.vripper.services.AppSettingsService;
|
||||
import tn.mnlr.vripper.services.AppStateService;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
@@ -105,6 +105,9 @@ public class ExecutionService {
|
||||
DownloadJob take = null;
|
||||
try {
|
||||
take = downloadQ.take();
|
||||
if (take == null) {
|
||||
continue;
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
break;
|
||||
@@ -127,7 +130,7 @@ public class ExecutionService {
|
||||
appStateService.doneDownloadJob(finalTake.getImage());
|
||||
logger.info(String.format("Finished downloading %s", finalTake.getImage().getUrl()));
|
||||
synchronized (threadCount) {
|
||||
int i = threadCount.decrementAndGet();
|
||||
threadCount.decrementAndGet();
|
||||
running.remove(finalTake);
|
||||
futures.remove(finalTake.getImage().getUrl());
|
||||
threadCount.notify();
|
||||
@@ -149,4 +152,8 @@ public class ExecutionService {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int runningCount() {
|
||||
return running.size();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
import tn.mnlr.vripper.exception.ValidationException;
|
||||
|
||||
import javax.annotation.PreDestroy;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.InvalidPathException;
|
||||
import java.nio.file.Path;
|
||||
@@ -63,6 +64,7 @@ public class AppSettingsService {
|
||||
desktopClipboard = prefs.getBoolean(DESKTOP_CLIPBOARD, false);
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
public void save() {
|
||||
|
||||
prefs.put(DOWNLOAD_PATH, downloadPath);
|
||||
|
||||
@@ -49,8 +49,8 @@ public class AppStateService {
|
||||
return new Image(pageUrl, postId, postName, host, this);
|
||||
}
|
||||
|
||||
public Post createPost(String title, String url, List<Image> images, Map<String, String> metadata, String postId) {
|
||||
return new Post(title, url, images, metadata, postId, this);
|
||||
public Post createPost(String title, String url, List<Image> images, Map<String, String> metadata, String postId, String postCounter) {
|
||||
return new Post(title, url, images, metadata, postId, postCounter, this);
|
||||
}
|
||||
|
||||
public Post getPost(String postId) {
|
||||
@@ -79,7 +79,9 @@ public class AppStateService {
|
||||
if (post.getImages().stream().map(Image::getStatus).filter(e -> e.equals(Image.Status.ERROR)).count() > 0) {
|
||||
post.setStatus(Post.Status.ERROR);
|
||||
} else {
|
||||
post.setStatus(Post.Status.COMPLETE);
|
||||
if (!Post.Status.STOPPED.equals(post.getStatus())) {
|
||||
post.setStatus(Post.Status.COMPLETE);
|
||||
}
|
||||
}
|
||||
livePostsState.onNext(post);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package tn.mnlr.vripper.services;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
public class DownloadSpeed {
|
||||
|
||||
private final String type = "downSpeed";
|
||||
private String speed;
|
||||
|
||||
public DownloadSpeed(long bytes) {
|
||||
speed = formatSI(bytes);
|
||||
}
|
||||
|
||||
private String formatSI(long bytes) {
|
||||
return humanReadableByteCount(bytes, true);
|
||||
}
|
||||
|
||||
private String humanReadableByteCount(long bytes, boolean si) {
|
||||
int unit = si ? 1000 : 1024;
|
||||
if (bytes < unit) return bytes + " B";
|
||||
int exp = (int) (Math.log(bytes) / Math.log(unit));
|
||||
String pre = (si ? "kMGTPE" : "KMGTPE").charAt(exp - 1) + (si ? "" : "i");
|
||||
return String.format("%.1f %sB", bytes / Math.pow(unit, exp), pre);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package tn.mnlr.vripper.services;
|
||||
|
||||
import io.reactivex.processors.PublishProcessor;
|
||||
import lombok.Getter;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
@Service
|
||||
@EnableScheduling
|
||||
public class DownloadSpeedService {
|
||||
|
||||
private AtomicLong read = new AtomicLong(0);
|
||||
|
||||
@Getter
|
||||
private PublishProcessor<Long> readBytesPerSecond = PublishProcessor.create();
|
||||
|
||||
public void increase(long read) {
|
||||
this.read.addAndGet(read);
|
||||
}
|
||||
|
||||
@Scheduled(fixedDelay = 1000)
|
||||
private void calc() {
|
||||
readBytesPerSecond.onNext(read.getAndSet(0));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package tn.mnlr.vripper.services;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
@Getter
|
||||
public class GlobalState {
|
||||
|
||||
private final String type = "globalState";
|
||||
private long running;
|
||||
private long queued;
|
||||
private long remaining;
|
||||
private long error;
|
||||
|
||||
public GlobalState(long running, long queued, long remaining, long error) {
|
||||
this.running = running;
|
||||
this.queued = queued;
|
||||
this.remaining = remaining;
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
GlobalState that = (GlobalState) o;
|
||||
return running == that.running &&
|
||||
queued == that.queued &&
|
||||
remaining == that.remaining &&
|
||||
error == that.error &&
|
||||
Objects.equals(type, that.type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(type, running, queued, remaining, error);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package tn.mnlr.vripper.services;
|
||||
|
||||
import io.reactivex.processors.PublishProcessor;
|
||||
import lombok.Getter;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Service;
|
||||
import tn.mnlr.vripper.entities.Image;
|
||||
import tn.mnlr.vripper.q.DownloadQ;
|
||||
import tn.mnlr.vripper.q.ExecutionService;
|
||||
|
||||
@Service
|
||||
@EnableScheduling
|
||||
public class GlobalStateService {
|
||||
|
||||
@Autowired
|
||||
private DownloadQ downloadQ;
|
||||
|
||||
@Autowired
|
||||
private ExecutionService executionService;
|
||||
|
||||
@Autowired
|
||||
private AppStateService appStateService;
|
||||
|
||||
@Getter
|
||||
private GlobalState currentState;
|
||||
|
||||
@Getter
|
||||
private PublishProcessor<GlobalState> liveGlobalState = PublishProcessor.create();
|
||||
|
||||
@Scheduled(fixedDelay = 3000)
|
||||
private void interval() {
|
||||
GlobalState newGlobalState = new GlobalState(
|
||||
executionService.runningCount(),
|
||||
downloadQ.size(),
|
||||
appStateService.getCurrentImages()
|
||||
.values()
|
||||
.stream()
|
||||
.filter(e -> e.getTotal() == 0 || e.getTotal() != e.getCurrent().get())
|
||||
.count(),
|
||||
appStateService.getCurrentImages()
|
||||
.values()
|
||||
.stream()
|
||||
.filter(e -> e.getStatus().equals(Image.Status.ERROR))
|
||||
.count());
|
||||
if (!newGlobalState.equals(currentState)) {
|
||||
currentState = newGlobalState;
|
||||
liveGlobalState.onNext(currentState);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,9 +16,10 @@ import tn.mnlr.vripper.entities.mixin.persistance.PostPersistanceMixin;
|
||||
|
||||
import javax.annotation.PreDestroy;
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Arrays;
|
||||
@@ -48,6 +49,9 @@ public class PersistenceService {
|
||||
try {
|
||||
if(dataFile.createNewFile()) {
|
||||
logger.info("Data file successfully created");
|
||||
try (FileWriter fw = new FileWriter(dataFile)) {
|
||||
fw.write("{}");
|
||||
}
|
||||
} else {
|
||||
logger.info("Data file already exists");
|
||||
}
|
||||
@@ -100,7 +104,7 @@ public class PersistenceService {
|
||||
|
||||
String jsonContent;
|
||||
try {
|
||||
jsonContent = Files.readAllLines(Paths.get(VripperApplication.dataPath), Charset.forName("UTF-8")).stream().collect(Collectors.joining());
|
||||
jsonContent = Files.readAllLines(Paths.get(VripperApplication.dataPath), StandardCharsets.UTF_8).stream().collect(Collectors.joining());
|
||||
} catch (Exception e) {
|
||||
logger.warn("data file not found, previous state cannot be restored", e);
|
||||
return;
|
||||
|
||||
@@ -29,7 +29,8 @@ public class PostParser {
|
||||
private final static String POSTS_XPATH = "//li[contains(@id,'post_')][not(contains(@id,'post_thank'))]";
|
||||
private final static String REAL_THREAD_XPATH = ".//a[@class='postcounter']";
|
||||
private final static String THREAD_TITLE_XPATH = "//li[contains(@class, 'lastnavbit')]/span";
|
||||
private final static String POST_TITLE_XPATH = ".//h2";
|
||||
private final static String POST_TITLE_XPATH = ".//h2[contains(@class, 'title')]";
|
||||
private final static String POST_COUNTER_XPATH = ".//a[contains(@class, 'postcounter')]";
|
||||
private final static String POST_LINKS_XPATH = ".//a";
|
||||
|
||||
@Autowired
|
||||
@@ -147,6 +148,20 @@ public class PostParser {
|
||||
throw new PostParseException(e);
|
||||
}
|
||||
|
||||
String postCounter;
|
||||
try {
|
||||
logger.info(String.format("Finding post's counter"));
|
||||
Node counterNode = xpathService.getAsNode(postsNodeList.item(i), POST_COUNTER_XPATH);
|
||||
if (counterNode != null) {
|
||||
postCounter = counterNode.getTextContent().trim();
|
||||
logger.info(String.format("Found post's counter: %s", postCounter));
|
||||
} else {
|
||||
postCounter = "";
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new PostParseException(e);
|
||||
}
|
||||
|
||||
ArrayList<Image> imagesList = new ArrayList<>();
|
||||
try {
|
||||
logger.info(String.format("Finding all links for post with id %s using xpath %s", postId, POST_LINKS_XPATH));
|
||||
@@ -177,7 +192,7 @@ public class PostParser {
|
||||
|
||||
if (!imagesList.isEmpty()) {
|
||||
logger.info(String.format("Found %d images for post with id %s", imagesList.size(), postId));
|
||||
posts.add(appStateService.createPost(postTitle, realUrl, imagesList, null, postId));
|
||||
posts.add(appStateService.createPost(postTitle, realUrl, imagesList, null, postId, postCounter));
|
||||
} else {
|
||||
logger.warn(String.format("No images found for post with id %s, skipping", postId));
|
||||
}
|
||||
|
||||
@@ -90,12 +90,24 @@ public class PostRestEndpoint {
|
||||
downloadQ.restart(postId.getPostId());
|
||||
}
|
||||
|
||||
@PostMapping("/post/restart/all")
|
||||
@ResponseStatus(value = HttpStatus.OK)
|
||||
public void restartPost() throws Exception {
|
||||
downloadQ.restartAll();
|
||||
}
|
||||
|
||||
@PostMapping("/post/stop")
|
||||
@ResponseStatus(value = HttpStatus.OK)
|
||||
public void stop(@RequestBody PostId postId) {
|
||||
downloadQ.stop(postId.getPostId());
|
||||
}
|
||||
|
||||
@PostMapping("/post/stop/all")
|
||||
@ResponseStatus(value = HttpStatus.OK)
|
||||
public void stopAll() {
|
||||
downloadQ.stopAll();
|
||||
}
|
||||
|
||||
@PostMapping("/post/remove")
|
||||
@ResponseStatus(value = HttpStatus.OK)
|
||||
public ResponseEntity remove(@RequestBody PostId postId) {
|
||||
|
||||
+103
-17
@@ -17,8 +17,12 @@ import tn.mnlr.vripper.entities.Post;
|
||||
import tn.mnlr.vripper.entities.mixin.ui.ImageUIMixin;
|
||||
import tn.mnlr.vripper.entities.mixin.ui.PostUIMixin;
|
||||
import tn.mnlr.vripper.services.AppStateService;
|
||||
import tn.mnlr.vripper.services.DownloadSpeed;
|
||||
import tn.mnlr.vripper.services.DownloadSpeedService;
|
||||
import tn.mnlr.vripper.services.GlobalStateService;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
@@ -30,37 +34,38 @@ public class WebSocketHandler extends TextWebSocketHandler {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(WebSocketHandler.class);
|
||||
|
||||
@Getter
|
||||
private static class WSMessage {
|
||||
|
||||
private String cmd;
|
||||
private String payload;
|
||||
|
||||
enum CMD {
|
||||
POSTS_SUB,
|
||||
POST_DETAILS_SUB,
|
||||
POSTS_UNSUB,
|
||||
POST_DETAILS_UNSUB
|
||||
}
|
||||
}
|
||||
|
||||
public WebSocketHandler() {
|
||||
om.addMixIn(Image.class, ImageUIMixin.class).addMixIn(Post.class, PostUIMixin.class);
|
||||
}
|
||||
|
||||
private Map<String, Disposable> postsSubscriptions = new ConcurrentHashMap<>();
|
||||
private Map<String, Disposable> postDetailsSubscriptions = new ConcurrentHashMap<>();
|
||||
private ObjectMapper om = new ObjectMapper();
|
||||
@Autowired
|
||||
private GlobalStateService globalStateService;
|
||||
|
||||
@Autowired
|
||||
private AppStateService appStateService;
|
||||
|
||||
@Autowired
|
||||
private DownloadSpeedService downloadSpeedService;
|
||||
|
||||
private Map<String, Disposable> postsSubscriptions = new ConcurrentHashMap<>();
|
||||
private Map<String, Disposable> postDetailsSubscriptions = new ConcurrentHashMap<>();
|
||||
private Map<String, Disposable> stateSubscriptions = new ConcurrentHashMap<>();
|
||||
private Map<String, Disposable> downloadSpeedSubscriptions = new ConcurrentHashMap<>();
|
||||
|
||||
private ObjectMapper om = new ObjectMapper();
|
||||
|
||||
@Override
|
||||
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
|
||||
|
||||
WSMessage wsMessage = om.readValue(message.getPayload(), WSMessage.class);
|
||||
WSMessage.CMD cmd = WSMessage.CMD.valueOf(wsMessage.getCmd());
|
||||
switch (cmd) {
|
||||
case GLOBAL_STATE_SUB:
|
||||
subscribeForGlobalState(session);
|
||||
break;
|
||||
case SPEED_SUB:
|
||||
subscribeForSpeed(session);
|
||||
break;
|
||||
case POSTS_SUB:
|
||||
subscribeForPosts(session);
|
||||
break;
|
||||
@@ -75,9 +80,70 @@ public class WebSocketHandler extends TextWebSocketHandler {
|
||||
logger.info(String.format("Client %s unsubscribed from posts", session.getId()));
|
||||
Optional.ofNullable(postsSubscriptions.remove(session.getId())).ifPresent(d -> d.dispose());
|
||||
break;
|
||||
case GLOBAL_STATE_UNSUB:
|
||||
logger.info(String.format("Client %s unsubscribed from global state", session.getId()));
|
||||
Optional.ofNullable(stateSubscriptions.remove(session.getId())).ifPresent(d -> d.dispose());
|
||||
break;
|
||||
case SPEED_UNSUB:
|
||||
logger.info(String.format("Client %s unsubscribed from download speed info", session.getId()));
|
||||
Optional.ofNullable(downloadSpeedSubscriptions.remove(session.getId())).ifPresent(d -> d.dispose());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void subscribeForGlobalState(WebSocketSession session) {
|
||||
|
||||
logger.info(String.format("Client %s subscribed for global state", session.getId()));
|
||||
if (stateSubscriptions.containsKey(session.getId())) {
|
||||
stateSubscriptions.get(session.getId()).dispose();
|
||||
}
|
||||
|
||||
try {
|
||||
send(session, new TextMessage(om.writeValueAsString(Arrays.asList(globalStateService.getCurrentState()))));
|
||||
} catch (Exception e) {
|
||||
logger.error("Unexpected error occurred", e);
|
||||
}
|
||||
|
||||
stateSubscriptions.put(session.getId(),
|
||||
globalStateService.getLiveGlobalState()
|
||||
.onBackpressureBuffer()
|
||||
.observeOn(Schedulers.io())
|
||||
.map(Arrays::asList)
|
||||
.filter(e -> !e.isEmpty())
|
||||
.map(e -> e.subList(0, 1))
|
||||
.map(om::writeValueAsString)
|
||||
.map(TextMessage::new)
|
||||
.subscribe(msg -> send(session, msg), e -> logger.error("Failed to send data to client", e))
|
||||
);
|
||||
}
|
||||
|
||||
private void subscribeForSpeed(WebSocketSession session) {
|
||||
|
||||
logger.info(String.format("Client %s subscribed for download speed info", session.getId()));
|
||||
if (downloadSpeedSubscriptions.containsKey(session.getId())) {
|
||||
downloadSpeedSubscriptions.get(session.getId()).dispose();
|
||||
}
|
||||
|
||||
try {
|
||||
send(session, new TextMessage(om.writeValueAsString(Arrays.asList(new DownloadSpeed(0)))));
|
||||
} catch (Exception e) {
|
||||
logger.error("Unexpected error occurred", e);
|
||||
}
|
||||
|
||||
downloadSpeedSubscriptions.put(session.getId(),
|
||||
downloadSpeedService.getReadBytesPerSecond()
|
||||
.onBackpressureBuffer()
|
||||
.observeOn(Schedulers.io())
|
||||
.map(Arrays::asList)
|
||||
.filter(e -> !e.isEmpty())
|
||||
.map(e -> e.subList(0, 1))
|
||||
.map(e -> e.stream().map(DownloadSpeed::new).collect(Collectors.toList()))
|
||||
.map(om::writeValueAsString)
|
||||
.map(TextMessage::new)
|
||||
.subscribe(msg -> send(session, msg), e -> logger.error("Failed to send data to client", e))
|
||||
);
|
||||
}
|
||||
|
||||
private void subscribeForPosts(WebSocketSession session) {
|
||||
|
||||
logger.info(String.format("Client %s subscribed for posts", session.getId()));
|
||||
@@ -151,5 +217,25 @@ public class WebSocketHandler extends TextWebSocketHandler {
|
||||
|
||||
Optional.ofNullable(postsSubscriptions.remove(session.getId())).ifPresent(d -> d.dispose());
|
||||
Optional.ofNullable(postDetailsSubscriptions.remove(session.getId())).ifPresent(d -> d.dispose());
|
||||
Optional.ofNullable(stateSubscriptions.remove(session.getId())).ifPresent(d -> d.dispose());
|
||||
Optional.ofNullable(downloadSpeedSubscriptions.remove(session.getId())).ifPresent(d -> d.dispose());
|
||||
}
|
||||
|
||||
@Getter
|
||||
private static class WSMessage {
|
||||
|
||||
private String cmd;
|
||||
private String payload;
|
||||
|
||||
enum CMD {
|
||||
POSTS_SUB,
|
||||
POST_DETAILS_SUB,
|
||||
POSTS_UNSUB,
|
||||
POST_DETAILS_UNSUB,
|
||||
GLOBAL_STATE_SUB,
|
||||
GLOBAL_STATE_UNSUB,
|
||||
SPEED_SUB,
|
||||
SPEED_UNSUB
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,4 +2,7 @@ logging.level.org.springframework.web=INFO
|
||||
logging.level.root=INFO
|
||||
logging.level.org.apache.http=INFO
|
||||
logging.file=${user.dir}/vripper.log
|
||||
server.port=${vripper.server.port:8080}
|
||||
server.port=${vripper.server.port:8080}
|
||||
management.endpoints.web.exposure.include=shutdown
|
||||
management.endpoint.shutdown.enabled=true
|
||||
endpoints.shutdown.enabled=true
|
||||
Generated
+302
-207
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "vripper-ui",
|
||||
"version": "1.4.3",
|
||||
"version": "1.4.7",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
@@ -1759,12 +1759,6 @@
|
||||
"integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==",
|
||||
"dev": true
|
||||
},
|
||||
"array-slice": {
|
||||
"version": "0.2.3",
|
||||
"resolved": "https://registry.npmjs.org/array-slice/-/array-slice-0.2.3.tgz",
|
||||
"integrity": "sha1-3Tz7gO15c6dRF82sabC5nshhhvU=",
|
||||
"dev": true
|
||||
},
|
||||
"array-union": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz",
|
||||
@@ -2226,31 +2220,59 @@
|
||||
"dev": true
|
||||
},
|
||||
"body-parser": {
|
||||
"version": "1.18.3",
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz",
|
||||
"integrity": "sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ=",
|
||||
"version": "1.19.0",
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz",
|
||||
"integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"bytes": "3.0.0",
|
||||
"bytes": "3.1.0",
|
||||
"content-type": "~1.0.4",
|
||||
"debug": "2.6.9",
|
||||
"depd": "~1.1.2",
|
||||
"http-errors": "~1.6.3",
|
||||
"iconv-lite": "0.4.23",
|
||||
"http-errors": "1.7.2",
|
||||
"iconv-lite": "0.4.24",
|
||||
"on-finished": "~2.3.0",
|
||||
"qs": "6.5.2",
|
||||
"raw-body": "2.3.3",
|
||||
"type-is": "~1.6.16"
|
||||
"qs": "6.7.0",
|
||||
"raw-body": "2.4.0",
|
||||
"type-is": "~1.6.17"
|
||||
},
|
||||
"dependencies": {
|
||||
"iconv-lite": {
|
||||
"version": "0.4.23",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz",
|
||||
"integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==",
|
||||
"bytes": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz",
|
||||
"integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==",
|
||||
"dev": true
|
||||
},
|
||||
"http-errors": {
|
||||
"version": "1.7.2",
|
||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz",
|
||||
"integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"safer-buffer": ">= 2.1.2 < 3"
|
||||
"depd": "~1.1.2",
|
||||
"inherits": "2.0.3",
|
||||
"setprototypeof": "1.1.1",
|
||||
"statuses": ">= 1.5.0 < 2",
|
||||
"toidentifier": "1.0.0"
|
||||
}
|
||||
},
|
||||
"qs": {
|
||||
"version": "6.7.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
|
||||
"integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==",
|
||||
"dev": true
|
||||
},
|
||||
"setprototypeof": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz",
|
||||
"integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==",
|
||||
"dev": true
|
||||
},
|
||||
"statuses": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
|
||||
"integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -2637,12 +2659,6 @@
|
||||
"integrity": "sha512-oC7/DVAyfcY3UWKm0sN/oVoDedQDQiw/vIiAnuTWTpE5s0zWf7l3WY417Xw/Fbi/QbAjctAkxgMiS9P0s3zkmA==",
|
||||
"dev": true
|
||||
},
|
||||
"circular-json": {
|
||||
"version": "0.5.9",
|
||||
"resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.5.9.tgz",
|
||||
"integrity": "sha512-4ivwqHpIFJZBuhN3g/pEcdbnGUywkBblloGbkglyloVjjR3uT6tieI89MVOfbP2tHX5sgb01FuLgAOzebNlJNQ==",
|
||||
"dev": true
|
||||
},
|
||||
"class-utils": {
|
||||
"version": "0.3.6",
|
||||
"resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz",
|
||||
@@ -2798,15 +2814,6 @@
|
||||
"integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=",
|
||||
"dev": true
|
||||
},
|
||||
"combine-lists": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/combine-lists/-/combine-lists-1.0.1.tgz",
|
||||
"integrity": "sha1-RYwH4J4NkA/Ci3Cj/sLazR0st/Y=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"lodash": "^4.5.0"
|
||||
}
|
||||
},
|
||||
"combined-stream": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz",
|
||||
@@ -2903,36 +2910,21 @@
|
||||
}
|
||||
},
|
||||
"connect": {
|
||||
"version": "3.6.6",
|
||||
"resolved": "https://registry.npmjs.org/connect/-/connect-3.6.6.tgz",
|
||||
"integrity": "sha1-Ce/2xVr3I24TcTWnJXSFi2eG9SQ=",
|
||||
"version": "3.7.0",
|
||||
"resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz",
|
||||
"integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"debug": "2.6.9",
|
||||
"finalhandler": "1.1.0",
|
||||
"parseurl": "~1.3.2",
|
||||
"finalhandler": "1.1.2",
|
||||
"parseurl": "~1.3.3",
|
||||
"utils-merge": "1.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"finalhandler": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.0.tgz",
|
||||
"integrity": "sha1-zgtoVbRYU+eRsvzGgARtiCU91/U=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"debug": "2.6.9",
|
||||
"encodeurl": "~1.0.1",
|
||||
"escape-html": "~1.0.3",
|
||||
"on-finished": "~2.3.0",
|
||||
"parseurl": "~1.3.2",
|
||||
"statuses": "~1.3.1",
|
||||
"unpipe": "~1.0.0"
|
||||
}
|
||||
},
|
||||
"statuses": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz",
|
||||
"integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=",
|
||||
"parseurl": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
|
||||
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
@@ -3202,9 +3194,9 @@
|
||||
}
|
||||
},
|
||||
"date-format": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/date-format/-/date-format-1.2.0.tgz",
|
||||
"integrity": "sha1-YV6CjiM90aubua4JUODOzPpuytg=",
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/date-format/-/date-format-2.1.0.tgz",
|
||||
"integrity": "sha512-bYQuGLeFxhkxNOF3rcMtiZxvCBAquGzZm6oWA1oZ0g2THUzivaRhv8uOhdr19LmoobSOLoIAxeUK2RdbM8IFTA==",
|
||||
"dev": true
|
||||
},
|
||||
"date-now": {
|
||||
@@ -3879,56 +3871,6 @@
|
||||
"integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=",
|
||||
"dev": true
|
||||
},
|
||||
"expand-braces": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/expand-braces/-/expand-braces-0.1.2.tgz",
|
||||
"integrity": "sha1-SIsdHSRRyz06axks/AMPRMWFX+o=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"array-slice": "^0.2.3",
|
||||
"array-unique": "^0.2.1",
|
||||
"braces": "^0.1.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"array-unique": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz",
|
||||
"integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=",
|
||||
"dev": true
|
||||
},
|
||||
"braces": {
|
||||
"version": "0.1.5",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-0.1.5.tgz",
|
||||
"integrity": "sha1-wIVxEIUpHYt1/ddOqw+FlygHEeY=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"expand-range": "^0.1.0"
|
||||
}
|
||||
},
|
||||
"expand-range": {
|
||||
"version": "0.1.1",
|
||||
"resolved": "https://registry.npmjs.org/expand-range/-/expand-range-0.1.1.tgz",
|
||||
"integrity": "sha1-TLjtoJk8pW+k9B/ELzy7TMrf8EQ=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-number": "^0.1.1",
|
||||
"repeat-string": "^0.2.2"
|
||||
}
|
||||
},
|
||||
"is-number": {
|
||||
"version": "0.1.1",
|
||||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-0.1.1.tgz",
|
||||
"integrity": "sha1-aaevEWlj1HIG7JvZtIoUIW8eOAY=",
|
||||
"dev": true
|
||||
},
|
||||
"repeat-string": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-0.2.2.tgz",
|
||||
"integrity": "sha1-x6jTI2BoNiBZp+RlH8aITosftK4=",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"expand-brackets": {
|
||||
"version": "2.1.4",
|
||||
"resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz",
|
||||
@@ -4401,9 +4343,9 @@
|
||||
}
|
||||
},
|
||||
"flatted": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.0.tgz",
|
||||
"integrity": "sha512-R+H8IZclI8AAkSBRQJLVOsxwAoHd6WC40b4QTNWIjzAa6BXOBfQcM587MXDTVPeYaopFNWHUFLx7eNmHDSxMWg==",
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.1.tgz",
|
||||
"integrity": "sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg==",
|
||||
"dev": true
|
||||
},
|
||||
"flush-write-stream": {
|
||||
@@ -6075,28 +6017,27 @@
|
||||
}
|
||||
},
|
||||
"karma": {
|
||||
"version": "3.1.4",
|
||||
"resolved": "https://registry.npmjs.org/karma/-/karma-3.1.4.tgz",
|
||||
"integrity": "sha512-31Vo8Qr5glN+dZEVIpnPCxEGleqE0EY6CtC2X9TagRV3rRQ3SNrvfhddICkJgUK3AgqpeKSZau03QumTGhGoSw==",
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/karma/-/karma-4.2.0.tgz",
|
||||
"integrity": "sha512-fmCuxN1rwJxTdZfOXK5LjlmS4Ana/OvzNMpkyLL/TLE8hmgSkpVpMYQ7RTVa8TNKRVQDZNl5W1oF5cfKfgIMlA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"bluebird": "^3.3.0",
|
||||
"body-parser": "^1.16.1",
|
||||
"chokidar": "^2.0.3",
|
||||
"braces": "^3.0.2",
|
||||
"chokidar": "^3.0.0",
|
||||
"colors": "^1.1.0",
|
||||
"combine-lists": "^1.0.0",
|
||||
"connect": "^3.6.0",
|
||||
"core-js": "^2.2.0",
|
||||
"core-js": "^3.1.3",
|
||||
"di": "^0.0.1",
|
||||
"dom-serialize": "^2.2.0",
|
||||
"expand-braces": "^0.1.1",
|
||||
"flatted": "^2.0.0",
|
||||
"glob": "^7.1.1",
|
||||
"graceful-fs": "^4.1.2",
|
||||
"http-proxy": "^1.13.0",
|
||||
"isbinaryfile": "^3.0.0",
|
||||
"lodash": "^4.17.5",
|
||||
"log4js": "^3.0.0",
|
||||
"lodash": "^4.17.11",
|
||||
"log4js": "^4.0.0",
|
||||
"mime": "^2.3.1",
|
||||
"minimatch": "^3.0.2",
|
||||
"optimist": "^0.6.1",
|
||||
@@ -6110,17 +6051,137 @@
|
||||
"useragent": "2.3.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"mime": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-2.4.0.tgz",
|
||||
"integrity": "sha512-ikBcWwyqXQSHKtciCcctu9YfPbFYZ4+gbHEmE0Q8jzcTYQg5dHCr3g2wwAZjPoJfQVXZq6KXAjpXOTf5/cjT7w==",
|
||||
"anymatch": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.0.3.tgz",
|
||||
"integrity": "sha512-c6IvoeBECQlMVuYUjSwimnhmztImpErfxJzWZhIQinIvQWoGOnB0dLIgifbPHQt5heS6mNlaZG16f06H3C8t1g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"normalize-path": "^3.0.0",
|
||||
"picomatch": "^2.0.4"
|
||||
}
|
||||
},
|
||||
"binary-extensions": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz",
|
||||
"integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==",
|
||||
"dev": true
|
||||
},
|
||||
"braces": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
|
||||
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fill-range": "^7.0.1"
|
||||
}
|
||||
},
|
||||
"chokidar": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.0.2.tgz",
|
||||
"integrity": "sha512-c4PR2egjNjI1um6bamCQ6bUNPDiyofNQruHvKgHQ4gDUP/ITSVSzNsiI5OWtHOsX323i5ha/kk4YmOZ1Ktg7KA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"anymatch": "^3.0.1",
|
||||
"braces": "^3.0.2",
|
||||
"fsevents": "^2.0.6",
|
||||
"glob-parent": "^5.0.0",
|
||||
"is-binary-path": "^2.1.0",
|
||||
"is-glob": "^4.0.1",
|
||||
"normalize-path": "^3.0.0",
|
||||
"readdirp": "^3.1.1"
|
||||
}
|
||||
},
|
||||
"core-js": {
|
||||
"version": "3.1.4",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.1.4.tgz",
|
||||
"integrity": "sha512-YNZN8lt82XIMLnLirj9MhKDFZHalwzzrL9YLt6eb0T5D0EDl4IQ90IGkua8mHbnxNrkj1d8hbdizMc0Qmg1WnQ==",
|
||||
"dev": true
|
||||
},
|
||||
"fill-range": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
|
||||
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"to-regex-range": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"fsevents": {
|
||||
"version": "2.0.7",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.0.7.tgz",
|
||||
"integrity": "sha512-a7YT0SV3RB+DjYcppwVDLtn13UQnmg0SWZS7ezZD0UjnLwXmy8Zm21GMVGLaFGimIqcvyMQaOJBrop8MyOp1kQ==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"glob-parent": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.0.0.tgz",
|
||||
"integrity": "sha512-Z2RwiujPRGluePM6j699ktJYxmPpJKCfpGA13jz2hmFZC7gKetzrWvg5KN3+OsIFmydGyZ1AVwERCq1w/ZZwRg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-glob": "^4.0.1"
|
||||
}
|
||||
},
|
||||
"is-binary-path": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
|
||||
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"binary-extensions": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"is-glob": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz",
|
||||
"integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-extglob": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"is-number": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
||||
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
||||
"dev": true
|
||||
},
|
||||
"mime": {
|
||||
"version": "2.4.4",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz",
|
||||
"integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==",
|
||||
"dev": true
|
||||
},
|
||||
"normalize-path": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
||||
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
|
||||
"dev": true
|
||||
},
|
||||
"readdirp": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.1.1.tgz",
|
||||
"integrity": "sha512-XXdSXZrQuvqoETj50+JAitxz1UPdt5dupjT6T5nVB+WvjMv2XKYj+s7hPeAVCXvmJrL36O4YYyWlIC3an2ePiQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"picomatch": "^2.0.4"
|
||||
}
|
||||
},
|
||||
"source-map": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||
"dev": true
|
||||
},
|
||||
"to-regex-range": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-number": "^7.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -6294,9 +6355,9 @@
|
||||
}
|
||||
},
|
||||
"lodash": {
|
||||
"version": "4.17.11",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
|
||||
"integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==",
|
||||
"version": "4.17.15",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
|
||||
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
|
||||
"dev": true
|
||||
},
|
||||
"lodash.clonedeep": {
|
||||
@@ -6318,31 +6379,31 @@
|
||||
"dev": true
|
||||
},
|
||||
"log4js": {
|
||||
"version": "3.0.6",
|
||||
"resolved": "https://registry.npmjs.org/log4js/-/log4js-3.0.6.tgz",
|
||||
"integrity": "sha512-ezXZk6oPJCWL483zj64pNkMuY/NcRX5MPiB0zE6tjZM137aeusrOnW1ecxgF9cmwMWkBMhjteQxBPoZBh9FDxQ==",
|
||||
"version": "4.5.1",
|
||||
"resolved": "https://registry.npmjs.org/log4js/-/log4js-4.5.1.tgz",
|
||||
"integrity": "sha512-EEEgFcE9bLgaYUKuozyFfytQM2wDHtXn4tAN41pkaxpNjAykv11GVdeI4tHtmPWW4Xrgh9R/2d7XYghDVjbKKw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"circular-json": "^0.5.5",
|
||||
"date-format": "^1.2.0",
|
||||
"debug": "^3.1.0",
|
||||
"rfdc": "^1.1.2",
|
||||
"streamroller": "0.7.0"
|
||||
"date-format": "^2.0.0",
|
||||
"debug": "^4.1.1",
|
||||
"flatted": "^2.0.0",
|
||||
"rfdc": "^1.1.4",
|
||||
"streamroller": "^1.0.6"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "3.2.6",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
|
||||
"integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
|
||||
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ms": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
|
||||
"integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==",
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
@@ -6753,9 +6814,9 @@
|
||||
}
|
||||
},
|
||||
"mixin-deep": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz",
|
||||
"integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==",
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz",
|
||||
"integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"for-in": "^1.0.2",
|
||||
@@ -7782,6 +7843,12 @@
|
||||
"integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=",
|
||||
"dev": true
|
||||
},
|
||||
"picomatch": {
|
||||
"version": "2.0.7",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.0.7.tgz",
|
||||
"integrity": "sha512-oLHIdio3tZ0qH76NybpeneBhYVj0QFTfXEFTc/B3zKQspYfYYkWYgFsmzo+4kvId/bQRcNkVeguI3y+CD22BtA==",
|
||||
"dev": true
|
||||
},
|
||||
"pify": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
|
||||
@@ -8264,25 +8331,47 @@
|
||||
"dev": true
|
||||
},
|
||||
"raw-body": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz",
|
||||
"integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==",
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz",
|
||||
"integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"bytes": "3.0.0",
|
||||
"http-errors": "1.6.3",
|
||||
"iconv-lite": "0.4.23",
|
||||
"bytes": "3.1.0",
|
||||
"http-errors": "1.7.2",
|
||||
"iconv-lite": "0.4.24",
|
||||
"unpipe": "1.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"iconv-lite": {
|
||||
"version": "0.4.23",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz",
|
||||
"integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==",
|
||||
"bytes": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz",
|
||||
"integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==",
|
||||
"dev": true
|
||||
},
|
||||
"http-errors": {
|
||||
"version": "1.7.2",
|
||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz",
|
||||
"integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"safer-buffer": ">= 2.1.2 < 3"
|
||||
"depd": "~1.1.2",
|
||||
"inherits": "2.0.3",
|
||||
"setprototypeof": "1.1.1",
|
||||
"statuses": ">= 1.5.0 < 2",
|
||||
"toidentifier": "1.0.0"
|
||||
}
|
||||
},
|
||||
"setprototypeof": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz",
|
||||
"integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==",
|
||||
"dev": true
|
||||
},
|
||||
"statuses": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
|
||||
"integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -8633,9 +8722,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"rfdc": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.1.2.tgz",
|
||||
"integrity": "sha512-92ktAgvZhBzYTIK0Mja9uen5q5J3NRVMoDkJL2VMwq6SXjVCgqvQeVP2XAaUY6HT+XpQYeLSjb3UoitBryKmdA==",
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.1.4.tgz",
|
||||
"integrity": "sha512-5C9HXdzK8EAqN7JDif30jqsBzavB7wLpaubisuQIGHWf2gUXSpzy6ArX/+Da8RjFpagWsCn+pIgxTMAmKw9Zug==",
|
||||
"dev": true
|
||||
},
|
||||
"rimraf": {
|
||||
@@ -8950,9 +9039,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"set-value": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz",
|
||||
"integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==",
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz",
|
||||
"integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"extend-shallow": "^2.0.1",
|
||||
@@ -9652,15 +9741,16 @@
|
||||
"dev": true
|
||||
},
|
||||
"streamroller": {
|
||||
"version": "0.7.0",
|
||||
"resolved": "https://registry.npmjs.org/streamroller/-/streamroller-0.7.0.tgz",
|
||||
"integrity": "sha512-WREzfy0r0zUqp3lGO096wRuUp7ho1X6uo/7DJfTlEi0Iv/4gT7YHqXDjKC2ioVGBZtE8QzsQD9nx1nIuoZ57jQ==",
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/streamroller/-/streamroller-1.0.6.tgz",
|
||||
"integrity": "sha512-3QC47Mhv3/aZNFpDDVO44qQb9gwB9QggMEE0sQmkTAwBVYdBRWISdsywlkfm5II1Q5y/pmrHflti/IgmIzdDBg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"date-format": "^1.2.0",
|
||||
"debug": "^3.1.0",
|
||||
"mkdirp": "^0.5.1",
|
||||
"readable-stream": "^2.3.0"
|
||||
"async": "^2.6.2",
|
||||
"date-format": "^2.0.0",
|
||||
"debug": "^3.2.6",
|
||||
"fs-extra": "^7.0.1",
|
||||
"lodash": "^4.17.14"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
@@ -9672,10 +9762,21 @@
|
||||
"ms": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"fs-extra": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz",
|
||||
"integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"graceful-fs": "^4.1.2",
|
||||
"jsonfile": "^4.0.0",
|
||||
"universalify": "^0.1.0"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
|
||||
"integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==",
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
@@ -10311,13 +10412,30 @@
|
||||
"dev": true
|
||||
},
|
||||
"type-is": {
|
||||
"version": "1.6.16",
|
||||
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz",
|
||||
"integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==",
|
||||
"version": "1.6.18",
|
||||
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
|
||||
"integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"media-typer": "0.3.0",
|
||||
"mime-types": "~2.1.18"
|
||||
"mime-types": "~2.1.24"
|
||||
},
|
||||
"dependencies": {
|
||||
"mime-db": {
|
||||
"version": "1.40.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz",
|
||||
"integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==",
|
||||
"dev": true
|
||||
},
|
||||
"mime-types": {
|
||||
"version": "2.1.24",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz",
|
||||
"integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"mime-db": "1.40.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"typedarray": {
|
||||
@@ -10359,38 +10477,15 @@
|
||||
"dev": true
|
||||
},
|
||||
"union-value": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz",
|
||||
"integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=",
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz",
|
||||
"integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"arr-union": "^3.1.0",
|
||||
"get-value": "^2.0.6",
|
||||
"is-extendable": "^0.1.1",
|
||||
"set-value": "^0.4.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"extend-shallow": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
|
||||
"integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-extendable": "^0.1.0"
|
||||
}
|
||||
},
|
||||
"set-value": {
|
||||
"version": "0.4.3",
|
||||
"resolved": "https://registry.npmjs.org/set-value/-/set-value-0.4.3.tgz",
|
||||
"integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"extend-shallow": "^2.0.1",
|
||||
"is-extendable": "^0.1.1",
|
||||
"is-plain-object": "^2.0.1",
|
||||
"to-object-path": "^0.3.0"
|
||||
}
|
||||
}
|
||||
"set-value": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"unique-filename": {
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "vripper-ui",
|
||||
"version": "1.4.3",
|
||||
"version": "1.4.7",
|
||||
"scripts": {
|
||||
"ng": "ng",
|
||||
"start": "ng serve",
|
||||
"build": "ng build",
|
||||
"build-electron": "ng build --base-href ./",
|
||||
"prod": "ng build --prod",
|
||||
"build-electron": "ng build --prod --base-href ./",
|
||||
"build-web": "ng build --prod",
|
||||
"test": "ng test",
|
||||
"lint": "ng lint",
|
||||
"e2e": "ng e2e"
|
||||
@@ -45,7 +45,7 @@
|
||||
"electron": "^5.0.5",
|
||||
"jasmine-core": "~2.99.1",
|
||||
"jasmine-spec-reporter": "~4.2.1",
|
||||
"karma": "~3.1.1",
|
||||
"karma": "^4.2.0",
|
||||
"karma-chrome-launcher": "~2.2.0",
|
||||
"karma-coverage-istanbul-reporter": "~2.0.1",
|
||||
"karma-jasmine": "~1.1.2",
|
||||
|
||||
+96
-31
@@ -5,46 +5,111 @@
|
||||
<parent>
|
||||
<groupId>tn.mnlr</groupId>
|
||||
<artifactId>vripper</artifactId>
|
||||
<version>1.4.3</version>
|
||||
<version>1.4.7</version>
|
||||
</parent>
|
||||
<artifactId>vripper-ui</artifactId>
|
||||
<name>vripper-ui</name>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>com.github.eirslett</groupId>
|
||||
<artifactId>frontend-maven-plugin</artifactId>
|
||||
<version>1.3</version>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<configuration>
|
||||
<nodeVersion>v12.2.0</nodeVersion>
|
||||
<npmVersion>6.9.0</npmVersion>
|
||||
<workingDirectory>build-dir</workingDirectory>
|
||||
<classifier>${buildClassifier}</classifier>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>install node and npm</id>
|
||||
<goals>
|
||||
<goal>install-node-and-npm</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>npm install</id>
|
||||
<goals>
|
||||
<goal>npm</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>npm run build</id>
|
||||
<goals>
|
||||
<goal>npm</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<arguments>run prod</arguments>
|
||||
</configuration>
|
||||
<phase>generate-resources</phase>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>web</id>
|
||||
<activation>
|
||||
<activeByDefault>true</activeByDefault>
|
||||
</activation>
|
||||
<properties>
|
||||
<buildClassifier>web</buildClassifier>
|
||||
</properties>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>com.github.eirslett</groupId>
|
||||
<artifactId>frontend-maven-plugin</artifactId>
|
||||
<version>1.3</version>
|
||||
<configuration>
|
||||
<nodeVersion>v12.2.0</nodeVersion>
|
||||
<npmVersion>6.9.0</npmVersion>
|
||||
<workingDirectory>build-dir</workingDirectory>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>install node and npm</id>
|
||||
<goals>
|
||||
<goal>install-node-and-npm</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>npm install</id>
|
||||
<goals>
|
||||
<goal>npm</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>npm run build</id>
|
||||
<goals>
|
||||
<goal>npm</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<arguments>run build-web</arguments>
|
||||
</configuration>
|
||||
<phase>generate-resources</phase>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
<profile>
|
||||
<id>electron</id>
|
||||
<properties>
|
||||
<buildClassifier>electron</buildClassifier>
|
||||
</properties>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>com.github.eirslett</groupId>
|
||||
<artifactId>frontend-maven-plugin</artifactId>
|
||||
<version>1.3</version>
|
||||
<configuration>
|
||||
<nodeVersion>v12.2.0</nodeVersion>
|
||||
<npmVersion>6.9.0</npmVersion>
|
||||
<workingDirectory>build-dir</workingDirectory>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>install node and npm</id>
|
||||
<goals>
|
||||
<goal>install-node-and-npm</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>npm install</id>
|
||||
<goals>
|
||||
<goal>npm</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>npm run build</id>
|
||||
<goals>
|
||||
<goal>npm</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<arguments>run build-electron</arguments>
|
||||
</configuration>
|
||||
<phase>generate-resources</phase>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
</profiles>
|
||||
</project>
|
||||
|
||||
@@ -12,6 +12,7 @@ import { ServerService } from './server-service';
|
||||
export class ClipboardService {
|
||||
private links$: Subject<string> = new Subject();
|
||||
private interval: NodeJS.Timer;
|
||||
private lastText = '';
|
||||
constructor(
|
||||
private electronService: ElectronService,
|
||||
private serverService: ServerService,
|
||||
@@ -47,12 +48,11 @@ export class ClipboardService {
|
||||
}
|
||||
|
||||
const clipboard: Clipboard = this.electronService.clipboard;
|
||||
let lastText = '';
|
||||
this.interval = setInterval(() => {
|
||||
const text = clipboard.readText();
|
||||
|
||||
if (this.textHasDiff(text, lastText)) {
|
||||
lastText = text;
|
||||
if (this.textHasDiff(text, this.lastText)) {
|
||||
this.lastText = text;
|
||||
if (text.indexOf('https://vipergirls.to/threads') !== -1) {
|
||||
this.links$.next(text);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
export enum CMD {
|
||||
POSTS_SUB = 'POSTS_SUB',
|
||||
POST_DETAILS_SUB = 'POST_DETAILS_SUB',
|
||||
GLOBAL_STATE_SUB = 'GLOBAL_STATE_SUB',
|
||||
SPEED_SUB = 'SPEED_SUB',
|
||||
POSTS_UNSUB = 'POSTS_UNSUB',
|
||||
POST_DETAILS_UNSUB = 'POST_DETAILS_UNSUB'
|
||||
POST_DETAILS_UNSUB = 'POST_DETAILS_UNSUB',
|
||||
GLOBAL_STATE_UNSUB = 'GLOBAL_STATE_UNSUB',
|
||||
SPEED_UNSUB = 'SPEED_UNSUB'
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
export class DownloadSpeed {
|
||||
constructor(public speed: string) {}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export class GlobalState {
|
||||
constructor(public running: number, public queued: number, public remaining: number, public error: number) {}
|
||||
}
|
||||
@@ -19,18 +19,28 @@
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div fxLayout="row" fxLayoutAlign="end" fxLayoutGap="10px">
|
||||
<button (click)="clear()" aria-label="Clear completed" color="primary" mat-stroked-button title="Clear completed">
|
||||
Clear completed
|
||||
<div fxLayout="row" fxLayoutAlign="end" fxLayoutGap="10px" id="controls">
|
||||
<button (click)="restartAll()" aria-label="Start" color="primary" mat-mini-fab title="Start">
|
||||
<mat-icon>play_arrow</mat-icon>
|
||||
</button>
|
||||
<button (click)="stopAll()" aria-label="Stop" color="primary" mat-mini-fab title="Stop">
|
||||
<mat-icon>stop</mat-icon>
|
||||
</button>
|
||||
<div fxFlex="grow"></div>
|
||||
<button (click)="clear()" aria-label="Clear completed" color="primary" mat-mini-fab title="Clear completed">
|
||||
<mat-icon>clear_all</mat-icon>
|
||||
</button>
|
||||
<button (click)="remove()" aria-label="Remove All" color="primary" mat-stroked-button title="Remove All">
|
||||
Remove All
|
||||
<button (click)="remove()" aria-label="Remove All" color="primary" mat-mini-fab title="Remove All">
|
||||
<mat-icon>delete_forever</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
<div fxLayout="column" fxFlex="grow">
|
||||
<app-posts [ngStyle]="{'height': electronService.isElectronApp ? 'calc(100% - 27px)' : '100%'}" fxFlex="nogrow"
|
||||
style="width: 100%; overflow-x: hidden"></app-posts>
|
||||
<mat-divider></mat-divider>
|
||||
<div [fxFlex]="electronService.isElectronApp ? '0 0 calc(100% - 190px)' : '0 0 calc(100% - 163px)'">
|
||||
<app-posts [ngStyle]="{'height': electronService.isElectronApp ? 'calc(100% - 37px)' : '100%'}"
|
||||
style="width: 100%;"></app-posts>
|
||||
</div>
|
||||
<div style="text-align: end">{{ downloadSpeed!.speed + '/s' }} | Downloading: {{ globalState!.running }} | Queued: {{
|
||||
globalState!.queued }} | Remaining:
|
||||
{{ globalState!.remaining }} | Error: {{ globalState!.error }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
div#controls {
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
@@ -1,37 +1,107 @@
|
||||
import { GlobalState } from './../common/global-state.model';
|
||||
import { ElectronService } from 'ngx-electron';
|
||||
import { ClipboardService } from './../clipboard.service';
|
||||
import { ParseResponse } from './../common/parse-response.model';
|
||||
import { Component, OnInit, ViewChild, Inject } from '@angular/core';
|
||||
import { Component, OnInit, ViewChild, Inject, NgZone, OnDestroy } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { finalize, filter, flatMap } from 'rxjs/operators';
|
||||
import { MatSnackBar, MatDialogRef, MAT_DIALOG_DATA, MatDialog } from '@angular/material';
|
||||
import { MatSnackBar, MatDialog } from '@angular/material';
|
||||
import { NgForm } from '@angular/forms';
|
||||
import { ServerService } from '../server-service';
|
||||
import { RemoveAllResponse } from '../common/remove-all-response.model';
|
||||
import { PostsComponent } from '../posts/posts.component';
|
||||
import { ConfirmDialogComponent } from '../common/confirmation-component/confirmation-dialog';
|
||||
import { WsConnectionService } from '../ws-connection.service';
|
||||
import { WsHandler } from '../ws-handler';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { WSMessage } from '../common/ws-message.model';
|
||||
import { CMD } from '../common/cmd.enum';
|
||||
import { DownloadSpeed } from '../common/download-speed.model';
|
||||
|
||||
@Component({
|
||||
selector: 'app-home',
|
||||
templateUrl: './home.component.html',
|
||||
styleUrls: ['./home.component.scss']
|
||||
})
|
||||
export class HomeComponent implements OnInit {
|
||||
export class HomeComponent implements OnInit, OnDestroy {
|
||||
@ViewChild(PostsComponent)
|
||||
private postsComponent: PostsComponent;
|
||||
loading = false;
|
||||
input: string;
|
||||
|
||||
constructor(
|
||||
private httpClient: HttpClient,
|
||||
private _snackBar: MatSnackBar,
|
||||
private serverService: ServerService,
|
||||
private clipboardService: ClipboardService,
|
||||
public dialog: MatDialog,
|
||||
public electronService: ElectronService
|
||||
public electronService: ElectronService,
|
||||
private ngZone: NgZone,
|
||||
private wsConnectionService: WsConnectionService,
|
||||
) {
|
||||
this.websocketHandlerPromise = this.wsConnectionService.getConnection();
|
||||
}
|
||||
|
||||
websocketHandlerPromise: Promise<WsHandler>;
|
||||
subscriptions: Subscription[] = [];
|
||||
globalState: GlobalState = new GlobalState(0, 0, 0, 0);
|
||||
downloadSpeed: DownloadSpeed = new DownloadSpeed('0 B');
|
||||
|
||||
ngOnInit() {
|
||||
this.clipboardService.links.subscribe(e => {
|
||||
this.ngZone.run(() => {
|
||||
this.loading = true;
|
||||
this.httpClient
|
||||
.post<ParseResponse>(this.serverService.baseUrl + '/clipboard/post', { url: e })
|
||||
.pipe(
|
||||
finalize(() => {
|
||||
this.loading = false;
|
||||
})
|
||||
)
|
||||
.subscribe(
|
||||
data => {
|
||||
this._snackBar.open(`${data.parsed} posts parsed`, null, { duration: 5000 });
|
||||
},
|
||||
error => {
|
||||
this._snackBar.open(error.error, null, {
|
||||
duration: 5000
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
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 = e[0];
|
||||
});
|
||||
})
|
||||
);
|
||||
this.subscriptions.push(
|
||||
handler.subscribeForSpeed((e: DownloadSpeed[]) => {
|
||||
this.ngZone.run(() => {
|
||||
this.downloadSpeed = e[0];
|
||||
});
|
||||
})
|
||||
);
|
||||
handler.send(new WSMessage(CMD.GLOBAL_STATE_SUB.toString()));
|
||||
handler.send(new WSMessage(CMD.SPEED_SUB.toString()));
|
||||
});
|
||||
}
|
||||
|
||||
submit(form: NgForm) {
|
||||
this.ngZone.run(() => {
|
||||
this.loading = true;
|
||||
this.httpClient
|
||||
.post<ParseResponse>(this.serverService.baseUrl + '/clipboard/post', { url: e })
|
||||
.post<ParseResponse>(this.serverService.baseUrl + '/post', { url: this.input })
|
||||
.pipe(
|
||||
finalize(() => {
|
||||
this.loading = false;
|
||||
this.input = null;
|
||||
form.resetForm();
|
||||
})
|
||||
)
|
||||
.subscribe(
|
||||
@@ -47,28 +117,12 @@ export class HomeComponent implements OnInit {
|
||||
});
|
||||
}
|
||||
|
||||
@ViewChild(PostsComponent)
|
||||
private postsComponent: PostsComponent;
|
||||
|
||||
loading = false;
|
||||
input: string;
|
||||
|
||||
ngOnInit() {}
|
||||
|
||||
submit(form: NgForm) {
|
||||
this.loading = true;
|
||||
this.httpClient
|
||||
.post<ParseResponse>(this.serverService.baseUrl + '/post', { url: this.input })
|
||||
.pipe(
|
||||
finalize(() => {
|
||||
this.loading = false;
|
||||
this.input = null;
|
||||
form.resetForm();
|
||||
})
|
||||
)
|
||||
.subscribe(
|
||||
clear() {
|
||||
this.ngZone.run(() => {
|
||||
this.httpClient.post<RemoveAllResponse>(this.serverService.baseUrl + '/post/clear/all', {}).subscribe(
|
||||
data => {
|
||||
this._snackBar.open(`${data.parsed} posts parsed`, null, { duration: 5000 });
|
||||
this.postsComponent.removeRows(data.postIds);
|
||||
this._snackBar.open(`${data.removed} items cleared`, null, { duration: 5000 });
|
||||
},
|
||||
error => {
|
||||
this._snackBar.open(error.error, null, {
|
||||
@@ -76,47 +130,74 @@ export class HomeComponent implements OnInit {
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.httpClient.post<RemoveAllResponse>(this.serverService.baseUrl + '/post/clear/all', {}).subscribe(
|
||||
data => {
|
||||
this.postsComponent.removeRows(data.postIds);
|
||||
this._snackBar.open(`${data.removed} items cleared`, null, { duration: 5000 });
|
||||
},
|
||||
error => {
|
||||
this._snackBar.open(error.error, null, {
|
||||
duration: 5000
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
remove() {
|
||||
this.ngZone.run(() => {
|
||||
this.dialog
|
||||
.open(ConfirmDialogComponent, {
|
||||
maxHeight: '100vh',
|
||||
maxWidth: '100vw',
|
||||
height: '200px',
|
||||
width: '60%',
|
||||
data: { header: 'Confirmation', content: 'Are you sure you want to remove all items ?' }
|
||||
})
|
||||
.afterClosed()
|
||||
.pipe(
|
||||
filter(e => e === 'yes'),
|
||||
flatMap(e => this.httpClient.post<RemoveAllResponse>(this.serverService.baseUrl + '/post/remove/all', {}))
|
||||
)
|
||||
.subscribe(
|
||||
data => {
|
||||
this.postsComponent.removeRows(data.postIds);
|
||||
this._snackBar.open(`${data.removed} items removed`, null, { duration: 5000 });
|
||||
},
|
||||
error => {
|
||||
this._snackBar.open(error.error, null, {
|
||||
duration: 5000
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
this.dialog
|
||||
.open(ConfirmDialogComponent, {
|
||||
maxHeight: '100vh',
|
||||
maxWidth: '100vw',
|
||||
height: '200px',
|
||||
width: '60%',
|
||||
data: {header: 'Confirmation', content: 'Are you sure you want to remove all items ?'}
|
||||
})
|
||||
.afterClosed()
|
||||
.pipe(
|
||||
filter(e => e === 'yes'),
|
||||
flatMap(e => this.httpClient.post<RemoveAllResponse>(this.serverService.baseUrl + '/post/remove/all', {}))
|
||||
)
|
||||
.subscribe(
|
||||
data => {
|
||||
this.postsComponent.removeRows(data.postIds);
|
||||
this._snackBar.open(`${data.removed} items removed`, null, { duration: 5000 });
|
||||
},
|
||||
error => {
|
||||
this._snackBar.open(error.error, null, {
|
||||
duration: 5000
|
||||
});
|
||||
}
|
||||
);
|
||||
stopAll() {
|
||||
this.ngZone.run(() => {
|
||||
this.httpClient.post(this.serverService.baseUrl + '/post/stop/all', {})
|
||||
.subscribe(
|
||||
() => {
|
||||
this._snackBar.open(`Download stopped`, null, { duration: 5000 });
|
||||
},
|
||||
error => {
|
||||
this._snackBar.open(error.error, null, {
|
||||
duration: 5000
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
restartAll() {
|
||||
this.ngZone.run(() => {
|
||||
this.httpClient.post(this.serverService.baseUrl + '/post/restart/all', {})
|
||||
.subscribe(
|
||||
() => {
|
||||
this._snackBar.open(`Download started`, null, { duration: 5000 });
|
||||
},
|
||||
error => {
|
||||
this._snackBar.open(error.error, null, {
|
||||
duration: 5000
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.subscriptions.forEach(e => e.unsubscribe());
|
||||
this.websocketHandlerPromise.then((handler: WsHandler) => {
|
||||
handler.send(new WSMessage(CMD.GLOBAL_STATE_UNSUB.toString()));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,8 @@ import {
|
||||
MatSlideToggleModule,
|
||||
MatSnackBarModule,
|
||||
MatSnackBar,
|
||||
MatTabsModule
|
||||
MatTabsModule,
|
||||
MatDividerModule
|
||||
} from '@angular/material';
|
||||
|
||||
@NgModule({
|
||||
@@ -32,7 +33,8 @@ import {
|
||||
MatCardModule,
|
||||
MatSlideToggleModule,
|
||||
MatSnackBarModule,
|
||||
MatTabsModule
|
||||
MatTabsModule,
|
||||
MatDividerModule
|
||||
],
|
||||
providers: [MatSnackBar]
|
||||
})
|
||||
|
||||
@@ -30,6 +30,7 @@ export class PostDetailComponent implements OnInit, OnDestroy {
|
||||
cellClass: 'no-padding'
|
||||
}
|
||||
],
|
||||
enableBrowserTooltips: true,
|
||||
rowHeight: 48,
|
||||
rowData: [],
|
||||
frameworkComponents: {
|
||||
|
||||
@@ -10,67 +10,96 @@ import { ICellRendererParams } from 'ag-grid-community';
|
||||
selector: 'app-progress-cell',
|
||||
template: `
|
||||
<div style="height: 100%;">
|
||||
<div class="progress-text" fxLayout="row" fxLayoutAlign="space-between">
|
||||
<div>{{ postState.title + postState.postCounter }}</div>
|
||||
<div>{{ trunc(postState.progress) + '%' }}</div>
|
||||
</div>
|
||||
<div
|
||||
class="progress-bar"
|
||||
[ngClass]="{
|
||||
complete: postState.status === 'COMPLETE',
|
||||
downloading: postState.status === 'DOWNLOADING',
|
||||
stopped: postState.status === 'STOPPED',
|
||||
partial: postState.status === 'PARTIAL'
|
||||
'complete': postState.status === 'COMPLETE',
|
||||
'downloading': postState.status === 'DOWNLOADING',
|
||||
'stopped': postState.status === 'STOPPED',
|
||||
'partial': postState.status === 'PARTIAL',
|
||||
'error': postState.status === 'ERROR',
|
||||
'pending': postState.status === 'PENDING'
|
||||
}"
|
||||
[style.width]="trunc(postState.progress) + '%'"
|
||||
></div>
|
||||
<div
|
||||
class="progress-bar-back"
|
||||
fxLayout="row"
|
||||
fxLayoutAlign="space-between"
|
||||
[ngClass]="{ error: postState.status === 'ERROR', pending: postState.status === 'PENDING' }"
|
||||
>
|
||||
<div>{{ postState.title }}</div>
|
||||
<div>{{ trunc(postState.progress) + '%' }}</div>
|
||||
</div>
|
||||
[ngClass]="{
|
||||
'complete-back': postState.status === 'COMPLETE',
|
||||
'downloading-back': postState.status === 'DOWNLOADING',
|
||||
'stopped-back': postState.status === 'STOPPED',
|
||||
'partial-back': postState.status === 'PARTIAL',
|
||||
'error-back': postState.status === 'ERROR',
|
||||
'pending-back': postState.status === 'PENDING'
|
||||
}"
|
||||
></div>
|
||||
</div>
|
||||
`,
|
||||
styles: [
|
||||
`
|
||||
.progress-bar-back {
|
||||
.progress-text {
|
||||
position: relative;
|
||||
height: 45px;
|
||||
bottom: 45px;
|
||||
padding: 0 8px;
|
||||
transition: background-color 0.5s;
|
||||
z-index: 2;
|
||||
}
|
||||
.progress-bar {
|
||||
background-color: #87a2c7;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
bottom: 45px;
|
||||
transition: width 0.5s, background-color 0.5s;
|
||||
z-index: 1;
|
||||
}
|
||||
.progress-bar-back {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
bottom: 90px;
|
||||
transition: background-color 0.5s;
|
||||
}
|
||||
.complete {
|
||||
background-color: #3865a3;
|
||||
}
|
||||
.complete-back {
|
||||
background-color: lightgrey;
|
||||
}
|
||||
.pending {
|
||||
background-color: white;
|
||||
}
|
||||
.pending-back {
|
||||
background-color: lightgrey;
|
||||
}
|
||||
.error {
|
||||
background-color: #eb6060;
|
||||
}
|
||||
.error-back {
|
||||
background-color: #eb6060;
|
||||
}
|
||||
.downloading {
|
||||
background-color: #87a2c7;
|
||||
}
|
||||
.downloading-back {
|
||||
background-color: white;
|
||||
}
|
||||
.stopped {
|
||||
background-color: grey;
|
||||
}
|
||||
.stopped-back {
|
||||
background-color: lightgrey;
|
||||
}
|
||||
.partial {
|
||||
background-color: #ffcc00;
|
||||
background-color: #ffcc00;
|
||||
}
|
||||
.partial-back {
|
||||
background-color: white;
|
||||
}
|
||||
`
|
||||
]
|
||||
})
|
||||
export class PostProgressRendererComponent implements AgRendererComponent, OnInit, OnDestroy {
|
||||
constructor(
|
||||
private wsConnectionService: WsConnectionService,
|
||||
private zone: NgZone
|
||||
) {
|
||||
constructor(private wsConnectionService: WsConnectionService, private zone: NgZone) {
|
||||
this.websocketHandlerPromise = this.wsConnectionService.getConnection();
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ export class PostState {
|
||||
constructor(
|
||||
public type: string,
|
||||
public postId: string,
|
||||
public postCounter: string,
|
||||
public title: string,
|
||||
public progress: number,
|
||||
public status: string,
|
||||
|
||||
@@ -37,6 +37,7 @@ export class PostsComponent implements OnInit, OnDestroy {
|
||||
suppressAutoSize: true
|
||||
}
|
||||
],
|
||||
enableBrowserTooltips: true,
|
||||
rowHeight: 48,
|
||||
rowData: [],
|
||||
frameworkComponents: {
|
||||
|
||||
@@ -3,10 +3,59 @@ import { WSMessage } from './common/ws-message.model';
|
||||
import { PostState } from './posts/post-state.model';
|
||||
import { map, filter } from 'rxjs/operators';
|
||||
import { PostDetails } from './post-detail/post-details.model';
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
subscribeForPosts(callback: (postStream: Array<PostState>) => void): Subscription {
|
||||
return this.websocket
|
||||
.pipe(
|
||||
@@ -19,6 +68,7 @@ export class WsHandler {
|
||||
new PostState(
|
||||
element.type,
|
||||
element.postId,
|
||||
element.postCounter,
|
||||
element.title,
|
||||
element.done === 0 && element.total === 0 ? 0 : (element.done / element.total) * 100,
|
||||
element.status,
|
||||
|
||||
@@ -65,3 +65,11 @@ div.ag-cell.no-padding {
|
||||
text-overflow:ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.ag-body-horizontal-scroll {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.ag-center-cols-viewport {
|
||||
overflow: hidden !important;
|
||||
}
|
||||
Reference in New Issue
Block a user