diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index 55a6162d6..78a8652bd 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -2,7 +2,8 @@ import ch.qos.logback.classic.spi.Configurator; import org.cryptomator.common.locationpresets.DropboxLinuxLocationPresetsProvider; import org.cryptomator.common.locationpresets.DropboxMacLocationPresetsProvider; import org.cryptomator.common.locationpresets.DropboxWindowsLocationPresetsProvider; -import org.cryptomator.common.locationpresets.GoogleDriveLocationPresetsProvider; +import org.cryptomator.common.locationpresets.GoogleDriveMacLocationPresetsProvider; +import org.cryptomator.common.locationpresets.GoogleDriveWindowsLocationPresetsProvider; import org.cryptomator.common.locationpresets.ICloudMacLocationPresetsProvider; import org.cryptomator.common.locationpresets.ICloudWindowsLocationPresetsProvider; import org.cryptomator.common.locationpresets.LeitzcloudLocationPresetsProvider; @@ -56,7 +57,7 @@ open module org.cryptomator.desktop { provides Configurator with LogbackConfiguratorFactory; provides LocationPresetsProvider with // DropboxWindowsLocationPresetsProvider, DropboxMacLocationPresetsProvider, DropboxLinuxLocationPresetsProvider, // - GoogleDriveLocationPresetsProvider, // + GoogleDriveMacLocationPresetsProvider, GoogleDriveWindowsLocationPresetsProvider, // ICloudWindowsLocationPresetsProvider, ICloudMacLocationPresetsProvider, // LeitzcloudLocationPresetsProvider, // MegaLocationPresetsProvider, // diff --git a/src/main/java/org/cryptomator/common/locationpresets/GoogleDriveMacLocationPresetsProvider.java b/src/main/java/org/cryptomator/common/locationpresets/GoogleDriveMacLocationPresetsProvider.java new file mode 100644 index 000000000..9cb428d1b --- /dev/null +++ b/src/main/java/org/cryptomator/common/locationpresets/GoogleDriveMacLocationPresetsProvider.java @@ -0,0 +1,144 @@ +package org.cryptomator.common.locationpresets; + +import org.cryptomator.integrations.common.CheckAvailability; +import org.cryptomator.integrations.common.OperatingSystem; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.function.Predicate; +import java.util.regex.Pattern; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +import static org.cryptomator.integrations.common.OperatingSystem.Value.MAC; + +@OperatingSystem(MAC) +@CheckAvailability +public final class GoogleDriveMacLocationPresetsProvider implements LocationPresetsProvider { + + private static final Path ROOT_LOCATION = LocationPresetsProvider.resolveLocation("~/Library/CloudStorage/").toAbsolutePath(); + private static final Predicate PATTERN = Pattern.compile("^GoogleDrive-[^/]+$").asMatchPredicate(); + + private static final List FALLBACK_LOCATIONS = List.of( // + LocationPresetsProvider.resolveLocation("~/GoogleDrive/My Drive"), // + LocationPresetsProvider.resolveLocation("~/Google Drive/My Drive"), // + LocationPresetsProvider.resolveLocation("~/GoogleDrive"), // + LocationPresetsProvider.resolveLocation("~/Google Drive") // + ); + + @Override + public Stream getLocations() { + List cloudStorageDirLocations = getCloudStorageDirLocations(); + return cloudStorageDirLocations.isEmpty() ? getFallbackLocation().stream() : cloudStorageDirLocations.stream(); + + } + + @CheckAvailability + public static boolean isPresent() { + return isRootLocationPresent() || FALLBACK_LOCATIONS.stream().anyMatch(Files::isDirectory); + } + + /** + * Checks if a root location directory is present that matches the specified pattern. + *

+ * This method scans the {@code ROOT_LOCATION} directory for subdirectories and tests each one against a pre-defined pattern ({@code PATTERN}). + * + * @return {@code true} if a matching root location is present, otherwise {@code false}. + */ + public static boolean isRootLocationPresent() { + try (var dirStream = Files.list(ROOT_LOCATION)) { + return dirStream.anyMatch(path -> Files.isDirectory(path) && PATTERN.test(path.getFileName().toString())); + } catch (IOException | UncheckedIOException e) { + return false; + } + } + + /** + * Returns Google Drive preset String. + * + * @param accountPath The path to the Google Drive account directory (e.g. {@code ~/Library/CloudStorage/GoogleDrive-username}) + * @return {@code String}. For example: "Google Drive - username" + */ + private String getDriveLocationString(Path accountPath) { + String accountName = accountPath.getFileName().toString().replace("GoogleDrive-", ""); + return STR."Google Drive - \{accountName}"; + } + + /** + * Retrieves a list of cloud storage directory locations based on the {@code ROOT_LOCATION}. + *

+ * This method lists all directories in the {@code ROOT_LOCATION}, filters them based on whether their names match + * a predefined pattern ({@code PATTERN}), and then extracts presets using {@code getPresetsFromAccountPath(Path)}. + *

+ * + * @return a list of {@code LocationPreset} objects representing valid cloud storage directory locations. + */ + private List getCloudStorageDirLocations() { + try (var dirStream = Files.list(ROOT_LOCATION)) { + return dirStream.filter(path -> Files.isDirectory(path) && PATTERN.test(path.getFileName().toString())) + .flatMap(this::getPresetsFromAccountPath) + .toList(); + } catch (IOException | UncheckedIOException e) { + return List.of(); + } + } + + /** + * Retrieves a stream of {@code LocationPreset} objects from a given Google Drive account path. + *

+ * This method lists all directories within the provided {@code accountPath} and filters them + * to identify folders whose names match any of the translations defined in {@code MY_DRIVE_TRANSLATIONS}. + * + * @param accountPath the root path of the Google Drive account to scan. + * @return a stream of {@code LocationPreset} objects representing matching directories. + */ + private Stream getPresetsFromAccountPath(Path accountPath) { + try (var driveStream = Files.list(accountPath)) { + return driveStream + .filter(preset -> MY_DRIVE_TRANSLATIONS + .contains(preset.getFileName().toString())) + .map(drivePath -> new LocationPreset( + getDriveLocationString(accountPath), + drivePath + )).toList().stream(); + } catch (IOException e) { + return Stream.empty(); + } + } + + /** + * Returns a list containing a fallback location preset for Google Drive. + *

+ * This method iterates through the predefined fallback locations, checks if any of them is a directory, + * and creates a {@code LocationPreset} object for the first matching directory found. + * + * @return a list containing a single fallback location preset if a valid directory is found, otherwise an empty list. + * @deprecated This method is intended for legacy support and may be removed in future releases. + */ + @Deprecated + private List getFallbackLocation() { + return FALLBACK_LOCATIONS.stream() + .filter(Files::isDirectory) + .map(location -> new LocationPreset("Google Drive", location)) + .findFirst() + .stream() + .toList(); + } + + /** + * Set of translations for "My Drive" in various languages. + *

+ * This constant is used to identify different language-specific labels for "My Drive" in Google Drive. + *

+ * The translations were originally extracted from the Chromium project’s Chrome OS translation files. + *

+ * Source: `ui/chromeos/translations` directory in the Chromium repository. + */ + private static final Set MY_DRIVE_TRANSLATIONS = Set.of("My Drive", "የእኔ Drive", "ملفاتي", "মোৰ ড্ৰাইভ", "Diskim", "Мой Дыск", "Моят диск", "আমার ড্রাইভ", "Moj disk", "La meva unitat", "Můj disk", "Mit drev", "Meine Ablage", "Το Drive μου", "Mi unidad", "Minu ketas", "Nire unitatea", "Aking Drive", "Oma Drive", "Mon disque", "Mon Drive", "A miña unidade", "મારી ડ્રાઇવ", "मेरी ड्राइव", "Saját meghajtó", "Իմ դրայվը", "Drive Saya", "Drifið mitt", "I miei file", "האחסון שלי", "マイドライブ", "ჩემი Drive", "Менің Drive дискім", "ដ្រាយរបស់ខ្ញុំ", "ನನ್ನ ಡ್ರೈವ್", "내 드라이브", "Менин Drive'ым", "Mano Diskas", "Mans disks", "Мојот Drive", "എന്റെ ഡ്രൈവ്", "Миний Драйв", "माझा ड्राइव्ह", "मेरो ड्राइभ", "Mijn Drive", "Min disk", "ମୋ ଡ୍ରାଇଭ୍", "Mój dysk", "Meu Drive", "O meu disco", "Contul meu Drive", "Мой диск", "මගේ Drive", "Môj disk", "Disku im", "Мој диск", "Min enhet", "Hifadhi Yangu", "எனது இயக்ககம்", "నా డ్రైవ్‌", "ไดรฟ์ของฉัน", "Drive'ım", "Мій диск", "میری ڈرائیو", "Drive của tôi", "我的云端硬盘", "我的雲端硬碟", "IDrayivu yami"); +} diff --git a/src/main/java/org/cryptomator/common/locationpresets/GoogleDriveLocationPresetsProvider.java b/src/main/java/org/cryptomator/common/locationpresets/GoogleDriveWindowsLocationPresetsProvider.java similarity index 85% rename from src/main/java/org/cryptomator/common/locationpresets/GoogleDriveLocationPresetsProvider.java rename to src/main/java/org/cryptomator/common/locationpresets/GoogleDriveWindowsLocationPresetsProvider.java index 970bea042..fea1632c4 100644 --- a/src/main/java/org/cryptomator/common/locationpresets/GoogleDriveLocationPresetsProvider.java +++ b/src/main/java/org/cryptomator/common/locationpresets/GoogleDriveWindowsLocationPresetsProvider.java @@ -9,13 +9,11 @@ import java.util.Arrays; import java.util.List; import java.util.stream.Stream; -import static org.cryptomator.integrations.common.OperatingSystem.Value.MAC; import static org.cryptomator.integrations.common.OperatingSystem.Value.WINDOWS; @OperatingSystem(WINDOWS) -@OperatingSystem(MAC) @CheckAvailability -public final class GoogleDriveLocationPresetsProvider implements LocationPresetsProvider { +public final class GoogleDriveWindowsLocationPresetsProvider implements LocationPresetsProvider { private static final List LOCATIONS = Arrays.asList( // LocationPresetsProvider.resolveLocation("~/GoogleDrive/My Drive"), // @@ -37,5 +35,4 @@ public final class GoogleDriveLocationPresetsProvider implements LocationPresets .findFirst() // .stream(); } - }