/* * AnBox, and an Android Blackbox application for the have-not-so-much-money's * Copyright (C) 2010 Yoonsoo Kim, Heekuk Lee, Heejin Sohn * * 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.ivehicle.AnBox; import com.ivehicle.util.Log; import java.io.File; import java.util.Arrays; import java.util.LinkedList; import java.util.NoSuchElementException; import java.util.Vector; import com.ivehicle.util.FilenameFilterByShock; import com.ivehicle.util.NotFilter; import android.os.Handler; public class DataStorageManager implements Runnable { public interface DataRecorder { public String prepareRecording(long timeInMs); public void start(); public void stop(); public boolean isRecording(); } private Vector<DataRecorder> recorders = new Vector<DataRecorder>(); private Handler hdlr = new Handler(); private LinkedList<File> alreadyRecordedDataFiles = new LinkedList<File>(); private Vector<File> beingRecordedFiles = new Vector<File>(); private LinkedList<File> alreadyRecordedShockFiles = new LinkedList<File>(); private File dataDir = new File(Config.getDataDir()); private long occupiedDataStorage = 0; private long occupiedShockStorage = 0; private long beingOccupiedDataStorage = 0; private long beingOccupiedShockStorage = 0; private long recordingStarted = 0; private boolean shockRunning = false; private int shockHappened = 0; public void registerDataRecorder(DataRecorder recorder) { recorders.add(recorder); } public void unregisterDataRecorder(DataRecorder recorder) { recorders.remove(recorder); } private void scanDataDir() { Log.d(Config.TAG, toString() + ".scanDataDir()"); String[] fileNames = dataDir.list( new NotFilter(new FilenameFilterByShock())); occupiedDataStorage = 0; if (fileNames.length > 0) { Arrays.sort(fileNames); for (String fn : fileNames) { File f = new File(Config.getDataDirWithSeparator() + fn); alreadyRecordedDataFiles.add(f); occupiedDataStorage += f.length(); } } fileNames = dataDir.list(new FilenameFilterByShock()); occupiedShockStorage = 0; if (fileNames.length > 0) { Arrays.sort(fileNames); for (String fn : fileNames) { File f = new File(Config.getDataDirWithSeparator() + fn); alreadyRecordedShockFiles.add(f); occupiedShockStorage += f.length(); } } Log.d(Config.TAG, toString() + ".scanDataDir(): Returning"); } public DataStorageManager() { File dataDir = new File(Config.getDataDir()); dataDir.mkdirs(); scanDataDir(); } public void run() { Log.d(Config.TAG, toString() + ".run()"); checkDataStorage(); checkShockStorage(); Log.d(Config.TAG, toString() + ".run(): after checking storage"); // process shock event: DO NOT change data file under a shock if (shockRunning) return; if (System.currentTimeMillis() - recordingStarted > Config.getCaptureDuration()) { int i = 0; recordingStarted = System.currentTimeMillis(); String[] newFileNames = new String[recorders.size()]; for (DataRecorder r : recorders) { newFileNames[i++] = r.prepareRecording(recordingStarted); } for (File f : beingRecordedFiles) { if (f == null) continue; if (shockHappened > 0) { alreadyRecordedShockFiles.add(f); occupiedShockStorage += f.length(); } else { alreadyRecordedDataFiles.add(f); occupiedDataStorage += f.length(); } shockHappened = 0; } beingRecordedFiles.clear(); for (String fn : newFileNames) { if (fn != null) beingRecordedFiles.add(new File(fn)); else { beingRecordedFiles.add(null); Log.w(Config.TAG, "Recorder returned null file name"); } } } hdlr.postDelayed(this, 1000); Log.d(Config.TAG, toString() + ".run(): Returning"); } public void prepare() { Log.d(Config.TAG, toString() + ".prepare()"); recordingStarted = System.currentTimeMillis(); for (DataRecorder r : recorders) { String fn = r.prepareRecording(recordingStarted); if (fn != null) beingRecordedFiles.add(new File(fn)); else { beingRecordedFiles.add(null); Log.w(Config.TAG, "Recorder returned null file name"); } } Log.d(Config.TAG, toString() + ".prepare(): Returning"); } public void start() { Log.d(Config.TAG, toString() + ".start()"); for (DataRecorder r : recorders) { r.start(); } run(); Log.d(Config.TAG, toString() + ".start(): Returning"); } public void stop() { hdlr.removeCallbacksAndMessages(null); for (DataRecorder r : recorders) { r.stop(); } for (File f : beingRecordedFiles) { if (f == null) continue; if (shockHappened > 0) { alreadyRecordedShockFiles.add(f); occupiedShockStorage += f.length(); } else { alreadyRecordedDataFiles.add(f); occupiedDataStorage += f.length(); } shockHappened = 0; } beingRecordedFiles.clear(); } private void checkDataStorage() { beingOccupiedDataStorage = occupiedDataStorage; for (File f : beingRecordedFiles) { if (f != null) beingOccupiedDataStorage += f.length(); } long availSize = 0; long configSize = Config.getMaximumStorageForMotionCapture(); if (configSize < 0) availSize = Config.getAvailableStorage(); else availSize = configSize - beingOccupiedDataStorage; if (availSize > Config.getMinimumStorageLevel()) { Log.i(Config.TAG, "Storage capacity is enough, occupied = " + beingOccupiedDataStorage + ", maximum = " + configSize + ", available = " + availSize); } else { Log.w(Config.TAG, "Out of storage: occupied = " + beingOccupiedDataStorage + ", maximum = " + configSize + ", available = " + availSize); Log.d(Config.TAG, "Reclaiming data storage..."); try { reclaimDataStorage(); } catch (Exception e) { Log.e(Config.TAG, e.toString()); } } } private void reclaimDataStorage() { // At first, reclaim storage from normal data long maxSize = Config.getMaximumStorageForMotionCapture(); try { while (alreadyRecordedDataFiles.size() > 0) { long deletedStorage = deleteRelatedDataAtFirst(alreadyRecordedDataFiles); beingOccupiedDataStorage -= deletedStorage; occupiedDataStorage -= deletedStorage; long availSize = 0; long configSize = Config.getMaximumStorageForMotionCapture(); if (configSize < 0) availSize = Config.getAvailableStorage(); else availSize = configSize - beingOccupiedDataStorage; if (availSize > Config.getMinimumStorageLevelAtReclaiming()) { Log.d(Config.TAG, "Data storage reclaimed: occupied = " + beingOccupiedDataStorage + ", maximum = " + maxSize); break; } else { Log.d(Config.TAG, "Still out of data storage: occupied = " + beingOccupiedDataStorage + ", maximum = " + maxSize); } } } catch (NoSuchElementException e) { Log.i(Config.TAG, "Now history data is empty"); occupiedDataStorage = 0; } } private void checkShockStorage() { beingOccupiedShockStorage = occupiedShockStorage; if (shockHappened > 0) { for (File f : beingRecordedFiles) { if (f != null) beingOccupiedShockStorage += f.length(); } } long availSize = Config.getMaximumStorageForShockCapture() - beingOccupiedShockStorage; if (availSize > Config.getMinimumStorageLevel()) { Log.i(Config.TAG, "Shock storage is enough, occupied = " + beingOccupiedShockStorage + ", maximum = " + Config.getMaximumStorageForShockCapture()); } else { Log.w(Config.TAG, "Out of shock storage: occupied = " + beingOccupiedShockStorage + ", maximum = " + Config.getMaximumStorageForShockCapture()); Log.d(Config.TAG, "Reclaiming shock data storage..."); try { reclaimShockStorage(); } catch (Exception e) { Log.e(Config.TAG, e.toString()); } } } private void reclaimShockStorage() { try { while (alreadyRecordedShockFiles.size() > 0) { long deletedStorage = deleteRelatedDataAtFirst(alreadyRecordedShockFiles); beingOccupiedShockStorage -= deletedStorage; occupiedShockStorage -= deletedStorage; long availSize = Config.getMaximumStorageForShockCapture() - beingOccupiedShockStorage; if (availSize > Config.getMinimumStorageLevel()) { Log.d(Config.TAG, "Shock storage reclaimed: occupied = " + beingOccupiedShockStorage + ", maximum = " + Config.getMaximumStorageForShockCapture()); break; } else { Log.d(Config.TAG, "Still out of shock storage: occupied = " + beingOccupiedShockStorage + ", maximum = " + Config.getMaximumStorageForShockCapture()); } } } catch (NoSuchElementException e) { Log.i(Config.TAG, "Now shock history data is empty"); occupiedShockStorage = 0; } } private long deleteRelatedDataAtFirst(LinkedList<File> list) { // Assumption: list is sorted in ascending order long deletedStorage = 0; File f = list.element(); long len = f.length(); f.delete(); list.remove(f); deletedStorage += len; Log.d(Config.TAG, f.getPath() + " deleted"); String[] names = f.getName().split("\\."); while (list.size() > 0 && (f = list.element()) != null && f.getName().startsWith(names[0])) { len = f.length(); f.delete(); list.remove(f); deletedStorage += len; Log.d(Config.TAG, f.getPath() + " deleted"); } return deletedStorage; } public void setShockRunning(boolean shock) { shockRunning = shock; if (shock) ++shockHappened; } public boolean isAllRecording() { boolean ret = true; for (DataRecorder r : recorders) { ret = (ret && r.isRecording()); } return ret; } }