/*
* Copyright (C) 2014 Shashank Tulsyan
*
* 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, either version 3 of the License, or
* (at your option) any later version.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package neembuu.release1.ui.linkpanel;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.logging.Level;
import javax.swing.Timer;
import neembuu.rangearray.DissolvabilityRule;
import neembuu.rangearray.Range;
import neembuu.rangearray.RangeArray;
import neembuu.rangearray.RangeArrayFactory;
import neembuu.rangearray.RangeArrayParams;
import neembuu.rangearray.RangeRejectedByFilterException;
import neembuu.rangearray.RangeUtils;
import neembuu.rangearray.UIRangeArrayAccess;
import neembuu.rangearray.UnsyncRangeArrayCopy;
import neembuu.release1.api.log.LoggerUtil;
import neembuu.release1.api.ui.ExpansionState;
import neembuu.release1.api.ui.linkpanel.ProgressProvider;
import neembuu.release1.api.ui.linkpanel.Graph;
import neembuu.release1.api.ui.linkpanel.Progress;
import neembuu.release1.api.ui.access.ProgressUI;
import neembuu.release1.api.ui.access.ProgressUIA;
import neembuu.release1.api.ui.actions.SaveAction;
import neembuu.release1.ui.Colors;
import neembuu.swing.RangeArrayComponent;
import neembuu.swing.RangeArrayComponentBuilder;
import neembuu.swing.RangeArrayElementColorProvider;
import neembuu.swing.RangeArrayElementToolTipTextProvider;
import neembuu.swing.RangeSelectedListener;
import neembuu.vfs.file.FileBeingDownloaded;
import neembuu.vfs.file.RequestPatternListener;
import neembuu.vfs.readmanager.ReadRequestState;
/**
*
* @author Shashank Tulsyan
*/
public class ProgressImpl implements Progress {
//private VirtualFile vf;
private final ProgressUIA ui;
private final Graph graph;
private final Mode m;
private final ProgressUI progressUI;
private final SaveAction saveAction;
private RangeArrayComponent progress;
private RangeArray<Boolean> overallProgress;
private UnsyncRangeArrayCopy regionHandlersUnsync;
private UnsyncRangeArrayCopy overallProgressUnsync;
private UIRangeArrayAccess downloadedRegionHandlers ;
private volatile long latestRequestStarting,latestRequestEnding;
private Range selectedRange = null;
private long total_Downloaded = 0;
public enum Mode {
OverallProgressUI,
VariantProgressUI
}
public ProgressImpl(ProgressUIA ui, Graph graph, Mode m, SaveAction saveAction) {
this.ui = ui; this.graph = graph; this.m = m; this.saveAction = saveAction;
switch (m) {
case OverallProgressUI: progressUI = ui.overallProgressUI(); break;
case VariantProgressUI: progressUI = ui.variantProgressUI(); break;
default: throw new AssertionError();
}
}
public final ProgressProvider progressProvider = new ProgressProvider() {
@Override public Progress progress() {
return ProgressImpl.this;
}
};
private volatile FileBeingDownloaded file;
private RangeSelectedListener rangeSelectedListener;
private RequestPatternListener requestPatternListener;
private final RangeArrayElementToolTipTextProvider toolTipTextProvider = new RangeArrayElementToolTipTextProvider() {
@Override public String getToolTipText(Range element, long absolutePosition, long largestEntry, RangeArrayElementColorProvider.SelectionState selectionState) {
return ProgressImpl.this.getToolTipText(element, absolutePosition, largestEntry, selectionState);
}};
private final RangeArrayElementColorProvider colorProvider = new RangeArrayElementColorProvider() {
@Override public Color getColor(Color defaultColor, Range element, RangeArrayElementColorProvider.SelectionState selectionState) {
Object p = element.getProperty();
Color base = Colors.PROGRESS_BAR_FILL_ACTIVE;
if(!file.isAutoCompleteEnabled()){
base = Colors.PROGRESS_DOWNLOAD_LESS_MODE;
}
if(p!=null){
if(p==Boolean.TRUE){
base = Colors.PROGRESS_BAR_FILL_BUFFER;
if(!file.isAutoCompleteEnabled()){
base = Colors.TINTED_IMAGE;
}
switch (selectionState) {
case LIST: base = RangeArrayComponent.lightenColor(base, 0.9f); break;
case MOUSE_OVER: base = RangeArrayComponent.lightenColor(base, 0.8f); break;
case SELECTED: base = RangeArrayComponent.lightenColor(base, 0.7f); break;
case NONE:
break;
}
}
}
return base;
}
};
@Override
public void init(final FileBeingDownloaded fbd){
file = fbd;
graph.init(file.getRegionHandlers());
overallProgress = RangeArrayFactory.newDefaultRangeArray(new RangeArrayParams.Builder()
//.setDoesCarryProperty()
.addDissolvabilityRule(DissolvabilityRule.COMPARE_PROPERTY_OBJECT)
.setFileSize(file.getFileSize())
.build());
progress = RangeArrayComponentBuilder.create()
.setArray(overallProgress)
.setToolTipTextProvider(toolTipTextProvider)
.setArrayElementColorProvider(colorProvider)
.setUnprogressedBaseColor(Colors.PROGRESS_BAR_BACKGROUND)
.build();
progress.setBackground(Color.WHITE);
// removing all previous range selected listeners
if(rangeSelectedListener!=null)
progress.removeRangeSelectedListener(rangeSelectedListener);
rangeSelectedListener = new RangeSelectedListener() {
@Override
public void rangeSelected(Range arrayElement) {
ProgressImpl.this.rangeSelected(arrayElement);
}}; progress.addRangeSelectedListener(rangeSelectedListener);
layout();
requestPatternListener = new RequestPatternListener() {
@Override public void requested(long requestStarting, long requestEnding) {
latestRequestStarting = requestStarting;
latestRequestEnding = requestEnding; }};
file.addRequestPatternListener(requestPatternListener);
t.start();
}
private final Timer t = new Timer(300, new ActionListener() {
@Override public void actionPerformed(ActionEvent e) {
if(ui.getExpansionState()==ExpansionState.Contracted){
/* no point in painting*/ return;
} try{
handleChange();
}catch(Exception a){
LoggerUtil.L().log(Level.SEVERE, "Error in refreshing graphs",a);
}
}
});
@Override
public void unInit(FileBeingDownloaded fbd) {
if(file != fbd){ throw new IllegalStateException("Incorrect fbd");}
if(rangeSelectedListener!=null) progress.removeRangeSelectedListener(rangeSelectedListener);
if(requestPatternListener!=null) fbd.removeRequestPatternListener(requestPatternListener);
progressUI.progressBarPanel().remove(progress);
t.stop(); this.file = null;
}
@Override public UIRangeArrayAccess getRegionHandlers() { return downloadedRegionHandlers; }
@Override public void repaint(){ progress.repaint(); }
public Exception previouslyLoggedException = null;
private void handleChange(){
downloadedRegionHandlers = file.getRegionHandlers();
long totalDownloaded = 0;
regionHandlersUnsync = downloadedRegionHandlers.tryToGetUnsynchronizedCopy();
progress.setUpdateQuickly(false);
for (int i = 0; i < regionHandlersUnsync.size(); i++) {
Range<ReadRequestState> r = regionHandlersUnsync.get(i);
totalDownloaded += RangeUtils.getSize(r);
try{
overallProgress.addElement(r.starting(), r.getProperty().authorityLimit(),false);
}catch(Exception reason){// ignore
if(previouslyLoggedException!=null && !previouslyLoggedException.getClass().equals(reason.getClass())){
String message;
if(reason instanceof RangeRejectedByFilterException.GreaterThanFileSize){
message = "Evil download pattern as server probably"
+ " giving data even after reaching end, or probably"
+ " the server doesn't respond to http offset headers.";
}else if(reason instanceof RangeRejectedByFilterException){
message = "Bug here : cannot add element";
}else {
message = "Some weird unknown error ";
}
LoggerUtil.L().log(Level.SEVERE, message,reason);
previouslyLoggedException = reason;
}
}
}
overallProgressUnsync = overallProgress.tryToGetUnsynchronizedCopy();
for (int i = 0; i < overallProgressUnsync.size(); i++) {
Range r = overallProgressUnsync.get(i);
if(r.starting() < latestRequestEnding &&
latestRequestEnding < r.ending()){
try{
overallProgress.addElement(latestRequestEnding+1,r.ending(),true);
}catch(RangeRejectedByFilterException.GreaterThanFileSize reason){// ignore
LoggerUtil.L().log(Level.SEVERE,"Evil read request ",reason);
}catch(RangeRejectedByFilterException reason){// ignore
LoggerUtil.L().log(Level.SEVERE,"Bug here : read request was rejected ",reason);
}
break;
}
}
updateTotalDownloaded(totalDownloaded);
//progress.setUpdateQuickly(true);
progress.repaint();
}
private void updateTotalDownloaded(long newTotal){
total_Downloaded = newTotal;
long left = file.getFileSize() - total_Downloaded;
if(left == 0){
ui.saveButton().setVisible(true);
}else if(total_Downloaded > file.getFileSize()){
saveAction.sendWarning("There seems to be some problem with the website which is sending this file.\n"
+ "The website is sending data although the download has finished 100% .\n"
+ "The file is most probably corrupt and there is no point in saving it.\n"
+ "Do you want to still give it a try and save this file ?");
ui.saveButton().setVisible(true);
//throw new RuntimeException("Total download size of file greater than filesize");
}
if(ui.getExpansionState() == ExpansionState.SemiExpanded || ui.getExpansionState() == ExpansionState.FullyExpanded){
double perPro = total_Downloaded*100.0/file.getFileSize();
progressUI.progressPercentLabel().setText(Math.round(perPro) + " %");
}
}
private void rangeSelected(Range arrayElement){
this.selectedRange = arrayElement;
graph.initGraph(arrayElement,false);
if(arrayElement!=null){
ui.killConnectionButton().setEnabled(true);
}else {
ui.killConnectionButton().setEnabled(false);
}
}
@Override public void switchToRegion(Range arrayElement){
if(arrayElement==null){
return;
}
progress.selectRange(arrayElement);// this sends call to rangeSelected(Range arrayElemt)
}
@Override public Range getSelectedRange() { return selectedRange; }
@Override
public String getSelectedRangeTooltip(){
String toRet = null;
try{
toRet= getToolTipText(selectedRange, selectedRange.starting(), file.getFileSize(), null);
}catch(Exception a){
}
return toRet;
}
private String getToolTipText(Range element, long absolutePosition, long largestEntry, RangeArrayElementColorProvider.SelectionState selectionState) {
return "<html>No connection selected. <font size=-1> No connection selected. "+getToolTipText_(element, absolutePosition, largestEntry, selectionState)
+ "</font></html>";
}
private String getToolTipText_(Range element, long absolutePosition, long largestEntry, RangeArrayElementColorProvider.SelectionState selectionState) {
String totalSpeeds = "<br/>("+totalDownloadSpeed()+","+totalRequestSpeed()+")KBps<br/>";
if(element==null){
return absolutePosition+totalSpeeds;
}
String region_start_end = element.starting()+","+
c(Colors.PROGRESS_BAR_FILL_ACTIVE)+element.ending()+c_();
if(ui.getExpansionState()!=ExpansionState.FullyExpanded){
return region_start_end+totalSpeeds;
}
if(downloadedRegionHandlers==null){
return region_start_end+ " not initialized ";
}
element = downloadedRegionHandlers.getUnsynchronized(absolutePosition);
if(element==null || !(element.getProperty() instanceof ReadRequestState) ){
return region_start_end+ " not ReadRequestState";
}
ReadRequestState r = (ReadRequestState)element.getProperty();
String region_start_end_auth = region_start_end;
region_start_end_auth+=","+c(Colors.PROGRESS_DOWNLOAD_LESS_MODE)+r.authorityLimit()+c_()+"<br/>";
//file.getDownloadConstrainHandler().isComplete()
return region_start_end_auth + "("+downloadSpeed(r)+","+requestSpeed(r)+")KBps<br/>"+
isAlive(r)+","+isMain(r)+","+throttlingState();
}
private String isAlive(ReadRequestState r){
return r.isAlive()?c(Colors.PROGRESS_BAR_FILL_ACTIVE)+"alive"+c_():"dead";
}
private String isMain(ReadRequestState r){
return (r.isMainDirectionOfDownload()?"main":"notmain");
}
private String downloadSpeed(ReadRequestState r){
return c(Colors.PROGRESS_DOWNLOAD_LESS_MODE)+Math.round(r.getThrottleStatistics().getDownloadSpeed_KiBps())+c_();
}
private String requestSpeed(ReadRequestState r){
return c(Colors.PROGRESS_BAR_FILL_ACTIVE)+Math.round(r.getThrottleStatistics().getRequestSpeed_KiBps())+c_();
}
private String totalDownloadSpeed(){
return c(Colors.PROGRESS_DOWNLOAD_LESS_MODE)+Math.round(file.getTotalFileReadStatistics()
.getTotalAverageDownloadSpeedProvider().getDownloadSpeed_KiBps())+c_();
}
private String totalRequestSpeed(){
return c(Colors.PROGRESS_BAR_FILL_ACTIVE)+Math.round(
file.getTotalFileReadStatistics().getTotalAverageRequestSpeedProvider().getRequestSpeed_KiBps())
+c_();
}
private String throttlingState(){
String throttlingState = "";
try {
throttlingState = ((ReadRequestState)selectedRange.getProperty()).getThrottleStatistics().getThrottleState().toString();
} catch (Exception e) {
}
return throttlingState;
}
private static String c(Color c){
return "<font color=#"+ Integer.toHexString(c.getRGB()).substring(2)+ ">";
}
private static String c_(){
return "</font>";
}
private void layout(){
javax.swing.GroupLayout layout = (javax.swing.GroupLayout)progressUI.progressBarPanel().getLayout();
int right, left;
int top, bottom; right = left = top = bottom = 0;
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
.addGap(left, left, left)
.addComponent(progress, javax.swing.GroupLayout.DEFAULT_SIZE, 20, Short.MAX_VALUE)
.addGap(right, right, right))
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addGap(top, top, top)
.addComponent(progress, javax.swing.GroupLayout.DEFAULT_SIZE, 20, Short.MAX_VALUE)
.addGap(bottom, bottom, bottom))
);
}
}