/*
* The MIT License
*
* Copyright 2015 Ahseya.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of 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 of 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 of 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.outcome.Outcome;
import com.github.horrorho.liquiddonkey.cloud.data.Core;
import com.github.horrorho.liquiddonkey.cloud.engine.ConcurrentEngine;
import com.github.horrorho.liquiddonkey.cloud.data.FileGroups;
import com.github.horrorho.liquiddonkey.cloud.data.Snapshot;
import com.github.horrorho.liquiddonkey.cloud.data.Snapshots;
import com.github.horrorho.liquiddonkey.cloud.protobuf.ChunkServer;
import com.github.horrorho.liquiddonkey.cloud.protobuf.ICloud;
import com.github.horrorho.liquiddonkey.cloud.store.ChunkManager;
import com.github.horrorho.liquiddonkey.exception.BadDataException;
import com.github.horrorho.liquiddonkey.settings.config.EngineConfig;
import com.github.horrorho.liquiddonkey.settings.config.FileConfig;
import com.google.protobuf.ByteString;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeoutException;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
import net.jcip.annotations.Immutable;
import net.jcip.annotations.ThreadSafe;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* SnapshotDownloader.
*
* @author Ahseya
*/
@Immutable
@ThreadSafe
public final class SnapshotDownloader {
public static SnapshotDownloader from(
EngineConfig engineConfig,
FileConfig fileConfig) {
ConcurrentEngine engine = ConcurrentEngine.from(engineConfig);
Function<Snapshot, SignatureManager> signatureManagers = s -> SignatureManager.from(s, fileConfig);
return new SnapshotDownloader(engine, signatureManagers);
}
public static SnapshotDownloader from(
ConcurrentEngine engine,
Function<Snapshot, SignatureManager> signatureWriters) {
return new SnapshotDownloader(engine, signatureWriters);
}
private static final Logger logger = LoggerFactory.getLogger(SnapshotDownloader.class);
private final ConcurrentEngine engine;
private final Function<Snapshot, SignatureManager> signatureManagers;
SnapshotDownloader(
ConcurrentEngine engine,
Function<Snapshot, SignatureManager> signatureWriters) {
this.engine = Objects.requireNonNull(engine);
this.signatureManagers = Objects.requireNonNull(signatureWriters);
}
public void download(
HttpAgent agent,
Core core,
Snapshot snapshot,
Consumer<Map<ICloud.MBSFile, Outcome>> outcomes
) throws BadDataException, IOException, InterruptedException {
logger.trace("<< download() < dsPrsID: {} udid: {} snapshot: {}",
snapshot.dsPrsID(), snapshot.backupUDID(), snapshot.snapshotID());
boolean isCompleted = false;
while (!isCompleted && !snapshot.files().isEmpty() && !agent.authenticatorIsInvalid()) {
logger.debug("-- download() > loop, files: {}", snapshot.filesCount());
// FilesGroups
Snapshot get = snapshot;
ChunkServer.FileGroups fileGroups
= agent.execute((client, mmeAuthToken) -> FileGroups.from(client, core, mmeAuthToken, get));
// Store manager
ChunkManager storeManager = ChunkManager.from(fileGroups.getFileGroupsList());
// Filter snapshots to reflect downloadbles.
// ICloud.MBSFiles may be non-downloadable, e.g. directories, empty files.
Set<ByteString> downloadables = storeManager.remainingSignatures();
snapshot = Snapshots.from(snapshot, file -> downloadables.contains(file.getSignature()));
// Signature manager.
SignatureManager signatureManager = signatureManagers.apply(snapshot);
// Sanity check.
logger.debug("-- download() > loaded signatures, StoreManager: {} SignatureManager: {}",
storeManager.remainingSignatures().size(), signatureManager.remainingSignatures().size());
try {
List<ChunkServer.StorageHostChunkList> collect = fileGroups.getFileGroupsList()
.stream()
.map(x -> x.getStorageHostChunkListList())
.flatMap(Collection::stream)
.collect(Collectors.toList());
// Execute.
engine.execute(agent, storeManager, signatureManager, outcomes, collect);
isCompleted = true;
} catch (TimeoutException ex) {
logger.warn("-- download() > exception: {}", ex);
isCompleted = false;
}
// Mismatches are possible.
// DataWriters may have left the Store but termination occured before the contents were consumed.
logger.debug("-- download() > remaining signatures, StoreManager: {} SignatureManager: {}",
storeManager.remainingSignatures().size(), signatureManager.remainingSignatures().size());
// Filter out completed files. We use the SignatureManager as our reference.
Set<ICloud.MBSFile> remaining = signatureManager.remainingFiles();
snapshot = Snapshots.from(snapshot, file -> remaining.contains(file));
// TODO checksum/ salvage completed chunks from the ChunkManager in the case of timeout downloads.
logger.debug("-- download() > end loop, is completed: {} remaining files: {}",
isCompleted, snapshot.filesCount());
}
logger.trace(">> download()");
}
}