Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
78 commits
Select commit Hold shift + click to select a range
d770b93
Upgrade gradle
iqqu Jul 19, 2025
ff6297c
Upgrade vulnerable json lib
iqqu Jul 19, 2025
85c0f51
Upgrade vulnerable commons-io lib
iqqu Jul 19, 2025
0be9a97
Fix log4j2 "Unrecognized format specifier"
iqqu Jul 19, 2025
168c499
Limit log pane lines
iqqu Jul 19, 2025
d3af179
Add graceful stop
iqqu Jul 20, 2025
7436b3d
Fix method name
iqqu Sep 9, 2025
078b042
Add transfer rate monitor
iqqu Jul 20, 2025
98c15f4
Throttle progress update
iqqu Sep 11, 2025
8f99769
Handle no space left on device
iqqu Jul 20, 2025
306a4a0
Fix sorter usage warning
iqqu Jul 20, 2025
6305b5e
Set spammy log message to trace
iqqu Jul 20, 2025
732632e
Clarify download try # log message
iqqu Jul 21, 2025
aeabca2
Simplify expression
iqqu Jul 21, 2025
25e715a
Fix download error handling
iqqu Jul 21, 2025
e0af7e6
Retry with continue
iqqu Aug 13, 2025
d1f67e6
Support fetching media from single-use token URLs
iqqu Jul 21, 2025
c9edecd
Use more specific method name
iqqu Jul 21, 2025
a1d0634
Remove dead code
iqqu Jul 21, 2025
95bd2cd
Extract duplicate code
iqqu Jul 21, 2025
860ff7a
Replace DownloadVideoThread with DownloadFileThread
iqqu Jul 21, 2025
ee37c74
Extract duplicate code
iqqu Jul 21, 2025
19c0061
Unify thread pools
iqqu Sep 1, 2025
ba88600
Fix race condition, handle dupes in albums
iqqu Sep 9, 2025
174a70c
Reconfigure logger from new Configuration
iqqu Jul 21, 2025
17a98f9
Set antialiasing hint
iqqu Jul 24, 2025
23c1778
Reduce duplicate code
iqqu Jul 24, 2025
b3de651
Log error with logger
iqqu Jul 24, 2025
d2ae329
Reduce extra borders
iqqu Jul 24, 2025
e5c12b4
Avoid layout shifting
iqqu Jul 24, 2025
3d76fd3
Extract duplicate code
iqqu Jul 24, 2025
67780b8
Prettier status
iqqu Jul 24, 2025
ec46ca4
Add ripper support for estimated total item count
iqqu Jul 24, 2025
0d21ee9
Enable changing thread count live
iqqu Jul 25, 2025
c54a3bb
Enable reordering queue
iqqu Jul 29, 2025
4d618df
Target current Java LTS version
iqqu Aug 4, 2025
b021834
Use virtual threads
iqqu Sep 9, 2025
b188cf7
Prevent data loss: save queue on change
iqqu Jul 29, 2025
9249470
Add rip number column to History table
iqqu Jul 29, 2025
d96a193
Update localization key
iqqu Aug 27, 2025
67c039a
Add confirmation for single selection queue remove
iqqu Aug 27, 2025
3713427
Enable moving queue selection to top or bottom
iqqu Aug 27, 2025
db4cf24
Simplify queue initialization
iqqu Aug 27, 2025
648d191
Do not clobber rip text field on auto rip
iqqu Aug 27, 2025
8152b51
Fix open button
iqqu Jul 29, 2025
98a7eca
FlickrRipper: set total items
iqqu Jul 29, 2025
faad8f4
Flatten nesting
iqqu Jul 30, 2025
856569e
Add simple addUrlToDownload TokenedUrlGetter helper
iqqu Aug 4, 2025
c2ac3eb
Fix log message
iqqu Aug 4, 2025
8ba7729
Add comment
iqqu Aug 4, 2025
861275a
Fix opening folders and links on Linux
iqqu Aug 4, 2025
8f589e2
Improve logging
iqqu Aug 4, 2025
91c2d0c
Fix initial pack
iqqu Aug 5, 2025
59f86d6
Reduce button padding
iqqu Aug 5, 2025
71b7c32
No HTML buttons
iqqu Aug 5, 2025
05e19da
Prevent status collapse; no empty string
iqqu Aug 5, 2025
6ebaf45
Set date column preferred width
iqqu Aug 5, 2025
da5c8cb
Extract Pattern field
iqqu Aug 5, 2025
a1408e9
Reduce clipboard rip interval
iqqu Aug 5, 2025
235ae48
Add padding to queue button for queue size
iqqu Aug 5, 2025
29f6172
Add log message
iqqu Aug 5, 2025
590ad10
Silence unimportant warning
iqqu Aug 5, 2025
ad4ac80
Set transfer rate value width hint
iqqu Aug 5, 2025
5a762a1
Fix label alignment when setting preferred size
iqqu Aug 5, 2025
5948fda
Set preferred size of other values
iqqu Aug 5, 2025
eeb91dd
Preserve status when gracefully stopping
iqqu Aug 6, 2025
60d8e78
Add Mockito
iqqu Aug 6, 2025
ae5d694
Minimize transfer rate width
iqqu Aug 6, 2025
778f1b9
Stop retry when not ripping (ugly)
iqqu Aug 6, 2025
5696177
Only open log if no panel is already open
iqqu Aug 6, 2025
63be4ea
Check if rip is possible
iqqu Aug 13, 2025
9420258
Check ENOSPC locale independently
iqqu Aug 14, 2025
1a208fd
Localize some strings
iqqu Aug 14, 2025
c268010
Localize status labels
iqqu Aug 14, 2025
fc87938
Update tab button preferred size on locale change
iqqu Aug 14, 2025
42afdcc
Open https URL
iqqu Aug 14, 2025
f566bb6
Fix minimum label width
iqqu Aug 15, 2025
852027e
Work around approximate active status
iqqu Sep 11, 2025
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 16 additions & 3 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@
// gradle clean build -PjavacRelease=21
// gradle clean build -PcustomVersion=1.0.0-10-asdf
val customVersion = (project.findProperty("customVersion") ?: "") as String
val javacRelease = (project.findProperty("javacRelease") ?: "17") as String
val javacRelease = (project.findProperty("javacRelease") ?: "21") as String
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See #2057 for why we have the minimum version set to Java 17. Is there a good reason (necessary language feature) to force the minimum version higher?
We've definitely had people complain when the minimum version is too high per default Java version on various systems.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Java Virtual threads require Java 21. I switch DownloadThreadPool to use Thread.ofVirtual().factory() in the following commit to improve performance of churning through many small files. In the commit after that, I use a virtual thread to debounce rapid calls to Utils.saveConfig, which happens when reordering the queue by dragging a queue list item with the mouse.
These could be native threads, and maybe I'm wrong, but I assume virtual threads are more lightweight to start and stop.

