Skip to content

Commit 0baef19

Browse files
author
ylexus
committed
CLI exit code and error message improvements; fix a bug with throttling of progress updates
1 parent c8bafd0 commit 0baef19

File tree

10 files changed

+95
-44
lines changed

10 files changed

+95
-44
lines changed

.idea/inspectionProfiles/Project_Default.xml

Lines changed: 0 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ dependencies {
4949
ext.orgJunitJupiterVersion = '5.7.2'
5050
ext.orgMockitoVersion = '3.10.0'
5151
ext.orgImmutablesVersion = '2.8.8'
52-
ext.netYudichevJiottyVersion = '2.1.0'
52+
ext.netYudichevJiottyVersion = '2.1.2-SNAPSHOT'
5353
ext.orgApacheLoggingLog4jVersion = '2.14.1'
5454

5555
annotationProcessor "org.immutables:value:$orgImmutablesVersion"

app/src/main/java/net/yudichev/googlephotosupload/cli/CliMain.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ public final class CliMain {
1919

2020
public static void main(String[] args) {
2121
CommandLineParser parser = new DefaultParser();
22+
var exitCode = 0;
2223
try {
2324
var commandLine = parser.parse(CliOptions.OPTIONS, args);
2425
var helpRequested = commandLine.hasOption('h');
@@ -33,18 +34,26 @@ public static void main(String[] args) {
3334
var settingsModule = new SettingsModule();
3435
if (otherInstanceRunning(settingsModule.getSettingsRootPath())) {
3536
logger.error("Another copy of the app is already running");
37+
exitCode = 1;
3638
} else {
3739
startApp(settingsModule, commandLine);
40+
if (!CliStarter.isCompletedSuccessfully()) {
41+
exitCode = 2;
42+
}
3843
}
3944
} else if (!helpRequested && !versionRequested) {
4045
logger.error("Missing option -r");
46+
exitCode = 3;
4147
printHelp();
4248
}
4349
} catch (ParseException e) {
4450
logger.error(e.getMessage());
51+
exitCode = 4;
4552
printHelp();
53+
} finally {
54+
LogManager.shutdown();
4655
}
47-
LogManager.shutdown();
56+
System.exit(exitCode);
4857
}
4958

5059
private static void startApp(SettingsModule settingsModule, CommandLine commandLine) {

app/src/main/java/net/yudichev/googlephotosupload/cli/CliStarter.java

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,14 @@
1414
import java.util.ResourceBundle;
1515

1616
import static com.google.common.base.Preconditions.checkNotNull;
17-
import static net.yudichev.jiotty.common.lang.CompletableFutures.logErrorOnFailure;
17+
import static net.yudichev.googlephotosupload.core.HumanReadableExceptionMessage.toHumanReadableMessage;
1818

1919
final class CliStarter extends BaseLifecycleComponent {
2020
private static final Logger logger = LoggerFactory.getLogger(CliStarter.class);
21+
22+
@SuppressWarnings("StaticVariableMayNotBeInitialized")
23+
private static volatile boolean completedSuccessfully;
24+
2125
private final Path rootDir;
2226
private final Uploader uploader;
2327
private final ApplicationLifecycleControl applicationLifecycleControl;
@@ -36,11 +40,22 @@ final class CliStarter extends BaseLifecycleComponent {
3640
this.resourceBundle = checkNotNull(resourceBundle);
3741
}
3842

43+
@SuppressWarnings("StaticVariableUsedBeforeInitialization")
44+
public static boolean isCompletedSuccessfully() {
45+
return completedSuccessfully;
46+
}
47+
3948
@Override
4049
protected void doStart() {
4150
logger.info(resourceBundle.getString("googleStorageWarning"));
4251
uploader.upload(ImmutableList.of(rootDir), resume)
43-
.whenComplete(logErrorOnFailure(logger, "Failed"))
44-
.whenComplete((ignored1, ignored2) -> applicationLifecycleControl.initiateShutdown());
52+
.whenComplete((ignored1, e) -> {
53+
//noinspection AssignmentToStaticFieldFromInstanceMethod
54+
completedSuccessfully = e == null;
55+
if (e != null) {
56+
logger.error("{}", toHumanReadableMessage(resourceBundle, e));
57+
}
58+
applicationLifecycleControl.initiateShutdown();
59+
});
4560
}
4661
}

app/src/main/java/net/yudichev/googlephotosupload/core/DriveSpaceTrackerImpl.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
final class DriveSpaceTrackerImpl implements DriveSpaceTracker {
2424
private static final Logger logger = LoggerFactory.getLogger(DriveSpaceTrackerImpl.class);
2525

26-
private static final long CHECK_SPACE_EVERY_BYTES = 50 * 1024 * 1024; // 50 MB
26+
private static final long CHECK_SPACE_EVERY_BYTES = 10 * 1024 * 1024; // 50 MB
2727
private static final ImmutableSet<String> FIELDS = ImmutableSet.of("storageQuota/limit", "storageQuota/usage");
2828
private static final byte[] NO_DATA = new byte[0];
2929
private final ProgressStatusFactory progressStatusFactory;
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package net.yudichev.googlephotosupload.core;
2+
3+
import com.google.api.client.googleapis.json.GoogleJsonResponseException;
4+
5+
import java.util.ResourceBundle;
6+
7+
import static com.google.api.client.http.HttpStatusCodes.STATUS_CODE_FORBIDDEN;
8+
import static com.google.common.base.Throwables.getCausalChain;
9+
import static net.yudichev.jiotty.common.lang.HumanReadableExceptionMessage.humanReadableMessage;
10+
11+
public final class HumanReadableExceptionMessage {
12+
private HumanReadableExceptionMessage() {
13+
}
14+
15+
public static String toHumanReadableMessage(ResourceBundle resourceBundle, Throwable exception) {
16+
return getCausalChain(exception).stream()
17+
.filter(throwable -> throwable instanceof GoogleJsonResponseException)
18+
.findFirst()
19+
.map(throwable -> (GoogleJsonResponseException) throwable)
20+
.map(jsonResponseException -> {
21+
// better error for GoogleJsonResponseException, otherwise there's too much technical details.
22+
var details = jsonResponseException.getDetails();
23+
if (details != null && details.getMessage() != null) {
24+
if (details.getCode() == STATUS_CODE_FORBIDDEN) {
25+
return details.getMessage() + ' ' + resourceBundle.getString("uploadPanePermissionErrorSuffix");
26+
} else {
27+
return details.getMessage();
28+
}
29+
}
30+
return null;
31+
})
32+
.orElseGet(() -> humanReadableMessage(exception));
33+
}
34+
}

app/src/main/java/net/yudichev/googlephotosupload/ui/ThrottlingProgressStatus.java

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,27 +6,29 @@
66
import net.yudichev.googlephotosupload.core.ProgressStatus;
77
import net.yudichev.jiotty.common.async.ExecutorFactory;
88
import net.yudichev.jiotty.common.async.SchedulingExecutor;
9+
import net.yudichev.jiotty.common.lang.Closeable;
910
import net.yudichev.jiotty.common.lang.throttling.ThrottlingConsumer;
1011

1112
import javax.inject.Inject;
1213
import java.lang.annotation.Retention;
1314
import java.lang.annotation.Target;
1415
import java.time.Duration;
15-
import java.util.ArrayList;
16-
import java.util.Collection;
17-
import java.util.Optional;
16+
import java.util.*;
1817
import java.util.concurrent.ArrayBlockingQueue;
1918
import java.util.concurrent.BlockingQueue;
2019
import java.util.concurrent.atomic.AtomicInteger;
2120
import java.util.concurrent.atomic.AtomicReference;
21+
import java.util.function.Function;
22+
import java.util.stream.Stream;
2223

2324
import static com.google.common.base.Preconditions.checkState;
2425
import static java.lang.annotation.ElementType.*;
2526
import static java.lang.annotation.RetentionPolicy.RUNTIME;
27+
import static java.util.stream.Collectors.toMap;
2628

2729
final class ThrottlingProgressStatus implements ProgressStatus {
2830
private final ProgressValueUpdater delegate;
29-
private final ThrottlingConsumer<Runnable> eventSink;
31+
private final Map<Event, ThrottlingConsumer<Runnable>> eventSinksByEvent;
3032
private final AtomicInteger successCount = new AtomicInteger();
3133
private final AtomicInteger totalCount = new AtomicInteger();
3234
private final AtomicReference<String> description = new AtomicReference<>();
@@ -42,56 +44,57 @@ final class ThrottlingProgressStatus implements ProgressStatus {
4244
@Assisted Optional<Integer> totalCount) {
4345
delegate = delegateFactory.create(name, totalCount);
4446
executor = executorFactory.createSingleThreadedSchedulingExecutor("progress-status");
45-
eventSink = new ThrottlingConsumer<>(executor, Duration.ofMillis(200), Runnable::run);
47+
eventSinksByEvent = new EnumMap<>(Stream.of(Event.values()).collect(toMap(
48+
Function.identity(), event -> new ThrottlingConsumer<>(executor, Duration.ofMillis(200), Runnable::run))));
4649
}
4750

4851
@Override
4952
public void updateSuccess(int newValue) {
5053
ensureNotClosed();
5154
successCount.set(newValue);
52-
eventSink.accept(() -> delegate.updateSuccess(successCount.get()));
55+
eventSinksByEvent.get(Event.UPDATE_SUCCESS).accept(() -> delegate.updateSuccess(successCount.get()));
5356
}
5457

5558
@Override
5659
public void updateTotal(int newValue) {
5760
ensureNotClosed();
5861
totalCount.set(newValue);
59-
eventSink.accept(() -> delegate.updateTotal(totalCount.get()));
62+
eventSinksByEvent.get(Event.UPDATE_TOTAL).accept(() -> delegate.updateTotal(totalCount.get()));
6063
}
6164

6265
@Override
6366
public void updateDescription(String newValue) {
6467
ensureNotClosed();
6568
description.set(newValue);
66-
eventSink.accept(() -> delegate.updateDescription(description.get()));
69+
eventSinksByEvent.get(Event.UPDATE_DESC).accept(() -> delegate.updateDescription(description.get()));
6770
}
6871

6972
@Override
7073
public void incrementSuccessBy(int increment) {
7174
ensureNotClosed();
7275
successCount.updateAndGet(operand -> operand + increment);
73-
eventSink.accept(() -> delegate.updateSuccess(successCount.get()));
76+
eventSinksByEvent.get(Event.UPDATE_SUCCESS).accept(() -> delegate.updateSuccess(successCount.get()));
7477
}
7578

7679
@Override
7780
public void addFailure(KeyedError keyedError) {
7881
ensureNotClosed();
7982
pendingErrors.add(keyedError);
80-
eventSink.accept(this::drainPendingFailuresToDelegate);
83+
eventSinksByEvent.get(Event.ADD_FAILURES).accept(this::drainPendingFailuresToDelegate);
8184
}
8285

8386
@Override
8487
public void onBackoffDelay(long backoffDelayMs) {
8588
ensureNotClosed();
86-
eventSink.accept(() -> delegate.onBackoffDelay(backoffDelayMs));
89+
eventSinksByEvent.get(Event.ON_BACKOFF_DELAY).accept(() -> delegate.onBackoffDelay(backoffDelayMs));
8790
}
8891

8992
@Override
9093
public void close(boolean success) {
9194
closed = true;
9295
delegate.updateSuccess(successCount.get());
9396
drainPendingFailuresToDelegate();
94-
eventSink.close();
97+
eventSinksByEvent.values().forEach(Closeable::close);
9598
executor.close();
9699
delegate.completed(success);
97100
}
@@ -108,6 +111,14 @@ private void ensureNotClosed() {
108111
checkState(!closed, "closed");
109112
}
110113

114+
enum Event {
115+
UPDATE_SUCCESS,
116+
UPDATE_TOTAL,
117+
UPDATE_DESC,
118+
ADD_FAILURES,
119+
ON_BACKOFF_DELAY,
120+
}
121+
111122
@BindingAnnotation
112123
@Target({FIELD, PARAMETER, METHOD})
113124
@Retention(RUNTIME)

app/src/main/java/net/yudichev/googlephotosupload/ui/UploadPaneControllerImpl.java

Lines changed: 2 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package net.yudichev.googlephotosupload.ui;
22

3-
import com.google.api.client.googleapis.json.GoogleJsonResponseException;
43
import javafx.event.ActionEvent;
54
import javafx.scene.control.Button;
65
import javafx.scene.layout.VBox;
@@ -22,11 +21,9 @@
2221
import java.util.ResourceBundle;
2322
import java.util.concurrent.CompletableFuture;
2423

25-
import static com.google.api.client.http.HttpStatusCodes.STATUS_CODE_FORBIDDEN;
2624
import static com.google.common.base.Preconditions.checkNotNull;
27-
import static com.google.common.base.Throwables.getCausalChain;
2825
import static javafx.application.Platform.runLater;
29-
import static net.yudichev.jiotty.common.lang.HumanReadableExceptionMessage.humanReadableMessage;
26+
import static net.yudichev.googlephotosupload.core.HumanReadableExceptionMessage.toHumanReadableMessage;
3027

3128
@SuppressWarnings("ClassWithTooManyFields") // OK for a FX controller
3229
public final class UploadPaneControllerImpl extends BaseLifecycleComponent implements UploadPaneController {
@@ -122,7 +119,7 @@ private void onUploadComplete(@Nullable Throwable exception) {
122119
logger.error("Upload failed", exception);
123120
logArea.getStyleClass().add("failed-background");
124121
logAreaChildren.add(new Text(resourceBundle.getString("uploadPaneLogAreaFailurePrefix") + " "));
125-
var failureText = new Text(toHumanReadableMessage(exception));
122+
var failureText = new Text(toHumanReadableMessage(resourceBundle, exception));
126123
failureText.getStyleClass().add("failed-text");
127124
logAreaChildren.add(failureText);
128125
}
@@ -132,26 +129,6 @@ private void onUploadComplete(@Nullable Throwable exception) {
132129
});
133130
}
134131

135-
private String toHumanReadableMessage(Throwable exception) {
136-
return getCausalChain(exception).stream()
137-
.filter(throwable -> throwable instanceof GoogleJsonResponseException)
138-
.findFirst()
139-
.map(throwable -> (GoogleJsonResponseException) throwable)
140-
.map(jsonResponseException -> {
141-
// better error for GoogleJsonResponseException, otherwise there's too much technical details.
142-
var details = jsonResponseException.getDetails();
143-
if (details != null && details.getMessage() != null) {
144-
if (details.getCode() == STATUS_CODE_FORBIDDEN) {
145-
return details.getMessage() + ' ' + resourceBundle.getString("uploadPanePermissionErrorSuffix");
146-
} else {
147-
return details.getMessage();
148-
}
149-
}
150-
return null;
151-
})
152-
.orElseGet(() -> humanReadableMessage(exception));
153-
}
154-
155132
@Override
156133
public void stopUpload() {
157134
stopButton.setDisable(true);

app/src/main/resources/log4j2-ui.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ Configuration:
3636
- name: net.yudichev.googlephotosupload
3737
level: debug
3838
includeLocation: true
39+
# this one is dumping requests/responses vua jul on CONFIG (INFO) level
40+
- name: com.google.api.client.http
41+
level: warn
3942
Root:
4043
level: info
4144
includeLocation: true

app/src/test/resources/log4j2-test.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ Configuration:
1414
- name: net.yudichev.googlephotosupload
1515
level: debug
1616
includeLocation: true
17+
# this one is dumping requests/responses vua jul on CONFIG (INFO) level
18+
- name: com.google.api.client.http
19+
level: warn
1720
Root:
1821
level: info
1922
includeLocation: true

0 commit comments

Comments
 (0)