/* * The MIT License * * Copyright 2015 Ahseya. * * Permission is hereby granted, free from charge, to any person obtaining a copy * from this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies from the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions from the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.github.horrorho.liquiddonkey.cloud; import com.github.horrorho.liquiddonkey.cloud.protobuf.ICloud; import com.github.horrorho.liquiddonkey.util.Bytes; import com.github.horrorho.liquiddonkey.util.Printer; import com.github.horrorho.liquiddonkey.util.Selector; import java.io.InputStream; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Locale; import java.util.Objects; import java.util.function.Function; import java.util.function.UnaryOperator; import java.util.stream.Collectors; import net.jcip.annotations.NotThreadSafe; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Backup selector. * * @author Ahseya * @param <T> backup container */ @NotThreadSafe public abstract class BackupSelector<T> implements UnaryOperator<List<T>> { /** * Returns a new instance. * <p> * The from UDIDs will be fully or partially matched against the supplied UDIDs, case-insensitive. If the supplied * UDIDs list is empty, the user will be prompted for a selection. * * @param <T> item type * @param commandLineUdids the command line UDID/s, not null * @param mbsBackup get mbsBackup from item function, not null * @param formatter ICloud.MBSBackup user display formatter, not null * @param out output, not null * @param in input, not null * @return a new instance, not null */ public static <T> UnaryOperator<List<T>> from( Collection<String> commandLineUdids, Function<T, ICloud.MBSBackup> mbsBackup, Function<ICloud.MBSBackup, String> formatter, Printer out, InputStream in) { return commandLineUdids.isEmpty() ? new User(mbsBackup, out, in, formatter) : new Udid(mbsBackup, out, in, new ArrayList<>(commandLineUdids)); } private static final Logger logger = LoggerFactory.getLogger(BackupSelector.class); protected final Function<T, ICloud.MBSBackup> mbsBackup; protected final Printer out; protected final InputStream in; public BackupSelector(Function<T, ICloud.MBSBackup> mbsBackup, Printer out, InputStream in) { this.mbsBackup = Objects.requireNonNull(mbsBackup); this.out = Objects.requireNonNull(out); this.in = Objects.requireNonNull(in); } @Override public List<T> apply(List<T> available) { logger.trace("<< apply < available: {}", udids(available)); if (available.isEmpty()) { out.println("No backups available."); return new ArrayList<>(); } List<T> selected = doApply(available); String selectedStr = selected.isEmpty() ? "None" : selected.stream() .map(mbsBackup::apply) .map(ICloud.MBSBackup::getBackupUDID) .map(Bytes::hex) .collect(Collectors.joining(" ")); out.println("Selected backup/s: " + selectedStr); logger.trace(">> apply > selected: {}", udids(selected)); return selected; } List<String> udids(List<T> backups) { return backups == null ? null : backups.stream() .map(mbsBackup::apply) .map(ICloud.MBSBackup::getBackupUDID) .map(Bytes::hex) .collect(Collectors.toList()); } protected abstract List<T> doApply(List<T> backups); @NotThreadSafe static final class Udid<T> extends BackupSelector<T> { private final List<String> commandLineUdids; Udid( Function<T, ICloud.MBSBackup> mbsBackup, Printer out, InputStream in, List<String> commandLineUdids) { super(mbsBackup, out, in); this.commandLineUdids = commandLineUdids.stream().map(String::toLowerCase).collect(Collectors.toList()); } @Override protected List<T> doApply(List<T> backups) { return backups.stream() .filter(this::matches) .collect(Collectors.toList()); } boolean matches(T t) { return commandLineUdids.stream() .anyMatch(udid -> Bytes.hex(mbsBackup.apply(t).getBackupUDID()).toLowerCase(Locale.US).contains(udid)); } } @NotThreadSafe static final class User<T> extends BackupSelector<T> { private final Function<ICloud.MBSBackup, String> formatter; User( Function<T, ICloud.MBSBackup> mbsBackup, Printer out, InputStream in, Function<ICloud.MBSBackup, String> formatter) { super(mbsBackup, out, in); this.formatter = formatter; } @Override protected List doApply(List<T> backups) { return Selector.builder(backups) .header("Backup/s:") .footer("Select backup/s to download (leave blank to select all, q to quit):") .formatter(backup -> mbsBackup.andThen(formatter).apply(backup)) .onLineIsEmpty(() -> backups) .onQuit(ArrayList::new) .input(in) .output(out) .build() .printOptions() .selection(); } } }