/** * DiskUsage - displays sdcard usage on android. * Copyright (C) 2008-2011 Ivan Volosyuk * * 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 2 * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package com.google.android.diskusage; import java.util.ArrayList; import java.util.PriorityQueue; import android.util.Log; import com.google.android.diskusage.DiskUsage.ProgressGenerator; import com.google.android.diskusage.datasource.LegacyFile; import com.google.android.diskusage.entity.FileSystemEntry; import com.google.android.diskusage.entity.FileSystemEntry.ExcludeFilter; import com.google.android.diskusage.entity.FileSystemEntrySmall; import com.google.android.diskusage.entity.FileSystemFile; public class Scanner implements ProgressGenerator { private final int maxdepth; private final int blockSize; private final ExcludeFilter excludeFilter; private final long sizeThreshold; private FileSystemEntry createdNode; private int createdNodeSize; private int createdNodeNumFiles; private int createdNodeNumDirs; private int heapSize; private final int maxHeapSize; private final PriorityQueue<SmallList> smallLists = new PriorityQueue<SmallList>(); long pos; FileSystemEntry lastCreatedFile; public FileSystemEntry lastCreatedFile() { return lastCreatedFile; } public long pos() { return pos; } private class SmallList implements Comparable<SmallList> { FileSystemEntry parent; FileSystemEntry[] children; int heapSize; float spaceEfficiency; SmallList(FileSystemEntry parent, FileSystemEntry[] children, int heapSize, long blocks) { this.parent = parent; this.children = children; this.heapSize = heapSize; this.spaceEfficiency = blocks / (float) heapSize; } @Override public int compareTo(SmallList that) { return spaceEfficiency < that.spaceEfficiency ? -1 : (spaceEfficiency == that.spaceEfficiency ? 0 : 1); } }; Scanner(int maxdepth, int blockSize, ExcludeFilter excludeFilter, long allocatedBlocks, int maxHeap) { this.maxdepth = maxdepth; this.blockSize = blockSize; this.excludeFilter = excludeFilter; this.sizeThreshold = (allocatedBlocks << FileSystemEntry.blockOffset) / (maxHeap / 2); this.maxHeapSize = maxHeap; // this.blockAllowance = (allocatedBlocks << FileSystemEntry.blockOffset) / 2; // this.blockAllowance = (maxHeap / 2) * sizeThreshold; Log.d("diskusage", "allocatedBlocks " + allocatedBlocks); Log.d("diskusage", "maxHeap " + maxHeap); Log.d("diskusage", "sizeThreshold = " + sizeThreshold / (float) (1 << FileSystemEntry.blockOffset)); } private void print(String msg, SmallList list) { String hidden_path = ""; // FIXME: this is debug for(FileSystemEntry p = list.parent; p != null; p = p.parent) { hidden_path = p.name + "/" + hidden_path; } Log.d("diskusage", msg + " " + hidden_path + " = " + list.heapSize + " " + list.spaceEfficiency); } FileSystemEntry scan(LegacyFile file) { // file = new NativeFile(file); scanDirectory(null, file, 0, excludeFilter); Log.d("diskusage", "allocated " + createdNodeSize + " B of heap"); int extraHeap = 0; // Restoring blocks for (SmallList list : smallLists) { print("restored", list); FileSystemEntry[] oldChildren = list.parent.children; FileSystemEntry[] addChildren = list.children; FileSystemEntry[] newChildren = new FileSystemEntry[oldChildren.length - 1 + addChildren.length]; System.arraycopy(addChildren, 0, newChildren, 0, addChildren.length); for(int pos = addChildren.length, i = 0; i < oldChildren.length; i++) { FileSystemEntry c = oldChildren[i]; if (! (c instanceof FileSystemEntrySmall)) { newChildren[pos++] = c; } } java.util.Arrays.sort(newChildren, FileSystemEntry.COMPARE); list.parent.children = newChildren; extraHeap += list.heapSize; } Log.d("diskusage", "allocated " + extraHeap + " B of extra heap"); Log.d("diskusage", "allocated " + (extraHeap + createdNodeSize) + " B total"); return createdNode; } /** * Scan directory object. * This constructor starts recursive scan to find all descendent files and directories. * Stores parent into field, name obtained from file, size of this directory * is calculated as a sum of all children. * @param parent parent directory object. * @param file corresponding File object * @param depth current directory tree depth * @param maxdepth maximum directory tree depth */ private void scanDirectory(FileSystemEntry parent, LegacyFile file, int depth, ExcludeFilter excludeFilter) { String name = file.getName(); makeNode(parent, name); createdNodeNumDirs = 1; createdNodeNumFiles = 0; ExcludeFilter childFilter = null; if (excludeFilter != null) { // this path is requested for exclusion if (excludeFilter.childFilter == null) { return; } childFilter = excludeFilter.childFilter.get(name); if (childFilter != null && childFilter.childFilter == null) { return; } } if (depth == maxdepth) { createdNode.setSizeInBlocks(calculateSize(file), blockSize); // FIXME: get num of dirs and files return; } String[] listNames = null; try { listNames = file.list(); } catch (SecurityException io) { Log.d("diskusage", "list files", io); } if (listNames == null) return; FileSystemEntry thisNode = createdNode; int thisNodeSize = createdNodeSize; int thisNodeNumDirs = 1; int thisNodeNumFiles = 0; int thisNodeSizeSmall = 0; int thisNodeNumFilesSmall = 0; int thisNodeNumDirsSmall = 0; long smallBlocks = 0; ArrayList<FileSystemEntry> children = new ArrayList<FileSystemEntry>(); ArrayList<FileSystemEntry> smallChildren = new ArrayList<FileSystemEntry>(); long blocks = 0; for (int i = 0; i < listNames.length; i++) { LegacyFile childFile = file.getChild(listNames[i]); // if (isLink(child)) continue; // if (isSpecial(child)) continue; int dirs = 0, files = 1; if (childFile.isFile()) { makeNode(thisNode, childFile.getName()); createdNode.initSizeInBytes(childFile.length(), blockSize); pos += createdNode.getSizeInBlocks(); lastCreatedFile = createdNode; } else { // directory scanDirectory(thisNode, childFile, depth + 1, childFilter); dirs = createdNodeNumDirs; files = createdNodeNumFiles; } long createdNodeBlocks = createdNode.getSizeInBlocks(); blocks += createdNodeBlocks; if (this.createdNodeSize * sizeThreshold > createdNode.encodedSize) { smallChildren.add(createdNode); thisNodeSizeSmall += this.createdNodeSize; thisNodeNumFilesSmall += files; thisNodeNumDirsSmall += dirs; smallBlocks += createdNodeBlocks; } else { children.add(createdNode); thisNodeSize += this.createdNodeSize; thisNodeNumFiles += files; thisNodeNumDirs += dirs; } } thisNode.setSizeInBlocks(blocks, blockSize); thisNodeNumDirs += thisNodeNumDirsSmall; thisNodeNumFiles += thisNodeNumFilesSmall; FileSystemEntry smallFilesEntry = null; if ((thisNodeSizeSmall + thisNodeSize) * sizeThreshold <= thisNode.encodedSize || smallChildren.isEmpty()) { children.addAll(smallChildren); thisNodeSize += thisNodeSizeSmall; } else { String msg = null; if (thisNodeNumDirsSmall == 0) { msg = String.format("<%d files>", thisNodeNumFilesSmall); } else if (thisNodeNumFilesSmall == 0) { msg = String.format("<%d dirs>", thisNodeNumDirsSmall); } else { msg = String.format("<%d dirs and %d files>", thisNodeNumDirsSmall, thisNodeNumFilesSmall); } // String hidden_path = msg; // // FIXME: this is debug // for(FileSystemEntry p = thisNode; p != null; p = p.parent) { // hidden_path = p.name + "/" + hidden_path; // } // Log.d("diskusage", hidden_path + " = " + thisNodeSizeSmall); makeNode(thisNode, msg); // create another one with right type createdNode = FileSystemEntrySmall.makeNode(thisNode, msg, thisNodeNumFilesSmall + thisNodeNumDirsSmall); createdNode.setSizeInBlocks(smallBlocks, blockSize); smallFilesEntry = createdNode; children.add(createdNode); thisNodeSize += createdNodeSize; SmallList list = new SmallList( thisNode, smallChildren.toArray(new FileSystemEntry[smallChildren.size()]), thisNodeSizeSmall, smallBlocks); smallLists.add(list); } // Magic to sort children and keep small files last in the array. if (children.size() != 0) { long smallFilesEntrySize = 0; if (smallFilesEntry != null) { smallFilesEntrySize = smallFilesEntry.encodedSize; smallFilesEntry.encodedSize = -1; } thisNode.children = children.toArray(new FileSystemEntry[children.size()]); java.util.Arrays.sort(thisNode.children, FileSystemEntry.COMPARE); if (smallFilesEntry != null) { smallFilesEntry.encodedSize = smallFilesEntrySize; } } createdNode = thisNode; createdNodeSize = thisNodeSize; createdNodeNumDirs = thisNodeNumDirs; createdNodeNumFiles = thisNodeNumFiles; } private void makeNode(FileSystemEntry parent, String name) { // try { // Thread.sleep(10); // } catch (Throwable t) {} createdNode = FileSystemFile.makeNode(parent, name); createdNodeSize = 4 /* ref in FileSystemEntry[] */ + 16 /* FileSystemEntry */ // + 10000 /* dummy in FileSystemEntry */ + 8 + 10 /* aproximation of size string */ + 8 /* name header */ + name.length() * 2; /* name length */ heapSize += createdNodeSize; while (heapSize > maxHeapSize && !smallLists.isEmpty()) { SmallList removed = smallLists.remove(); heapSize -= removed.heapSize; print("killed", removed); } } /** * Calculate size of the entry reading directory tree * @param file is file corresponding to this entry * @return size of entry in blocks */ private final long calculateSize(LegacyFile file) { if (file.isLink()) return 0; if (file.isFile()) { long size = (file.length() + (blockSize - 1)) / blockSize; if (size == 0) size = 1; return size; } LegacyFile[] list = null; try { list = file.listFiles(); } catch (SecurityException io) { Log.e("diskusage", "list files", io); } if (list == null) return 0; long size = 1; for (int i = 0; i < list.length; i++) size += calculateSize(list[i]); return size; } }