Skip to content

Commit f84237a

Browse files
authoredJan 30, 2025··
feat: bundle download retries on fail (#893)
1 parent a5467bd commit f84237a

File tree

7 files changed

+77
-22
lines changed

7 files changed

+77
-22
lines changed
 

‎src/main/java/com/crowdin/cli/client/ClientBundle.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public interface ClientBundle extends Client {
1717

1818
URL downloadBundle(Long id, String exportId);
1919

20-
BundleExport startExportingBundle(Long id);
20+
BundleExport startExportingBundle(Long id) throws ResponseException;
2121

2222
BundleExport checkExportingBundle(Long tmId, String exportId);
2323

‎src/main/java/com/crowdin/cli/client/CrowdinClientBundle.java

+19-3
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
package com.crowdin.cli.client;
22

3+
import com.crowdin.cli.utils.Utils;
34
import com.crowdin.client.bundles.model.AddBundleRequest;
45
import com.crowdin.client.bundles.model.Bundle;
56
import com.crowdin.client.bundles.model.BundleExport;
67
import lombok.SneakyThrows;
78

89
import java.net.URL;
10+
import java.util.LinkedHashMap;
911
import java.util.List;
12+
import java.util.Map;
13+
import java.util.function.BiPredicate;
1014

1115
public class CrowdinClientBundle extends CrowdinClientCore implements ClientBundle {
1216

@@ -46,10 +50,22 @@ public URL downloadBundle(Long id, String exportId) {
4650
}
4751

4852
@Override
49-
public BundleExport startExportingBundle(Long id) {
50-
return executeRequest(() -> this.client.getBundlesApi()
53+
public BundleExport startExportingBundle(Long id) throws ResponseException {
54+
Map<BiPredicate<String, String>, ResponseException> errorHandler = new LinkedHashMap<>() {{
55+
put((code, message) -> message.contains("Another export is currently in progress. Please wait until it's finished."),
56+
new RepeatException("Another export is currently in progress. Please wait until it's finished."));
57+
put((code, message) -> Utils.isServerErrorCode(code), new RepeatException("Server Error"));
58+
put((code, message) -> message.contains("Request aborted"), new RepeatException("Request aborted"));
59+
put((code, message) -> message.contains("Connection reset"), new RepeatException("Connection reset"));
60+
}};
61+
return executeRequestWithPossibleRetries(
62+
errorHandler,
63+
() -> this.client.getBundlesApi()
5164
.exportBundle(Long.valueOf(projectId), id)
52-
.getData());
65+
.getData(),
66+
3,
67+
3 * 1000
68+
);
5369
}
5470

5571
@Override

‎src/main/java/com/crowdin/cli/client/CrowdinClientCore.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ protected static <T> T executeRequestWithPossibleRetries(Map<BiPredicate<String,
9191
} catch (InterruptedException ie) {
9292
// ignore
9393
}
94-
System.out.printf("Attempting to retry the request due to the error: %s%n", e.getMessage());
94+
System.out.println(String.format("%nAttempting to retry the request due to the error: %s", e.getMessage()));
9595
return executeRequestWithPossibleRetries(errorHandlers, request, maxAttempts - 1, millisToRetry);
9696
}
9797
}

‎src/main/java/com/crowdin/cli/commands/actions/BundleDownloadAction.java

+19-12
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.crowdin.cli.commands.actions;
22

33
import com.crowdin.cli.client.ClientBundle;
4+
import com.crowdin.cli.client.MaxNumberOfRetriesException;
45
import com.crowdin.cli.commands.NewAction;
56
import com.crowdin.cli.commands.Outputter;
67
import com.crowdin.cli.commands.functionality.*;
@@ -19,8 +20,7 @@
1920
import java.util.stream.Collectors;
2021

2122
import static com.crowdin.cli.BaseCli.RESOURCE_BUNDLE;
22-
import static com.crowdin.cli.utils.console.ExecutionStatus.ERROR;
23-
import static com.crowdin.cli.utils.console.ExecutionStatus.OK;
23+
import static com.crowdin.cli.utils.console.ExecutionStatus.*;
2424

2525
public class BundleDownloadAction implements NewAction<ProjectProperties, ClientBundle> {
2626

@@ -48,6 +48,9 @@ public void act(Outputter out, ProjectProperties pb, ClientBundle client) {
4848
this.out = out;
4949
Bundle bundle = getBundle(client);
5050
BundleExport status = this.buildBundle(out, client, bundle.getId());
51+
if (status == null) {
52+
throw new RuntimeException(RESOURCE_BUNDLE.getString("error.bundle.build_bundle"));
53+
}
5154
to = new File("bundle-" + status.getIdentifier() + ".zip");
5255
downloadBundle(client, bundle.getId(), status.getIdentifier());
5356
out.println(OK.withIcon(String.format(RESOURCE_BUNDLE.getString("message.bundle.download_success"), bundle.getId(), bundle.getName())));
@@ -94,21 +97,25 @@ private BundleExport buildBundle(Outputter out, ClientBundle client, Long bundle
9497
this.noProgress,
9598
false,
9699
() -> {
97-
BundleExport status = client.startExportingBundle(bundleId);
100+
BundleExport status = null;
101+
try {
102+
status = client.startExportingBundle(bundleId);
98103

99-
while (!status.getStatus().equalsIgnoreCase("finished")) {
100-
ConsoleSpinner.update(String.format(RESOURCE_BUNDLE.getString("message.spinner.building_bundle_percents"), status.getProgress()));
101-
Thread.sleep(1000);
104+
while (!status.getStatus().equalsIgnoreCase("finished")) {
105+
ConsoleSpinner.update(String.format(RESOURCE_BUNDLE.getString("message.spinner.building_bundle_percents"), status.getProgress()));
106+
Thread.sleep(1000);
102107

103-
status = client.checkExportingBundle(bundleId, status.getIdentifier());
108+
status = client.checkExportingBundle(bundleId, status.getIdentifier());
104109

105-
if (status.getStatus().equalsIgnoreCase("failed")) {
106-
throw new RuntimeException(RESOURCE_BUNDLE.getString("message.spinner.build_has_failed"));
110+
if (status.getStatus().equalsIgnoreCase("failed")) {
111+
throw new RuntimeException(RESOURCE_BUNDLE.getString("message.spinner.build_has_failed"));
112+
}
107113
}
108-
}
109-
110-
ConsoleSpinner.update(String.format(RESOURCE_BUNDLE.getString("message.spinner.building_bundle_percents"), 100));
111114

115+
ConsoleSpinner.update(String.format(RESOURCE_BUNDLE.getString("message.spinner.building_bundle_percents"), 100));
116+
} catch (MaxNumberOfRetriesException e) {
117+
ConsoleSpinner.stop(WARNING, String.format(RESOURCE_BUNDLE.getString("message.warning.maximum_retries_exceeded"), 3));
118+
}
112119
return status;
113120
}
114121
);

‎src/main/java/com/crowdin/cli/commands/actions/DownloadAction.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -437,7 +437,7 @@ private ProjectBuild buildTranslation(ProjectClient client, BuildProjectTranslat
437437
}
438438
ConsoleSpinner.update(String.format(RESOURCE_BUNDLE.getString("message.building_translation"), 100));
439439
} catch (MaxNumberOfRetriesException e) {
440-
ConsoleSpinner.stop(WARNING, RESOURCE_BUNDLE.getString("message.warning.another_build_in_progress"));
440+
ConsoleSpinner.stop(WARNING, String.format(RESOURCE_BUNDLE.getString("message.warning.maximum_retries_exceeded"), 3));
441441
}
442442
return build;
443443
}

‎src/main/resources/messages/messages.properties

+1-1
Original file line numberDiff line numberDiff line change
@@ -817,7 +817,7 @@ message.warning.file_not_uploaded_cause_of_language=Translation file @|yellow '%
817817
message.warning.auto_approve_option_with_mt='--auto-approve-option' is used only for the TM Pre-Translation method
818818
message.warning.no_file_to_download=Couldn't find any file to download
819819
message.warning.no_file_to_download_skipuntranslated=Couldn't find any file to download. Since you are using the 'Skip untranslated files' option, please make sure you have fully translated files
820-
message.warning.another_build_in_progress=Another build is currently in progress. Please wait until it's finished
820+
message.warning.maximum_retries_exceeded=The operation failed after %d retries. Please try again later.
821821
message.spinner.fetching_project_info=Fetching project info
822822
message.spinner.building_translations=Building translations
823823
message.spinner.building_translation=Building translation

‎src/test/java/com/crowdin/cli/commands/actions/BundleDownloadActionTest.java

+35-3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import com.crowdin.cli.MockitoUtils;
44
import com.crowdin.cli.client.ClientBundle;
5+
import com.crowdin.cli.client.MaxNumberOfRetriesException;
6+
import com.crowdin.cli.client.ResponseException;
57
import com.crowdin.cli.commands.NewAction;
68
import com.crowdin.cli.commands.Outputter;
79
import com.crowdin.cli.commands.functionality.FilesInterface;
@@ -16,8 +18,8 @@
1618
import org.junit.jupiter.api.Test;
1719

1820
import java.net.URL;
19-
import java.util.Optional;
2021

22+
import static org.junit.jupiter.api.Assertions.assertThrows;
2123
import static org.mockito.Mockito.*;
2224

2325
public class BundleDownloadActionTest {
@@ -35,7 +37,7 @@ public void deleteProj() {
3537
}
3638

3739
@Test
38-
public void testDownloadBundle() {
40+
public void testDownloadBundle() throws ResponseException {
3941
PropertiesWithFiles pb = NewPropertiesWithFilesUtilBuilder
4042
.minimalBuiltPropertiesBean(
4143
"/values/strings.xml", "/values-%two_letters_code%/%original_file_name%",
@@ -73,7 +75,7 @@ public void testDownloadBundle() {
7375
}
7476

7577
@Test
76-
public void testDryRun() {
78+
public void testDryRun() throws ResponseException {
7779
PropertiesWithFiles pb = NewPropertiesWithFilesUtilBuilder
7880
.minimalBuiltPropertiesBean(
7981
"/values/strings.xml", "/values-%two_letters_code%/%original_file_name%",
@@ -110,4 +112,34 @@ public void testDryRun() {
110112

111113
verifyNoMoreInteractions(client);
112114
}
115+
116+
@Test
117+
public void testDownloadBundle_FailBundleExport() throws ResponseException {
118+
PropertiesWithFiles pb = NewPropertiesWithFilesUtilBuilder
119+
.minimalBuiltPropertiesBean(
120+
"/values/strings.xml", "/values-%two_letters_code%/%original_file_name%",
121+
null, "/common/%original_file_name%")
122+
.setBasePath(project.getBasePath())
123+
.build();
124+
125+
Bundle bundle = new Bundle();
126+
bundle.setId(1L);
127+
ClientBundle client = mock(ClientBundle.class);
128+
129+
when(client.getBundle(bundle.getId()))
130+
.thenReturn(bundle);
131+
when(client.startExportingBundle(bundle.getId()))
132+
.thenThrow(new MaxNumberOfRetriesException());
133+
134+
FilesInterface files = mock(FilesInterface.class);
135+
136+
NewAction<ProjectProperties, ClientBundle> action =
137+
new BundleDownloadAction(bundle.getId(), files, false, false, false, true);
138+
assertThrows(RuntimeException.class, () -> action.act(Outputter.getDefault(), pb, client));
139+
140+
verify(client).getBundle(bundle.getId());
141+
verify(client).startExportingBundle(bundle.getId());
142+
143+
verifyNoMoreInteractions(client);
144+
}
113145
}

0 commit comments

Comments
 (0)
Please sign in to comment.