/******************************************************************************* * Copyright (C) 2014 Travis Ralston (turt2live) * * 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 com.turt2live.antishare.io.flatfile; import com.turt2live.antishare.io.BlockStore; import com.turt2live.antishare.io.generics.GenericBlockManager; import com.turt2live.antishare.object.ASLocation; import java.io.File; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; /** * A file-based block manager. This manager is a collection of {@link FileBlockStore}s to store * block information. * <br/><br/> * When {@link #loadAll()} is called, any file stores that do not match the block size defined will * be silently rejected. * * @author turt2live * @see FileBlockStore */ public class FileBlockManager extends GenericBlockManager { private ConcurrentMap<ASLocation, String> index = new ConcurrentHashMap<>(); private File folder; /** * Creates a new FileBlockManager * * @param blocksPerStore the blocks per store * @param folder the folder to use. This must exist, be a folder, and cannot be null */ public FileBlockManager(int blocksPerStore, File folder) { super(blocksPerStore); if (folder == null || !folder.exists() || !folder.isDirectory()) throw new IllegalArgumentException("folder must be a folder, cannot be null, and must exist"); this.folder = folder; index(); } @Override protected BlockStore createStore(int sx, int sy, int sz) { // File name is irrelevant. Check index, if not found then use default naming scheme ASLocation indexLocation = new ASLocation(sx, sy, sz); String fileName = index.get(indexLocation); File storeFile = fileName == null ? new File(folder, sx + "." + sy + "." + sz + ".dat") : new File(fileName); // Check for invalid index if (!storeFile.exists() && fileName != null) storeFile = new File(folder, sx + "." + sy + "." + sz + ".dat"); // Update index if (fileName == null || !fileName.equals(storeFile.getAbsolutePath()) || !storeFile.exists()) index.put(indexLocation, storeFile.getAbsolutePath()); return new FileBlockStore(storeFile, sx, sy, sz, blocksPerStore); } @Override public List<BlockStore> loadAll() { File[] files = folder.listFiles(); ConcurrentMap<ASLocation, BlockStore> stores = getLiveStores(); stores.clear(); // Assume a save has been done if (files != null) { for (File file : files) { FileBlockStore store = new FileBlockStore(file); if (store.header()[3] != blocksPerStore) continue; // Ignore anything that does not match our size int[] header = store.header(); ASLocation storeLocation = new ASLocation(header[0], header[1], header[2]); stores.put(storeLocation, store); store.load(); // Only load data once we know the header is OK } } List<BlockStore> storesList = new ArrayList<>(); storesList.addAll(stores.values()); return storesList; } private void index() { File[] files = folder.listFiles(); if (files != null) { for (File file : files) { FileBlockStore store = new FileBlockStore(file); if (store.header()[3] != blocksPerStore) continue; // Ignore anything that does not match our size int[] header = store.header(); ASLocation storeLocation = new ASLocation(header[0], header[1], header[2]); index.put(storeLocation, file.getAbsolutePath()); } } } /** * Gets the largest block size from the block stores * * @return the largest block size, or -1 for none found */ public int getLargestBlockSize() { File[] files = folder.listFiles(); int highHeader = -1; if (files != null) { for (File file : files) { FileBlockStore store = new FileBlockStore(file); int[] header = store.header(); if (header[3] > highHeader) highHeader = header[3]; } } return highHeader; } /** * Gets the smallest block size from the block stores * * @return the smallest block size, or -1 for none found */ public int getSmallestBlockSize() { File[] files = folder.listFiles(); int highHeader = Integer.MAX_VALUE; int count = 0; if (files != null) { for (File file : files) { FileBlockStore store = new FileBlockStore(file); int[] header = store.header(); if (header[3] < highHeader) highHeader = header[3]; count++; } } return count == 0 ? -1 : highHeader; } /** * Gets the most common block size from the block stores * * @return the most common block size, or -1 for none found */ public int getMostCommonBlockSize() { File[] files = folder.listFiles(); Map<Integer, Integer> counts = new HashMap<>(); if (files != null) { for (File file : files) { FileBlockStore store = new FileBlockStore(file); int[] header = store.header(); if (counts.containsKey(header[3])) counts.put(header[3], counts.get(header[3]) + 1); else counts.put(header[3], 1); } } if (!counts.isEmpty()) { int header = -1; int count = -1; for (Map.Entry<Integer, Integer> entry : counts.entrySet()) { if (entry.getValue() > count) { header = entry.getKey(); count = entry.getValue(); } } return header; } return -1; } /** * Gets the average block size from the block stores * * @return the average block size, or -1 if not found */ public int getAverageBlockSize() { File[] files = folder.listFiles(); int total = 0; int count = 0; if (files != null) { for (File file : files) { FileBlockStore store = new FileBlockStore(file); int[] header = store.header(); total += header[3]; count++; } } return count == 0 ? -1 : (int) (total / (double) count); } /** * Gets an array sorted by no particular order of all the unique block sizes * represented by the block stores. * * @return the unsorted array. Never null, but may be of length 0 to represent "no stores" */ public int[] getBlockSizes() { List<Integer> sizes = new ArrayList<>(); File[] files = folder.listFiles(); if (files != null) { for (File file : files) { FileBlockStore store = new FileBlockStore(file); int[] header = store.header(); if (!sizes.contains(header[3])) sizes.add(header[3]); } } int[] data = new int[sizes.size()]; for (int i = 0; i < sizes.size(); i++) data[i] = sizes.get(i); return data; } /** * Gets a list of block stores that are of a particular block size. This method only loads header information * for the specified file stores and will not attempt to load the entire file. * * @param blockSize the block size to look for. Value must be greater than 0. See {@link FileBlockStore} for more information * * @return the list of block stores. Never null but may be empty * * @see FileBlockStore */ public List<BlockStore> getStoresOfSize(int blockSize) { if (blockSize <= 0) throw new IllegalArgumentException("block size must be a positive, non-zero, number"); List<BlockStore> stores = new ArrayList<>(); File[] files = folder.listFiles(); if (files != null) { for (File file : files) { FileBlockStore store = new FileBlockStore(file); int[] header = store.header(); if (header[3] == blockSize) stores.add(store); } } return stores; } }