Debian is famous for packaging ancient versions of software. Debian 13 was released last month as "stable", and it includes OpenJDK 21. Debian 12 "oldstable" still only has OpenJDK 17.

I would accept removing virtual threads and downgrading to Java 17 again, but it's trivial for a Debian user to just download and extract any JRE version they want from https://adoptium.net and run the jar with that.

Copy link
Collaborator

@soloturn soloturn Oct 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See #2057 for why we have the minimum version set to Java 17. Is there a good reason (necessary language feature) to force the minimum version higher? We've definitely had people complain when the minimum version is too high per default Java version on various systems.

contrary to the time of #2057, debian has now openjdk-21 in stable, so i not see any hinderance to upgrade to 21 now, what you think @metaprime ?
https://tracker.debian.org/pkg/openjdk-21

but, @iqqu , the continuous build and release is not successful, as it uses ubuntu with java-17. you mind updating the github build file as well to java-21?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am busy most of this weekend but early next week I will push an update for this

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will have the update ready later today.


plugins {
id("fr.brouillard.oss.gradle.jgitver") version "0.9.1"
id("jacoco")
id("java")
id("maven-publish")
id("com.gradleup.shadow") version "9.0.0-rc1"
}

repositories {
Expand All @@ -23,11 +24,11 @@ dependencies {
implementation("com.lmax:disruptor:3.4.4")
implementation("org.java-websocket:Java-WebSocket:1.5.3")
implementation("org.jsoup:jsoup:1.16.1")
implementation("org.json:json:20211205")
implementation("org.json:json:20231013")
implementation("com.j2html:j2html:1.6.0")
implementation("commons-configuration:commons-configuration:1.10")
implementation("commons-cli:commons-cli:1.5.0")
implementation("commons-io:commons-io:2.13.0")
implementation("commons-io:commons-io:2.14.0")
implementation("org.apache.httpcomponents:httpclient:4.5.14")
implementation("org.apache.httpcomponents:httpmime:4.5.14")
implementation("org.apache.logging.log4j:log4j-api:2.20.0")
Expand All @@ -36,13 +37,19 @@ dependencies {
implementation("org.graalvm.js:js:22.3.2")
testImplementation(enforcedPlatform("org.junit:junit-bom:5.10.0"))
testImplementation("org.junit.jupiter:junit-jupiter")
testImplementation("org.mockito:mockito-core:5.+")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}

group = "com.rarchives.ripme"
version = "1.7.94"
description = "ripme"

java {
sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_21
}

jacoco {
toolVersion = "0.8.12"
}
Expand Down Expand Up @@ -80,6 +87,10 @@ tasks.withType<Jar> {
})
}

