mirror of
https://github.com/dev-claw/vripper-project.git
synced 2026-08-19 08:35:41 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2220410f02 |
+2
-1
@@ -4,4 +4,5 @@
|
||||
/**/*/build-dir
|
||||
/**/*/java-runtime
|
||||
.idea
|
||||
.vripper
|
||||
.vripper
|
||||
.flattened-pom.xml
|
||||
@@ -13,7 +13,7 @@ Direct access to `vipergirls.to` domain.
|
||||
|
||||
<img src="https://github.com/stashapp/stash/raw/develop/docs/readme_assets/windows_logo.svg" width="100%" height="75"> Windows | <img src="https://github.com/stashapp/stash/raw/develop/docs/readme_assets/mac_logo.svg" width="100%" height="75"> macOS | <img src="https://github.com/stashapp/stash/raw/develop/docs/readme_assets/linux_logo.svg" width="100%" height="75"> Linux | <img src="https://images.vexels.com/media/users/3/166401/isolated/preview/b82aa7ac3f736dd78570dd3fa3fa9e24-java-programming-language-icon-by-vexels.png" width="100%" height="75"> Java
|
||||
:---:|:---:|:---:|:---:
|
||||
[Installer (EXE)](https://github.com/death-claw/vripper-project/releases/download/5.8.0/vripper-windows-installer-5.8.0.exe) <br /> [Installer (MSI)](https://github.com/death-claw/vripper-project/releases/download/5.8.0/vripper-windows-installer-5.8.0.msi) <br /> [Portable (ZIP)](https://github.com/death-claw/vripper-project/releases/download/5.8.0/vripper-windows-portable-5.8.0.zip) | [Installer (DMG)](https://github.com/death-claw/vripper-project/releases/download/5.8.0/vripper-macos-5.8.0.dmg) <br /> [Installer (PKG)](https://github.com/death-claw/vripper-project/releases/download/5.8.0/vripper-macos-5.8.0.pkg) <br /> [Portable (ZIP)](https://github.com/death-claw/vripper-project/releases/download/5.8.0/vripper-macos-portable-5.8.0.zip) | [Linux (amd64) (DEB)](https://github.com/death-claw/vripper-project/releases/download/5.8.0/vripper-linux-5.8.0_amd64.deb) <br /> [Linux (x86_64) (RPM)](https://github.com/death-claw/vripper-project/releases/download/5.8.0/vripper-linux-5.8.0.x86_64.rpm) <br /> [Portable (ZIP)](https://github.com/death-claw/vripper-project/releases/download/5.8.0/vripper-linux-portable-5.8.0.zip) | [Java GUI (noarch)](https://github.com/death-claw/vripper-project/releases/download/5.8.0/vripper-noarch-gui-5.8.0.jar) <br /> [Java Web (noarch)](https://github.com/death-claw/vripper-project/releases/download/5.8.0/vripper-noarch-web-5.8.0.jar)
|
||||
[Installer (EXE)](https://github.com/death-claw/vripper-project/releases/download/6.0.0/vripper-windows-installer-6.0.0.exe) <br /> [Installer (MSI)](https://github.com/death-claw/vripper-project/releases/download/6.0.0/vripper-windows-installer-6.0.0.msi) <br /> [Portable (ZIP)](https://github.com/death-claw/vripper-project/releases/download/6.0.0/vripper-windows-portable-6.0.0.zip) | [Installer (DMG)](https://github.com/death-claw/vripper-project/releases/download/6.0.0/vripper-macos-6.0.0.dmg) <br /> [Installer (PKG)](https://github.com/death-claw/vripper-project/releases/download/6.0.0/vripper-macos-6.0.0.pkg) <br /> [Portable (ZIP)](https://github.com/death-claw/vripper-project/releases/download/6.0.0/vripper-macos-portable-6.0.0.zip) | [Linux (amd64) (DEB)](https://github.com/death-claw/vripper-project/releases/download/6.0.0/vripper-linux-6.0.0_amd64.deb) <br /> [Linux (x86_64) (RPM)](https://github.com/death-claw/vripper-project/releases/download/6.0.0/vripper-linux-6.0.0.x86_64.rpm) <br /> [Portable (ZIP)](https://github.com/death-claw/vripper-project/releases/download/6.0.0/vripper-linux-portable-6.0.0.zip) | [Java GUI (noarch)](https://github.com/death-claw/vripper-project/releases/download/6.0.0/vripper-noarch-gui-6.0.0.jar) <br /> [Java Web (noarch)](https://github.com/death-claw/vripper-project/releases/download/6.0.0/vripper-noarch-web-6.0.0.jar)
|
||||
|
||||
Source code and previous versions are available on the [Releases page](https://github.com/death-claw/vripper-project/releases).
|
||||
|
||||
|
||||
@@ -15,27 +15,29 @@
|
||||
<maven.compiler.source>${java.version}</maven.compiler.source>
|
||||
<maven.compiler.target>${java.version}</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<revision>5.8.0</revision>
|
||||
<kotlin.version>1.9.22</kotlin.version>
|
||||
<slf4j.version>2.0.12</slf4j.version>
|
||||
<logback.version>1.5.0</logback.version>
|
||||
<kotlinx-coroutines.version>1.8.0</kotlinx-coroutines.version>
|
||||
<exposed.version>0.47.0</exposed.version>
|
||||
<revision>6.0.0</revision>
|
||||
<kotlin.version>2.0.21</kotlin.version>
|
||||
<slf4j.version>2.0.16</slf4j.version>
|
||||
<logback.version>1.5.12</logback.version>
|
||||
<kotlinx-coroutines.version>1.9.0</kotlinx-coroutines.version>
|
||||
<exposed.version>0.55.0</exposed.version>
|
||||
<snakeyaml.version>2.0</snakeyaml.version>
|
||||
<koin.version>3.5.3</koin.version>
|
||||
<koin.version>4.0.0</koin.version>
|
||||
<h2.version>2.2.224</h2.version>
|
||||
<htmlcleaner.version>2.29</htmlcleaner.version>
|
||||
<failsafe.version>2.4.4</failsafe.version>
|
||||
<caffeine.version>3.1.8</caffeine.version>
|
||||
<jna-platform.version>5.14.0</jna-platform.version>
|
||||
<kotlinx-serialization-json.version>1.6.2</kotlinx-serialization-json.version>
|
||||
<httpclient5.version>5.3.1</httpclient5.version>
|
||||
<commons-codec.version>1.16.1</commons-codec.version>
|
||||
<liquibase-core.version>4.26.0</liquibase-core.version>
|
||||
<kotlinx-serialization-json.version>1.7.3</kotlinx-serialization-json.version>
|
||||
<httpclient5.version>5.4</httpclient5.version>
|
||||
<httpcore5.version>5.3</httpcore5.version>
|
||||
<commons-codec.version>1.17.1</commons-codec.version>
|
||||
<liquibase-core.version>4.29.2</liquibase-core.version>
|
||||
<liquibase-slf4j.version>5.0.0</liquibase-slf4j.version>
|
||||
<grpc.kotlin.version>1.4.1</grpc.kotlin.version>
|
||||
<java.grpc.version>1.62.2</java.grpc.version>
|
||||
<protobuf.version>3.25.3</protobuf.version>
|
||||
<java.grpc.version>1.68.1</java.grpc.version>
|
||||
<protobuf.version>3.25.5</protobuf.version>
|
||||
<sqlite-jdbc.version>3.47.0.0</sqlite-jdbc.version>
|
||||
</properties>
|
||||
<build>
|
||||
<plugins>
|
||||
@@ -129,6 +131,11 @@
|
||||
<groupId>com.h2database</groupId>
|
||||
<version>${h2.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.xerial</groupId>
|
||||
<artifactId>sqlite-jdbc</artifactId>
|
||||
<version>${sqlite-jdbc.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<artifactId>htmlcleaner</artifactId>
|
||||
<groupId>net.sourceforge.htmlcleaner</groupId>
|
||||
@@ -169,6 +176,16 @@
|
||||
<groupId>org.apache.httpcomponents.client5</groupId>
|
||||
<version>${httpclient5.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<artifactId>httpcore5</artifactId>
|
||||
<groupId>org.apache.httpcomponents.core5</groupId>
|
||||
<version>${httpcore5.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<artifactId>httpcore5-h2</artifactId>
|
||||
<groupId>org.apache.httpcomponents.core5</groupId>
|
||||
<version>${httpcore5.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<artifactId>commons-codec</artifactId>
|
||||
<groupId>commons-codec</groupId>
|
||||
@@ -194,16 +211,16 @@
|
||||
<artifactId>grpc-protobuf</artifactId>
|
||||
<version>${java.grpc.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.protobuf</groupId>
|
||||
<artifactId>protobuf-kotlin</artifactId>
|
||||
<version>${protobuf.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.grpc</groupId>
|
||||
<artifactId>grpc-netty-shaded</artifactId>
|
||||
<version>${java.grpc.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.protobuf</groupId>
|
||||
<artifactId>protobuf-kotlin</artifactId>
|
||||
<version>${protobuf.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
</project>
|
||||
|
||||
@@ -51,6 +51,10 @@
|
||||
<artifactId>h2</artifactId>
|
||||
<groupId>com.h2database</groupId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.xerial</groupId>
|
||||
<artifactId>sqlite-jdbc</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<artifactId>httpclient5</artifactId>
|
||||
<groupId>org.apache.httpcomponents.client5</groupId>
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
package me.vripper
|
||||
|
||||
import me.vripper.data.repositories.ImageRepository
|
||||
import me.vripper.data.repositories.MetadataRepository
|
||||
import me.vripper.data.repositories.PostDownloadStateRepository
|
||||
import me.vripper.data.repositories.ThreadRepository
|
||||
import me.vripper.data.repositories.impl.ImageRepositoryImpl
|
||||
import me.vripper.data.repositories.impl.MetadataRepositoryImpl
|
||||
import me.vripper.data.repositories.impl.PostDownloadStateRepositoryImpl
|
||||
import me.vripper.data.repositories.impl.ThreadRepositoryImpl
|
||||
import me.vripper.download.DownloadService
|
||||
import me.vripper.event.EventBus
|
||||
import me.vripper.host.*
|
||||
import me.vripper.repositories.*
|
||||
import me.vripper.repositories.impl.*
|
||||
import me.vripper.services.*
|
||||
import org.koin.core.qualifier.named
|
||||
import org.koin.dsl.bind
|
||||
@@ -17,6 +23,9 @@ val coreModule = module {
|
||||
single<SettingsService> {
|
||||
SettingsService(get())
|
||||
}
|
||||
single<LogService> {
|
||||
LogService(get())
|
||||
}
|
||||
single<ImageRepository> {
|
||||
ImageRepositoryImpl()
|
||||
}
|
||||
@@ -29,11 +38,8 @@ val coreModule = module {
|
||||
single<ThreadRepository> {
|
||||
ThreadRepositoryImpl()
|
||||
}
|
||||
single<LogRepository> {
|
||||
LogRepositoryImpl(get())
|
||||
}
|
||||
single<DataTransaction> {
|
||||
DataTransaction(get(), get(), get(), get(), get(), get(), get())
|
||||
DataTransaction(get(), get(), get(), get(), get(), get())
|
||||
}
|
||||
single<RetryPolicyService> {
|
||||
RetryPolicyService(get(), get())
|
||||
@@ -55,11 +61,11 @@ val coreModule = module {
|
||||
}
|
||||
|
||||
single<AppEndpointService> {
|
||||
AppEndpointService(get(), get(), get(), get(), get())
|
||||
AppEndpointService(get(), get(), get(), get(), get(), get())
|
||||
}
|
||||
|
||||
single((named("localAppEndpointService"))) {
|
||||
AppEndpointService(get(), get(), get(), get(), get())
|
||||
AppEndpointService(get(), get(), get(), get(), get(), get())
|
||||
} bind IAppEndpointService::class
|
||||
|
||||
single<MetadataService> {
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package me.vripper
|
||||
|
||||
import ch.qos.logback.classic.spi.ILoggingEvent
|
||||
import ch.qos.logback.core.AppenderBase
|
||||
import org.apache.commons.collections4.queue.CircularFifoQueue
|
||||
|
||||
class RingAppender : AppenderBase<ILoggingEvent>() {
|
||||
|
||||
object Static {
|
||||
val events = CircularFifoQueue<ILoggingEvent>()
|
||||
}
|
||||
|
||||
override fun append(event: ILoggingEvent) {
|
||||
Static.events.offer(event)
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -1,9 +1,9 @@
|
||||
package me.vripper.repositories
|
||||
package me.vripper.data.repositories
|
||||
|
||||
import me.vripper.entities.ImageEntity
|
||||
import java.util.*
|
||||
|
||||
interface ImageRepository {
|
||||
internal interface ImageRepository {
|
||||
fun save(imageEntity: ImageEntity): ImageEntity
|
||||
fun save(imageEntityList: List<ImageEntity>)
|
||||
fun deleteAllByPostId(postId: Long)
|
||||
+2
-2
@@ -1,9 +1,9 @@
|
||||
package me.vripper.repositories
|
||||
package me.vripper.data.repositories
|
||||
|
||||
import me.vripper.entities.MetadataEntity
|
||||
import java.util.*
|
||||
|
||||
interface MetadataRepository {
|
||||
internal interface MetadataRepository {
|
||||
fun save(metadataEntity: MetadataEntity): MetadataEntity
|
||||
fun findByPostId(postId: Long): Optional<MetadataEntity>
|
||||
fun deleteByPostId(postId: Long): Int
|
||||
+2
-2
@@ -1,9 +1,9 @@
|
||||
package me.vripper.repositories
|
||||
package me.vripper.data.repositories
|
||||
|
||||
import me.vripper.entities.PostEntity
|
||||
import java.util.*
|
||||
|
||||
interface PostDownloadStateRepository {
|
||||
internal interface PostDownloadStateRepository {
|
||||
fun save(postEntities: List<PostEntity>): List<PostEntity>
|
||||
fun findByPostId(postId: Long): Optional<PostEntity>
|
||||
fun findById(id: Long): Optional<PostEntity>
|
||||
+2
-2
@@ -1,9 +1,9 @@
|
||||
package me.vripper.repositories
|
||||
package me.vripper.data.repositories
|
||||
|
||||
import me.vripper.entities.ThreadEntity
|
||||
import java.util.*
|
||||
|
||||
interface ThreadRepository {
|
||||
internal interface ThreadRepository {
|
||||
fun save(threadEntity: ThreadEntity): ThreadEntity
|
||||
fun update(threadEntity: ThreadEntity)
|
||||
fun findByThreadId(threadId: Long): Optional<ThreadEntity>
|
||||
+13
-13
@@ -1,16 +1,16 @@
|
||||
package me.vripper.repositories.impl
|
||||
package me.vripper.data.repositories.impl
|
||||
|
||||
import me.vripper.data.repositories.ImageRepository
|
||||
import me.vripper.data.tables.ImageTable
|
||||
import me.vripper.entities.ImageEntity
|
||||
import me.vripper.entities.domain.Status
|
||||
import me.vripper.repositories.ImageRepository
|
||||
import me.vripper.tables.ImageTable
|
||||
import me.vripper.entities.Status
|
||||
import org.jetbrains.exposed.sql.*
|
||||
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
|
||||
import org.jetbrains.exposed.sql.transactions.TransactionManager
|
||||
import java.sql.Connection
|
||||
import java.util.*
|
||||
|
||||
class ImageRepositoryImpl : ImageRepository {
|
||||
internal class ImageRepositoryImpl : ImageRepository {
|
||||
override fun save(imageEntity: ImageEntity): ImageEntity {
|
||||
val id = ImageTable.insertAndGetId {
|
||||
it[downloaded] = imageEntity.downloaded
|
||||
@@ -47,21 +47,21 @@ class ImageRepositoryImpl : ImageRepository {
|
||||
}
|
||||
|
||||
override fun findByPostId(postId: Long): List<ImageEntity> {
|
||||
return ImageTable.select {
|
||||
return ImageTable.selectAll().where {
|
||||
ImageTable.postId eq postId
|
||||
}.map(this::transform)
|
||||
}
|
||||
|
||||
override fun countError(): Int {
|
||||
return ImageTable
|
||||
.slice(ImageTable.id)
|
||||
.select { ImageTable.status eq Status.ERROR.name }
|
||||
.select(ImageTable.id)
|
||||
.where { ImageTable.status eq Status.ERROR.name }
|
||||
.count().toInt()
|
||||
}
|
||||
|
||||
override fun findByPostIdAndIsNotCompleted(postId: Long): List<ImageEntity> {
|
||||
return ImageTable
|
||||
.select {
|
||||
.selectAll().where {
|
||||
(ImageTable.postId eq postId) and (ImageTable.status neq Status.FINISHED.name)
|
||||
}.map(this::transform)
|
||||
}
|
||||
@@ -79,13 +79,13 @@ class ImageRepositoryImpl : ImageRepository {
|
||||
}
|
||||
|
||||
override fun findByPostIdAndIsError(postId: Long): List<ImageEntity> {
|
||||
return ImageTable.select {
|
||||
return ImageTable.selectAll().where {
|
||||
(ImageTable.postId eq postId) and (ImageTable.status eq Status.ERROR.name)
|
||||
}.map(this::transform)
|
||||
}
|
||||
|
||||
override fun findById(id: Long): Optional<ImageEntity> {
|
||||
val result = ImageTable.select {
|
||||
val result = ImageTable.selectAll().where {
|
||||
ImageTable.id eq id
|
||||
}.map(this::transform)
|
||||
return if (result.isEmpty()) {
|
||||
@@ -122,7 +122,7 @@ class ImageRepositoryImpl : ImageRepository {
|
||||
|
||||
override fun deleteAllByPostId(postIds: List<Long>) {
|
||||
val conn = TransactionManager.current().connection.connection as Connection
|
||||
conn.prepareStatement("CREATE LOCAL TEMPORARY TABLE IMAGES_DELETE(POST_ID BIGINT PRIMARY KEY)")
|
||||
conn.prepareStatement("CREATE TEMPORARY TABLE IMAGES_DELETE(POST_ID BIGINT PRIMARY KEY)")
|
||||
.use {
|
||||
it.execute()
|
||||
}
|
||||
@@ -140,7 +140,7 @@ class ImageRepositoryImpl : ImageRepository {
|
||||
it.execute()
|
||||
}
|
||||
|
||||
conn.prepareStatement("TRUNCATE TABLE IMAGES_DELETE").use {
|
||||
conn.prepareStatement("DELETE FROM IMAGES_DELETE").use {
|
||||
it.execute()
|
||||
}
|
||||
}
|
||||
+8
-8
@@ -1,20 +1,20 @@
|
||||
package me.vripper.repositories.impl
|
||||
package me.vripper.data.repositories.impl
|
||||
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import me.vripper.data.repositories.MetadataRepository
|
||||
import me.vripper.data.tables.MetadataTable
|
||||
import me.vripper.entities.MetadataEntity
|
||||
import me.vripper.repositories.MetadataRepository
|
||||
import me.vripper.tables.MetadataTable
|
||||
import org.jetbrains.exposed.sql.ResultRow
|
||||
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
|
||||
import org.jetbrains.exposed.sql.deleteWhere
|
||||
import org.jetbrains.exposed.sql.insert
|
||||
import org.jetbrains.exposed.sql.select
|
||||
import org.jetbrains.exposed.sql.selectAll
|
||||
import org.jetbrains.exposed.sql.transactions.TransactionManager
|
||||
import java.sql.Connection
|
||||
import java.util.*
|
||||
|
||||
class MetadataRepositoryImpl: MetadataRepository {
|
||||
internal class MetadataRepositoryImpl : MetadataRepository {
|
||||
|
||||
override fun save(metadataEntity: MetadataEntity): MetadataEntity {
|
||||
MetadataTable.insert {
|
||||
@@ -27,7 +27,7 @@ class MetadataRepositoryImpl: MetadataRepository {
|
||||
|
||||
override fun findByPostId(postId: Long): Optional<MetadataEntity> {
|
||||
|
||||
val result = MetadataTable.select {
|
||||
val result = MetadataTable.selectAll().where {
|
||||
MetadataTable.postId eq postId
|
||||
}.map(::transform)
|
||||
|
||||
@@ -50,7 +50,7 @@ class MetadataRepositoryImpl: MetadataRepository {
|
||||
|
||||
override fun deleteAllByPostId(postIds: List<Long>) {
|
||||
val conn = TransactionManager.current().connection.connection as Connection
|
||||
conn.prepareStatement("CREATE LOCAL TEMPORARY TABLE METADATA_DELETE(POST_ID BIGINT PRIMARY KEY)")
|
||||
conn.prepareStatement("CREATE TEMPORARY TABLE METADATA_DELETE(POST_ID BIGINT PRIMARY KEY)")
|
||||
.use {
|
||||
it.execute()
|
||||
}
|
||||
@@ -68,7 +68,7 @@ class MetadataRepositoryImpl: MetadataRepository {
|
||||
it.execute()
|
||||
}
|
||||
|
||||
conn.prepareStatement("TRUNCATE TABLE METADATA_DELETE").use {
|
||||
conn.prepareStatement("DELETE FROM METADATA_DELETE").use {
|
||||
it.execute()
|
||||
}
|
||||
}
|
||||
+13
-14
@@ -1,16 +1,16 @@
|
||||
package me.vripper.repositories.impl
|
||||
package me.vripper.data.repositories.impl
|
||||
|
||||
import me.vripper.data.repositories.PostDownloadStateRepository
|
||||
import me.vripper.data.tables.PostTable
|
||||
import me.vripper.entities.PostEntity
|
||||
import me.vripper.entities.domain.Status
|
||||
import me.vripper.repositories.PostDownloadStateRepository
|
||||
import me.vripper.tables.PostTable
|
||||
import me.vripper.entities.Status
|
||||
import org.jetbrains.exposed.sql.*
|
||||
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
|
||||
import org.jetbrains.exposed.sql.transactions.TransactionManager
|
||||
import java.sql.Connection
|
||||
import java.util.*
|
||||
|
||||
class PostDownloadStateRepositoryImpl :
|
||||
internal class PostDownloadStateRepositoryImpl :
|
||||
PostDownloadStateRepository {
|
||||
|
||||
private val delimiter = ";"
|
||||
@@ -38,7 +38,7 @@ class PostDownloadStateRepositoryImpl :
|
||||
}
|
||||
|
||||
override fun findByPostId(postId: Long): Optional<PostEntity> {
|
||||
val result = PostTable.select {
|
||||
val result = PostTable.selectAll().where {
|
||||
PostTable.postId eq postId
|
||||
}.map(::transform)
|
||||
return if (result.isEmpty()) {
|
||||
@@ -49,13 +49,13 @@ class PostDownloadStateRepositoryImpl :
|
||||
}
|
||||
|
||||
override fun findCompleted(): List<Long> {
|
||||
return PostTable.slice(PostTable.postId).select {
|
||||
return PostTable.select(PostTable.postId).where {
|
||||
(PostTable.status eq Status.FINISHED.name) and (PostTable.done greaterEq PostTable.total)
|
||||
}.map { it[PostTable.postId] }
|
||||
}
|
||||
|
||||
override fun findById(id: Long): Optional<PostEntity> {
|
||||
val result = PostTable.select {
|
||||
val result = PostTable.selectAll().where {
|
||||
PostTable.id eq id
|
||||
}.map { transform(it) }
|
||||
|
||||
@@ -71,7 +71,7 @@ class PostDownloadStateRepositoryImpl :
|
||||
}
|
||||
|
||||
override fun existByPostId(postId: Long): Boolean {
|
||||
return PostTable.slice(PostTable.id).select { PostTable.postId eq postId }.count() > 0
|
||||
return PostTable.select(PostTable.id).where { PostTable.postId eq postId }.count() > 0
|
||||
}
|
||||
|
||||
override fun setDownloadingToStopped(): Int {
|
||||
@@ -118,15 +118,14 @@ class PostDownloadStateRepositoryImpl :
|
||||
}
|
||||
|
||||
override fun findMaxRank(): Int? {
|
||||
return PostTable.slice(PostTable.rank.max())
|
||||
.selectAll()
|
||||
return PostTable.select(PostTable.rank.max())
|
||||
.firstOrNull()
|
||||
?.get(PostTable.rank.max())
|
||||
}
|
||||
|
||||
override fun deleteAll(postIds: List<Long>) {
|
||||
val conn = TransactionManager.current().connection.connection as Connection
|
||||
conn.prepareStatement("CREATE LOCAL TEMPORARY TABLE POSTS_DELETE(POST_ID BIGINT PRIMARY KEY)")
|
||||
conn.prepareStatement("CREATE TEMPORARY TABLE POSTS_DELETE(POST_ID BIGINT PRIMARY KEY)")
|
||||
.use {
|
||||
it.execute()
|
||||
}
|
||||
@@ -144,7 +143,7 @@ class PostDownloadStateRepositoryImpl :
|
||||
it.execute()
|
||||
}
|
||||
|
||||
conn.prepareStatement("TRUNCATE TABLE POSTS_DELETE").use {
|
||||
conn.prepareStatement("DELETE FROM POSTS_DELETE").use {
|
||||
it.execute()
|
||||
}
|
||||
}
|
||||
@@ -156,7 +155,7 @@ class PostDownloadStateRepositoryImpl :
|
||||
}
|
||||
|
||||
override fun findAllNonCompletedPostIds(): List<Long> {
|
||||
return PostTable.slice(PostTable.postId).select {
|
||||
return PostTable.select(PostTable.postId).where {
|
||||
PostTable.status neq Status.FINISHED.name
|
||||
}.map { it[PostTable.postId] }
|
||||
}
|
||||
+6
-6
@@ -1,13 +1,13 @@
|
||||
package me.vripper.repositories.impl
|
||||
package me.vripper.data.repositories.impl
|
||||
|
||||
import me.vripper.data.repositories.ThreadRepository
|
||||
import me.vripper.data.tables.ThreadTable
|
||||
import me.vripper.entities.ThreadEntity
|
||||
import me.vripper.repositories.ThreadRepository
|
||||
import me.vripper.tables.ThreadTable
|
||||
import org.jetbrains.exposed.sql.*
|
||||
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
|
||||
import java.util.*
|
||||
|
||||
class ThreadRepositoryImpl : ThreadRepository {
|
||||
internal class ThreadRepositoryImpl : ThreadRepository {
|
||||
|
||||
override fun save(threadEntity: ThreadEntity): ThreadEntity {
|
||||
val id = ThreadTable.insertAndGetId {
|
||||
@@ -27,7 +27,7 @@ class ThreadRepositoryImpl : ThreadRepository {
|
||||
}
|
||||
|
||||
override fun findByThreadId(threadId: Long): Optional<ThreadEntity> {
|
||||
val result = ThreadTable.select {
|
||||
val result = ThreadTable.selectAll().where {
|
||||
ThreadTable.threadId eq threadId
|
||||
}.map(this::transform)
|
||||
return if (result.isEmpty()) {
|
||||
@@ -42,7 +42,7 @@ class ThreadRepositoryImpl : ThreadRepository {
|
||||
}
|
||||
|
||||
override fun findById(id: Long): Optional<ThreadEntity> {
|
||||
val result = ThreadTable.select {
|
||||
val result = ThreadTable.selectAll().where {
|
||||
ThreadTable.id eq id
|
||||
}.map(this::transform)
|
||||
return if (result.isEmpty()) {
|
||||
+3
-3
@@ -1,8 +1,8 @@
|
||||
package me.vripper.tables
|
||||
package me.vripper.data.tables
|
||||
|
||||
import org.jetbrains.exposed.dao.id.LongIdTable
|
||||
|
||||
object ImageTable : LongIdTable(name = "IMAGE", columnName = "ID") {
|
||||
internal object ImageTable : LongIdTable(name = "IMAGE", columnName = "ID") {
|
||||
val downloaded = long("DOWNLOADED")
|
||||
val host = byte("HOST")
|
||||
val index = integer("INDEX")
|
||||
@@ -12,5 +12,5 @@ object ImageTable : LongIdTable(name = "IMAGE", columnName = "ID") {
|
||||
val size = long("SIZE")
|
||||
val url = varchar("URL", 200)
|
||||
val thumbUrl = varchar("THUMB_URL", 200)
|
||||
val postIdRef = long("POST_ID_REF").references(PostTable.id, fkName = "IMAGE_POST_ID_REF_POST_ID_FK")
|
||||
val postIdRef = long("POST_ID_REF").references(id, fkName = "IMAGE_POST_ID_REF_POST_ID_FK")
|
||||
}
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
package me.vripper.tables
|
||||
package me.vripper.data.tables
|
||||
|
||||
import org.jetbrains.exposed.sql.Table
|
||||
|
||||
object MetadataTable : Table(name = "METADATA") {
|
||||
internal object MetadataTable : Table(name = "METADATA") {
|
||||
val postId = long("POST_ID")
|
||||
val data = varchar("DATA", 1_000_000)
|
||||
}
|
||||
+2
-2
@@ -1,10 +1,10 @@
|
||||
package me.vripper.tables
|
||||
package me.vripper.data.tables
|
||||
|
||||
import org.jetbrains.exposed.dao.id.LongIdTable
|
||||
import org.jetbrains.exposed.sql.javatime.datetime
|
||||
import java.time.LocalDateTime
|
||||
|
||||
object PostTable : LongIdTable(name = "POST", columnName = "ID") {
|
||||
internal object PostTable : LongIdTable(name = "POST", columnName = "ID") {
|
||||
val done = integer("DONE")
|
||||
val hosts = varchar("HOSTS", 255)
|
||||
val outputPath = varchar("OUTPUT_PATH", 260)
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
package me.vripper.tables
|
||||
package me.vripper.data.tables
|
||||
|
||||
import org.jetbrains.exposed.dao.id.LongIdTable
|
||||
|
||||
object ThreadTable : LongIdTable("THREAD", columnName = "ID") {
|
||||
internal object ThreadTable : LongIdTable("THREAD", columnName = "ID") {
|
||||
val total = integer("TOTAL").default(0)
|
||||
val url = varchar("URL", 200)
|
||||
val threadId = long("THREAD_ID")
|
||||
@@ -5,9 +5,8 @@ import kotlinx.coroutines.Runnable
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.launch
|
||||
import me.vripper.entities.ImageEntity
|
||||
import me.vripper.entities.LogEntryEntity
|
||||
import me.vripper.entities.PostEntity
|
||||
import me.vripper.entities.domain.Status
|
||||
import me.vripper.entities.Status
|
||||
import me.vripper.event.ErrorCountEvent
|
||||
import me.vripper.event.EventBus
|
||||
import me.vripper.event.QueueStateEvent
|
||||
@@ -19,16 +18,15 @@ import me.vripper.services.DataTransaction
|
||||
import me.vripper.services.RetryPolicyService
|
||||
import me.vripper.services.SettingsService
|
||||
import me.vripper.services.VGAuthService
|
||||
import me.vripper.utilities.GLOBAL_EXECUTOR
|
||||
import me.vripper.utilities.formatToString
|
||||
import me.vripper.utilities.GlobalScopeCoroutine
|
||||
import me.vripper.utilities.LoggerDelegate
|
||||
import net.jodah.failsafe.Failsafe
|
||||
import net.jodah.failsafe.RetryPolicy
|
||||
import org.jetbrains.exposed.sql.transactions.transaction
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
import kotlin.concurrent.withLock
|
||||
|
||||
class DownloadService(
|
||||
internal class DownloadService(
|
||||
private val settingsService: SettingsService,
|
||||
private val dataTransaction: DataTransaction,
|
||||
private val retryPolicyService: RetryPolicyService,
|
||||
@@ -36,7 +34,7 @@ class DownloadService(
|
||||
private val eventBus: EventBus
|
||||
) {
|
||||
private val maxPoolSize: Int = 24
|
||||
private val log by me.vripper.delegate.LoggerDelegate()
|
||||
private val log by LoggerDelegate()
|
||||
private val running: MutableMap<Byte, MutableList<ImageDownloadRunnable>> = mutableMapOf()
|
||||
private val pending: MutableMap<Byte, MutableList<ImageDownloadRunnable>> = mutableMapOf()
|
||||
private val lock = ReentrantLock()
|
||||
@@ -163,6 +161,9 @@ class DownloadService(
|
||||
lock.withLock {
|
||||
pending.values.clear()
|
||||
running.values.flatten().forEach { obj: ImageDownloadRunnable -> obj.stop() }
|
||||
while (running.values.flatten().count { !it.context.completed } > 0) {
|
||||
Thread.sleep(100)
|
||||
}
|
||||
dataTransaction.findAllNonCompletedPostIds().forEach {
|
||||
dataTransaction.stopImagesByPostIdAndIsNotCompleted(it)
|
||||
dataTransaction.finishPost(it)
|
||||
@@ -179,6 +180,11 @@ class DownloadService(
|
||||
running.values.flatten()
|
||||
.filter { p: ImageDownloadRunnable -> p.context.imageEntity.postId == postId }
|
||||
.forEach { obj: ImageDownloadRunnable -> obj.stop() }
|
||||
while (running.values.flatten()
|
||||
.count { !it.context.completed && it.context.imageEntity.postId == postId } > 0
|
||||
) {
|
||||
Thread.sleep(100)
|
||||
}
|
||||
}
|
||||
postIds.forEach {
|
||||
dataTransaction.stopImagesByPostIdAndIsNotCompleted(it)
|
||||
@@ -230,49 +236,43 @@ class DownloadService(
|
||||
|
||||
private fun scheduleForDownload(imageDownloadRunnable: ImageDownloadRunnable) {
|
||||
log.debug("Scheduling a job for ${imageDownloadRunnable.context.imageEntity.url}")
|
||||
CompletableFuture.runAsync({
|
||||
GlobalScopeCoroutine.launch {
|
||||
coroutineScope.launch {
|
||||
eventBus.publishEvent(QueueStateEvent(QueueState(runningCount(), pendingCount())))
|
||||
}
|
||||
Failsafe.with<Any, RetryPolicy<Any>>(retryPolicyService.buildRetryPolicyForDownload())
|
||||
.onFailure {
|
||||
try {
|
||||
dataTransaction.saveLog(
|
||||
LogEntryEntity(
|
||||
type = LogEntryEntity.Type.DOWNLOAD,
|
||||
status = LogEntryEntity.Status.ERROR,
|
||||
message = "Failed to download ${imageDownloadRunnable.context.imageEntity.url}\n ${it.failure.formatToString()}"
|
||||
)
|
||||
try {
|
||||
Failsafe.with<Any, RetryPolicy<Any>>(retryPolicyService.buildRetryPolicyForDownload("Failed to download ${imageDownloadRunnable.context.imageEntity.url}: "))
|
||||
.onFailure {
|
||||
log.error(
|
||||
"Failed to download ${imageDownloadRunnable.context.imageEntity.url} after ${it.attemptCount} tries",
|
||||
it.failure
|
||||
)
|
||||
} catch (exp: Exception) {
|
||||
log.error("Failed to save event", exp)
|
||||
val image = imageDownloadRunnable.context.imageEntity
|
||||
image.status = Status.ERROR
|
||||
dataTransaction.updateImage(image)
|
||||
}
|
||||
log.error(
|
||||
"Failed to download ${imageDownloadRunnable.context.imageEntity.url} after ${it.attemptCount} tries",
|
||||
it.failure
|
||||
)
|
||||
val image = imageDownloadRunnable.context.imageEntity
|
||||
image.status = Status.ERROR
|
||||
dataTransaction.updateImage(image)
|
||||
}.onComplete {
|
||||
afterJobFinish(imageDownloadRunnable)
|
||||
coroutineScope.launch {
|
||||
eventBus.publishEvent(
|
||||
QueueStateEvent(
|
||||
QueueState(
|
||||
runningCount(), pendingCount()
|
||||
.onComplete {
|
||||
afterJobFinish(imageDownloadRunnable)
|
||||
coroutineScope.launch {
|
||||
eventBus.publishEvent(
|
||||
QueueStateEvent(
|
||||
QueueState(
|
||||
runningCount(), pendingCount()
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
coroutineScope.launch {
|
||||
eventBus.publishEvent(ErrorCountEvent(ErrorCount(dataTransaction.countImagesInError())))
|
||||
}
|
||||
log.debug(
|
||||
"Finished downloading ${imageDownloadRunnable.context.imageEntity.url}"
|
||||
)
|
||||
}
|
||||
coroutineScope.launch {
|
||||
eventBus.publishEvent(ErrorCountEvent(ErrorCount(dataTransaction.countImagesInError())))
|
||||
}
|
||||
log.debug(
|
||||
"Finished downloading ${imageDownloadRunnable.context.imageEntity.url}"
|
||||
)
|
||||
}.run(imageDownloadRunnable::run)
|
||||
}, GLOBAL_EXECUTOR)
|
||||
}.run(imageDownloadRunnable::run)
|
||||
} catch (e: Exception) {
|
||||
log.error("Download Failure", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun afterJobFinish(imageDownloadRunnable: ImageDownloadRunnable) {
|
||||
|
||||
@@ -7,11 +7,11 @@ import org.apache.hc.client5.http.cookie.BasicCookieStore
|
||||
import org.apache.hc.client5.http.protocol.HttpClientContext
|
||||
import org.koin.core.component.KoinComponent
|
||||
|
||||
class ImageDownloadContext(val imageEntity: ImageEntity, val settings: Settings) : KoinComponent {
|
||||
|
||||
internal class ImageDownloadContext(val imageEntity: ImageEntity, val settings: Settings) : KoinComponent {
|
||||
val httpContext: HttpClientContext =
|
||||
HttpClientContext.create().apply { cookieStore = BasicCookieStore() }
|
||||
val requests = mutableListOf<HttpUriRequestBase>()
|
||||
val postId = imageEntity.postIdRef
|
||||
var stopped = false
|
||||
var completed = false
|
||||
}
|
||||
@@ -1,8 +1,7 @@
|
||||
package me.vripper.download
|
||||
|
||||
import kotlinx.coroutines.*
|
||||
import me.vripper.entities.ImageEntity
|
||||
import me.vripper.entities.domain.Status
|
||||
import me.vripper.entities.Status
|
||||
import me.vripper.exception.DownloadException
|
||||
import me.vripper.exception.HostException
|
||||
import me.vripper.host.DownloadedImage
|
||||
@@ -10,6 +9,7 @@ import me.vripper.host.Host
|
||||
import me.vripper.host.ImageMimeType
|
||||
import me.vripper.model.Settings
|
||||
import me.vripper.services.*
|
||||
import me.vripper.utilities.LoggerDelegate
|
||||
import me.vripper.utilities.PathUtils.getExtension
|
||||
import me.vripper.utilities.PathUtils.getFileNameWithoutExtension
|
||||
import me.vripper.utilities.PathUtils.sanitize
|
||||
@@ -24,20 +24,15 @@ import java.util.*
|
||||
import kotlin.io.path.Path
|
||||
import kotlin.io.path.pathString
|
||||
|
||||
class ImageDownloadRunnable(
|
||||
internal class ImageDownloadRunnable(
|
||||
private val imageEntity: ImageEntity, val postRank: Int, private val settings: Settings
|
||||
) : KoinComponent, CheckedRunnable {
|
||||
private val log by me.vripper.delegate.LoggerDelegate()
|
||||
private val log by LoggerDelegate()
|
||||
|
||||
private val dataTransaction: DataTransaction by inject()
|
||||
private val hosts: List<Host> = getKoin().getAll()
|
||||
|
||||
val context: ImageDownloadContext = ImageDownloadContext(imageEntity, settings)
|
||||
private var stopped: Boolean
|
||||
get() = context.stopped
|
||||
set(value) {
|
||||
context.stopped = value
|
||||
}
|
||||
|
||||
@Throws(DownloadException::class)
|
||||
fun download() {
|
||||
@@ -74,7 +69,7 @@ class ImageDownloadRunnable(
|
||||
dataTransaction.updateImage(imageEntity)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
if (stopped) {
|
||||
if (context.stopped) {
|
||||
return
|
||||
}
|
||||
imageEntity.status = Status.ERROR
|
||||
@@ -125,10 +120,14 @@ class ImageDownloadRunnable(
|
||||
|
||||
@Throws(Exception::class)
|
||||
override fun run() {
|
||||
if (stopped) {
|
||||
return
|
||||
try {
|
||||
if (context.stopped) {
|
||||
return
|
||||
}
|
||||
download()
|
||||
} finally {
|
||||
context.completed = true
|
||||
}
|
||||
download()
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
@@ -144,6 +143,6 @@ class ImageDownloadRunnable(
|
||||
|
||||
fun stop() {
|
||||
context.requests.forEach { it.abort() }
|
||||
stopped = true
|
||||
context.stopped = true
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
package me.vripper.entities
|
||||
|
||||
import me.vripper.entities.domain.Status
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class ImageEntity(
|
||||
val id: Long = -1,
|
||||
val postId: Long,
|
||||
|
||||
@@ -1,22 +1,14 @@
|
||||
package me.vripper.entities
|
||||
|
||||
import java.time.LocalDateTime
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class LogEntryEntity(
|
||||
val id: Long = -1,
|
||||
val type: Type,
|
||||
val status: Status,
|
||||
val time: LocalDateTime = LocalDateTime.now(),
|
||||
val message: String,
|
||||
) {
|
||||
|
||||
enum class Type(val stringValue: String) {
|
||||
POST("Post"), THREAD("Thread"), THANKS("Thanks"), METADATA("Metadata"), SCAN("Scan"), DOWNLOAD(
|
||||
"Download"
|
||||
)
|
||||
}
|
||||
|
||||
enum class Status(val stringValue: String) {
|
||||
PENDING("Pending"), PROCESSING("Processing"), DONE("Done"), ERROR("Error")
|
||||
}
|
||||
}
|
||||
val sequence: Long,
|
||||
val timestamp: String,
|
||||
val threadName: String,
|
||||
val loggerName: String,
|
||||
val levelString: String,
|
||||
val formattedMessage: String,
|
||||
val throwable: String,
|
||||
)
|
||||
@@ -2,6 +2,7 @@ package me.vripper.entities
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class MetadataEntity(val postId: Long, val data: Data) {
|
||||
@Serializable
|
||||
data class Data(
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
package me.vripper.entities
|
||||
|
||||
import me.vripper.entities.domain.Status
|
||||
import kotlinx.serialization.Contextual
|
||||
import kotlinx.serialization.Serializable
|
||||
import java.time.LocalDateTime
|
||||
|
||||
@Serializable
|
||||
data class PostEntity(
|
||||
val id: Long = -1,
|
||||
val postTitle: String,
|
||||
@@ -15,7 +17,7 @@ data class PostEntity(
|
||||
val total: Int,
|
||||
val hosts: Set<String>,
|
||||
val downloadDirectory: String,
|
||||
val addedOn: LocalDateTime = LocalDateTime.now(),
|
||||
@Contextual val addedOn: LocalDateTime = LocalDateTime.now(),
|
||||
var folderName: String,
|
||||
var status: Status = Status.STOPPED,
|
||||
var done: Int = 0,
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package me.vripper.entities.domain
|
||||
package me.vripper.entities
|
||||
|
||||
enum class Status(val stringValue: String) {
|
||||
PENDING("Pending"), DOWNLOADING("Downloading"), FINISHED("Finished"), ERROR("Error"), STOPPED("Stopped")
|
||||
@@ -1,5 +1,8 @@
|
||||
package me.vripper.entities
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class ThreadEntity(
|
||||
val id: Long = -1,
|
||||
val title: String,
|
||||
|
||||
@@ -21,7 +21,5 @@ data class ErrorCountEvent(val errorCount: ErrorCount)
|
||||
data class SettingsUpdateEvent(val settings: Settings)
|
||||
data class MetadataUpdateEvent(val metadataEntity: MetadataEntity)
|
||||
data class LogCreateEvent(val logEntryEntity: LogEntryEntity)
|
||||
data class LogUpdateEvent(val logEntryEntity: LogEntryEntity)
|
||||
data class LogDeleteEvent(val deleted: List<Long>)
|
||||
data class LoadingTasks(val loading: Boolean)
|
||||
data class StoppedEvent(val postIds: List<Long>)
|
||||
@@ -5,6 +5,7 @@ import me.vripper.exception.HostException
|
||||
import me.vripper.exception.XpathException
|
||||
import me.vripper.services.*
|
||||
import me.vripper.utilities.HtmlUtils
|
||||
import me.vripper.utilities.LoggerDelegate
|
||||
import me.vripper.utilities.XpathUtils
|
||||
import org.apache.hc.client5.http.classic.methods.HttpPost
|
||||
import org.apache.hc.client5.http.entity.UrlEncodedFormEntity
|
||||
@@ -12,12 +13,12 @@ import org.apache.hc.core5.http.message.BasicNameValuePair
|
||||
import org.w3c.dom.Document
|
||||
import org.w3c.dom.Node
|
||||
|
||||
class AcidimgHost(
|
||||
internal class AcidimgHost(
|
||||
private val httpService: HTTPService,
|
||||
dataTransaction: DataTransaction,
|
||||
downloadSpeedService: DownloadSpeedService,
|
||||
) : Host("acidimg.cc", 0, httpService, dataTransaction, downloadSpeedService) {
|
||||
private val log by me.vripper.delegate.LoggerDelegate()
|
||||
private val log by LoggerDelegate()
|
||||
|
||||
@Throws(HostException::class)
|
||||
override fun resolve(
|
||||
|
||||
@@ -4,17 +4,18 @@ import me.vripper.download.ImageDownloadContext
|
||||
import me.vripper.exception.HostException
|
||||
import me.vripper.exception.XpathException
|
||||
import me.vripper.services.*
|
||||
import me.vripper.utilities.LoggerDelegate
|
||||
import me.vripper.utilities.XpathUtils
|
||||
import org.w3c.dom.Document
|
||||
import org.w3c.dom.Node
|
||||
import java.util.*
|
||||
|
||||
class DPicMeHost(
|
||||
internal class DPicMeHost(
|
||||
httpService: HTTPService,
|
||||
dataTransaction: DataTransaction,
|
||||
downloadSpeedService: DownloadSpeedService,
|
||||
) : Host("dpic.me", 1, httpService, dataTransaction, downloadSpeedService) {
|
||||
private val log by me.vripper.delegate.LoggerDelegate()
|
||||
private val log by LoggerDelegate()
|
||||
|
||||
@Throws(HostException::class)
|
||||
override fun resolve(
|
||||
|
||||
@@ -3,8 +3,11 @@ package me.vripper.host
|
||||
import me.vripper.download.ImageDownloadContext
|
||||
import me.vripper.exception.DownloadException
|
||||
import me.vripper.exception.HostException
|
||||
import me.vripper.services.*
|
||||
import me.vripper.services.DataTransaction
|
||||
import me.vripper.services.DownloadSpeedService
|
||||
import me.vripper.services.HTTPService
|
||||
import me.vripper.utilities.HtmlUtils
|
||||
import me.vripper.utilities.LoggerDelegate
|
||||
import me.vripper.utilities.PathUtils.getFileNameWithoutExtension
|
||||
import org.apache.hc.client5.http.classic.methods.HttpGet
|
||||
import org.apache.hc.client5.http.classic.methods.HttpHead
|
||||
@@ -14,15 +17,18 @@ import org.jetbrains.exposed.sql.transactions.transaction
|
||||
import org.w3c.dom.Document
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.time.Duration
|
||||
import java.time.LocalDateTime
|
||||
import kotlin.Throws
|
||||
|
||||
abstract class Host(
|
||||
internal abstract class Host(
|
||||
val hostName: String,
|
||||
val hostId: Byte,
|
||||
private val httpService: HTTPService,
|
||||
private val dataTransaction: DataTransaction,
|
||||
private val downloadSpeedService: DownloadSpeedService
|
||||
) {
|
||||
private val log by me.vripper.delegate.LoggerDelegate()
|
||||
private val log by LoggerDelegate()
|
||||
|
||||
companion object {
|
||||
private const val READ_BUFFER_SIZE = 8192
|
||||
@@ -118,14 +124,21 @@ abstract class Host(
|
||||
)
|
||||
val buffer = ByteArray(READ_BUFFER_SIZE)
|
||||
var read: Int
|
||||
var lastImageUpdateDate = LocalDateTime.now()
|
||||
while (response.entity.content.read(buffer, 0, READ_BUFFER_SIZE)
|
||||
.also { read = it } != -1 && !context.stopped
|
||||
) {
|
||||
fos.write(buffer, 0, read)
|
||||
image.downloaded += read
|
||||
dataTransaction.updateImage(image)
|
||||
if (Duration.between(lastImageUpdateDate, LocalDateTime.now()).toMillis() > 1750) {
|
||||
dataTransaction.updateImage(image)
|
||||
lastImageUpdateDate = LocalDateTime.now()
|
||||
} else {
|
||||
dataTransaction.updateImage(image, false)
|
||||
}
|
||||
downloadSpeedService.reportDownloadedBytes(read.toLong())
|
||||
}
|
||||
dataTransaction.updateImage(image)
|
||||
Pair(tempImage, mimeType)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import me.vripper.exception.HostException
|
||||
import me.vripper.exception.XpathException
|
||||
import me.vripper.services.*
|
||||
import me.vripper.utilities.HtmlUtils
|
||||
import me.vripper.utilities.LoggerDelegate
|
||||
import me.vripper.utilities.XpathUtils
|
||||
import org.apache.hc.client5.http.impl.cookie.BasicClientCookie
|
||||
import org.w3c.dom.Document
|
||||
@@ -14,12 +15,12 @@ import java.time.LocalDateTime
|
||||
import java.time.ZoneId
|
||||
import java.util.*
|
||||
|
||||
class ImageBamHost(
|
||||
internal class ImageBamHost(
|
||||
httpService: HTTPService,
|
||||
dataTransaction: DataTransaction,
|
||||
downloadSpeedService: DownloadSpeedService,
|
||||
) : Host("imagebam.com", 2, httpService, dataTransaction, downloadSpeedService) {
|
||||
private val log by me.vripper.delegate.LoggerDelegate()
|
||||
private val log by LoggerDelegate()
|
||||
|
||||
@Throws(HostException::class)
|
||||
override fun resolve(
|
||||
|
||||
@@ -4,17 +4,18 @@ import me.vripper.download.ImageDownloadContext
|
||||
import me.vripper.exception.HostException
|
||||
import me.vripper.exception.XpathException
|
||||
import me.vripper.services.*
|
||||
import me.vripper.utilities.LoggerDelegate
|
||||
import me.vripper.utilities.XpathUtils
|
||||
import org.w3c.dom.Document
|
||||
import org.w3c.dom.Node
|
||||
import java.util.*
|
||||
|
||||
class ImageTwistHost(
|
||||
internal class ImageTwistHost(
|
||||
httpService: HTTPService,
|
||||
dataTransaction: DataTransaction,
|
||||
downloadSpeedService: DownloadSpeedService,
|
||||
) : Host("imagetwist.com", 3, httpService, dataTransaction, downloadSpeedService) {
|
||||
private val log by me.vripper.delegate.LoggerDelegate()
|
||||
private val log by LoggerDelegate()
|
||||
|
||||
@Throws(HostException::class)
|
||||
override fun resolve(
|
||||
|
||||
@@ -5,16 +5,17 @@ import me.vripper.exception.HostException
|
||||
import me.vripper.exception.XpathException
|
||||
import me.vripper.services.*
|
||||
import me.vripper.utilities.HtmlUtils
|
||||
import me.vripper.utilities.LoggerDelegate
|
||||
import me.vripper.utilities.XpathUtils
|
||||
import org.w3c.dom.Document
|
||||
import org.w3c.dom.Node
|
||||
|
||||
class ImageVenueHost(
|
||||
internal class ImageVenueHost(
|
||||
httpService: HTTPService,
|
||||
dataTransaction: DataTransaction,
|
||||
downloadSpeedService: DownloadSpeedService,
|
||||
) : Host("imagevenue.com", 4, httpService, dataTransaction, downloadSpeedService) {
|
||||
private val log by me.vripper.delegate.LoggerDelegate()
|
||||
private val log by LoggerDelegate()
|
||||
|
||||
@Throws(HostException::class)
|
||||
override fun resolve(
|
||||
|
||||
@@ -4,15 +4,16 @@ import me.vripper.download.ImageDownloadContext
|
||||
import me.vripper.exception.HostException
|
||||
import me.vripper.exception.XpathException
|
||||
import me.vripper.services.*
|
||||
import me.vripper.utilities.LoggerDelegate
|
||||
import me.vripper.utilities.XpathUtils
|
||||
import org.w3c.dom.Document
|
||||
|
||||
class ImageZillaHost(
|
||||
internal class ImageZillaHost(
|
||||
httpService: HTTPService,
|
||||
dataTransaction: DataTransaction,
|
||||
downloadSpeedService: DownloadSpeedService,
|
||||
) : Host("imagezilla.net", 5, httpService, dataTransaction, downloadSpeedService) {
|
||||
private val log by me.vripper.delegate.LoggerDelegate()
|
||||
private val log by LoggerDelegate()
|
||||
|
||||
@Throws(HostException::class)
|
||||
override fun resolve(
|
||||
|
||||
@@ -4,16 +4,17 @@ import me.vripper.download.ImageDownloadContext
|
||||
import me.vripper.exception.HostException
|
||||
import me.vripper.exception.XpathException
|
||||
import me.vripper.services.*
|
||||
import me.vripper.utilities.LoggerDelegate
|
||||
import me.vripper.utilities.XpathUtils
|
||||
import org.w3c.dom.Document
|
||||
import org.w3c.dom.Node
|
||||
|
||||
class ImgSpiceHost(
|
||||
internal class ImgSpiceHost(
|
||||
httpService: HTTPService,
|
||||
dataTransaction: DataTransaction,
|
||||
downloadSpeedService: DownloadSpeedService,
|
||||
) : Host("imgspice.com", 7, httpService, dataTransaction, downloadSpeedService) {
|
||||
private val log by me.vripper.delegate.LoggerDelegate()
|
||||
private val log by LoggerDelegate()
|
||||
|
||||
@Throws(HostException::class)
|
||||
override fun resolve(
|
||||
|
||||
@@ -4,16 +4,17 @@ import me.vripper.download.ImageDownloadContext
|
||||
import me.vripper.exception.HostException
|
||||
import me.vripper.exception.XpathException
|
||||
import me.vripper.services.*
|
||||
import me.vripper.utilities.LoggerDelegate
|
||||
import me.vripper.utilities.XpathUtils
|
||||
import org.w3c.dom.Document
|
||||
import org.w3c.dom.Node
|
||||
|
||||
class ImgboxHost(
|
||||
internal class ImgboxHost(
|
||||
httpService: HTTPService,
|
||||
dataTransaction: DataTransaction,
|
||||
downloadSpeedService: DownloadSpeedService,
|
||||
) : Host("imgbox.com", 6, httpService, dataTransaction, downloadSpeedService) {
|
||||
private val log by me.vripper.delegate.LoggerDelegate()
|
||||
private val log by LoggerDelegate()
|
||||
|
||||
@Throws(HostException::class)
|
||||
override fun resolve(
|
||||
|
||||
@@ -6,6 +6,7 @@ import me.vripper.exception.HtmlProcessorException
|
||||
import me.vripper.exception.XpathException
|
||||
import me.vripper.services.*
|
||||
import me.vripper.utilities.HtmlUtils
|
||||
import me.vripper.utilities.LoggerDelegate
|
||||
import me.vripper.utilities.XpathUtils
|
||||
import org.apache.hc.client5.http.classic.methods.HttpPost
|
||||
import org.apache.hc.client5.http.entity.UrlEncodedFormEntity
|
||||
@@ -14,12 +15,12 @@ import org.w3c.dom.Document
|
||||
import org.w3c.dom.Node
|
||||
import java.io.IOException
|
||||
|
||||
class ImxHost(
|
||||
internal class ImxHost(
|
||||
private val httpService: HTTPService,
|
||||
dataTransaction: DataTransaction,
|
||||
downloadSpeedService: DownloadSpeedService,
|
||||
) : Host("imx.to", 8, httpService, dataTransaction, downloadSpeedService) {
|
||||
private val log by me.vripper.delegate.LoggerDelegate()
|
||||
private val log by LoggerDelegate()
|
||||
|
||||
@Throws(HostException::class)
|
||||
override fun resolve(
|
||||
|
||||
@@ -5,18 +5,19 @@ import me.vripper.exception.HostException
|
||||
import me.vripper.exception.XpathException
|
||||
import me.vripper.services.*
|
||||
import me.vripper.utilities.HtmlUtils
|
||||
import me.vripper.utilities.LoggerDelegate
|
||||
import me.vripper.utilities.XpathUtils
|
||||
import org.w3c.dom.Document
|
||||
import org.w3c.dom.Node
|
||||
import java.net.URI
|
||||
import java.net.URISyntaxException
|
||||
|
||||
class PimpandhostHost(
|
||||
internal class PimpandhostHost(
|
||||
httpService: HTTPService,
|
||||
dataTransaction: DataTransaction,
|
||||
downloadSpeedService: DownloadSpeedService,
|
||||
) : Host("pimpandhost.com", 9, httpService, dataTransaction, downloadSpeedService) {
|
||||
private val log by me.vripper.delegate.LoggerDelegate()
|
||||
private val log by LoggerDelegate()
|
||||
|
||||
@Throws(HostException::class)
|
||||
override fun resolve(
|
||||
|
||||
@@ -4,16 +4,17 @@ import me.vripper.download.ImageDownloadContext
|
||||
import me.vripper.exception.HostException
|
||||
import me.vripper.exception.XpathException
|
||||
import me.vripper.services.*
|
||||
import me.vripper.utilities.LoggerDelegate
|
||||
import me.vripper.utilities.XpathUtils
|
||||
import org.w3c.dom.Document
|
||||
import org.w3c.dom.Node
|
||||
|
||||
class PixRouteHost(
|
||||
internal class PixRouteHost(
|
||||
httpService: HTTPService,
|
||||
dataTransaction: DataTransaction,
|
||||
downloadSpeedService: DownloadSpeedService,
|
||||
) : Host("pixroute.com", 11, httpService, dataTransaction, downloadSpeedService) {
|
||||
private val log by me.vripper.delegate.LoggerDelegate()
|
||||
private val log by LoggerDelegate()
|
||||
|
||||
@Throws(HostException::class)
|
||||
override fun resolve(
|
||||
|
||||
@@ -4,16 +4,17 @@ import me.vripper.download.ImageDownloadContext
|
||||
import me.vripper.exception.HostException
|
||||
import me.vripper.exception.XpathException
|
||||
import me.vripper.services.*
|
||||
import me.vripper.utilities.LoggerDelegate
|
||||
import me.vripper.utilities.XpathUtils
|
||||
import org.w3c.dom.Document
|
||||
import org.w3c.dom.Node
|
||||
|
||||
class PixhostHost(
|
||||
internal class PixhostHost(
|
||||
httpService: HTTPService,
|
||||
dataTransaction: DataTransaction,
|
||||
downloadSpeedService: DownloadSpeedService,
|
||||
) : Host("pixhost.to", 10, httpService, dataTransaction, downloadSpeedService) {
|
||||
private val log by me.vripper.delegate.LoggerDelegate()
|
||||
private val log by LoggerDelegate()
|
||||
|
||||
@Throws(HostException::class)
|
||||
override fun resolve(
|
||||
|
||||
@@ -4,15 +4,16 @@ import me.vripper.download.ImageDownloadContext
|
||||
import me.vripper.exception.HostException
|
||||
import me.vripper.exception.XpathException
|
||||
import me.vripper.services.*
|
||||
import me.vripper.utilities.LoggerDelegate
|
||||
import me.vripper.utilities.XpathUtils
|
||||
import org.w3c.dom.Document
|
||||
|
||||
class PixxxelsHost(
|
||||
internal class PixxxelsHost(
|
||||
httpService: HTTPService,
|
||||
dataTransaction: DataTransaction,
|
||||
downloadSpeedService: DownloadSpeedService,
|
||||
) : Host("pixxxels.cc", 12, httpService, dataTransaction, downloadSpeedService) {
|
||||
private val log by me.vripper.delegate.LoggerDelegate()
|
||||
private val log by LoggerDelegate()
|
||||
|
||||
@Throws(HostException::class)
|
||||
override fun resolve(
|
||||
|
||||
@@ -4,17 +4,18 @@ import me.vripper.download.ImageDownloadContext
|
||||
import me.vripper.exception.HostException
|
||||
import me.vripper.exception.XpathException
|
||||
import me.vripper.services.*
|
||||
import me.vripper.utilities.LoggerDelegate
|
||||
import me.vripper.utilities.XpathUtils
|
||||
import org.w3c.dom.Document
|
||||
import org.w3c.dom.Node
|
||||
import java.util.*
|
||||
|
||||
class PostImgHost(
|
||||
internal class PostImgHost(
|
||||
httpService: HTTPService,
|
||||
dataTransaction: DataTransaction,
|
||||
downloadSpeedService: DownloadSpeedService,
|
||||
) : Host("postimg.cc", 13, httpService, dataTransaction, downloadSpeedService) {
|
||||
private val log by me.vripper.delegate.LoggerDelegate()
|
||||
private val log by LoggerDelegate()
|
||||
|
||||
@Throws(HostException::class)
|
||||
override fun resolve(
|
||||
|
||||
@@ -4,16 +4,17 @@ import me.vripper.download.ImageDownloadContext
|
||||
import me.vripper.exception.HostException
|
||||
import me.vripper.exception.XpathException
|
||||
import me.vripper.services.*
|
||||
import me.vripper.utilities.LoggerDelegate
|
||||
import me.vripper.utilities.XpathUtils
|
||||
import org.w3c.dom.Document
|
||||
import org.w3c.dom.Node
|
||||
|
||||
class TurboImageHost(
|
||||
internal class TurboImageHost(
|
||||
httpService: HTTPService,
|
||||
dataTransaction: DataTransaction,
|
||||
downloadSpeedService: DownloadSpeedService,
|
||||
) : Host("turboimagehost.com", 14, httpService, dataTransaction, downloadSpeedService) {
|
||||
private val log by me.vripper.delegate.LoggerDelegate()
|
||||
private val log by LoggerDelegate()
|
||||
|
||||
@Throws(HostException::class)
|
||||
override fun resolve(
|
||||
|
||||
@@ -4,17 +4,18 @@ import me.vripper.download.ImageDownloadContext
|
||||
import me.vripper.exception.HostException
|
||||
import me.vripper.exception.XpathException
|
||||
import me.vripper.services.*
|
||||
import me.vripper.utilities.LoggerDelegate
|
||||
import me.vripper.utilities.XpathUtils
|
||||
import org.w3c.dom.Document
|
||||
import org.w3c.dom.Node
|
||||
import java.util.*
|
||||
|
||||
class ViprImHost(
|
||||
internal class ViprImHost(
|
||||
httpService: HTTPService,
|
||||
dataTransaction: DataTransaction,
|
||||
downloadSpeedService: DownloadSpeedService,
|
||||
) : Host("vipr.im", 15, httpService, dataTransaction, downloadSpeedService) {
|
||||
private val log by me.vripper.delegate.LoggerDelegate()
|
||||
private val log by LoggerDelegate()
|
||||
|
||||
@Throws(HostException::class)
|
||||
override fun resolve(
|
||||
|
||||
@@ -11,6 +11,7 @@ open class OnStartupListener : KoinComponent {
|
||||
|
||||
open fun run() {
|
||||
dataTransaction.setDownloadingToStopped()
|
||||
dataTransaction.stopImagesByPostIdAndIsNotCompleted()
|
||||
metadataService.init()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
package me.vripper.model
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class DownloadSpeed(val speed: Long)
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
package me.vripper.model
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class ErrorCount(val count: Int)
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
package me.vripper.model
|
||||
|
||||
import me.vripper.entities.domain.Status
|
||||
import kotlinx.serialization.Contextual
|
||||
import kotlinx.serialization.Serializable
|
||||
import me.vripper.entities.Status
|
||||
import java.time.LocalDateTime
|
||||
import kotlin.io.path.Path
|
||||
import kotlin.io.path.pathString
|
||||
|
||||
@Serializable
|
||||
data class Post(
|
||||
val id: Long = -1,
|
||||
val postTitle: String,
|
||||
@@ -17,7 +20,7 @@ data class Post(
|
||||
val total: Int,
|
||||
val hosts: Set<String>,
|
||||
val downloadDirectory: String,
|
||||
val addedOn: LocalDateTime = LocalDateTime.now(),
|
||||
@Contextual val addedOn: LocalDateTime = LocalDateTime.now(),
|
||||
var folderName: String,
|
||||
var status: Status = Status.STOPPED,
|
||||
var done: Int = 0,
|
||||
@@ -28,7 +31,5 @@ data class Post(
|
||||
val postedBy: String,
|
||||
val resolvedNames: List<String>
|
||||
) {
|
||||
fun getDownloadFolder(): String {
|
||||
return Path(downloadDirectory, folderName).pathString
|
||||
}
|
||||
fun getDownloadFolder() = Path(downloadDirectory, folderName).pathString
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
package me.vripper.model
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class QueueState(
|
||||
val running: Int,
|
||||
val remaining: Int
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package me.vripper.model
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import me.vripper.utilities.ApplicationProperties.VRIPPER_DIR
|
||||
import java.nio.file.Files
|
||||
import kotlin.io.path.pathString
|
||||
|
||||
@Serializable
|
||||
data class Settings(
|
||||
@@ -21,7 +24,7 @@ data class ViperSettings(
|
||||
|
||||
@Serializable
|
||||
data class DownloadSettings(
|
||||
val downloadPath: String = System.getProperty("user.home"),
|
||||
val downloadPath: String = VRIPPER_DIR.resolve("downloads").also { Files.createDirectories(it) }.pathString,
|
||||
val autoStart: Boolean = true,
|
||||
val autoQueueThreshold: Int = 1,
|
||||
val forceOrder: Boolean = false,
|
||||
@@ -41,7 +44,7 @@ data class ConnectionSettings(
|
||||
|
||||
@Serializable
|
||||
data class SystemSettings(
|
||||
val tempPath: String = System.getProperty("java.io.tmpdir"),
|
||||
val tempPath: String = VRIPPER_DIR.resolve("temp").also { Files.createDirectories(it) }.pathString,
|
||||
val enableClipboardMonitoring: Boolean = false,
|
||||
val clipboardPollingRate: Int = 500,
|
||||
val maxEventLog: Int = 1_000,
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
package me.vripper.parser
|
||||
|
||||
import me.vripper.host.Host
|
||||
|
||||
data class ImageItem(val mainLink: String, val thumbLink: String, val host: Host)
|
||||
@@ -1,14 +0,0 @@
|
||||
package me.vripper.repositories
|
||||
|
||||
import me.vripper.entities.LogEntryEntity
|
||||
import java.util.*
|
||||
|
||||
interface LogRepository {
|
||||
fun save(logEntryEntity: LogEntryEntity): LogEntryEntity
|
||||
fun update(logEntryEntity: LogEntryEntity)
|
||||
fun findAll(): List<LogEntryEntity>
|
||||
fun findById(id: Long): Optional<LogEntryEntity>
|
||||
|
||||
fun deleteOldest(): List<Long>
|
||||
fun deleteAll()
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
package me.vripper.repositories.impl
|
||||
|
||||
import me.vripper.entities.LogEntryEntity
|
||||
import me.vripper.repositories.LogRepository
|
||||
import me.vripper.services.SettingsService
|
||||
import me.vripper.tables.LogTable
|
||||
import org.jetbrains.exposed.sql.*
|
||||
import org.jetbrains.exposed.sql.SqlExpressionBuilder.notInSubQuery
|
||||
import java.util.*
|
||||
|
||||
class LogRepositoryImpl(val settingsService: SettingsService) : LogRepository {
|
||||
|
||||
@Synchronized
|
||||
override fun save(logEntryEntity: LogEntryEntity): LogEntryEntity {
|
||||
val id = LogTable.insertAndGetId {
|
||||
it[type] = logEntryEntity.type.name
|
||||
it[status] = logEntryEntity.status.name
|
||||
it[time] = logEntryEntity.time
|
||||
it[message] = logEntryEntity.message.take(500)
|
||||
}.value
|
||||
return logEntryEntity.copy(id = id)
|
||||
}
|
||||
|
||||
override fun update(logEntryEntity: LogEntryEntity) {
|
||||
LogTable.update({ LogTable.id eq logEntryEntity.id }) {
|
||||
it[type] = logEntryEntity.type.name
|
||||
it[status] = logEntryEntity.status.name
|
||||
it[message] = logEntryEntity.message.take(500)
|
||||
}
|
||||
}
|
||||
|
||||
override fun findById(id: Long): Optional<LogEntryEntity> {
|
||||
val result = LogTable.select {
|
||||
LogTable.id eq id
|
||||
}.map(::transform)
|
||||
|
||||
return if (result.isEmpty()) {
|
||||
Optional.empty()
|
||||
} else {
|
||||
Optional.of(result.first())
|
||||
}
|
||||
}
|
||||
|
||||
override fun findAll(): List<LogEntryEntity> {
|
||||
return LogTable.selectAll().map(::transform)
|
||||
}
|
||||
|
||||
override fun deleteOldest(): List<Long> {
|
||||
val sub = LogTable.slice(LogTable.id).selectAll().orderBy(LogTable.time to SortOrder.DESC)
|
||||
.limit(settingsService.settings.systemSettings.maxEventLog)
|
||||
val ids = LogTable.select { LogTable.id notInSubQuery sub }.map { it[LogTable.id].value }
|
||||
LogTable.deleteWhere { LogTable.id notInSubQuery sub }
|
||||
return ids
|
||||
}
|
||||
|
||||
override fun deleteAll() {
|
||||
LogTable.deleteAll()
|
||||
}
|
||||
|
||||
private fun transform(resultRow: ResultRow): LogEntryEntity {
|
||||
val id = resultRow[LogTable.id].value
|
||||
val type = LogEntryEntity.Type.valueOf(resultRow[LogTable.type])
|
||||
val status = LogEntryEntity.Status.valueOf(resultRow[LogTable.status])
|
||||
val time = resultRow[LogTable.time]
|
||||
val message = resultRow[LogTable.message]
|
||||
return LogEntryEntity(
|
||||
id,
|
||||
type,
|
||||
status,
|
||||
time,
|
||||
message
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,24 @@
|
||||
package me.vripper.services
|
||||
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.time.sample
|
||||
import kotlinx.serialization.json.Json
|
||||
import me.vripper.download.DownloadService
|
||||
import me.vripper.entities.LogEntryEntity
|
||||
import me.vripper.entities.PostEntity
|
||||
import me.vripper.entities.*
|
||||
import me.vripper.event.*
|
||||
import me.vripper.exception.PostParseException
|
||||
import me.vripper.model.*
|
||||
import me.vripper.tasks.AddPostRunnable
|
||||
import me.vripper.tasks.ThreadLookupRunnable
|
||||
import me.vripper.tasks.AddPostTask
|
||||
import me.vripper.tasks.ThreadLookupTask
|
||||
import me.vripper.utilities.ApplicationProperties
|
||||
import me.vripper.utilities.GLOBAL_EXECUTOR
|
||||
import me.vripper.utilities.ApplicationProperties.VRIPPER_DIR
|
||||
import me.vripper.utilities.GlobalScopeCoroutine
|
||||
import me.vripper.utilities.LoggerDelegate
|
||||
import me.vripper.utilities.PathUtils
|
||||
import org.h2.jdbc.JdbcSQLNonTransientConnectionException
|
||||
import java.sql.DriverManager
|
||||
import java.time.Duration
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
import java.util.regex.Pattern
|
||||
import kotlin.concurrent.withLock
|
||||
@@ -22,15 +26,16 @@ import kotlin.io.path.Path
|
||||
import kotlin.io.path.exists
|
||||
import kotlin.jvm.optionals.getOrNull
|
||||
|
||||
class AppEndpointService(
|
||||
internal class AppEndpointService(
|
||||
private val downloadService: DownloadService,
|
||||
private val dataTransaction: DataTransaction,
|
||||
private val threadCacheService: ThreadCacheService,
|
||||
private val settingsService: SettingsService,
|
||||
private val vgAuthService: VGAuthService,
|
||||
private val logService: LogService,
|
||||
) : IAppEndpointService {
|
||||
private val log by me.vripper.delegate.LoggerDelegate()
|
||||
|
||||
private val log by LoggerDelegate()
|
||||
private val lock = ReentrantLock()
|
||||
|
||||
override suspend fun scanLinks(postLinks: String) {
|
||||
@@ -54,27 +59,20 @@ class AppEndpointService(
|
||||
threadId = m.group(1).toLong()
|
||||
postId = m.group(4)?.toLong()
|
||||
if (postId == null) {
|
||||
CompletableFuture.runAsync(
|
||||
ThreadLookupRunnable(
|
||||
GlobalScopeCoroutine.launch {
|
||||
ThreadLookupTask(
|
||||
threadId, settingsService.settings
|
||||
), GLOBAL_EXECUTOR
|
||||
)
|
||||
).run()
|
||||
}
|
||||
} else {
|
||||
CompletableFuture.runAsync(
|
||||
AddPostRunnable(
|
||||
GlobalScopeCoroutine.launch {
|
||||
AddPostTask(
|
||||
listOf(ThreadPostId(threadId, postId))
|
||||
), GLOBAL_EXECUTOR
|
||||
)
|
||||
).run()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.error("Cannot retrieve thread id from URL $link")
|
||||
dataTransaction.saveLog(
|
||||
LogEntryEntity(
|
||||
type = LogEntryEntity.Type.SCAN,
|
||||
status = LogEntryEntity.Status.ERROR,
|
||||
message = "Invalid link $link, link is missing the threadId"
|
||||
)
|
||||
)
|
||||
log.error("Invalid link $link, link is missing the threadId")
|
||||
continue
|
||||
}
|
||||
}
|
||||
@@ -89,9 +87,9 @@ class AppEndpointService(
|
||||
}
|
||||
|
||||
override suspend fun download(posts: List<ThreadPostId>) {
|
||||
CompletableFuture.runAsync(
|
||||
AddPostRunnable(posts), GLOBAL_EXECUTOR
|
||||
)
|
||||
GlobalScopeCoroutine.launch {
|
||||
AddPostTask(posts).run()
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun stopAll(postIdList: List<Long>) {
|
||||
@@ -172,12 +170,6 @@ class AppEndpointService(
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun logClear() {
|
||||
lock.withLock {
|
||||
dataTransaction.deleteAllLogs()
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun renameToFirst(postIds: List<Long>) {
|
||||
postIds.forEach { postId ->
|
||||
dataTransaction
|
||||
@@ -188,8 +180,12 @@ class AppEndpointService(
|
||||
}
|
||||
}
|
||||
|
||||
override fun ready(): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override suspend fun rename(postId: Long, newName: String) {
|
||||
CompletableFuture.runAsync({
|
||||
GlobalScopeCoroutine.launch {
|
||||
synchronized(postId.toString().intern()) {
|
||||
dataTransaction.findPostByPostId(postId).ifPresent { post ->
|
||||
if (Path(post.downloadDirectory, post.folderName).exists()) {
|
||||
@@ -201,7 +197,7 @@ class AppEndpointService(
|
||||
dataTransaction.updatePost(post)
|
||||
}
|
||||
}
|
||||
}, GLOBAL_EXECUTOR)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onNewPosts(): Flow<Post> =
|
||||
@@ -262,31 +258,20 @@ class AppEndpointService(
|
||||
return dataTransaction.findImagesByPostId(postId)
|
||||
}
|
||||
|
||||
override fun onUpdateImages(postId: Long): Flow<Image> = EventBus.events.filterIsInstance(ImageEvent::class).map {
|
||||
it.imageEntities.filter { imageEntity: Image -> imageEntity.postId == postId }
|
||||
}.filter { it.isNotEmpty() }.flatMapConcat { it.asFlow() }
|
||||
override fun onUpdateImagesByPostId(postId: Long): Flow<Image> =
|
||||
EventBus.events.filterIsInstance(ImageEvent::class).map {
|
||||
it.imageEntities.filter { imageEntity: Image -> imageEntity.postId == postId }
|
||||
}.filter { it.isNotEmpty() }.flatMapConcat { it.asFlow() }
|
||||
|
||||
override fun onUpdateImages(): Flow<Image> =
|
||||
EventBus.events.filterIsInstance(ImageEvent::class).flatMapConcat { it.imageEntities.asFlow() }
|
||||
|
||||
override fun onStopped(): Flow<Long> =
|
||||
EventBus.events.filterIsInstance(StoppedEvent::class).flatMapConcat { it.postIds.asFlow() }
|
||||
|
||||
|
||||
override suspend fun findAllLogs(): List<LogEntry> {
|
||||
return dataTransaction.findAllLogs()
|
||||
}
|
||||
|
||||
override fun onNewLog() = EventBus.events.filterIsInstance(LogCreateEvent::class).map { it.logEntryEntity }
|
||||
|
||||
|
||||
override fun onUpdateLog() = EventBus.events.filterIsInstance(LogUpdateEvent::class).map {
|
||||
it.logEntryEntity
|
||||
}
|
||||
|
||||
|
||||
override fun onDeleteLogs(): Flow<Long> = EventBus.events.filterIsInstance(LogDeleteEvent::class).flatMapConcat {
|
||||
it.deleted.asFlow()
|
||||
}
|
||||
|
||||
override suspend fun initLogger() = logService.init()
|
||||
|
||||
override fun onNewThread(): Flow<Thread> =
|
||||
EventBus.events.filterIsInstance(ThreadCreateEvent::class).map { it.threadEntity }
|
||||
@@ -307,8 +292,8 @@ class AppEndpointService(
|
||||
return dataTransaction.findAllThreads()
|
||||
}
|
||||
|
||||
override fun onDownloadSpeed(): Flow<Long> =
|
||||
EventBus.events.filterIsInstance(DownloadSpeedEvent::class).map { it.downloadSpeed.speed }
|
||||
override fun onDownloadSpeed(): Flow<DownloadSpeed> =
|
||||
EventBus.events.filterIsInstance(DownloadSpeedEvent::class).map { it.downloadSpeed }
|
||||
|
||||
override fun onVGUserUpdate(): Flow<String> =
|
||||
EventBus.events.filterIsInstance(VGUserLoginEvent::class).map { it.username }
|
||||
@@ -316,8 +301,8 @@ class AppEndpointService(
|
||||
override fun onQueueStateUpdate(): Flow<QueueState> =
|
||||
EventBus.events.filterIsInstance(QueueStateEvent::class).map { it.queueState }
|
||||
|
||||
override fun onErrorCountUpdate(): Flow<Int> =
|
||||
EventBus.events.filterIsInstance(ErrorCountEvent::class).map { it.errorCount.count }
|
||||
override fun onErrorCountUpdate(): Flow<ErrorCount> =
|
||||
EventBus.events.filterIsInstance(ErrorCountEvent::class).map { it.errorCount }
|
||||
|
||||
override fun onTasksRunning(): Flow<Boolean> =
|
||||
EventBus.events.filterIsInstance(LoadingTasks::class).sample(Duration.ofMillis(500)).map { it.loading }
|
||||
@@ -335,4 +320,145 @@ class AppEndpointService(
|
||||
|
||||
override suspend fun getVersion(): String = ApplicationProperties.VERSION
|
||||
|
||||
override suspend fun dbMigration(): String {
|
||||
|
||||
val conn = try {
|
||||
DriverManager
|
||||
.getConnection("jdbc:h2:file:$VRIPPER_DIR/vripper;DB_CLOSE_DELAY=-1;IFEXISTS=TRUE")
|
||||
} catch (_: JdbcSQLNonTransientConnectionException) {
|
||||
return "Old database not found, nothing to do"
|
||||
}
|
||||
|
||||
var postsCount = 0;
|
||||
var threadCount = 0;
|
||||
|
||||
conn.use { conn ->
|
||||
conn.prepareStatement("select * from post").use {
|
||||
it.executeQuery().use {
|
||||
while (it.next()) {
|
||||
val done = it.getInt("DONE")
|
||||
val hosts = it.getString("HOSTS")
|
||||
val outputPath = it.getString("OUTPUT_PATH")
|
||||
val postId = it.getLong("POST_ID")
|
||||
val status = it.getString("STATUS")
|
||||
val threadId = it.getLong("THREAD_ID")
|
||||
val postTitle = it.getString("POST_TITLE")
|
||||
val threadTitle = it.getString("THREAD_TITLE")
|
||||
val forum = it.getString("FORUM")
|
||||
val total = it.getInt("TOTAL")
|
||||
val size = it.getLong("SIZE")
|
||||
val downloaded = it.getLong("DOWNLOADED")
|
||||
val url = it.getString("URL")
|
||||
val token = it.getString("TOKEN")
|
||||
val addedAt = it.getTimestamp("ADDED_AT")
|
||||
val folderName = it.getString("FOLDER_NAME") ?: ""
|
||||
|
||||
val exists = dataTransaction.findPostByPostId(postId).isPresent
|
||||
if (exists) {
|
||||
continue
|
||||
}
|
||||
|
||||
val post = PostEntity(
|
||||
postTitle = postTitle,
|
||||
threadTitle = threadTitle,
|
||||
forum = forum,
|
||||
url = url,
|
||||
token = token,
|
||||
postId = postId,
|
||||
threadId = threadId,
|
||||
total = total,
|
||||
hosts = hosts.split(";").dropLastWhile { it.isEmpty() }.toSet(),
|
||||
downloadDirectory = outputPath,
|
||||
addedOn = addedAt.toLocalDateTime(),
|
||||
folderName = folderName,
|
||||
status = Status.valueOf(status),
|
||||
done = done,
|
||||
size = size,
|
||||
downloaded = downloaded,
|
||||
)
|
||||
|
||||
val images = mutableListOf<ImageEntity>()
|
||||
|
||||
// load images
|
||||
conn.prepareStatement("select * from image where post_id = ?").use { statement ->
|
||||
statement.setLong(1, postId)
|
||||
statement.executeQuery().use { set ->
|
||||
while (set.next()) {
|
||||
val downloaded = set.getLong("DOWNLOADED")
|
||||
val host = set.getByte("HOST")
|
||||
val index = set.getInt("INDEX")
|
||||
val status = Status.valueOf(set.getString("STATUS"))
|
||||
val size = set.getLong("SIZE")
|
||||
val url = set.getString("URL")
|
||||
val thumbUrl = set.getString("THUMB_URL")
|
||||
val fileName = set.getString("FILENAME") ?: ""
|
||||
|
||||
ImageEntity(
|
||||
postId = postId,
|
||||
url = url,
|
||||
thumbUrl = thumbUrl,
|
||||
host = host,
|
||||
index = index,
|
||||
size = size,
|
||||
downloaded = downloaded,
|
||||
status = status,
|
||||
filename = fileName,
|
||||
).also { images.add(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dataTransaction.saveAndNotify(post, images)
|
||||
|
||||
//load meta
|
||||
conn.prepareStatement("select * from metadata where post_id = ?").use { statement ->
|
||||
statement.setLong(1, postId)
|
||||
statement.executeQuery().use { set ->
|
||||
if (set.next()) {
|
||||
val data = Json.decodeFromString(set.getString("DATA")) as MetadataEntity.Data
|
||||
val metadata = MetadataEntity(
|
||||
postId = postId,
|
||||
data = data
|
||||
)
|
||||
dataTransaction.saveMetadata(metadata)
|
||||
}
|
||||
}
|
||||
}
|
||||
postsCount++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
conn.prepareStatement("select * from thread").use { statement ->
|
||||
statement.executeQuery().use { set ->
|
||||
while (set.next()) {
|
||||
val total = set.getInt("TOTAL")
|
||||
val url = set.getString("URL")
|
||||
val threadId = set.getLong("THREAD_ID")
|
||||
val title = set.getString("TITLE")
|
||||
|
||||
if (dataTransaction.findThreadByThreadId(threadId).isPresent) {
|
||||
continue
|
||||
}
|
||||
|
||||
val threadEntity = ThreadEntity(
|
||||
title = title,
|
||||
link = url,
|
||||
threadId = threadId,
|
||||
total = total,
|
||||
)
|
||||
|
||||
dataTransaction.save(threadEntity)
|
||||
threadCount++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return if (postsCount == 0 && threadCount == 0) {
|
||||
"Nothing to do, everything was imported"
|
||||
} else {
|
||||
"Successfully imported $postsCount posts and $threadCount threads"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,34 +4,49 @@ import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.launch
|
||||
import me.vripper.data.repositories.ImageRepository
|
||||
import me.vripper.data.repositories.MetadataRepository
|
||||
import me.vripper.data.repositories.PostDownloadStateRepository
|
||||
import me.vripper.data.repositories.ThreadRepository
|
||||
import me.vripper.entities.*
|
||||
import me.vripper.entities.domain.Status
|
||||
import me.vripper.event.*
|
||||
import me.vripper.model.ErrorCount
|
||||
import me.vripper.parser.PostItem
|
||||
import me.vripper.repositories.*
|
||||
import me.vripper.utilities.PathUtils
|
||||
import me.vripper.utilities.PathUtils.sanitize
|
||||
import me.vripper.vgapi.PostItem
|
||||
import org.jetbrains.exposed.sql.transactions.transaction
|
||||
import java.util.*
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import kotlin.io.path.pathString
|
||||
|
||||
class DataTransaction(
|
||||
internal class DataTransaction(
|
||||
private val settingsService: SettingsService,
|
||||
private val postDownloadStateRepository: PostDownloadStateRepository,
|
||||
private val imageRepository: ImageRepository,
|
||||
private val threadRepository: ThreadRepository,
|
||||
private val metadataRepository: MetadataRepository,
|
||||
private val logRepository: LogRepository,
|
||||
private val eventBus: EventBus,
|
||||
) {
|
||||
|
||||
private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
|
||||
private val nextRank = AtomicInteger(transaction { getQueuePosition() }?.plus(1) ?: 0)
|
||||
|
||||
private fun save(postEntities: List<PostEntity>): List<PostEntity> {
|
||||
return transaction { postDownloadStateRepository.save(postEntities) }
|
||||
}
|
||||
|
||||
fun saveAndNotify(postEntity: PostEntity, images: List<ImageEntity>) {
|
||||
val savedPost = transaction {
|
||||
val savedPost =
|
||||
postDownloadStateRepository.save(listOf(postEntity.copy(rank = nextRank.andIncrement))).first()
|
||||
save(images.map { it.copy(postIdRef = savedPost.id) })
|
||||
savedPost
|
||||
}
|
||||
coroutineScope.launch {
|
||||
eventBus.publishEvent(PostCreateEvent(listOf(savedPost)))
|
||||
}
|
||||
}
|
||||
|
||||
fun updatePosts(postEntities: List<PostEntity>) {
|
||||
transaction { postDownloadStateRepository.update(postEntities) }
|
||||
coroutineScope.launch {
|
||||
@@ -67,8 +82,12 @@ class DataTransaction(
|
||||
}
|
||||
}
|
||||
|
||||
fun updateImage(imageEntity: ImageEntity) {
|
||||
transaction { imageRepository.update(imageEntity) }
|
||||
fun updateImage(imageEntity: ImageEntity, persist: Boolean = true) {
|
||||
if (persist) {
|
||||
transaction {
|
||||
imageRepository.update(imageEntity)
|
||||
}
|
||||
}
|
||||
coroutineScope.launch {
|
||||
eventBus.publishEvent(ImageEvent(listOf(imageEntity)))
|
||||
}
|
||||
@@ -80,10 +99,6 @@ class DataTransaction(
|
||||
|
||||
@Synchronized
|
||||
fun newPosts(postItems: List<PostItem>): List<PostEntity> {
|
||||
var queuePosition = transaction {
|
||||
getQueuePosition()?.plus(1) ?: 0
|
||||
}
|
||||
|
||||
val posts = postItems.associate { postItem ->
|
||||
val postEntity = PostEntity(
|
||||
postTitle = postItem.title,
|
||||
@@ -95,7 +110,7 @@ class DataTransaction(
|
||||
hosts = postItem.hosts.map { "${it.first} (${it.second})" }.toSet(),
|
||||
threadTitle = postItem.threadTitle,
|
||||
forum = postItem.forum,
|
||||
rank = queuePosition++,
|
||||
rank = nextRank.andIncrement,
|
||||
downloadDirectory = PathUtils.calculateDownloadPath(
|
||||
postItem.forum,
|
||||
postItem.threadTitle,
|
||||
@@ -210,6 +225,10 @@ class DataTransaction(
|
||||
transaction { imageRepository.stopByPostIdAndIsNotCompleted(postId) }
|
||||
}
|
||||
|
||||
fun stopImagesByPostIdAndIsNotCompleted() {
|
||||
transaction { imageRepository.stopByPostIdAndIsNotCompleted() }
|
||||
}
|
||||
|
||||
fun saveMetadata(metadataEntity: MetadataEntity) {
|
||||
transaction { metadataRepository.save(metadataEntity) }
|
||||
coroutineScope.launch {
|
||||
@@ -226,11 +245,15 @@ class DataTransaction(
|
||||
|
||||
@Synchronized
|
||||
fun sortPostsByRank() {
|
||||
val postEntities = findAllPosts().sortedWith(Comparator.comparing(PostEntity::addedOn))
|
||||
val postsToUpdate = mutableListOf<PostEntity>()
|
||||
val postEntities = findAllPosts().sortedWith(Comparator.comparing(PostEntity::rank))
|
||||
for (i in postEntities.indices) {
|
||||
postEntities[i].rank = i
|
||||
if (postEntities[i].rank != i) {
|
||||
postsToUpdate.add(postEntities[i].copy(rank = i))
|
||||
}
|
||||
}
|
||||
updatePosts(postEntities)
|
||||
updatePosts(postsToUpdate)
|
||||
nextRank.set(transaction { getQueuePosition() }?.plus(1) ?: 0)
|
||||
}
|
||||
|
||||
fun setDownloadingToStopped() {
|
||||
@@ -279,36 +302,6 @@ class DataTransaction(
|
||||
return transaction { threadRepository.findByThreadId(threadId) }
|
||||
}
|
||||
|
||||
fun saveLog(logEntryEntity: LogEntryEntity): LogEntryEntity {
|
||||
val pair = transaction {
|
||||
val saved = logRepository.save(logEntryEntity)
|
||||
val deleted = logRepository.deleteOldest()
|
||||
Pair(saved, deleted)
|
||||
}
|
||||
coroutineScope.launch {
|
||||
eventBus.publishEvent(LogCreateEvent(pair.first))
|
||||
}
|
||||
coroutineScope.launch {
|
||||
eventBus.publishEvent(LogDeleteEvent(pair.second))
|
||||
}
|
||||
return pair.first
|
||||
}
|
||||
|
||||
fun updateLog(logEntryEntity: LogEntryEntity) {
|
||||
transaction { logRepository.update(logEntryEntity) }
|
||||
coroutineScope.launch {
|
||||
eventBus.publishEvent(LogUpdateEvent(logEntryEntity))
|
||||
}
|
||||
}
|
||||
|
||||
fun deleteAllLogs() {
|
||||
transaction { logRepository.deleteAll() }
|
||||
}
|
||||
|
||||
fun findAllLogs(): List<LogEntryEntity> {
|
||||
return transaction { logRepository.findAll() }
|
||||
}
|
||||
|
||||
fun findAllNonCompletedPostIds(): List<Long> {
|
||||
return transaction { postDownloadStateRepository.findAllNonCompletedPostIds() }
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import me.vripper.event.QueueStateEvent
|
||||
import me.vripper.model.DownloadSpeed
|
||||
import java.util.concurrent.atomic.AtomicLong
|
||||
|
||||
class DownloadSpeedService(
|
||||
internal class DownloadSpeedService(
|
||||
private val eventBus: EventBus,
|
||||
) {
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ import org.apache.hc.core5.pool.PoolReusePolicy
|
||||
import org.apache.hc.core5.util.TimeValue
|
||||
import org.apache.hc.core5.util.Timeout
|
||||
|
||||
class HTTPService(
|
||||
internal class HTTPService(
|
||||
private val eventBus: EventBus,
|
||||
settingsService: SettingsService
|
||||
) {
|
||||
|
||||
@@ -17,13 +17,10 @@ interface IAppEndpointService {
|
||||
fun onDeletePosts(): Flow<Long>
|
||||
fun onUpdateMetadata(): Flow<Metadata>
|
||||
suspend fun findImagesByPostId(postId: Long): List<Image>
|
||||
fun onUpdateImages(postId: Long): Flow<Image>
|
||||
fun onUpdateImagesByPostId(postId: Long): Flow<Image>
|
||||
fun onUpdateImages(): Flow<Image>
|
||||
fun onStopped(): Flow<Long>
|
||||
suspend fun logClear()
|
||||
suspend fun findAllLogs(): List<LogEntry>
|
||||
fun onNewLog(): Flow<LogEntry>
|
||||
fun onUpdateLog(): Flow<LogEntry>
|
||||
fun onDeleteLogs(): Flow<Long>
|
||||
fun onNewThread(): Flow<Thread>
|
||||
fun onUpdateThread(): Flow<Thread>
|
||||
fun onDeleteThread(): Flow<Long>
|
||||
@@ -33,10 +30,10 @@ interface IAppEndpointService {
|
||||
suspend fun threadClear()
|
||||
suspend fun grab(threadId: Long): List<PostSelection>
|
||||
suspend fun download(posts: List<ThreadPostId>)
|
||||
fun onDownloadSpeed(): Flow<Long>
|
||||
fun onDownloadSpeed(): Flow<DownloadSpeed>
|
||||
fun onVGUserUpdate(): Flow<String>
|
||||
fun onQueueStateUpdate(): Flow<QueueState>
|
||||
fun onErrorCountUpdate(): Flow<Int>
|
||||
fun onErrorCountUpdate(): Flow<ErrorCount>
|
||||
fun onTasksRunning(): Flow<Boolean>
|
||||
suspend fun getSettings(): Settings
|
||||
suspend fun saveSettings(settings: Settings)
|
||||
@@ -45,4 +42,7 @@ interface IAppEndpointService {
|
||||
suspend fun loggedInUser(): String
|
||||
suspend fun getVersion(): String
|
||||
suspend fun renameToFirst(postIds: List<Long>)
|
||||
fun ready(): Boolean
|
||||
suspend fun dbMigration(): String
|
||||
suspend fun initLogger()
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package me.vripper.services
|
||||
|
||||
import kotlinx.coroutines.*
|
||||
import me.vripper.RingAppender
|
||||
import me.vripper.entities.LogEntryEntity
|
||||
import me.vripper.event.EventBus
|
||||
import me.vripper.event.LogCreateEvent
|
||||
import me.vripper.utilities.StackTraceElementUtils
|
||||
import java.time.ZoneId
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
class LogService(private val eventBus: EventBus) {
|
||||
private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
|
||||
private val dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS")
|
||||
private var job: Job? = null
|
||||
|
||||
fun init() {
|
||||
if (job != null) {
|
||||
return
|
||||
}
|
||||
job = coroutineScope.launch {
|
||||
while (isActive) {
|
||||
while (!RingAppender.Static.events.isEmpty()) {
|
||||
RingAppender.Static.events.poll()?.also { loggingEvent ->
|
||||
eventBus.publishEvent(
|
||||
LogCreateEvent(
|
||||
LogEntryEntity(
|
||||
loggingEvent.sequenceNumber,
|
||||
loggingEvent.instant.atZone(ZoneId.systemDefault()).toLocalDateTime()
|
||||
.format(dateTimeFormatter),
|
||||
loggingEvent.threadName,
|
||||
loggingEvent.loggerName,
|
||||
loggingEvent.level.levelStr,
|
||||
loggingEvent.formattedMessage,
|
||||
loggingEvent.throwableProxy?.let { throwableProxy ->
|
||||
StackTraceElementUtils.format(
|
||||
throwableProxy
|
||||
)
|
||||
} ?: ""
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
delay(777)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,17 +8,18 @@ import kotlinx.coroutines.sync.withPermit
|
||||
import me.vripper.entities.MetadataEntity
|
||||
import me.vripper.exception.DownloadException
|
||||
import me.vripper.exception.VripperException
|
||||
import me.vripper.tasks.Tasks
|
||||
import me.vripper.utilities.HtmlUtils
|
||||
import me.vripper.utilities.RequestLimit
|
||||
import me.vripper.utilities.Tasks
|
||||
import me.vripper.utilities.XpathUtils
|
||||
import org.apache.hc.client5.http.classic.methods.HttpGet
|
||||
import org.apache.hc.core5.http.io.entity.EntityUtils
|
||||
import org.apache.hc.core5.net.URIBuilder
|
||||
import org.w3c.dom.Node
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import java.util.stream.Collectors
|
||||
|
||||
class MetadataService(
|
||||
internal class MetadataService(
|
||||
private val httpService: HTTPService,
|
||||
private val settingsService: SettingsService,
|
||||
private val dataTransaction: DataTransaction,
|
||||
@@ -36,45 +37,42 @@ class MetadataService(
|
||||
fun fetchMetadata(postId: Long) {
|
||||
Tasks.increment()
|
||||
coroutineScope.launch {
|
||||
RequestLimit.semaphore.withPermit {
|
||||
try {
|
||||
val httpGet = HttpGet(URIBuilder(settingsService.settings.viperSettings.host + "/threads/").also {
|
||||
it.setParameter(
|
||||
"p", postId.toString()
|
||||
)
|
||||
}.build())
|
||||
try {
|
||||
val httpGet = HttpGet(URIBuilder(settingsService.settings.viperSettings.host + "/threads/").also {
|
||||
it.setParameter(
|
||||
"p", postId.toString()
|
||||
)
|
||||
}.build())
|
||||
|
||||
val metadataEntity = httpService.client.execute(httpGet, vgAuthService.context) {
|
||||
val response = RequestLimit.semaphore.withPermit {
|
||||
httpService.client.execute(httpGet, vgAuthService.context) {
|
||||
if (it.code / 100 != 2) {
|
||||
throw DownloadException("Unexpected response code '${it.code}' for $httpGet")
|
||||
}
|
||||
val document = HtmlUtils.clean(it.entity.content)
|
||||
val postNode: Node = XpathUtils.getAsNode(
|
||||
document,
|
||||
"//li[@id='post_$postId']/div[contains(@class, 'postdetails')]",
|
||||
|
||||
) ?: throw VripperException("Unable to find post #'$postId'")
|
||||
|
||||
val postedBy: String = XpathUtils.getAsNode(
|
||||
postNode, "./div[contains(@class, 'userinfo')]//a[contains(@class, 'username')]//font"
|
||||
)?.textContent?.trim()
|
||||
?: throw VripperException("Unable to find the poster for post #'$postId'")
|
||||
|
||||
|
||||
val node: Node = XpathUtils.getAsNode(
|
||||
document, java.lang.String.format("//div[@id='post_message_%s']", postId)
|
||||
) ?: throw VripperException("Unable to locate post content")
|
||||
val titles = findTitleInContent(node)
|
||||
MetadataEntity(postId, MetadataEntity.Data(postedBy, titles))
|
||||
EntityUtils.toString(it.entity)
|
||||
}
|
||||
try {
|
||||
dataTransaction.saveMetadata(metadataEntity)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
} finally {
|
||||
Tasks.decrement()
|
||||
}
|
||||
val document = HtmlUtils.clean(response)
|
||||
val postNode: Node = XpathUtils.getAsNode(
|
||||
document,
|
||||
"//li[@id='post_$postId']/div[contains(@class, 'postdetails')]",
|
||||
|
||||
) ?: throw VripperException("Unable to find post #'$postId'")
|
||||
|
||||
val postedBy: String = XpathUtils.getAsNode(
|
||||
postNode, "./div[contains(@class, 'userinfo')]//a[contains(@class, 'username')]//font"
|
||||
)?.textContent?.trim()
|
||||
?: throw VripperException("Unable to find the poster for post #'$postId'")
|
||||
|
||||
|
||||
val node: Node = XpathUtils.getAsNode(
|
||||
document, java.lang.String.format("//div[@id='post_message_%s']", postId)
|
||||
) ?: throw VripperException("Unable to locate post content")
|
||||
val titles = findTitleInContent(node)
|
||||
val metadataEntity = MetadataEntity(postId, MetadataEntity.Data(postedBy, titles))
|
||||
dataTransaction.saveMetadata(metadataEntity)
|
||||
} finally {
|
||||
Tasks.decrement()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,14 +7,15 @@ import kotlinx.coroutines.flow.filterIsInstance
|
||||
import kotlinx.coroutines.launch
|
||||
import me.vripper.event.EventBus
|
||||
import me.vripper.event.SettingsUpdateEvent
|
||||
import me.vripper.utilities.LoggerDelegate
|
||||
import net.jodah.failsafe.RetryPolicy
|
||||
import net.jodah.failsafe.event.ExecutionAttemptedEvent
|
||||
import java.time.temporal.ChronoUnit
|
||||
|
||||
class RetryPolicyService(
|
||||
internal class RetryPolicyService(
|
||||
val eventBus: EventBus, settingsService: SettingsService
|
||||
) {
|
||||
private val log by me.vripper.delegate.LoggerDelegate()
|
||||
private val log by LoggerDelegate()
|
||||
private var maxAttempts: Int = settingsService.settings.connectionSettings.maxAttempts
|
||||
private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
|
||||
|
||||
@@ -28,16 +29,16 @@ class RetryPolicyService(
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> buildRetryPolicyForDownload(): RetryPolicy<T> {
|
||||
fun <T> buildRetryPolicyForDownload(message: String): RetryPolicy<T> {
|
||||
return RetryPolicy<T>().withDelay(2, 5, ChronoUnit.SECONDS).withMaxAttempts(maxAttempts).onFailedAttempt {
|
||||
log.warn("#${it.attemptCount} tries failed", it.lastFailure)
|
||||
log.warn(message + "#${it.attemptCount} tries failed", it.lastFailure)
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> buildGenericRetryPolicy(): RetryPolicy<T> {
|
||||
fun <T> buildGenericRetryPolicy(message: String): RetryPolicy<T> {
|
||||
return RetryPolicy<T>().withDelay(2, 5, ChronoUnit.SECONDS).withMaxAttempts(maxAttempts)
|
||||
.onFailedAttempt { e: ExecutionAttemptedEvent<T> ->
|
||||
log.warn("#${e.attemptCount} tries failed", e.lastFailure)
|
||||
log.warn(message + "#${e.attemptCount} tries failed", e.lastFailure)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import me.vripper.event.SettingsUpdateEvent
|
||||
import me.vripper.exception.ValidationException
|
||||
import me.vripper.model.Settings
|
||||
import me.vripper.utilities.ApplicationProperties.VRIPPER_DIR
|
||||
import me.vripper.utilities.LoggerDelegate
|
||||
import org.apache.commons.codec.digest.DigestUtils
|
||||
import java.io.FileWriter
|
||||
import java.nio.file.*
|
||||
@@ -19,7 +20,7 @@ import kotlin.io.path.readText
|
||||
|
||||
class SettingsService(private val eventBus: EventBus) {
|
||||
|
||||
private val log by me.vripper.delegate.LoggerDelegate()
|
||||
private val log by LoggerDelegate()
|
||||
private val configPath = VRIPPER_DIR.resolve("config.json")
|
||||
private val customProxiesPath = VRIPPER_DIR.resolve("proxies.json")
|
||||
private val proxies: MutableSet<String> = HashSet()
|
||||
@@ -42,7 +43,6 @@ class SettingsService(private val eventBus: EventBus) {
|
||||
coroutineScope.launch {
|
||||
eventBus.publishEvent(SettingsUpdateEvent(settings))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun loadViperProxies() {
|
||||
@@ -57,7 +57,7 @@ class SettingsService(private val eventBus: EventBus) {
|
||||
customProxiesPath.readText()
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
log.error("Failed to read custom proxies", e)
|
||||
emptyList()
|
||||
}
|
||||
} else {
|
||||
@@ -111,7 +111,7 @@ class SettingsService(private val eventBus: EventBus) {
|
||||
settings = json.decodeFromString(configPath.readText())
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
log.error("Failed restore user settings", e)
|
||||
log.error("Failed to restore user settings", e)
|
||||
settings = Settings()
|
||||
}
|
||||
if (!proxies.contains(settings.viperSettings.host)) {
|
||||
|
||||
@@ -4,17 +4,14 @@ import com.github.benmanes.caffeine.cache.Caffeine
|
||||
import com.github.benmanes.caffeine.cache.LoadingCache
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.filterIsInstance
|
||||
import kotlinx.coroutines.sync.withPermit
|
||||
import me.vripper.event.EventBus
|
||||
import me.vripper.event.SettingsUpdateEvent
|
||||
import me.vripper.parser.ThreadItem
|
||||
import me.vripper.parser.ThreadLookupAPIParser
|
||||
import me.vripper.utilities.RequestLimit
|
||||
import java.util.*
|
||||
import me.vripper.vgapi.ThreadItem
|
||||
import me.vripper.vgapi.ThreadLookupAPIParser
|
||||
import java.util.concurrent.ExecutionException
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class ThreadCacheService(val eventBus: EventBus) {
|
||||
internal class ThreadCacheService(val eventBus: EventBus) {
|
||||
|
||||
private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
|
||||
|
||||
@@ -29,9 +26,7 @@ class ThreadCacheService(val eventBus: EventBus) {
|
||||
private val cache: LoadingCache<Long, ThreadItem> =
|
||||
Caffeine.newBuilder().expireAfterWrite(20, TimeUnit.MINUTES).build { threadId ->
|
||||
runBlocking {
|
||||
RequestLimit.semaphore.withPermit {
|
||||
ThreadLookupAPIParser(threadId).parse()
|
||||
}
|
||||
ThreadLookupAPIParser(threadId).parse()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,8 +11,9 @@ import me.vripper.event.SettingsUpdateEvent
|
||||
import me.vripper.event.VGUserLoginEvent
|
||||
import me.vripper.exception.VripperException
|
||||
import me.vripper.model.Settings
|
||||
import me.vripper.tasks.LeaveThanksRunnable
|
||||
import me.vripper.utilities.GLOBAL_EXECUTOR
|
||||
import me.vripper.tasks.LeaveThanksTask
|
||||
import me.vripper.utilities.GlobalScopeCoroutine
|
||||
import me.vripper.utilities.LoggerDelegate
|
||||
import org.apache.hc.client5.http.classic.methods.HttpPost
|
||||
import org.apache.hc.client5.http.cookie.BasicCookieStore
|
||||
import org.apache.hc.client5.http.cookie.Cookie
|
||||
@@ -20,15 +21,14 @@ import org.apache.hc.client5.http.entity.UrlEncodedFormEntity
|
||||
import org.apache.hc.client5.http.protocol.HttpClientContext
|
||||
import org.apache.hc.core5.http.io.entity.EntityUtils
|
||||
import org.apache.hc.core5.http.message.BasicNameValuePair
|
||||
import java.util.concurrent.CompletableFuture
|
||||
|
||||
class VGAuthService(
|
||||
internal class VGAuthService(
|
||||
private val cm: HTTPService,
|
||||
private val settingsService: SettingsService,
|
||||
private val eventBus: EventBus
|
||||
) {
|
||||
private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
|
||||
private val log by me.vripper.delegate.LoggerDelegate()
|
||||
private val log by LoggerDelegate()
|
||||
val context: HttpClientContext = HttpClientContext.create()
|
||||
var loggedUser = ""
|
||||
|
||||
@@ -122,8 +122,8 @@ class VGAuthService(
|
||||
}
|
||||
|
||||
fun leaveThanks(postEntity: PostEntity) {
|
||||
CompletableFuture.runAsync(
|
||||
LeaveThanksRunnable(postEntity, authenticated, context), GLOBAL_EXECUTOR
|
||||
)
|
||||
GlobalScopeCoroutine.launch {
|
||||
LeaveThanksTask(postEntity, authenticated, context).run()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
package me.vripper.tables
|
||||
|
||||
import org.jetbrains.exposed.dao.id.LongIdTable
|
||||
import org.jetbrains.exposed.sql.javatime.datetime
|
||||
|
||||
object LogTable : LongIdTable(name = "LOG", columnName = "ID") {
|
||||
val type = varchar("TYPE", 8)
|
||||
val status = varchar("STATUS", 10)
|
||||
val time = datetime("TIME")
|
||||
val message = varchar("MESSAGE", 500)
|
||||
}
|
||||
+12
-41
@@ -1,23 +1,20 @@
|
||||
package me.vripper.tasks
|
||||
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.sync.withPermit
|
||||
import me.vripper.download.DownloadService
|
||||
import me.vripper.entities.LogEntryEntity
|
||||
import me.vripper.model.ThreadPostId
|
||||
import me.vripper.parser.PostItem
|
||||
import me.vripper.parser.PostLookupAPIParser
|
||||
import me.vripper.services.DataTransaction
|
||||
import me.vripper.services.MetadataService
|
||||
import me.vripper.services.SettingsService
|
||||
import me.vripper.services.ThreadCacheService
|
||||
import me.vripper.utilities.RequestLimit
|
||||
import me.vripper.utilities.Tasks
|
||||
import me.vripper.utilities.LoggerDelegate
|
||||
import me.vripper.vgapi.PostItem
|
||||
import me.vripper.vgapi.PostLookupAPIParser
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
|
||||
class AddPostRunnable(private val items: List<ThreadPostId>) : KoinComponent, Runnable {
|
||||
private val log by me.vripper.delegate.LoggerDelegate()
|
||||
internal class AddPostTask(private val items: List<ThreadPostId>) : KoinComponent, Runnable {
|
||||
private val log by LoggerDelegate()
|
||||
private val dataTransaction: DataTransaction by inject()
|
||||
private val settingsService: SettingsService by inject()
|
||||
private val downloadService: DownloadService by inject()
|
||||
@@ -38,53 +35,27 @@ class AddPostRunnable(private val items: List<ThreadPostId>) : KoinComponent, Ru
|
||||
|
||||
val cachedThread = threadCacheService.getIfPresent(threadId)
|
||||
val threadItem = cachedThread ?: runBlocking {
|
||||
RequestLimit.semaphore.withPermit {
|
||||
PostLookupAPIParser(
|
||||
threadId, postId
|
||||
).parse()
|
||||
}
|
||||
PostLookupAPIParser(
|
||||
threadId, postId
|
||||
).parse()
|
||||
}
|
||||
|
||||
if (threadItem == null) {
|
||||
dataTransaction.saveLog(
|
||||
LogEntryEntity(
|
||||
type = LogEntryEntity.Type.POST,
|
||||
status = LogEntryEntity.Status.ERROR,
|
||||
message = "Failed to load $link"
|
||||
)
|
||||
)
|
||||
log.error("Failed to load $link")
|
||||
continue
|
||||
} else {
|
||||
if (threadItem.error.isNotBlank()) {
|
||||
dataTransaction.saveLog(
|
||||
LogEntryEntity(
|
||||
type = LogEntryEntity.Type.POST,
|
||||
status = LogEntryEntity.Status.ERROR,
|
||||
message = "Error loading $link: ${threadItem.error}"
|
||||
)
|
||||
)
|
||||
log.error("Error loading $link: ${threadItem.error}")
|
||||
continue
|
||||
} else if (threadItem.postItemList.isEmpty()) {
|
||||
dataTransaction.saveLog(
|
||||
LogEntryEntity(
|
||||
type = LogEntryEntity.Type.POST,
|
||||
status = LogEntryEntity.Status.ERROR,
|
||||
message = "Nothing found for $link"
|
||||
)
|
||||
)
|
||||
log.error("Nothing found for $link")
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
val postItem = threadItem.postItemList.firstOrNull { it.postId == postId }
|
||||
if (postItem == null) {
|
||||
dataTransaction.saveLog(
|
||||
LogEntryEntity(
|
||||
type = LogEntryEntity.Type.POST,
|
||||
status = LogEntryEntity.Status.ERROR,
|
||||
message = "Unable to load $link"
|
||||
)
|
||||
)
|
||||
log.error("Unable to load $link")
|
||||
continue
|
||||
}
|
||||
toProcess.add(postItem)
|
||||
+4
-16
@@ -1,13 +1,10 @@
|
||||
package me.vripper.tasks
|
||||
|
||||
import me.vripper.entities.LogEntryEntity
|
||||
import me.vripper.entities.LogEntryEntity.Status.ERROR
|
||||
import me.vripper.entities.PostEntity
|
||||
import me.vripper.services.DataTransaction
|
||||
import me.vripper.services.HTTPService
|
||||
import me.vripper.services.SettingsService
|
||||
import me.vripper.utilities.Tasks
|
||||
import me.vripper.utilities.formatToString
|
||||
import me.vripper.utilities.LoggerDelegate
|
||||
import org.apache.hc.client5.http.classic.methods.HttpPost
|
||||
import org.apache.hc.client5.http.entity.UrlEncodedFormEntity
|
||||
import org.apache.hc.client5.http.protocol.HttpClientContext
|
||||
@@ -15,12 +12,12 @@ import org.apache.hc.core5.http.message.BasicNameValuePair
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
|
||||
class LeaveThanksRunnable(
|
||||
internal class LeaveThanksTask(
|
||||
private val postEntity: PostEntity,
|
||||
private val authenticated: Boolean,
|
||||
private val context: HttpClientContext
|
||||
) : KoinComponent, Runnable {
|
||||
private val log by me.vripper.delegate.LoggerDelegate()
|
||||
private val log by LoggerDelegate()
|
||||
private val cm: HTTPService by inject()
|
||||
private val settingsService: SettingsService by inject()
|
||||
private val dataTransaction: DataTransaction by inject()
|
||||
@@ -54,16 +51,7 @@ class LeaveThanksRunnable(
|
||||
}
|
||||
cm.client.execute(postThanks, context) { }
|
||||
} catch (e: Exception) {
|
||||
val error = "Failed to leave a thanks for $postEntity"
|
||||
log.error(error, e)
|
||||
dataTransaction.saveLog(
|
||||
LogEntryEntity(
|
||||
type = LogEntryEntity.Type.THANKS, status = ERROR, message = """
|
||||
$error
|
||||
${e.formatToString()}
|
||||
""".trimIndent()
|
||||
)
|
||||
)
|
||||
log.error("Failed to leave a thanks for $postEntity", e)
|
||||
} finally {
|
||||
Tasks.decrement()
|
||||
}
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
package me.vripper.utilities
|
||||
package me.vripper.tasks
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -9,7 +9,7 @@ import me.vripper.event.LoadingTasks
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
|
||||
object Tasks : KoinComponent {
|
||||
internal object Tasks : KoinComponent {
|
||||
|
||||
private val eventBus: EventBus by inject()
|
||||
private var current = 0
|
||||
+15
-37
@@ -1,22 +1,19 @@
|
||||
package me.vripper.tasks
|
||||
|
||||
import me.vripper.entities.LogEntryEntity
|
||||
import me.vripper.entities.LogEntryEntity.Status.ERROR
|
||||
import kotlinx.coroutines.launch
|
||||
import me.vripper.entities.ThreadEntity
|
||||
import me.vripper.model.Settings
|
||||
import me.vripper.model.ThreadPostId
|
||||
import me.vripper.services.DataTransaction
|
||||
import me.vripper.services.SettingsService
|
||||
import me.vripper.services.ThreadCacheService
|
||||
import me.vripper.utilities.GLOBAL_EXECUTOR
|
||||
import me.vripper.utilities.Tasks
|
||||
import me.vripper.utilities.formatToString
|
||||
import me.vripper.utilities.GlobalScopeCoroutine
|
||||
import me.vripper.utilities.LoggerDelegate
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
import java.util.concurrent.CompletableFuture
|
||||
|
||||
class ThreadLookupRunnable(private val threadId: Long, private val settings: Settings) : KoinComponent, Runnable {
|
||||
private val log by me.vripper.delegate.LoggerDelegate()
|
||||
internal class ThreadLookupTask(private val threadId: Long, private val settings: Settings) : KoinComponent, Runnable {
|
||||
private val log by LoggerDelegate()
|
||||
private val dataTransaction by inject<DataTransaction>()
|
||||
private val settingsService by inject<SettingsService>()
|
||||
private val threadCacheService by inject<ThreadCacheService>()
|
||||
@@ -28,32 +25,22 @@ class ThreadLookupRunnable(private val threadId: Long, private val settings: Set
|
||||
if (dataTransaction.findThreadByThreadId(threadId).isEmpty) {
|
||||
val threadLookupResult = threadCacheService[threadId]
|
||||
if (threadLookupResult.error.isNotBlank()) {
|
||||
dataTransaction.saveLog(
|
||||
LogEntryEntity(
|
||||
type = LogEntryEntity.Type.THREAD,
|
||||
status = LogEntryEntity.Status.ERROR,
|
||||
message = "Error loading $link: ${threadLookupResult.error}"
|
||||
)
|
||||
)
|
||||
log.error("Error loading $link: ${threadLookupResult.error}")
|
||||
return
|
||||
}
|
||||
if (threadLookupResult.postItemList.isEmpty()) {
|
||||
dataTransaction.saveLog(
|
||||
LogEntryEntity(
|
||||
type = LogEntryEntity.Type.THREAD,
|
||||
status = LogEntryEntity.Status.ERROR,
|
||||
message = "Nothing found for $link"
|
||||
)
|
||||
)
|
||||
log.error("Nothing found for $link")
|
||||
return
|
||||
}
|
||||
|
||||
if (threadLookupResult.postItemList.size <= settings.downloadSettings.autoQueueThreshold) {
|
||||
CompletableFuture.runAsync(AddPostRunnable(threadLookupResult.postItemList.map {
|
||||
ThreadPostId(
|
||||
it.threadId, it.postId
|
||||
)
|
||||
}), GLOBAL_EXECUTOR)
|
||||
GlobalScopeCoroutine.launch {
|
||||
AddPostTask(threadLookupResult.postItemList.map {
|
||||
ThreadPostId(
|
||||
it.threadId, it.postId
|
||||
)
|
||||
}).run()
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
dataTransaction.save(
|
||||
@@ -70,16 +57,7 @@ class ThreadLookupRunnable(private val threadId: Long, private val settings: Set
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
val error = "Error when processing $link"
|
||||
log.error(error, e)
|
||||
dataTransaction.saveLog(
|
||||
LogEntryEntity(
|
||||
type = LogEntryEntity.Type.THREAD, status = ERROR, message = """
|
||||
$error
|
||||
${e.formatToString()}
|
||||
""".trimIndent()
|
||||
)
|
||||
)
|
||||
log.error("Error when processing $link", e)
|
||||
} finally {
|
||||
Tasks.decrement()
|
||||
}
|
||||
+6
-5
@@ -1,4 +1,4 @@
|
||||
package me.vripper.listeners
|
||||
package me.vripper.utilities
|
||||
|
||||
import me.vripper.utilities.ApplicationProperties.VRIPPER_DIR
|
||||
import java.io.RandomAccessFile
|
||||
@@ -7,24 +7,25 @@ import kotlin.io.path.pathString
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
object AppLock {
|
||||
fun exclusiveLock() {
|
||||
fun exclusiveLock(): Boolean {
|
||||
val lock = VRIPPER_DIR.resolve("lock")
|
||||
try {
|
||||
val randomFile = RandomAccessFile(lock.pathString, "rw")
|
||||
val channel = randomFile.channel
|
||||
val fileLock = channel.tryLock()
|
||||
if (fileLock == null) {
|
||||
System.err.println("Another instance is already running in ${lock.parent.pathString}")
|
||||
exitProcess(-1)
|
||||
return false
|
||||
} else {
|
||||
Runtime.getRuntime().addShutdownHook(Thread {
|
||||
fileLock.release()
|
||||
channel.close()
|
||||
Files.deleteIfExists(lock)
|
||||
})
|
||||
return true
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
println(e.toString())
|
||||
e.printStackTrace()
|
||||
exitProcess(-1)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package me.vripper.utilities
|
||||
|
||||
import liquibase.Contexts
|
||||
import liquibase.LabelExpression
|
||||
import liquibase.Liquibase
|
||||
import liquibase.database.DatabaseFactory
|
||||
import liquibase.database.jvm.JdbcConnection
|
||||
import liquibase.resource.ClassLoaderResourceAccessor
|
||||
import org.jetbrains.exposed.sql.Database
|
||||
import org.jetbrains.exposed.sql.transactions.TransactionManager
|
||||
import java.sql.Connection
|
||||
|
||||
object DatabaseManager {
|
||||
private var database: Database? = null
|
||||
|
||||
fun connect() {
|
||||
database =
|
||||
Database.connect("jdbc:sqlite:${ApplicationProperties.VRIPPER_DIR}/vripper.db")
|
||||
.also { update(it.connector.invoke().connection as Connection) }
|
||||
}
|
||||
|
||||
fun disconnect() {
|
||||
database?.let { TransactionManager.closeAndUnregister(it) }
|
||||
}
|
||||
|
||||
fun update(connection: Connection) {
|
||||
connection.use { cn ->
|
||||
val database: liquibase.database.Database = DatabaseFactory.getInstance()
|
||||
.findCorrectDatabaseImplementation(JdbcConnection(cn))
|
||||
Liquibase(
|
||||
"db.changelog-master.xml", ClassLoaderResourceAccessor(), database
|
||||
).use { liquibase ->
|
||||
liquibase.update(Contexts(), LabelExpression())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package me.vripper.utilities
|
||||
|
||||
import liquibase.Contexts
|
||||
import liquibase.LabelExpression
|
||||
import liquibase.Liquibase
|
||||
import liquibase.database.Database
|
||||
import liquibase.database.DatabaseFactory
|
||||
import liquibase.database.jvm.JdbcConnection
|
||||
import liquibase.resource.ClassLoaderResourceAccessor
|
||||
import me.vripper.utilities.ApplicationProperties.VRIPPER_DIR
|
||||
import java.sql.DriverManager
|
||||
|
||||
object DbUtils {
|
||||
|
||||
fun update() {
|
||||
val connection =
|
||||
DriverManager.getConnection("jdbc:h2:file:$VRIPPER_DIR/vripper;DB_CLOSE_DELAY=-1;")
|
||||
|
||||
connection.use { cn ->
|
||||
val database: Database = DatabaseFactory.getInstance()
|
||||
.findCorrectDatabaseImplementation(JdbcConnection(cn))
|
||||
Liquibase(
|
||||
"db.changelog-master.xml", ClassLoaderResourceAccessor(), database
|
||||
).use { liquibase ->
|
||||
liquibase.update(Contexts(), LabelExpression())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package me.vripper.utilities
|
||||
|
||||
import kotlinx.coroutines.CoroutineExceptionHandler
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
|
||||
private val log by LoggerDelegate()
|
||||
|
||||
val errorHandler = CoroutineExceptionHandler { _, exception ->
|
||||
log.error("Unexpected error", exception)
|
||||
}
|
||||
|
||||
val GlobalScopeCoroutine = CoroutineScope(SupervisorJob() + Dispatchers.IO + errorHandler)
|
||||
@@ -7,11 +7,12 @@ import org.htmlcleaner.HtmlCleaner
|
||||
import org.w3c.dom.Document
|
||||
import java.io.InputStream
|
||||
|
||||
object HtmlUtils {
|
||||
internal object HtmlUtils {
|
||||
|
||||
@Throws(HtmlProcessorException::class)
|
||||
fun clean(htmlContent: InputStream): Document {
|
||||
return try {
|
||||
htmlContent.use {
|
||||
htmlContent.use { stream ->
|
||||
val clean = HtmlCleaner().clean(htmlContent)
|
||||
DomSerializer(CleanerProperties()).createDOM(clean)
|
||||
}
|
||||
@@ -19,4 +20,15 @@ object HtmlUtils {
|
||||
throw HtmlProcessorException(e)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(HtmlProcessorException::class)
|
||||
fun clean(htmlContent: String): Document {
|
||||
return try {
|
||||
val clean = HtmlCleaner().clean(htmlContent)
|
||||
DomSerializer(CleanerProperties()).createDOM(clean)
|
||||
|
||||
} catch (e: Exception) {
|
||||
throw HtmlProcessorException(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package me.vripper.delegate
|
||||
package me.vripper.utilities
|
||||
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
@@ -11,8 +11,8 @@ import kotlin.io.path.Path
|
||||
import kotlin.io.path.listDirectoryEntries
|
||||
|
||||
|
||||
object PathUtils {
|
||||
private val log by me.vripper.delegate.LoggerDelegate()
|
||||
internal object PathUtils {
|
||||
private val log by LoggerDelegate()
|
||||
private val imageExtensions = listOf("bmp", "gif", "jpg", "jpeg", "png", "webp")
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ object PathUtils {
|
||||
try {
|
||||
Files.delete(currentDownloadDirectory)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
log.error("Failed to delete directory $currentDownloadDirectory", e)
|
||||
}
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
|
||||
@@ -2,6 +2,6 @@ package me.vripper.utilities
|
||||
|
||||
import kotlinx.coroutines.sync.Semaphore
|
||||
|
||||
object RequestLimit {
|
||||
val semaphore: Semaphore = Semaphore(6)
|
||||
internal object RequestLimit {
|
||||
val semaphore: Semaphore = Semaphore(2)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package me.vripper.utilities
|
||||
|
||||
import ch.qos.logback.classic.spi.IThrowableProxy
|
||||
|
||||
object StackTraceElementUtils {
|
||||
fun format(throwable: IThrowableProxy): String {
|
||||
val sb = StringBuilder()
|
||||
sb.appendLine(throwable.className + ": " + throwable.message)
|
||||
stackTraceElementFormat(throwable, sb)
|
||||
return sb.toString()
|
||||
|
||||
}
|
||||
|
||||
private fun stackTraceElementFormat(throwable: IThrowableProxy, sb: StringBuilder) {
|
||||
throwable.stackTraceElementProxyArray.forEach {
|
||||
sb.appendLine("\t" + it.steAsString)
|
||||
}
|
||||
|
||||
if (throwable.cause != null) {
|
||||
sb.appendLine("Caused by: " + throwable.cause.className + ": " + throwable.cause.message)
|
||||
stackTraceElementFormat(throwable.cause, sb)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
package me.vripper.utilities
|
||||
|
||||
import java.util.concurrent.ExecutorService
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
val GLOBAL_EXECUTOR: ExecutorService = Executors.newCachedThreadPool()
|
||||
@@ -6,7 +6,7 @@ import org.w3c.dom.NodeList
|
||||
import javax.xml.xpath.XPathConstants
|
||||
import javax.xml.xpath.XPathFactory
|
||||
|
||||
object XpathUtils {
|
||||
internal object XpathUtils {
|
||||
private val xpath = XPathFactory.newInstance().newXPath()
|
||||
|
||||
@Throws(XpathException::class)
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
package me.vripper.vgapi
|
||||
|
||||
import me.vripper.host.Host
|
||||
|
||||
internal data class ImageItem(val mainLink: String, val thumbLink: String, val host: Host)
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
package me.vripper.parser
|
||||
package me.vripper.vgapi
|
||||
|
||||
data class PostItem(
|
||||
internal data class PostItem(
|
||||
val threadId: Long,
|
||||
val threadTitle: String,
|
||||
val postId: Long,
|
||||
+31
-28
@@ -1,27 +1,33 @@
|
||||
package me.vripper.parser
|
||||
package me.vripper.vgapi
|
||||
|
||||
import me.vripper.entities.LogEntryEntity
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.sync.withPermit
|
||||
import me.vripper.exception.DownloadException
|
||||
import me.vripper.exception.PostParseException
|
||||
import me.vripper.services.*
|
||||
import me.vripper.utilities.Tasks
|
||||
import me.vripper.utilities.formatToString
|
||||
import me.vripper.services.HTTPService
|
||||
import me.vripper.services.RetryPolicyService
|
||||
import me.vripper.services.SettingsService
|
||||
import me.vripper.services.VGAuthService
|
||||
import me.vripper.tasks.Tasks
|
||||
import me.vripper.utilities.LoggerDelegate
|
||||
import me.vripper.utilities.RequestLimit
|
||||
import net.jodah.failsafe.Failsafe
|
||||
import net.jodah.failsafe.function.CheckedSupplier
|
||||
import org.apache.hc.client5.http.classic.HttpClient
|
||||
import org.apache.hc.client5.http.classic.methods.HttpGet
|
||||
import org.apache.hc.core5.http.io.entity.EntityUtils
|
||||
import org.apache.hc.core5.net.URIBuilder
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
import java.io.ByteArrayInputStream
|
||||
import javax.xml.parsers.SAXParserFactory
|
||||
import kotlin.Throws
|
||||
|
||||
class PostLookupAPIParser(private val threadId: Long, private val postId: Long) : KoinComponent {
|
||||
private val log by me.vripper.delegate.LoggerDelegate()
|
||||
internal class PostLookupAPIParser(private val threadId: Long, private val postId: Long) : KoinComponent {
|
||||
private val log by LoggerDelegate()
|
||||
private val settingsService: SettingsService by inject()
|
||||
private val retryPolicyService: RetryPolicyService by inject()
|
||||
private val httpService: HTTPService by inject()
|
||||
private val vgAuthService: VGAuthService by inject()
|
||||
private val dataTransaction: DataTransaction by inject()
|
||||
|
||||
@Throws(PostParseException::class)
|
||||
fun parse(): ThreadItem? {
|
||||
@@ -36,29 +42,26 @@ class PostLookupAPIParser(private val threadId: Long, private val postId: Long)
|
||||
log.debug("Requesting {}", httpGet)
|
||||
Tasks.increment()
|
||||
return try {
|
||||
Failsafe.with(retryPolicyService.buildGenericRetryPolicy<Any>()).onFailure {
|
||||
Failsafe.with(retryPolicyService.buildGenericRetryPolicy<Any>("Failed to parse $httpGet: ")).onFailure {
|
||||
log.error(
|
||||
"parsing failed for thread $threadId, post $postId", it.failure
|
||||
)
|
||||
dataTransaction.saveLog(
|
||||
LogEntryEntity(
|
||||
type = LogEntryEntity.Type.POST, status = LogEntryEntity.Status.ERROR, message = """
|
||||
Failed to process thread $threadId, post $postId
|
||||
${it.failure.formatToString()}
|
||||
""".trimIndent()
|
||||
)
|
||||
"Failed to process thread $threadId, post $postId", it.failure
|
||||
)
|
||||
}.get(CheckedSupplier {
|
||||
val connection: HttpClient = httpService.client
|
||||
connection.execute(
|
||||
httpGet, vgAuthService.context
|
||||
) { response ->
|
||||
if (response.code / 100 != 2) {
|
||||
throw DownloadException("Unexpected response code '${response.code}' for $httpGet")
|
||||
runBlocking {
|
||||
RequestLimit.semaphore.withPermit {
|
||||
httpService.client.execute(
|
||||
httpGet, vgAuthService.context
|
||||
) { response ->
|
||||
if (response.code / 100 != 2) {
|
||||
throw DownloadException("Unexpected response code '${response.code}' for $httpGet")
|
||||
}
|
||||
ByteArrayInputStream(EntityUtils.toByteArray(response.entity))
|
||||
}
|
||||
}.use {
|
||||
factory.newSAXParser()
|
||||
.parse(it, threadLookupAPIResponseHandler)
|
||||
threadLookupAPIResponseHandler.result
|
||||
}
|
||||
factory.newSAXParser()
|
||||
.parse(response.entity.content, threadLookupAPIResponseHandler)
|
||||
threadLookupAPIResponseHandler.result
|
||||
}
|
||||
})
|
||||
} catch (e: Exception) {
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
package me.vripper.parser
|
||||
package me.vripper.vgapi
|
||||
|
||||
data class ThreadItem(
|
||||
internal data class ThreadItem(
|
||||
val threadId: Long,
|
||||
val title: String,
|
||||
val securityToken: String,
|
||||
+33
-30
@@ -1,28 +1,33 @@
|
||||
package me.vripper.parser
|
||||
package me.vripper.vgapi
|
||||
|
||||
import me.vripper.entities.LogEntryEntity
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.sync.withPermit
|
||||
import me.vripper.exception.DownloadException
|
||||
import me.vripper.exception.PostParseException
|
||||
import me.vripper.services.*
|
||||
import me.vripper.utilities.Tasks
|
||||
import me.vripper.utilities.formatToString
|
||||
import me.vripper.services.HTTPService
|
||||
import me.vripper.services.RetryPolicyService
|
||||
import me.vripper.services.SettingsService
|
||||
import me.vripper.services.VGAuthService
|
||||
import me.vripper.tasks.Tasks
|
||||
import me.vripper.utilities.LoggerDelegate
|
||||
import me.vripper.utilities.RequestLimit
|
||||
import net.jodah.failsafe.Failsafe
|
||||
import net.jodah.failsafe.function.CheckedSupplier
|
||||
import org.apache.hc.client5.http.classic.methods.HttpGet
|
||||
import org.apache.hc.core5.http.io.entity.EntityUtils
|
||||
import org.apache.hc.core5.net.URIBuilder
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
import java.io.BufferedInputStream
|
||||
import java.util.*
|
||||
import java.io.ByteArrayInputStream
|
||||
import javax.xml.parsers.SAXParserFactory
|
||||
import kotlin.Throws
|
||||
|
||||
class ThreadLookupAPIParser(private val threadId: Long) : KoinComponent {
|
||||
private val log by me.vripper.delegate.LoggerDelegate()
|
||||
internal class ThreadLookupAPIParser(private val threadId: Long) : KoinComponent {
|
||||
private val log by LoggerDelegate()
|
||||
private val cm: HTTPService by inject()
|
||||
private val retryPolicyService: RetryPolicyService by inject()
|
||||
private val vgAuthService: VGAuthService by inject()
|
||||
private val settingsService: SettingsService by inject()
|
||||
private val dataTransaction: DataTransaction by inject()
|
||||
|
||||
@Throws(PostParseException::class)
|
||||
fun parse(): ThreadItem {
|
||||
@@ -38,31 +43,29 @@ class ThreadLookupAPIParser(private val threadId: Long) : KoinComponent {
|
||||
log.debug("Requesting {}", httpGet)
|
||||
Tasks.increment()
|
||||
return try {
|
||||
Failsafe.with(retryPolicyService.buildGenericRetryPolicy<Any>()).onFailure {
|
||||
Failsafe.with(retryPolicyService.buildGenericRetryPolicy<Any>("Failed to parse $httpGet: ")).onFailure {
|
||||
log.error(
|
||||
"parsing failed for thread $threadId",
|
||||
"Failed to process thread $threadId",
|
||||
it.failure
|
||||
)
|
||||
dataTransaction.saveLog(
|
||||
LogEntryEntity(
|
||||
type = LogEntryEntity.Type.THREAD, status = LogEntryEntity.Status.ERROR, message = """
|
||||
Failed to process thread $threadId
|
||||
${it.failure.formatToString()}
|
||||
""".trimIndent()
|
||||
)
|
||||
)
|
||||
}.get(CheckedSupplier {
|
||||
cm.client.execute(
|
||||
httpGet, vgAuthService.context
|
||||
) { response ->
|
||||
if (response.code / 100 != 2) {
|
||||
throw DownloadException("Unexpected response code '${response.code}' for $httpGet")
|
||||
runBlocking {
|
||||
RequestLimit.semaphore.withPermit {
|
||||
cm.client.execute(
|
||||
httpGet, vgAuthService.context
|
||||
) { response ->
|
||||
if (response.code / 100 != 2) {
|
||||
throw DownloadException("Unexpected response code '${response.code}' for $httpGet")
|
||||
}
|
||||
ByteArrayInputStream(EntityUtils.toByteArray(response.entity))
|
||||
}
|
||||
}.use {
|
||||
factory.newSAXParser().parse(
|
||||
it,
|
||||
threadLookupAPIResponseHandler
|
||||
)
|
||||
threadLookupAPIResponseHandler.result
|
||||
}
|
||||
factory.newSAXParser().parse(
|
||||
BufferedInputStream(response.entity.content),
|
||||
threadLookupAPIResponseHandler
|
||||
)
|
||||
threadLookupAPIResponseHandler.result
|
||||
}
|
||||
})
|
||||
} catch (e: Exception) {
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
package me.vripper.parser
|
||||
package me.vripper.vgapi
|
||||
|
||||
import me.vripper.host.Host
|
||||
import me.vripper.services.SettingsService
|
||||
@@ -7,7 +7,7 @@ import org.koin.core.component.inject
|
||||
import org.xml.sax.Attributes
|
||||
import org.xml.sax.helpers.DefaultHandler
|
||||
|
||||
class ThreadLookupAPIResponseHandler : KoinComponent, DefaultHandler() {
|
||||
internal class ThreadLookupAPIResponseHandler : KoinComponent, DefaultHandler() {
|
||||
private val supportedHosts: List<Host> = getKoin().getAll()
|
||||
private val settingsService: SettingsService by inject()
|
||||
private var error: String = ""
|
||||
@@ -6,7 +6,6 @@ import "image.proto";
|
||||
import "images.proto";
|
||||
import "metadata.proto";
|
||||
import "log.proto";
|
||||
import "logs.proto";
|
||||
import "thread.proto";
|
||||
import "threads.proto";
|
||||
import "settings.proto";
|
||||
@@ -76,6 +75,9 @@ message LoggedInUser {
|
||||
message Version {
|
||||
string version = 1;
|
||||
}
|
||||
message DBMigrationResponse {
|
||||
string message = 1;
|
||||
}
|
||||
|
||||
service EndpointService {
|
||||
rpc scanLinks (Links) returns (EmptyResponse) {}
|
||||
@@ -87,7 +89,8 @@ service EndpointService {
|
||||
rpc stopAll (IdList) returns (EmptyResponse) {}
|
||||
rpc findPost (Id) returns (Post) {}
|
||||
rpc findImagesByPostId (Id) returns (Images) {}
|
||||
rpc onUpdateImages (Id) returns (stream Image) {}
|
||||
rpc onUpdateImagesByPostId (Id) returns (stream Image) {}
|
||||
rpc onUpdateImages (EmptyRequest) returns (stream Image) {}
|
||||
rpc onDownloadSpeed (EmptyRequest) returns (stream DownloadSpeed) {}
|
||||
rpc onVGUserUpdate (EmptyRequest) returns (stream VGUser) {}
|
||||
rpc onQueueStateUpdate (EmptyRequest) returns (stream QueueState) {}
|
||||
@@ -99,11 +102,7 @@ service EndpointService {
|
||||
rpc rename (Rename) returns (EmptyResponse) {}
|
||||
rpc renameToFirst (RenameToFirst) returns (EmptyResponse) {}
|
||||
rpc onStopped (EmptyRequest) returns (stream Id) {}
|
||||
rpc logClear (EmptyRequest) returns (EmptyResponse) {}
|
||||
rpc findAllLogs (EmptyRequest) returns (Logs) {}
|
||||
rpc onNewLog (EmptyRequest) returns (stream Log) {}
|
||||
rpc onUpdateLog (EmptyRequest) returns (stream Log) {}
|
||||
rpc onDeleteLogs (EmptyRequest) returns (stream Id) {}
|
||||
rpc onNewThread (EmptyRequest) returns (stream Thread) {}
|
||||
rpc onUpdateThread (EmptyRequest) returns (stream Thread) {}
|
||||
rpc onDeleteThread (EmptyRequest) returns (stream Id) {}
|
||||
@@ -119,4 +118,6 @@ service EndpointService {
|
||||
rpc onUpdateSettings (EmptyRequest) returns (stream Settings) {}
|
||||
rpc loggedInUser (EmptyRequest) returns (LoggedInUser) {}
|
||||
rpc getVersion (EmptyRequest) returns (Version) {}
|
||||
rpc dbMigration (EmptyRequest) returns (DBMigrationResponse) {}
|
||||
rpc initLogger (EmptyRequest) returns (EmptyResponse) {}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,11 @@ syntax = "proto3";
|
||||
option java_package = "me.vripper.proto";
|
||||
|
||||
message Log {
|
||||
int64 id = 1;
|
||||
string type = 2;
|
||||
string status = 3;
|
||||
string time = 4;
|
||||
string message = 5;
|
||||
int64 sequence = 1;
|
||||
string timestamp = 2;
|
||||
string threadName = 3;
|
||||
string loggerName = 4;
|
||||
string levelString = 5;
|
||||
string formattedMessage = 6;
|
||||
string throwable = 7;
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
syntax = "proto3";
|
||||
option java_package = "me.vripper.proto";
|
||||
import "log.proto";
|
||||
|
||||
message Logs {
|
||||
repeated Log logs = 1;
|
||||
}
|
||||
@@ -1,172 +1,145 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<databaseChangeLog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.4.xsd">
|
||||
<changeSet author="death-claw" id="0001">
|
||||
<createTable tableName="POST">
|
||||
<column autoIncrement="true" name="ID" type="BIGINT">
|
||||
<constraints nullable="false" primaryKey="true" primaryKeyName="POST_PK"/>
|
||||
</column>
|
||||
<column name="DONE" type="INT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="HOSTS" type="VARCHAR(70)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="OUTPUT_PATH" type="VARCHAR(260)"/>
|
||||
<column name="POST_ID" type="BIGINT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="STATUS" type="VARCHAR(15)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="THREAD_ID" type="BIGINT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="POST_TITLE" type="VARCHAR(255)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="THREAD_TITLE" type="VARCHAR(255)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="FORUM" type="VARCHAR(255)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="TOTAL" type="INT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="SIZE" type="BIGINT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="DOWNLOADED" type="BIGINT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="URL" type="VARCHAR(200)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="TOKEN" type="VARCHAR(51)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column defaultValueComputed="CURRENT_TIMESTAMP" name="ADDED_AT" type="TIMESTAMP">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column defaultValue="0" name="RANK" type="INT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
</changeSet>
|
||||
<changeSet author="death-claw" id="0002">
|
||||
<createTable tableName="IMAGE">
|
||||
<column autoIncrement="true" name="ID" type="BIGINT">
|
||||
<constraints nullable="false" primaryKey="true" primaryKeyName="IMAGE_PK"/>
|
||||
</column>
|
||||
<column name="DOWNLOADED" type="BIGINT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="HOST" type="TINYINT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="INDEX" type="INT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="POST_ID" type="BIGINT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="STATUS" type="VARCHAR(15)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="SIZE" type="BIGINT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="URL" type="VARCHAR(200)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="THUMB_URL" type="VARCHAR(200)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="POST_ID_REF" type="BIGINT">
|
||||
<constraints foreignKeyName="IMAGE_POST_ID_REF_POST_ID_FK" nullable="false"
|
||||
references="POST(ID)"/>
|
||||
</column>
|
||||
</createTable>
|
||||
</changeSet>
|
||||
<changeSet author="death-claw" id="0003">
|
||||
<createTable tableName="THREAD">
|
||||
<column autoIncrement="true" name="ID" type="BIGINT">
|
||||
<constraints nullable="false" primaryKey="true" primaryKeyName="THREAD_PK"/>
|
||||
</column>
|
||||
<column name="TOTAL" type="INT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="URL" type="VARCHAR(200)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="THREAD_ID" type="BIGINT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="TITLE" type="VARCHAR(255)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
</changeSet>
|
||||
<changeSet author="death-claw" id="0004">
|
||||
<createTable tableName="LOG">
|
||||
<column autoIncrement="true" name="ID" type="BIGINT">
|
||||
<constraints nullable="false" primaryKey="true" primaryKeyName="EVENT_PK"/>
|
||||
</column>
|
||||
<column name="TYPE" type="VARCHAR(8)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="STATUS" type="VARCHAR(10)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column defaultValueComputed="CURRENT_TIMESTAMP" name="TIME" type="TIMESTAMP">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="MESSAGE" type="VARCHAR(500)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
</changeSet>
|
||||
<changeSet author="vripper" id="0005">
|
||||
<createIndex indexName="IMAGE_POST_ID_IDX" tableName="IMAGE">
|
||||
<column name="POST_ID"/>
|
||||
</createIndex>
|
||||
<createIndex indexName="IMAGE_STATUS_IDX" tableName="IMAGE">
|
||||
<column name="STATUS"/>
|
||||
</createIndex>
|
||||
<createIndex indexName="POST_POST_ID_IDX" tableName="POST">
|
||||
<column name="POST_ID"/>
|
||||
</createIndex>
|
||||
<createIndex indexName="POST_STATUS_IDX" tableName="POST">
|
||||
<column name="STATUS"/>
|
||||
</createIndex>
|
||||
<createIndex indexName="THREAD_THREAD_ID_IDX" tableName="THREAD">
|
||||
<column name="THREAD_ID"/>
|
||||
</createIndex>
|
||||
</changeSet>
|
||||
<changeSet id="0006" author="vripper">
|
||||
<addColumn tableName="POST">
|
||||
<column name="FOLDER_NAME" type="VARCHAR(260)" afterColumn="OUTPUT_PATH" defaultValue=""/>
|
||||
</addColumn>
|
||||
</changeSet>
|
||||
<changeSet author="vripper" id="0007">
|
||||
<createTable tableName="METADATA">
|
||||
<column name="POST_ID" type="BIGINT">
|
||||
<constraints unique="true" nullable="false"/>
|
||||
</column>
|
||||
<column name="DATA" type="VARCHAR">
|
||||
<constraints nullable="true"/>
|
||||
</column>
|
||||
</createTable>
|
||||
</changeSet>
|
||||
<changeSet author="vripper" id="0008">
|
||||
<addColumn tableName="IMAGE">
|
||||
<column name="FILENAME" type="VARCHAR(260)" afterColumn="STATUS" defaultValue=""/>
|
||||
</addColumn>
|
||||
</changeSet>
|
||||
<changeSet id="0009" author="vripper">
|
||||
<addUniqueConstraint tableName="POST" columnNames="POST_ID" constraintName="POST__POST_ID_UQ"/>
|
||||
<addUniqueConstraint tableName="THREAD" columnNames="THREAD_ID" constraintName="THREAD__THREAD_ID_UQ"/>
|
||||
</changeSet>
|
||||
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.4.xsd">
|
||||
<changeSet author="vripper" id="0001">
|
||||
<createTable tableName="POST">
|
||||
<column autoIncrement="true" name="ID" type="INTEGER">
|
||||
<constraints nullable="false" primaryKey="true" primaryKeyName="POST_PK"/>
|
||||
</column>
|
||||
<column name="DONE" type="INTEGER">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="HOSTS" type="TEXT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="OUTPUT_PATH" type="TEXT"/>
|
||||
<column name="POST_ID" type="INTEGER">
|
||||
<constraints unique="true" nullable="false"/>
|
||||
</column>
|
||||
<column name="STATUS" type="TEXT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="THREAD_ID" type="INTEGER">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="POST_TITLE" type="TEXT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="THREAD_TITLE" type="TEXT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="FOLDER_NAME" type="TEXT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="FORUM" type="TEXT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="TOTAL" type="INTEGER">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="SIZE" type="INTEGER">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="DOWNLOADED" type="INTEGER">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="URL" type="TEXT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="TOKEN" type="TEXT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column defaultValueComputed="CURRENT_TIMESTAMP" name="ADDED_AT" type="INTEGER">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column defaultValue="0" name="RANK" type="INTEGER">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
</changeSet>
|
||||
<changeSet author="vripper" id="0002">
|
||||
<createTable tableName="IMAGE">
|
||||
<column autoIncrement="true" name="ID" type="INTEGER">
|
||||
<constraints nullable="false" primaryKey="true" primaryKeyName="IMAGE_PK"/>
|
||||
</column>
|
||||
<column name="DOWNLOADED" type="INTEGER">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="HOST" type="INTEGER">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="INDEX" type="INTEGER">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="POST_ID" type="INTEGER">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="STATUS" type="TEXT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="FILENAME" type="TEXT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="SIZE" type="INTEGER">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="URL" type="TEXT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="THUMB_URL" type="TEXT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="POST_ID_REF" type="INTEGER">
|
||||
<constraints foreignKeyName="IMAGE_POST_ID_REF_POST_ID_FK" nullable="false"
|
||||
references="POST(ID)"/>
|
||||
</column>
|
||||
</createTable>
|
||||
</changeSet>
|
||||
<changeSet author="vripper" id="0003">
|
||||
<createTable tableName="THREAD">
|
||||
<column autoIncrement="true" name="ID" type="INTEGER">
|
||||
<constraints nullable="false" primaryKey="true" primaryKeyName="THREAD_PK"/>
|
||||
</column>
|
||||
<column name="TOTAL" type="INTEGER">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="URL" type="TEXT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="THREAD_ID" type="INTEGER">
|
||||
<constraints unique="true" nullable="false"/>
|
||||
</column>
|
||||
<column name="TITLE" type="TEXT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
</changeSet>
|
||||
<changeSet author="vripper" id="0005">
|
||||
<createIndex indexName="IMAGE_POST_ID_IDX" tableName="IMAGE">
|
||||
<column name="POST_ID"/>
|
||||
</createIndex>
|
||||
<createIndex indexName="IMAGE_STATUS_IDX" tableName="IMAGE">
|
||||
<column name="STATUS"/>
|
||||
</createIndex>
|
||||
<createIndex indexName="POST_POST_ID_IDX" tableName="POST">
|
||||
<column name="POST_ID"/>
|
||||
</createIndex>
|
||||
<createIndex indexName="POST_STATUS_IDX" tableName="POST">
|
||||
<column name="STATUS"/>
|
||||
</createIndex>
|
||||
<createIndex indexName="THREAD_THREAD_ID_IDX" tableName="THREAD">
|
||||
<column name="THREAD_ID"/>
|
||||
</createIndex>
|
||||
</changeSet>
|
||||
<changeSet author="vripper" id="0006">
|
||||
<createTable tableName="METADATA">
|
||||
<column name="POST_ID" type="INTEGER">
|
||||
<constraints unique="true" nullable="false"/>
|
||||
</column>
|
||||
<column name="DATA" type="TEXT">
|
||||
<constraints nullable="true"/>
|
||||
</column>
|
||||
</createTable>
|
||||
</changeSet>
|
||||
</databaseChangeLog>
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
|
||||
<sequenceNumberGenerator class="ch.qos.logback.core.spi.BasicSequenceNumberGenerator"/>
|
||||
|
||||
<appender class="ch.qos.logback.core.ConsoleAppender" name="STDOUT">
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||
@@ -21,10 +24,17 @@
|
||||
</rollingPolicy>
|
||||
</appender>
|
||||
|
||||
<appender name="RING" class="me.vripper.RingAppender">
|
||||
|
||||
</appender>
|
||||
|
||||
<logger name="Exposed" level="OFF"/>
|
||||
<logger name="me.vripper" level="info"/>
|
||||
<logger name="org.springframework.web.socket.config.WebSocketMessageBrokerStats" level="off"/>
|
||||
|
||||
<root level="INFO">
|
||||
<appender-ref ref="STDOUT"/>
|
||||
<appender-ref ref="FILE"/>
|
||||
<appender-ref ref="RING"/>
|
||||
</root>
|
||||
</configuration>
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@
|
||||
<description>vripper-gui</description>
|
||||
|
||||
<properties>
|
||||
<javafx.version>21.0.2</javafx.version>
|
||||
<javafx.version>23.0.1</javafx.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -10,7 +10,11 @@ import kotlinx.coroutines.*
|
||||
import me.vripper.gui.components.views.LoadingView
|
||||
import me.vripper.gui.controller.WidgetsController
|
||||
import me.vripper.gui.event.GuiEventBus
|
||||
import me.vripper.listeners.AppLock
|
||||
import me.vripper.gui.utils.Watcher
|
||||
import me.vripper.gui.utils.WidgetSettings
|
||||
import me.vripper.utilities.AppLock
|
||||
import me.vripper.utilities.ApplicationProperties.VRIPPER_DIR
|
||||
import me.vripper.utilities.LoggerDelegate
|
||||
import org.koin.core.context.GlobalContext
|
||||
import org.koin.core.context.startKoin
|
||||
import org.koin.core.qualifier.named
|
||||
@@ -24,7 +28,7 @@ import kotlin.system.exitProcess
|
||||
class VripperGuiApplication : App(
|
||||
LoadingView::class
|
||||
) {
|
||||
|
||||
private val log by LoggerDelegate()
|
||||
private var initialized = false
|
||||
private val widgetsController: WidgetsController by inject()
|
||||
private val coroutineScope = CoroutineScope(SupervisorJob())
|
||||
@@ -34,6 +38,9 @@ class VripperGuiApplication : App(
|
||||
}
|
||||
|
||||
override fun start(stage: Stage) {
|
||||
Thread.setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHandler { t, e ->
|
||||
log.error("Thread $t threw an exception", e)
|
||||
})
|
||||
if (widgetsController.currentSettings.darkMode) {
|
||||
setUserAgentStylesheet(CupertinoDark().userAgentStylesheet)
|
||||
} else {
|
||||
@@ -63,7 +70,7 @@ class VripperGuiApplication : App(
|
||||
if (widgetsController.currentSettings.width != stage.width || widgetsController.currentSettings.height != stage.height) {
|
||||
widgetsController.currentSettings.width = stage.width
|
||||
widgetsController.currentSettings.height = stage.height
|
||||
widgetsController.update()
|
||||
WidgetSettings.update(widgetsController.currentSettings)
|
||||
}
|
||||
}
|
||||
delay(1_000)
|
||||
@@ -83,7 +90,7 @@ class VripperGuiApplication : App(
|
||||
|
||||
}
|
||||
coroutineScope.launch {
|
||||
GuiEventBus.publishEvent(GuiEventBus.ApplicationInitialized)
|
||||
GuiEventBus.publishEvent(GuiEventBus.ApplicationInitialized(parameters.raw))
|
||||
initialized = true
|
||||
}
|
||||
}
|
||||
@@ -102,7 +109,30 @@ class VripperGuiApplication : App(
|
||||
}
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
System.setProperty("prism.lcdtext", "false");
|
||||
AppLock.exclusiveLock()
|
||||
Application.launch(VripperGuiApplication::class.java, *args)
|
||||
System.setProperty("prism.lcdtext", "false")
|
||||
val pattern = "(vr:t=)(\\d+)".toPattern()
|
||||
val threadId = if (args.isNotEmpty()) {
|
||||
val matcher = pattern.matcher(args[0])
|
||||
if (!matcher.matches()) {
|
||||
System.err.println("Illegal argument ${args[0]}")
|
||||
exitProcess(-1)
|
||||
}
|
||||
matcher.group(2)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
val haveTheLock = AppLock.exclusiveLock()
|
||||
if (!haveTheLock) {
|
||||
if (threadId == null) {
|
||||
System.err.println("Another instance is already running in $VRIPPER_DIR")
|
||||
exitProcess(-1)
|
||||
}
|
||||
Watcher.notify(threadId)
|
||||
exitProcess(0)
|
||||
}
|
||||
Watcher.init()
|
||||
Application.launch(
|
||||
VripperGuiApplication::class.java,
|
||||
*if (threadId != null) arrayOf(threadId) else emptyArray<String>()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
package me.vripper.gui.components
|
||||
|
||||
import javafx.beans.property.SimpleStringProperty
|
||||
|
||||
object Shared {
|
||||
val searchInput = SimpleStringProperty()
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import atlantafx.base.theme.Styles
|
||||
import javafx.geometry.Pos
|
||||
import javafx.scene.control.TableCell
|
||||
import javafx.scene.control.Tooltip
|
||||
import me.vripper.entities.domain.Status
|
||||
import me.vripper.entities.Status
|
||||
import org.kordamp.ikonli.feather.Feather
|
||||
import org.kordamp.ikonli.javafx.FontIcon
|
||||
|
||||
|
||||
+2
-1
@@ -10,7 +10,8 @@ import tornadofx.*
|
||||
class LogMessageFragment : Fragment("Log message") {
|
||||
|
||||
val logModel: LogModel by param()
|
||||
private val textAreaProperty = SimpleStringProperty(logModel.message)
|
||||
private val textAreaProperty =
|
||||
SimpleStringProperty(logModel.formattedMessage + logModel.throwable.let { if (it.isEmpty()) "" else "\n------------\n" + logModel.throwable })
|
||||
|
||||
override val root = vbox(alignment = Pos.CENTER_RIGHT) {
|
||||
padding = insets(all = 5)
|
||||
|
||||
@@ -12,19 +12,21 @@ import javafx.scene.control.ToggleGroup
|
||||
import javafx.scene.layout.VBox
|
||||
import kotlinx.coroutines.*
|
||||
import me.vripper.gui.VripperGuiApplication
|
||||
import me.vripper.gui.components.views.AppView
|
||||
import me.vripper.gui.controller.WidgetsController
|
||||
import me.vripper.gui.event.GuiEventBus
|
||||
import me.vripper.gui.services.DatabaseManager
|
||||
import me.vripper.gui.listener.GuiStartupLister
|
||||
import me.vripper.gui.services.GrpcEndpointService
|
||||
import me.vripper.gui.utils.ActiveUICoroutines
|
||||
import me.vripper.services.IAppEndpointService
|
||||
import me.vripper.utilities.DatabaseManager
|
||||
import tornadofx.*
|
||||
|
||||
class SessionFragment : Fragment("Change Session") {
|
||||
|
||||
val component: UIComponent by param()
|
||||
private val coroutineScope: CoroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
|
||||
private val widgetsController: WidgetsController by inject()
|
||||
private val grpcEndpointService: GrpcEndpointService by di("remoteAppEndpointService")
|
||||
private val appEndpointService: IAppEndpointService by di("localAppEndpointService")
|
||||
private val toggleGroup = ToggleGroup()
|
||||
private val disableConfirm = SimpleBooleanProperty(false)
|
||||
private val message = SimpleStringProperty()
|
||||
@@ -86,12 +88,15 @@ class SessionFragment : Fragment("Change Session") {
|
||||
message.set("Starting...")
|
||||
}
|
||||
widgetsController.currentSettings.localSession = true
|
||||
DatabaseManager.connect()
|
||||
GuiEventBus.publishEvent(GuiEventBus.ChangingSession)
|
||||
while (ActiveUICoroutines.all().isNotEmpty()) {
|
||||
delay(200)
|
||||
}
|
||||
grpcEndpointService.disconnect()
|
||||
DatabaseManager.connect()
|
||||
GuiStartupLister().run()
|
||||
GuiEventBus.publishEvent(GuiEventBus.LocalSession)
|
||||
runLater {
|
||||
component.replaceWith(find<AppView>())
|
||||
close()
|
||||
}
|
||||
}
|
||||
@@ -103,19 +108,25 @@ class SessionFragment : Fragment("Change Session") {
|
||||
disableConfirm.set(true)
|
||||
message.set("Connecting...")
|
||||
}
|
||||
if (widgetsController.currentSettings.localSession) {
|
||||
appEndpointService.stopAll()
|
||||
}
|
||||
widgetsController.currentSettings.localSession = false
|
||||
DatabaseManager.disconnect()
|
||||
GuiEventBus.publishEvent(GuiEventBus.ChangingSession)
|
||||
while (ActiveUICoroutines.all().isNotEmpty()) {
|
||||
delay(200)
|
||||
}
|
||||
DatabaseManager.disconnect()
|
||||
grpcEndpointService.disconnect()
|
||||
grpcEndpointService.connect(
|
||||
widgetsController.currentSettings.remoteSessionModel.host,
|
||||
widgetsController.currentSettings.remoteSessionModel.port
|
||||
)
|
||||
|
||||
repeat(10) {
|
||||
if (grpcEndpointService.connectionState() == ConnectivityState.READY) {
|
||||
GuiEventBus.publishEvent(GuiEventBus.RemoteSession)
|
||||
runLater {
|
||||
component.replaceWith(find<AppView>())
|
||||
close()
|
||||
}
|
||||
cancel()
|
||||
@@ -123,6 +134,9 @@ class SessionFragment : Fragment("Change Session") {
|
||||
delay(333)
|
||||
}
|
||||
runLater {
|
||||
runBlocking {
|
||||
GuiEventBus.publishEvent(GuiEventBus.RemoteSessionFailure)
|
||||
}
|
||||
disableConfirm.set(false)
|
||||
message.set("Unable to connect")
|
||||
}
|
||||
|
||||
@@ -31,6 +31,8 @@ class SettingsFragment : Fragment("Settings") {
|
||||
tabpane {
|
||||
tabClosingPolicy = TabPane.TabClosingPolicy.UNAVAILABLE
|
||||
VBox.setVgrow(this, Priority.ALWAYS)
|
||||
minWidth = 700.0
|
||||
minHeight = 400.0
|
||||
tab(downloadSettingsFragment.title) {
|
||||
add(downloadSettingsFragment)
|
||||
graphic = FontIcon.of(Feather.FOLDER)
|
||||
|
||||
+34
-39
@@ -61,6 +61,7 @@ class ThreadSelectionTableFragment : Fragment("Thread") {
|
||||
}
|
||||
|
||||
tableView = tableview(items) {
|
||||
isTableMenuButtonVisible = true
|
||||
addClass(Styles.DENSE)
|
||||
selectionModel.selectionMode = SelectionMode.MULTIPLE
|
||||
setRowFactory {
|
||||
@@ -85,46 +86,20 @@ class ThreadSelectionTableFragment : Fragment("Thread") {
|
||||
}
|
||||
val contextMenu = ContextMenu()
|
||||
contextMenu.items.addAll(urlItem)
|
||||
tableRow.contextMenuProperty().bind(tableRow.emptyProperty()
|
||||
.map { empty -> if (empty) null else contextMenu })
|
||||
tableRow.contextMenuProperty().bind(
|
||||
tableRow.emptyProperty()
|
||||
.map { empty -> if (empty) null else contextMenu })
|
||||
|
||||
tableRow
|
||||
}
|
||||
contextMenu = ContextMenu()
|
||||
contextMenu.items.add(MenuItem("Setup columns").apply {
|
||||
setOnAction {
|
||||
find<ColumnSelectionFragment>(
|
||||
mapOf(
|
||||
ColumnSelectionFragment::map to mapOf(
|
||||
Pair(
|
||||
"Preview",
|
||||
widgetsController.currentSettings.threadSelectionColumnsModel.previewProperty
|
||||
),
|
||||
Pair(
|
||||
"Post Index",
|
||||
widgetsController.currentSettings.threadSelectionColumnsModel.indexProperty
|
||||
),
|
||||
Pair(
|
||||
"Title",
|
||||
widgetsController.currentSettings.threadSelectionColumnsModel.titleProperty
|
||||
),
|
||||
Pair(
|
||||
"URL",
|
||||
widgetsController.currentSettings.threadSelectionColumnsModel.linkProperty
|
||||
),
|
||||
Pair(
|
||||
"Hosts",
|
||||
widgetsController.currentSettings.threadSelectionColumnsModel.hostsProperty
|
||||
),
|
||||
)
|
||||
)
|
||||
).openModal()
|
||||
}
|
||||
graphic = FontIcon.of(Feather.COLUMNS)
|
||||
})
|
||||
column("Preview", ThreadSelectionModel::previewListProperty) {
|
||||
isVisible = widgetsController.currentSettings.threadSelectionColumnsModel.previewProperty.get()
|
||||
visibleProperty().onChange {
|
||||
widgetsController.currentSettings.threadSelectionColumnsModel.previewProperty.set(
|
||||
it
|
||||
)
|
||||
}
|
||||
prefWidth = 100.0
|
||||
visibleProperty().bind(widgetsController.currentSettings.threadSelectionColumnsModel.previewProperty)
|
||||
cellFactory = Callback {
|
||||
val cell = PreviewTableCell<ThreadSelectionModel>()
|
||||
cell.onMouseExited = EventHandler {
|
||||
@@ -154,20 +129,30 @@ class ThreadSelectionTableFragment : Fragment("Thread") {
|
||||
}
|
||||
}
|
||||
column("Post Index", ThreadSelectionModel::indexProperty) {
|
||||
isVisible = widgetsController.currentSettings.threadSelectionColumnsModel.indexProperty.get()
|
||||
visibleProperty().onChange {
|
||||
widgetsController.currentSettings.threadSelectionColumnsModel.indexProperty.set(
|
||||
it
|
||||
)
|
||||
}
|
||||
prefWidth = widgetsController.currentSettings.threadSelectionColumnsWidthModel.index
|
||||
coroutineScope.launch {
|
||||
widthProperty().asFlow().debounce(200).collect {
|
||||
widgetsController.currentSettings.threadSelectionColumnsWidthModel.index = it as Double
|
||||
}
|
||||
}
|
||||
visibleProperty().bind(widgetsController.currentSettings.threadSelectionColumnsModel.indexProperty)
|
||||
sortOrder.add(this)
|
||||
cellFactory = Callback {
|
||||
TextFieldTableCell<ThreadSelectionModel?, Number?>().apply { alignment = Pos.CENTER_LEFT }
|
||||
}
|
||||
}
|
||||
column("Title", ThreadSelectionModel::titleProperty) {
|
||||
visibleProperty().bind(widgetsController.currentSettings.threadSelectionColumnsModel.titleProperty)
|
||||
isVisible = widgetsController.currentSettings.threadSelectionColumnsModel.titleProperty.get()
|
||||
visibleProperty().onChange {
|
||||
widgetsController.currentSettings.threadSelectionColumnsModel.titleProperty.set(
|
||||
it
|
||||
)
|
||||
}
|
||||
prefWidth = widgetsController.currentSettings.threadSelectionColumnsWidthModel.title
|
||||
coroutineScope.launch {
|
||||
widthProperty().asFlow().debounce(200).collect {
|
||||
@@ -179,7 +164,12 @@ class ThreadSelectionTableFragment : Fragment("Thread") {
|
||||
}
|
||||
}
|
||||
column("URL", ThreadSelectionModel::urlProperty) {
|
||||
visibleProperty().bind(widgetsController.currentSettings.threadSelectionColumnsModel.linkProperty)
|
||||
isVisible = widgetsController.currentSettings.threadSelectionColumnsModel.linkProperty.get()
|
||||
visibleProperty().onChange {
|
||||
widgetsController.currentSettings.threadSelectionColumnsModel.linkProperty.set(
|
||||
it
|
||||
)
|
||||
}
|
||||
prefWidth = widgetsController.currentSettings.threadSelectionColumnsWidthModel.link
|
||||
coroutineScope.launch {
|
||||
widthProperty().asFlow().debounce(200).collect {
|
||||
@@ -191,13 +181,18 @@ class ThreadSelectionTableFragment : Fragment("Thread") {
|
||||
}
|
||||
}
|
||||
column("Hosts", ThreadSelectionModel::hostsProperty) {
|
||||
isVisible = widgetsController.currentSettings.threadSelectionColumnsModel.hostsProperty.get()
|
||||
visibleProperty().onChange {
|
||||
widgetsController.currentSettings.threadSelectionColumnsModel.hostsProperty.set(
|
||||
it
|
||||
)
|
||||
}
|
||||
prefWidth = widgetsController.currentSettings.threadSelectionColumnsWidthModel.hosts
|
||||
coroutineScope.launch {
|
||||
widthProperty().asFlow().debounce(200).collect {
|
||||
widgetsController.currentSettings.threadSelectionColumnsWidthModel.hosts = it as Double
|
||||
}
|
||||
}
|
||||
visibleProperty().bind(widgetsController.currentSettings.threadSelectionColumnsModel.hostsProperty)
|
||||
cellFactory = Callback {
|
||||
TextFieldTableCell<ThreadSelectionModel?, String?>().apply { alignment = Pos.CENTER_LEFT }
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user