Skip to content

Commit

Permalink
feat: bundle download retries on fail (#893)
Browse files Browse the repository at this point in the history
  • Loading branch information
katerina20 authored Jan 30, 2025
1 parent a5467bd commit f84237a
Show file tree
Hide file tree
Showing 7 changed files with 77 additions and 22 deletions.
2 changes: 1 addition & 1 deletion src/main/java/com/crowdin/cli/client/ClientBundle.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public interface ClientBundle extends Client {

URL downloadBundle(Long id, String exportId);

BundleExport startExportingBundle(Long id);
BundleExport startExportingBundle(Long id) throws ResponseException;

BundleExport checkExportingBundle(Long tmId, String exportId);

Expand Down
22 changes: 19 additions & 3 deletions src/main/java/com/crowdin/cli/client/CrowdinClientBundle.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
package com.crowdin.cli.client;

import com.crowdin.cli.utils.Utils;
import com.crowdin.client.bundles.model.AddBundleRequest;
import com.crowdin.client.bundles.model.Bundle;
import com.crowdin.client.bundles.model.BundleExport;
import lombok.SneakyThrows;

import java.net.URL;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.BiPredicate;

public class CrowdinClientBundle extends CrowdinClientCore implements ClientBundle {

Expand Down Expand Up @@ -46,10 +50,22 @@ public URL downloadBundle(Long id, String exportId) {
}

@Override
public BundleExport startExportingBundle(Long id) {
return executeRequest(() -> this.client.getBundlesApi()
public BundleExport startExportingBundle(Long id) throws ResponseException {
Map<BiPredicate<String, String>, ResponseException> errorHandler = new LinkedHashMap<>() {{
put((code, message) -> message.contains("Another export is currently in progress. Please wait until it's finished."),
new RepeatException("Another export is currently in progress. Please wait until it's finished."));
put((code, message) -> Utils.isServerErrorCode(code), new RepeatException("Server Error"));
put((code, message) -> message.contains("Request aborted"), new RepeatException("Request aborted"));
put((code, message) -> message.contains("Connection reset"), new RepeatException("Connection reset"));
}};
return executeRequestWithPossibleRetries(
errorHandler,
() -> this.client.getBundlesApi()
.exportBundle(Long.valueOf(projectId), id)
.getData());
.getData(),
3,
3 * 1000
);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ protected static <T> T executeRequestWithPossibleRetries(Map<BiPredicate<String,
} catch (InterruptedException ie) {
// ignore
}
System.out.printf("Attempting to retry the request due to the error: %s%n", e.getMessage());
System.out.println(String.format("%nAttempting to retry the request due to the error: %s", e.getMessage()));
return executeRequestWithPossibleRetries(errorHandlers, request, maxAttempts - 1, millisToRetry);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.crowdin.cli.commands.actions;

import com.crowdin.cli.client.ClientBundle;
import com.crowdin.cli.client.MaxNumberOfRetriesException;
import com.crowdin.cli.commands.NewAction;
import com.crowdin.cli.commands.Outputter;
import com.crowdin.cli.commands.functionality.*;
Expand All @@ -19,8 +20,7 @@
import java.util.stream.Collectors;

import static com.crowdin.cli.BaseCli.RESOURCE_BUNDLE;
import static com.crowdin.cli.utils.console.ExecutionStatus.ERROR;
import static com.crowdin.cli.utils.console.ExecutionStatus.OK;
import static com.crowdin.cli.utils.console.ExecutionStatus.*;

public class BundleDownloadAction implements NewAction<ProjectProperties, ClientBundle> {

Expand Down Expand Up @@ -48,6 +48,9 @@ public void act(Outputter out, ProjectProperties pb, ClientBundle client) {
this.out = out;
Bundle bundle = getBundle(client);
BundleExport status = this.buildBundle(out, client, bundle.getId());
if (status == null) {
throw new RuntimeException(RESOURCE_BUNDLE.getString("error.bundle.build_bundle"));
}
to = new File("bundle-" + status.getIdentifier() + ".zip");
downloadBundle(client, bundle.getId(), status.getIdentifier());
out.println(OK.withIcon(String.format(RESOURCE_BUNDLE.getString("message.bundle.download_success"), bundle.getId(), bundle.getName())));
Expand Down Expand Up @@ -94,21 +97,25 @@ private BundleExport buildBundle(Outputter out, ClientBundle client, Long bundle
this.noProgress,
false,
() -> {
BundleExport status = client.startExportingBundle(bundleId);
BundleExport status = null;
try {
status = client.startExportingBundle(bundleId);

while (!status.getStatus().equalsIgnoreCase("finished")) {
ConsoleSpinner.update(String.format(RESOURCE_BUNDLE.getString("message.spinner.building_bundle_percents"), status.getProgress()));
Thread.sleep(1000);
while (!status.getStatus().equalsIgnoreCase("finished")) {
ConsoleSpinner.update(String.format(RESOURCE_BUNDLE.getString("message.spinner.building_bundle_percents"), status.getProgress()));
Thread.sleep(1000);

status = client.checkExportingBundle(bundleId, status.getIdentifier());
status = client.checkExportingBundle(bundleId, status.getIdentifier());

if (status.getStatus().equalsIgnoreCase("failed")) {
throw new RuntimeException(RESOURCE_BUNDLE.getString("message.spinner.build_has_failed"));
if (status.getStatus().equalsIgnoreCase("failed")) {
throw new RuntimeException(RESOURCE_BUNDLE.getString("message.spinner.build_has_failed"));
}
}
}

ConsoleSpinner.update(String.format(RESOURCE_BUNDLE.getString("message.spinner.building_bundle_percents"), 100));

ConsoleSpinner.update(String.format(RESOURCE_BUNDLE.getString("message.spinner.building_bundle_percents"), 100));
} catch (MaxNumberOfRetriesException e) {
ConsoleSpinner.stop(WARNING, String.format(RESOURCE_BUNDLE.getString("message.warning.maximum_retries_exceeded"), 3));
}
return status;
}
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -437,7 +437,7 @@ private ProjectBuild buildTranslation(ProjectClient client, BuildProjectTranslat
}
ConsoleSpinner.update(String.format(RESOURCE_BUNDLE.getString("message.building_translation"), 100));
} catch (MaxNumberOfRetriesException e) {
ConsoleSpinner.stop(WARNING, RESOURCE_BUNDLE.getString("message.warning.another_build_in_progress"));
ConsoleSpinner.stop(WARNING, String.format(RESOURCE_BUNDLE.getString("message.warning.maximum_retries_exceeded"), 3));
}
return build;
}
Expand Down
2 changes: 1 addition & 1 deletion src/main/resources/messages/messages.properties
Original file line number Diff line number Diff line change
Expand Up @@ -817,7 +817,7 @@ message.warning.file_not_uploaded_cause_of_language=Translation file @|yellow '%
message.warning.auto_approve_option_with_mt='--auto-approve-option' is used only for the TM Pre-Translation method
message.warning.no_file_to_download=Couldn't find any file to download
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
message.warning.another_build_in_progress=Another build is currently in progress. Please wait until it's finished
message.warning.maximum_retries_exceeded=The operation failed after %d retries. Please try again later.
message.spinner.fetching_project_info=Fetching project info
message.spinner.building_translations=Building translations
message.spinner.building_translation=Building translation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import com.crowdin.cli.MockitoUtils;
import com.crowdin.cli.client.ClientBundle;
import com.crowdin.cli.client.MaxNumberOfRetriesException;
import com.crowdin.cli.client.ResponseException;
import com.crowdin.cli.commands.NewAction;
import com.crowdin.cli.commands.Outputter;
import com.crowdin.cli.commands.functionality.FilesInterface;
Expand All @@ -16,8 +18,8 @@
import org.junit.jupiter.api.Test;

import java.net.URL;
import java.util.Optional;

import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.*;

public class BundleDownloadActionTest {
Expand All @@ -35,7 +37,7 @@ public void deleteProj() {
}

@Test
public void testDownloadBundle() {
public void testDownloadBundle() throws ResponseException {
PropertiesWithFiles pb = NewPropertiesWithFilesUtilBuilder
.minimalBuiltPropertiesBean(
"/values/strings.xml", "/values-%two_letters_code%/%original_file_name%",
Expand Down Expand Up @@ -73,7 +75,7 @@ public void testDownloadBundle() {
}

@Test
public void testDryRun() {
public void testDryRun() throws ResponseException {
PropertiesWithFiles pb = NewPropertiesWithFilesUtilBuilder
.minimalBuiltPropertiesBean(
"/values/strings.xml", "/values-%two_letters_code%/%original_file_name%",
Expand Down Expand Up @@ -110,4 +112,34 @@ public void testDryRun() {

verifyNoMoreInteractions(client);
}

@Test
public void testDownloadBundle_FailBundleExport() throws ResponseException {
PropertiesWithFiles pb = NewPropertiesWithFilesUtilBuilder
.minimalBuiltPropertiesBean(
"/values/strings.xml", "/values-%two_letters_code%/%original_file_name%",
null, "/common/%original_file_name%")
.setBasePath(project.getBasePath())
.build();

Bundle bundle = new Bundle();
bundle.setId(1L);
ClientBundle client = mock(ClientBundle.class);

when(client.getBundle(bundle.getId()))
.thenReturn(bundle);
when(client.startExportingBundle(bundle.getId()))
.thenThrow(new MaxNumberOfRetriesException());

FilesInterface files = mock(FilesInterface.class);

NewAction<ProjectProperties, ClientBundle> action =
new BundleDownloadAction(bundle.getId(), files, false, false, false, true);
assertThrows(RuntimeException.class, () -> action.act(Outputter.getDefault(), pb, client));

verify(client).getBundle(bundle.getId());
verify(client).startExportingBundle(bundle.getId());

verifyNoMoreInteractions(client);
}
}

0 comments on commit f84237a

Please sign in to comment.