/*
* Copyright 2015 Lafayette College
*
* This file is part of OpenCVTour.
*
* OpenCVTour 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.
*
* OpenCVTour 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 OpenCVTour. If not, see <http://www.gnu.org/licenses/>.
*/
package alicrow.opencvtour;
import android.content.Context;
import android.os.Environment;
import android.util.Log;
import org.yaml.snakeyaml.Yaml;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import com.thanh.photodetector.ImageDetector;
/**
* Created by daniel on 5/26/15.
*
* Class representing a virtual tour.
*/
public class Tour {
private static final String TAG = "Tour";
private static Tour _currentTour;
private static ArrayList<Tour> _tours;
private static File _tours_directory; /// directory to save our Tours in
private static File _imported_tours_directory; /// directory of tours that we imported from an archive. These can't be edited.
private ArrayList<TourItem> _tour_items;
private boolean _gps_enabled;
private boolean _enforce_order; /// indicates if the TourItems must be visited in sequence
private String _name;
private ImageDetector _detector = new ImageDetector();
private File _directory; /// directory we save this tour in
private boolean _editable = true;
private double _item_range; /// if GPS is enabled, we'll only check items within _item_range meters of the current location.
public static Tour getCurrentTour() {
if(_currentTour == null)
_currentTour = new Tour();
return _currentTour;
}
public static void setSelectedTour(Tour tour) {
_currentTour = tour;
}
public static ArrayList<Tour> getTours(Context context) {
if(_tours == null)
loadTours(context);
return _tours;
}
/**
* Loads the tours from disk
* @param context a context, necessary in order to retrieve the directory for the app's data.
*/
private static void loadTours(Context context) {
_tours = new ArrayList<>();
for(File dir : getToursDirectory(context).listFiles()) {
if(dir.isDirectory() && !dir.getName().equals("imported")) {
try {
File tour_file = new File(dir, "tour.yaml");
if(!tour_file.exists()) {
Log.w(TAG, "no tour description file found in directory " + dir);
continue;
}
Log.i(TAG, "loading tour from file '" + tour_file.toString() + "'");
_tours.add(new Tour(tour_file));
} catch(Exception e) {
Log.e(TAG, e.toString());
}
}
}
/// Automatically extract any Tours in the Downloads folder, since we cannot rely on other apps (e.g. email, bluetooth transfer) to open the tour with our app.
for(File archive : Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).listFiles()) {
if(archive.isFile() && archive.getName().endsWith(".zip.tour")) {
String folder_name = archive.getName().substring(0, archive.getName().length() - ".zip.tour".length());
File extracted_folder = new File(getImportedToursDirectory(context), folder_name);
if(extracted_folder.exists()) {
Log.d(TAG, "Already extracted '" + archive.getName() + "'.");
} else {
Log.i(TAG, "Extracting '" + archive.getName() + "' from Downloads folder");
Utilities.extractFolder(archive.getPath(), extracted_folder.getPath());
}
}
}
/// Load imported tours. These can't be edited, since we don't have the image files for them (just the image descriptors).
for(File dir : getImportedToursDirectory(context).listFiles()) {
if(dir.isDirectory()) {
try {
File tour_file = new File(dir, "tour.yaml");
if(!tour_file.exists()) {
Log.w(TAG, "no tour description file found in directory " + dir);
continue;
}
Log.i(TAG, "loading tour from file '" + tour_file.toString() + "'");
Tour tour = new Tour(tour_file);
tour.setEditable(false); /// imported Tours are only meant to be followed, not edited.
_tours.add(tour);
} catch(Exception e) {
Log.e(TAG, e.toString());
}
}
}
}
public static Tour addNewTour() {
Tour tour = new Tour();
_tours.add(tour);
return tour;
}
public static File getToursDirectory(Context context) {
Log.i(TAG, Environment.getExternalStorageState());
if(_tours_directory == null)
_tours_directory = context.getExternalFilesDir(null);
return _tours_directory;
}
public static File getImportedToursDirectory(Context context) {
Log.i(TAG, Environment.getExternalStorageState());
if(_imported_tours_directory == null)
_imported_tours_directory = new File(getToursDirectory(context), "imported");
if(!_imported_tours_directory.exists())
_imported_tours_directory.mkdir();
return _imported_tours_directory;
}
public Tour() {
_tour_items = new ArrayList<>();
_name = "Unnamed tour";
}
public Tour(File file) {
_directory = file.getParentFile();
_tour_items = new ArrayList<>();
loadFromFile(file);
}
/// Return a File object representing the directory this Tour is stored in.
public File getDirectory() {
if(_directory != null)
return _directory;
else
return new File(_tours_directory, _name);
}
/**
* Saves this Tour to a Map so we can save it to disk or export it.
* @return a Map containing the data from this Tour that we want to save
*/
public Map<String,Object> saveToMap() {
Map<String, Object> data = new HashMap<>();
data.put("gps_enabled", _gps_enabled);
data.put("name", _name);
data.put("enforce_order", _enforce_order);
data.put("item_range", _item_range);
ArrayList<Map<String, Object>> item_maps = new ArrayList<>();
for(TourItem item : _tour_items)
item_maps.add(item.saveToMap());
data.put("items", item_maps);
return data;
}
/**
* Load a Map containing the tour's data
* @param data Map containing the tour's data.
*/
public void loadFromMap(Map<String,Object> data) {
setGpsEnabled((Boolean) data.get("gps_enabled"));
setEnforceOrder((Boolean) data.get("enforce_order"));
setName((String) data.get("name"));
if(data.containsKey("item_range") && data.get("item_range") != null)
setItemRange((Double) data.get("item_range"));
else
setItemRange(50);
_tour_items.clear();
for(Map<String,Object> map : (ArrayList<Map<String,Object>>) data.get("items")) {
try {
_tour_items.add(new TourItem(this, map));
} catch (Exception e) {
Log.e(TAG, e.toString());
}
}
}
/**
* Saves the Tour to disk.
*/
public void saveToFile() {
Map<String, Object> data = saveToMap();
Yaml yaml = new Yaml();
try {
File dir = getDirectory();
if(!dir.exists())
dir.mkdir();
File file = new File(dir, "tour.yaml");
FileWriter writer = new FileWriter(file);
yaml.dump(data, writer);
writer.close();
Log.i(TAG, "saved '" + file + "'");
} catch (IOException e) {
Log.e(TAG, e.toString());
}
_detector.saveImageDescriptors();
}
/// Loads the tour from the given file. The file should be the "tour.yaml" file in the tour's folder.
public void loadFromFile(File file) {
try {
Yaml yaml = new Yaml();
Map<String, Object> data = (Map<String, Object>) yaml.load(new FileReader(file));
loadFromMap(data);
Log.i(TAG, "loaded '" + file + "'");
} catch (IOException e) {
Log.e(TAG, e.toString());
}
}
public ImageDetector getDetector() {
return _detector;
}
public String toString() {
return _name;
}
public ArrayList<TourItem> getTourItems() {
return _tour_items;
}
public TourItem getTourItem(long id) {
for(TourItem item : _tour_items) {
if(item.getId() == id)
return item;
}
return null;
}
public TourItem addNewTourItem() {
TourItem item = new TourItem(this);
_tour_items.add(item);
return item;
}
public void setGpsEnabled(boolean enabled) {
_gps_enabled = enabled;
}
public boolean getGpsEnabled() {
return _gps_enabled;
}
public String getName() {
return _name;
}
public void setName(String name) {
_name = name;
}
public boolean getEnforceOrder() {
return _enforce_order;
}
public void setEnforceOrder(boolean enforce) {
_enforce_order = enforce;
}
public boolean getEditable() {
return _editable;
}
public void setEditable(boolean editable) {
_editable = editable;
}
public double getItemRange() {
return _item_range;
}
public void setItemRange(double distance) {
_item_range = distance;
}
}