/* * Copyright 2004 - 2011 Christian Sprajc. All rights reserved. * * This file is part of PowerFolder. * * PowerFolder 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. * * PowerFolder 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 PowerFolder. If not, see <http://www.gnu.org/licenses/>. * * $Id: FolderStatistic.java 15056 2011-03-21 15:12:38Z tot $ */ package de.dal33t.powerfolder.light; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.OutputStream; import java.io.Serializable; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.logging.Logger; import de.dal33t.powerfolder.Controller; import de.dal33t.powerfolder.Member; import de.dal33t.powerfolder.disk.FolderStatistic; import de.dal33t.powerfolder.util.Format; import de.dal33t.powerfolder.util.Reject; import de.dal33t.powerfolder.util.Util; import de.dal33t.powerfolder.util.logging.Loggable; import de.schlichtherle.truezip.file.TFileInputStream; import de.schlichtherle.truezip.file.TFileOutputStream; /** * Contains the statistic calculation result / infos about one folder. This * object is produced by {@link FolderStatistic}. May be used to transfer info * to another computer over the wire. So make sure it is fully serializable. * * @author sprajc */ public class FolderStatisticInfo extends Loggable implements Serializable { private static final int MAX_FILE_SIZE = 100 * 1024; private static final long serialVersionUID = 1L; private FolderInfo folder; // Total size of folder in bytes private volatile long totalSize; // The archive size. private volatile long archiveSize; // Total number of files private volatile int totalFilesCount; // Date at which the folder should be synchronized. private volatile Date estimatedSyncDate; // Finer values private volatile int incomingFilesCount; private transient int analyzedFiles; // Number of files private final Map<MemberInfo, Integer> filesCount = new HashMap<MemberInfo, Integer>(); // Number of files in sync private final Map<MemberInfo, Integer> filesCountInSync = new HashMap<MemberInfo, Integer>(); // Size of folder per member private final Map<MemberInfo, Long> sizes = new HashMap<MemberInfo, Long>(); // Size of folder that are in sync per member private final Map<MemberInfo, Long> sizesInSync = new HashMap<MemberInfo, Long>(); /** Map of bytes received for a file for a member. */ private Map<MemberInfo, Map<FileInfo, Long>> partialSyncStatMap = Util .createConcurrentHashMap(); public FolderStatisticInfo(FolderInfo folder) { super(); Reject.ifNull(folder, "Folder"); this.folder = folder; } public FolderInfo getFolder() { return folder; } public long getTotalSize() { return totalSize; } public void setTotalSize(long totalSize) { this.totalSize = totalSize; } public int getTotalFilesCount() { return totalFilesCount; } public long getArchiveSize() { return archiveSize; } public void setArchiveSize(long archiveSize) { this.archiveSize = archiveSize; } public void setTotalFilesCount(int totalFilesCount) { this.totalFilesCount = totalFilesCount; } public Date getEstimatedSyncDate() { return estimatedSyncDate; } public void setEstimatedSyncDate(Date estimatedSyncDate) { this.estimatedSyncDate = estimatedSyncDate; } public int getIncomingFilesCount() { return incomingFilesCount; } public void setIncomingFilesCount(int incomingFilesCount) { this.incomingFilesCount = incomingFilesCount; } public int getAnalyzedFiles() { return analyzedFiles; } public void setAnalyzedFiles(int analyzedFiles) { this.analyzedFiles = analyzedFiles; } public Map<MemberInfo, Integer> getFilesCount() { return filesCount; } public Map<MemberInfo, Integer> getFilesCountInSync() { return filesCountInSync; } public Map<MemberInfo, Long> getSizes() { return sizes; } public Map<MemberInfo, Long> getSizesInSync() { return sizesInSync; } public Map<MemberInfo, Map<FileInfo, Long>> getPartialSyncStatMap() { if (partialSyncStatMap == null) { partialSyncStatMap = Util.createConcurrentHashMap(); } return partialSyncStatMap; } /** * Calculate the sync percentage for a member. This is the size of files in * sync divided by the total size of the folder. * * @param member * @return the sync percentage for the given member */ public double getSyncPercentage(MemberInfo memberInfo) { Long size = sizesInSync.get(memberInfo); if (size == null) { size = 0L; } if (totalSize == 0) { return 100.0; } else if (size == 0) { return 0; } else { // Total up partial transfers for this member. Map<FileInfo, Long> map = getPartialSyncStatMap().get(memberInfo); long partialTotal = 0; if (map != null) { for (FileInfo fileInfo : map.keySet()) { Long partial = map.get(fileInfo); if (partial != null) { partialTotal += partial; } } } // Sync = synchronized file sizes plus any partials divided by // total size. double sync = 100.0 * (size + partialTotal) / totalSize; if (isFiner()) { logFiner("Sync for member " + memberInfo.nick + ", " + size + " + " + partialTotal + " / " + totalSize + " = " + sync + ". map: " + map); } if (Double.compare(sync, 100.0) > 0) { logFiner("Sync percentage > 100% - folder=" + folder.name + ", member=" + memberInfo.nick + ", sync=" + sync); sync = 100.0; } return sync; } } /** * Calculate the average sync percentage for a folder. This is the sync * percentage for each member divided by the number of members. * * @return the total sync percentage for a folder */ public double getAverageSyncPercentage() { if (sizesInSync.isEmpty()) { return 100.0; } double syncSum = 0; for (MemberInfo memberInfo : sizesInSync.keySet()) { syncSum += getSyncPercentage(memberInfo); } double sync = syncSum / sizesInSync.size(); if (Double.compare(sync, 100.0) > 0) { logWarning("Average sync percentage > 100% - folder=" + folder.name + ", sync=" + sync); sync = 100.0; } return sync; } /** * @param controller * @return a sever node which is syncing the folder. null if not found. */ public Member getServerNode(Controller controller) { for (MemberInfo nodeInfo : filesCount.keySet()) { Member node = nodeInfo.getNode(controller, false); if (node != null && node.isServer()) { return node; } } return null; } // Writing / Loading ***************************************************** /** * Saves this FolderStatisticInfo contents to the given OutputStream. * * @param out * @throws IOException */ public boolean save(File file) { OutputStream fout = null; ObjectOutputStream oout = null; if (isFiner()) { logFiner("Writing folder " + folder.getName() + " stats to " + file); } try { fout = new TFileOutputStream(file); oout = new ObjectOutputStream(new BufferedOutputStream(fout)); oout.writeObject(this); oout.close(); } catch (Exception e) { logWarning("Unable to store stats for folder " + folder.getName() + " to " + file + ". " + e); } finally { if (oout != null) { try { oout.close(); } catch (IOException e) { } } if (fout != null) { try { fout.close(); } catch (IOException e) { } } } return true; } public static FolderStatisticInfo load(File file) { if (!file.exists()) { return null; } if (file.length() > MAX_FILE_SIZE) { Logger.getLogger(FolderStatisticInfo.class.getName()).warning( "Not reading folder stats from " + file + ". File is too big: " + Format.formatBytes(file.length())); return null; } InputStream fin = null; ObjectInputStream oin = null; try { fin = new TFileInputStream(file); oin = new ObjectInputStream(new BufferedInputStream(fin)); FolderStatisticInfo stats = (FolderStatisticInfo) oin.readObject(); // PFS-818: Check if not corrupt; if (stats.isValid()) { return stats; } } catch (Exception e) { Logger.getLogger(FolderStatisticInfo.class.getName()).warning( "Unable to read folder stats from " + file + ". " + e); } catch (OutOfMemoryError e) { Logger.getLogger(FolderStatisticInfo.class.getName()).severe( "Unable to read folder stats from " + file + ". " + e); } finally { if (oin != null) { try { oin.close(); } catch (IOException e) { } } if (fin != null) { try { fin.close(); } catch (IOException e) { } } } return null; } // Serialization ********************************************* private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); if (this.partialSyncStatMap == null) { this.partialSyncStatMap = Util.createConcurrentHashMap(); } } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + (int) (archiveSize ^ (archiveSize >>> 32)); result = prime * result + ((estimatedSyncDate == null) ? 0 : estimatedSyncDate.hashCode()); result = prime * result + ((filesCount == null) ? 0 : filesCount.hashCode()); result = prime * result + ((filesCountInSync == null) ? 0 : filesCountInSync.hashCode()); result = prime * result + ((folder == null) ? 0 : folder.hashCode()); result = prime * result + incomingFilesCount; result = prime * result + ((sizes == null) ? 0 : sizes.hashCode()); result = prime * result + ((sizesInSync == null) ? 0 : sizesInSync.hashCode()); result = prime * result + totalFilesCount; result = prime * result + (int) (totalSize ^ (totalSize >>> 32)); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; FolderStatisticInfo other = (FolderStatisticInfo) obj; if (archiveSize != other.archiveSize) return false; if (estimatedSyncDate == null) { if (other.estimatedSyncDate != null) return false; } else if (!estimatedSyncDate.equals(other.estimatedSyncDate)) return false; if (filesCount == null) { if (other.filesCount != null) return false; } else if (!filesCount.equals(other.filesCount)) return false; if (filesCountInSync == null) { if (other.filesCountInSync != null) return false; } else if (!filesCountInSync.equals(other.filesCountInSync)) return false; if (folder == null) { if (other.folder != null) return false; } else if (!folder.equals(other.folder)) return false; if (incomingFilesCount != other.incomingFilesCount) return false; if (sizes == null) { if (other.sizes != null) return false; } else if (!sizes.equals(other.sizes)) return false; if (sizesInSync == null) { if (other.sizesInSync != null) return false; } else if (!sizesInSync.equals(other.sizesInSync)) return false; if (totalFilesCount != other.totalFilesCount) return false; if (totalSize != other.totalSize) return false; return true; } @Override public String toString() { return "FolderStatisticInfo [folder=" + folder + ", totalSize=" + totalSize + ", archiveSize=" + archiveSize + ", totalFilesCount=" + totalFilesCount + ", estimatedSyncDate=" + estimatedSyncDate + ", incomingFilesCount=" + incomingFilesCount + ", filesCount=" + filesCount + ", filesCountInSync=" + filesCountInSync + ", sizes=" + sizes + ", sizesInSync=" + sizesInSync + "]"; } /** * PFS-818 * @return */ public boolean isValid() { return folder != null && filesCount != null && filesCountInSync != null && sizes != null && sizesInSync != null && partialSyncStatMap != null; } }