/*
* Copyright 2013-2016 EMC Corporation. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://www.apache.org/licenses/LICENSE-2.0.txt
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package com.emc.ecs.sync.storage.cas;
import com.emc.ecs.sync.model.ObjectMetadata;
import com.emc.ecs.sync.model.SyncObject;
import com.emc.ecs.sync.storage.SyncStorage;
import com.emc.ecs.sync.util.PerformanceListener;
import com.emc.ecs.sync.util.ReadOnlyIterator;
import com.emc.object.util.ProgressListener;
import com.filepool.fplibrary.FPClip;
import com.filepool.fplibrary.FPLibraryException;
import com.filepool.fplibrary.FPTag;
import javax.xml.bind.DatatypeConverter;
import java.io.ByteArrayInputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ExecutorService;
public class ClipSyncObject extends SyncObject {
private FPClip clip;
private List<ClipTag> tags; // lazily-loaded tags
private int clipIndex = 0;
private boolean allClipsLoaded = false;
private ExecutorService blobReadExecutor;
private ProgressListener progressListener;
private String md5Summary;
public ClipSyncObject(SyncStorage source, String clipId, FPClip clip, byte[] cdfData, ObjectMetadata metadata, ExecutorService blobReadExecutor) {
super(source, clipId, metadata, new ByteArrayInputStream(cdfData), null);
this.clip = clip;
this.tags = new ArrayList<>();
this.blobReadExecutor = blobReadExecutor;
progressListener = source.getOptions().isMonitorPerformance() ? new PerformanceListener(source.getReadWindow()) : null;
}
@Override
public synchronized long getBytesRead() {
long total = super.getBytesRead();
if (tags != null) {
for (ClipTag tag : tags) {
total += tag.getBytesRead();
}
}
return total;
}
@Override
public synchronized String getMd5Hex(boolean forceRead) {
if (md5Summary == null) {
// summarize the MD5s of the CDF content and all of the blob-tags
StringBuilder summary = new StringBuilder("{ CDF: ").append(super.getMd5Hex(forceRead));
// if we're forcing read, we want to get *all* tags; otherwise, just poll the tags we've already loaded
for (ClipTag tag : (forceRead ? getTags() : tags)) {
if (tag.isBlobAttached()) {
summary.append(", tag[").append(tag.getTagNum()).append("]: ");
summary.append(DatatypeConverter.printHexBinary(tag.getMd5Digest(forceRead)));
}
}
summary.append(" }");
md5Summary = summary.toString();
}
return md5Summary;
}
private synchronized boolean loadNextTag() {
if (allClipsLoaded) return false; // we already have all the tags
FPTag tag = null;
try {
// actually pull next tag from clip
tag = clip.FetchNext();
if (tag == null) {
// the tag before this was the last tag
allClipsLoaded = true;
return false;
}
ClipTag clipTag = new ClipTag(tag, clipIndex++, getSource().getOptions().getBufferSize(), progressListener, blobReadExecutor);
tags.add(clipTag);
return true;
} catch (FPLibraryException e) {
CasStorage.safeClose(tag, getRelativePath(), clipIndex);
throw new RuntimeException(CasStorage.summarizeError(e), e);
}
}
@Override
public synchronized void close() {
if (tags != null) {
for (ClipTag tag : tags) {
CasStorage.safeClose(tag, getRelativePath());
}
}
CasStorage.safeClose(clip, getRelativePath());
clip = null;
}
public FPClip getClip() {
return clip;
}
public Iterable<ClipTag> getTags() {
return new Iterable<ClipTag>() {
@Override
public Iterator<ClipTag> iterator() {
return new ReadOnlyIterator<ClipTag>() {
int nextClipIdx = 0;
@Override
protected ClipTag getNextObject() {
synchronized (ClipSyncObject.this) {
// if we're at the end of the local cache, try to load another tag
if (nextClipIdx >= tags.size() && !loadNextTag()) return null;
return tags.get(nextClipIdx++);
}
}
};
}
};
}
}