//SPDX-License-Identifier: GPL-3.0
/*
* gogarchiver-ng, an archival tool for GOG.com
* Copyright (C) 2024 Marko Zajc
*
* This program is free software: you can redistribute it and/or modify it under the
* terms of the GNU General Public License as published by the Free Software
* Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this
* program. If not, see .
*/
package zajc.gogarchiver;
import static java.lang.Runtime.getRuntime;
import static java.util.Collections.unmodifiableSet;
import static java.util.stream.Collectors.toUnmodifiableSet;
import static picocli.CommandLine.Help.Ansi.AUTO;
import static picocli.CommandLine.Help.Visibility.ALWAYS;
import static zajc.gogarchiver.api.GameDownload.Type.*;
import java.io.IOException;
import java.nio.file.*;
import java.util.*;
import java.util.function.Predicate;
import javax.annotation.Nonnull;
import org.eu.zajc.ef.supplier.except.all.AESupplier;
import picocli.CommandLine.*;
import picocli.CommandLine.Help.Ansi;
import zajc.gogarchiver.api.*;
import zajc.gogarchiver.api.GameDownload.Platform;
import zajc.gogarchiver.exception.NotLoggedInException;
import zajc.gogarchiver.util.LazyValue;
public class Arguments {
private final LazyValue user = new LazyValue<>();
@ArgGroup(exclusive = true, multiplicity = "1") private Token token;
private static class Token {
@Option(names = { "-k", "--token" }, description = "GOG token, which can be extracted from the gog-al cookie",
paramLabel = "TOKEN") private String tokenString;
@Option(names = { "-K", "--token-file" }, description = "read GOG token from a file",
paramLabel = "PATH") private Path tokenFile;
@Nonnull
@SuppressWarnings("null")
public String getTokenString() throws IOException {
var token = this.tokenString;
if (token == null)
token = Files.readString(this.tokenFile);
return token.strip();
}
}
@Option(names = { "-o", "--output" }, required = true, description = "directory to write downloaded games to",
paramLabel = "PATH") private Path output;
@Option(names = { "-t", "--threads" }, description = "number of download threads", paramLabel = "THREADS",
showDefaultValue = ALWAYS) private int threads = getRuntime().availableProcessors();
@Option(names = { "-q", "--quiet" }, description = "disable progress bars") private boolean quiet = false;
@Option(names = { "-c", "--color" }, description = "control output color. Supported are auto, on, off",
paramLabel = "MODE") private Ansi color = AUTO;
@ArgGroup(validate = false, heading = "%nFilter options%n") private Filters filters = new Filters();
private static class Filters {
@Option(names = { "--no-installers" }, description = "download installers", negatable = true,
showDefaultValue = ALWAYS) private boolean installers = true;
@Option(names = { "--no-patches" }, description = "download version patches", negatable = true,
showDefaultValue = ALWAYS) private boolean patches = true;
@Option(names = { "--no-dlcs" }, description = "download available DLCs", negatable = true,
showDefaultValue = ALWAYS) private boolean dlcs = true;
@ArgGroup(exclusive = true) private GameIds gameIds = new GameIds();
private static class GameIds {
@Option(names = { "-i", "--include-game" }, description = """
only the listed game IDs will be downloaded. Game IDs can be obtained from https://www.gogdb.org/""",
paramLabel = "ID", split = ",") private Set included;
@Option(names = { "-e", "--exclude-game" }, description = """
all games owned by the account except those listed will be downloaded""", paramLabel = "ID",
split = ",") private Set excluded;
@Nonnull
@SuppressWarnings("null")
public Set getGameIds(@Nonnull User user) {
if (this.included != null)
return unmodifiableSet(this.included);
var library = user.getLibraryIds();
if (this.excluded == null)
return library;
else
return library.stream().filter(Predicate.not(this.excluded::contains)).collect(toUnmodifiableSet());
}
}
@ArgGroup(exclusive = true) private Platforms platforms = new Platforms();
private static class Platforms {
@Option(names = { "--include-platform" }, description = """
platforms to download for. Supported are linux, windows, mac""", paramLabel = "PLATFORM",
split = ",") private EnumSet included;
@Option(names = { "--exclude-platform" }, description = """
platforms to not download for""", paramLabel = "PLATFORM",
split = ",") private EnumSet excluded;
@Nonnull
@SuppressWarnings("null")
public Set getPlatforms() {
if (this.included != null)
return unmodifiableSet(this.included);
else if (this.excluded != null)
return unmodifiableSet(this.excluded);
else
return EnumSet.allOf(Platform.class);
}
}
}
@ArgGroup(validate = false, heading = "%nAdvanced options%n") private AdvancedOptions advanced =
new AdvancedOptions();
private static class AdvancedOptions {
@Option(names = { "-v", "--verbose" }, description = "display verbose log messages") private boolean verbose =
false;
@Option(names = { "--unknown-types" }, description = "download unknown download types", negatable = true,
showDefaultValue = ALWAYS) private boolean unknown = false;
}
@Nonnull
@SuppressWarnings({ "unused", "null" })
public User getUser() throws IOException, NotLoggedInException {
return this.user.get((AESupplier) () -> new User(this.token.getTokenString()));
}
@Nonnull
public Set getGameIds() throws IOException, NotLoggedInException {
return this.filters.gameIds.getGameIds(getUser());
}
@Nonnull
@SuppressWarnings("null")
public Ansi getColorMode() {
return this.color;
}
public int getThreads() {
return this.threads;
}
public Path getOutputPath() {
return this.output;
}
public Set getPlatforms() {
return this.filters.platforms.getPlatforms();
}
public boolean downloadDlcs() {
return this.filters.dlcs;
}
public Set getTypes() {
var types = EnumSet.noneOf(GameDownload.Type.class);
if (this.filters.installers)
types.add(INSTALLER);
if (this.filters.patches)
types.add(PATCH);
if (this.advanced.unknown)
types.add(UNKNOWN);
return types;
}
public boolean isVerbose() {
return this.advanced.verbose;
}
public boolean isQuiet() {
return this.quiet;
}
}