tasks.shadowJar {
transform<com.github.jengelman.gradle.plugins.shadow.transformers.Log4j2PluginsCacheFileTransformer>()
}

publishing {
publications {
create<MavenPublication>("maven") {
Expand All @@ -105,6 +116,8 @@ tasks.test {
includeEngines("junit-vintage")
}
finalizedBy(tasks.jacocoTestReport) // report is always generated after tests run

jvmArgs("-javaagent:${classpath.find { it.name.contains("mockito") }?.absolutePath}")
}

tasks.register<Test>("testAll") {
Expand Down
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
Expand Down
3 changes: 3 additions & 0 deletions src/main/java/com/rarchives/ripme/App.java
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ public static void main(String[] args) throws IOException {
if (GraphicsEnvironment.isHeadless() || args.length > 0) {
handleArguments(args);
} else {
// Antialiasing hint, especially for Linux
System.setProperty("awt.useSystemAAFontSettings", "on");

if (SystemUtils.IS_OS_MAC_OSX) {
System.setProperty("apple.laf.useScreenMenuBar", "true");
System.setProperty("com.apple.mrj.application.apple.menu.about.name", "RipMe");
Expand Down
191 changes: 28 additions & 163 deletions src/main/java/com/rarchives/ripme/ripper/AbstractHTMLRipper.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,7 @@
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.*;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
Expand All @@ -36,9 +32,6 @@ public abstract class AbstractHTMLRipper extends AbstractRipper {

private static final Logger logger = LogManager.getLogger(AbstractHTMLRipper.class);

private final Map<URL, File> itemsPending = Collections.synchronizedMap(new HashMap<>());
private final Map<URL, Path> itemsCompleted = Collections.synchronizedMap(new HashMap<>());
private final Map<URL, String> itemsErrored = Collections.synchronizedMap(new HashMap<>());
Document cachedFirstPage;

protected AbstractHTMLRipper(URL url) throws IOException {
Expand Down Expand Up @@ -76,10 +69,6 @@ protected List<String> getDescriptionsFromPage(Document doc) throws IOException

protected abstract void downloadURL(URL url, int index);

protected DownloadThreadPool getThreadPool() {
return null;
}

protected boolean keepSortOrder() {
return true;
}
Expand Down Expand Up @@ -121,7 +110,7 @@ protected boolean pageContainsAlbums(URL url) {

@Override
public void rip() throws IOException, URISyntaxException {
int index = 0;
int imageIndex = 0;
int textindex = 0;
logger.info("Retrieving " + this.url);
sendUpdate(STATUS.LOADING_RESOURCE, this.url.toExternalForm());
Expand Down Expand Up @@ -176,9 +165,10 @@ public void rip() throws IOException, URISyntaxException {
}

for (String imageURL : imageURLs) {
index += 1;
logger.debug("Found image url #" + index + ": '" + imageURL + "'");
downloadURL(new URI(imageURL).toURL(), index);
imageIndex += 1;
logger.debug("Found image url #{} of album {}: {}", imageIndex, this.url, imageURL);
setItemsTotal(Math.max(getItemsTotal(), imageIndex));
downloadURL(new URI(imageURL).toURL(), imageIndex);
if (isStopped() || isThisATest()) {
break;
}
Expand Down Expand Up @@ -206,7 +196,7 @@ public void rip() throws IOException, URISyntaxException {
workingDir.getCanonicalPath()
+ ""
+ File.separator
+ getPrefix(index)
+ getPrefix(imageIndex)
+ (tempDesc.length > 1 ? tempDesc[1] : filename)
+ ".txt").exists();

Expand Down Expand Up @@ -236,12 +226,17 @@ public void rip() throws IOException, URISyntaxException {
}
}

// If they're using a thread pool, wait for it.
if (getThreadPool() != null) {
logger.debug("Waiting for threadpool " + getThreadPool().getClass().getName());
getThreadPool().waitForThreads();
logger.info("All items queued; total items: {}; url: {}", imageIndex, url);

// Final total item count is now known
setItemsTotal(imageIndex);

if (getCrawlerThreadPool() != null) {
logger.debug("Waiting for crawler threadpool: {}", url);
getCrawlerThreadPool().waitForThreads(imageIndex, shouldStop, url);
}
waitForThreads();

waitForRipperThreads();
}

/**
Expand Down Expand Up @@ -338,68 +333,6 @@ protected boolean allowDuplicates() {
return false;
}

@Override
/*
Returns total amount of files attempted.
*/
public int getCount() {
return itemsCompleted.size() + itemsErrored.size();
}

@Override
/*
Queues multiple URLs of single images to download from a single Album URL
*/
public boolean addURLToDownload(URL url, Path saveAs, String referrer, Map<String,String> cookies, Boolean getFileExtFromMIME) {
// Only download one file if this is a test.
if (isThisATest() && (itemsCompleted.size() > 0 || itemsErrored.size() > 0)) {
stop();
itemsPending.clear();
return false;
}
if (!allowDuplicates()
&& ( itemsPending.containsKey(url)
|| itemsCompleted.containsKey(url)
|| itemsErrored.containsKey(url) )) {
// Item is already downloaded/downloading, skip it.
logger.info("[!] Skipping " + url + " -- already attempted: " + Utils.removeCWD(saveAs));
return false;
}
if (shouldIgnoreURL(url)) {
sendUpdate(STATUS.DOWNLOAD_SKIP, "Skipping " + url.toExternalForm() + " - ignored extension");
return false;
}
if (Utils.getConfigBoolean("urls_only.save", false)) {
// Output URL to file
Path urlFile = Paths.get(this.workingDir + "/urls.txt");
String text = url.toExternalForm() + System.lineSeparator();
try {
Files.write(urlFile, text.getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE, StandardOpenOption.APPEND);
itemsCompleted.put(url, urlFile);
} catch (IOException e) {
logger.error("Error while writing to " + urlFile, e);
}
}
else {
itemsPending.put(url, saveAs.toFile());
DownloadFileThread dft = new DownloadFileThread(url, saveAs.toFile(), this, getFileExtFromMIME);
if (referrer != null) {
dft.setReferrer(referrer);
}
if (cookies != null) {
dft.setCookies(cookies);
}
threadPool.addThread(dft);
}

return true;
}

@Override
public boolean addURLToDownload(URL url, Path saveAs) {
return addURLToDownload(url, saveAs, null, null, false);
}

/**
* Queues image to be downloaded and saved.
* Uses filename from URL to decide filename.
Expand All @@ -413,72 +346,6 @@ protected boolean addURLToDownload(URL url) {
return addURLToDownload(url, "", "");
}

@Override
/*
Cleans up & tells user about successful download
*/
public void downloadCompleted(URL url, Path saveAs) {
if (observer == null) {
return;
}
try {
String path = Utils.removeCWD(saveAs);
RipStatusMessage msg = new RipStatusMessage(STATUS.DOWNLOAD_COMPLETE, path);
itemsPending.remove(url);
itemsCompleted.put(url, saveAs);
observer.update(this, msg);

checkIfComplete();
} catch (Exception e) {
logger.error("Exception while updating observer: ", e);
}
}

@Override
/*
* Cleans up & tells user about failed download.
*/
public void downloadErrored(URL url, String reason) {
if (observer == null) {
return;
}
itemsPending.remove(url);
itemsErrored.put(url, reason);
observer.update(this, new RipStatusMessage(STATUS.DOWNLOAD_ERRORED, url + " : " + reason));

checkIfComplete();
}

@Override
/*
Tells user that a single file in the album they wish to download has
already been downloaded in the past.
*/
public void downloadExists(URL url, Path file) {
if (observer == null) {
return;
}

itemsPending.remove(url);
itemsCompleted.put(url, file);
observer.update(this, new RipStatusMessage(STATUS.DOWNLOAD_WARN, url + " already saved as " + file));

checkIfComplete();
}

/**
* Notifies observers and updates state if all files have been ripped.
*/
@Override
protected void checkIfComplete() {
if (observer == null) {
return;
}
if (itemsPending.isEmpty()) {
super.checkIfComplete();
}
}

/**
* Sets directory to save all ripped files to.
* @param url
Expand Down Expand Up @@ -515,22 +382,20 @@ public void setWorkingDir(URL url) throws IOException, URISyntaxException {
*/
@Override
public int getCompletionPercentage() {
double total = itemsPending.size() + itemsErrored.size() + itemsCompleted.size();
return (int) (100 * ( (total - itemsPending.size()) / total));
double total = getTotalCount();
if (total == 0) {
return 0;
}
return (int) (100 * ( (itemsCompleted.size() + itemsErrored.size()) / total));
}

/**
* @return
* Human-readable information on the status of the current rip.
*/
@Override
public String getStatusText() {
return getCompletionPercentage() +
"% " +
"- Pending: " + itemsPending.size() +
", Completed: " + itemsCompleted.size() +
", Errored: " + itemsErrored.size();
public int getPendingCount() {
DownloadThreadPool threadPool = getRipperThreadPool();
if (threadPool != null) {
return threadPool.getPendingThreadCount();
}
return itemsPending.size();
}


}
Loading
Loading