package net.osmand.plus.download;
import net.osmand.IndexConstants;
import net.osmand.binary.BinaryMapDataObject;
import net.osmand.binary.BinaryMapIndexReader;
import net.osmand.data.LatLon;
import net.osmand.map.OsmandRegions;
import net.osmand.map.WorldRegion;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.download.DownloadOsmandIndexesHelper.AssetIndexItem;
import net.osmand.util.MapUtils;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
public class DownloadResources extends DownloadResourceGroup {
public boolean isDownloadedFromInternet = false;
public boolean downloadFromInternetFailed = false;
public boolean mapVersionIsIncreased = false;
public OsmandApplication app;
private Map<String, String> indexFileNames = new LinkedHashMap<>();
private Map<String, String> indexActivatedFileNames = new LinkedHashMap<>();
private List<IndexItem> rawResources;
private Map<WorldRegion, List<IndexItem> > groupByRegion;
private List<IndexItem> itemsToUpdate = new ArrayList<>();
public static final String WORLD_SEAMARKS_KEY = "world_seamarks";
public static final String WORLD_SEAMARKS_NAME = "World_seamarks";
public static final String WORLD_SEAMARKS_OLD_KEY = "world_seamarks_basemap";
public static final String WORLD_SEAMARKS_OLD_NAME = "World_seamarks_basemap";
public DownloadResources(OsmandApplication app) {
super(null, DownloadResourceGroupType.WORLD, "");
this.region = app.getRegions().getWorldRegion();
this.app = app;
}
public List<IndexItem> getItemsToUpdate() {
return itemsToUpdate;
}
public IndexItem getWorldBaseMapItem() {
DownloadResourceGroup worldMaps = getSubGroupById(DownloadResourceGroupType.WORLD_MAPS.getDefaultId());
IndexItem worldMap = null;
if (worldMaps != null) {
List<IndexItem> list = worldMaps.getIndividualResources();
if (list != null) {
for (IndexItem ii : list) {
if (ii.getBasename().equalsIgnoreCase(WorldRegion.WORLD_BASEMAP)) {
worldMap = ii;
break;
}
}
}
}
return worldMap;
}
public IndexItem getIndexItem(String fileName) {
IndexItem res = null;
if (rawResources == null) {
return null;
}
for (IndexItem item : rawResources) {
if (fileName.equals(item.getFileName())) {
res = item;
break;
}
}
return res;
}
public List<IndexItem> getIndexItems(WorldRegion region) {
if (groupByRegion != null) {
List<IndexItem> res = groupByRegion.get(region);
if (res != null) {
return res;
}
}
return new LinkedList<>();
}
public void updateLoadedFiles() {
initAlreadyLoadedFiles();
prepareFilesToUpdate();
}
private void initAlreadyLoadedFiles() {
java.text.DateFormat dateFormat = app.getResourceManager().getDateFormat();
Map<String, String> indexActivatedFileNames = app.getResourceManager().getIndexFileNames();
listWithAlternatives(dateFormat, app.getAppPath(""), IndexConstants.EXTRA_EXT, indexActivatedFileNames);
Map<String, String> indexFileNames = app.getResourceManager().getIndexFileNames();
listWithAlternatives(dateFormat, app.getAppPath(""), IndexConstants.EXTRA_EXT, indexFileNames);
listWithAlternatives(dateFormat, app.getAppPath(IndexConstants.TILES_INDEX_DIR), IndexConstants.SQLITE_EXT,
indexFileNames);
app.getResourceManager().getBackupIndexes(indexFileNames);
this.indexFileNames = indexFileNames;
this.indexActivatedFileNames = indexActivatedFileNames;
}
public boolean checkIfItemOutdated(IndexItem item, java.text.DateFormat format) {
boolean outdated = false;
String sfName = item.getTargetFileName();
String indexActivatedDate = indexActivatedFileNames.get(sfName);
String indexFilesDate = indexFileNames.get(sfName);
item.setDownloaded(false);
item.setOutdated(false);
if (indexActivatedDate == null && indexFilesDate == null) {
return false;
}
item.setDownloaded(true);
String date = item.getDate(format);
boolean parsed = false;
if (indexActivatedDate != null) {
try {
item.setLocalTimestamp(format.parse(indexActivatedDate).getTime());
parsed = true;
} catch (ParseException e) {
e.printStackTrace();
}
}
if (!parsed && indexFilesDate != null) {
try {
item.setLocalTimestamp(format.parse(indexFilesDate).getTime());
parsed = true;
} catch (ParseException e) {
e.printStackTrace();
}
}
if (date != null && !date.equals(indexActivatedDate) && !date.equals(indexFilesDate)) {
if ((item.getType() == DownloadActivityType.NORMAL_FILE && !item.extra)
|| item.getType() == DownloadActivityType.ROADS_FILE
|| item.getType() == DownloadActivityType.WIKIPEDIA_FILE
|| item.getType() == DownloadActivityType.DEPTH_CONTOUR_FILE
|| item.getType() == DownloadActivityType.SRTM_COUNTRY_FILE) {
outdated = true;
} else {
long itemSize = item.getContentSize();
long oldItemSize = 0;
if (item.getType() == DownloadActivityType.VOICE_FILE) {
if (item instanceof AssetIndexItem) {
File file = new File(((AssetIndexItem) item).getDestFile());
oldItemSize = file.length();
} else {
File fl = new File(item.getType().getDownloadFolder(app, item), sfName + "/_config.p");
if (fl.exists()) {
oldItemSize = fl.length();
try {
InputStream is = app.getAssets().open("voice/" + sfName + "/config.p");
if (is != null) {
itemSize = is.available();
is.close();
}
} catch (IOException e) {
}
}
}
} else if (item.getType() == DownloadActivityType.FONT_FILE) {
oldItemSize = new File(app.getAppPath(IndexConstants.FONT_INDEX_DIR), item.getTargetFileName()).length();
} else {
oldItemSize = app.getAppPath(item.getTargetFileName()).length();
}
if (itemSize != oldItemSize) {
outdated = true;
}
}
}
item.setOutdated(outdated);
return outdated;
}
protected void updateFilesToUpdate() {
initAlreadyLoadedFiles();;
recalculateFilesToUpdate();
}
private void recalculateFilesToUpdate() {
List<IndexItem> stillUpdate = new ArrayList<IndexItem>();
for (IndexItem item : itemsToUpdate) {
java.text.DateFormat format = app.getResourceManager().getDateFormat();
checkIfItemOutdated(item, format);
if (item.isOutdated()) {
stillUpdate.add(item);
}
}
itemsToUpdate = stillUpdate;
}
private Map<String, String> listWithAlternatives(final java.text.DateFormat dateFormat, File file,
final String ext, final Map<String, String> files) {
if (file.isDirectory()) {
file.list(new FilenameFilter() {
@Override
public boolean accept(File dir, String filename) {
if (filename.endsWith(ext)) {
String date = dateFormat.format(findFileInDir(new File(dir, filename)).lastModified());
files.put(filename, date);
return true;
} else {
return false;
}
}
});
}
return files;
}
private File findFileInDir(File file) {
if (file.isDirectory()) {
File[] lf = file.listFiles();
if (lf != null) {
for (File f : lf) {
if (f.isFile()) {
return f;
}
}
}
}
return file;
}
private void prepareFilesToUpdate() {
List<IndexItem> filtered = rawResources;
if (filtered != null) {
itemsToUpdate.clear();
java.text.DateFormat format = app.getResourceManager().getDateFormat();
for (IndexItem item : filtered) {
boolean outdated = checkIfItemOutdated(item, format);
// include only activated files here
if (outdated && indexActivatedFileNames.containsKey(item.getTargetFileName())) {
itemsToUpdate.add(item);
}
}
}
}
protected boolean prepareData(List<IndexItem> resources) {
this.rawResources = resources;
DownloadResourceGroup otherMapsGroup = new DownloadResourceGroup(this, DownloadResourceGroupType.OTHER_MAPS_GROUP);
DownloadResourceGroup otherMapsScreen = new DownloadResourceGroup(otherMapsGroup, DownloadResourceGroupType.OTHER_MAPS);
DownloadResourceGroup otherMaps = new DownloadResourceGroup(otherMapsGroup, DownloadResourceGroupType.OTHER_MAPS_HEADER);
otherMapsScreen.addGroup(otherMaps);
otherMapsGroup.addGroup(otherMapsScreen);
DownloadResourceGroup otherGroup = new DownloadResourceGroup(this, DownloadResourceGroupType.OTHER_GROUP);
DownloadResourceGroup voiceScreenTTS = new DownloadResourceGroup(otherGroup, DownloadResourceGroupType.VOICE_TTS);
DownloadResourceGroup voiceScreenRec = new DownloadResourceGroup(otherGroup, DownloadResourceGroupType.VOICE_REC);
DownloadResourceGroup fontScreen = new DownloadResourceGroup(otherGroup, DownloadResourceGroupType.FONTS);
DownloadResourceGroup voiceTTS = new DownloadResourceGroup(otherGroup, DownloadResourceGroupType.VOICE_HEADER_TTS);
DownloadResourceGroup voiceRec = new DownloadResourceGroup(otherGroup, DownloadResourceGroupType.VOICE_HEADER_REC);
DownloadResourceGroup fonts = new DownloadResourceGroup(otherGroup, DownloadResourceGroupType.FONTS_HEADER);
DownloadResourceGroup worldMaps = new DownloadResourceGroup(this, DownloadResourceGroupType.WORLD_MAPS);
DownloadResourceGroup nauticalMaps = new DownloadResourceGroup(this, DownloadResourceGroupType.NAUTICAL_MAPS_HEADER);
Map<WorldRegion, List<IndexItem> > groupByRegion = new LinkedHashMap<WorldRegion, List<IndexItem>>();
OsmandRegions regs = app.getRegions();
for (IndexItem ii : resources) {
if (ii.getType() == DownloadActivityType.VOICE_FILE) {
if (ii.getFileName().endsWith(IndexConstants.TTSVOICE_INDEX_EXT_ZIP)) {
voiceTTS.addItem(ii);
} else {
voiceRec.addItem(ii);
}
continue;
}
if (ii.getType() == DownloadActivityType.FONT_FILE) {
fonts.addItem(ii);
continue;
}
if (ii.getType() == DownloadActivityType.DEPTH_CONTOUR_FILE) {
if (app.getSettings().DEPTH_CONTOURS_PURCHASED.get() || nauticalMaps.size() == 0) {
nauticalMaps.addItem(ii);
}
continue;
}
String basename = ii.getBasename().toLowerCase();
WorldRegion wg = regs.getRegionDataByDownloadName(basename);
if (wg != null) {
if (!groupByRegion.containsKey(wg)) {
groupByRegion.put(wg, new ArrayList<IndexItem>());
}
groupByRegion.get(wg).add(ii);
} else {
if (ii.getFileName().startsWith("World_")) {
if (ii.getFileName().toLowerCase().startsWith(WORLD_SEAMARKS_KEY) ||
ii.getFileName().toLowerCase().startsWith(WORLD_SEAMARKS_OLD_KEY)) {
nauticalMaps.addItem(ii);
} else {
worldMaps.addItem(ii);
}
} else {
otherMaps.addItem(ii);
}
}
}
this.groupByRegion = groupByRegion;
LinkedList<WorldRegion> queue = new LinkedList<WorldRegion>();
LinkedList<DownloadResourceGroup> parent = new LinkedList<DownloadResourceGroup>();
DownloadResourceGroup worldSubregions = new DownloadResourceGroup(this, DownloadResourceGroupType.SUBREGIONS);
addGroup(worldSubregions);
for(WorldRegion rg : region.getSubregions()) {
queue.add(rg);
parent.add(worldSubregions);
}
while(!queue.isEmpty()) {
WorldRegion reg = queue.pollFirst();
DownloadResourceGroup parentGroup = parent.pollFirst();
List<WorldRegion> subregions = reg.getSubregions();
DownloadResourceGroup mainGrp = new DownloadResourceGroup(parentGroup, DownloadResourceGroupType.REGION, reg.getRegionId());
mainGrp.region = reg;
parentGroup.addGroup(mainGrp);
List<IndexItem> list = groupByRegion.get(reg);
if(list != null) {
DownloadResourceGroup flatFiles = new DownloadResourceGroup(mainGrp, DownloadResourceGroupType.REGION_MAPS);
for(IndexItem ii : list) {
flatFiles.addItem(ii);
}
mainGrp.addGroup(flatFiles);
}
DownloadResourceGroup subRegions = new DownloadResourceGroup(mainGrp, DownloadResourceGroupType.SUBREGIONS);
mainGrp.addGroup(subRegions);
// add to processing queue
for(WorldRegion rg : subregions) {
queue.add(rg);
parent.add(subRegions);
}
}
// Possible improvements
// 1. if there is no subregions no need to create resource group REGIONS_MAPS - objection raise diversity and there is no value
// 2. if there is no subregions and there only 1 index item it could be merged to the level up - objection there is no such maps
// 3. if hillshade/srtm is disabled, all maps from inner level could be combined into 1
addGroup(worldMaps);
addGroup(nauticalMaps);
if (otherMaps.size() > 0) {
addGroup(otherMapsGroup);
}
voiceScreenTTS.addGroup(voiceTTS);
voiceScreenRec.addGroup(voiceRec);
if (fonts.getIndividualResources() != null) {
fontScreen.addGroup(fonts);
}
otherGroup.addGroup(voiceScreenTTS);
otherGroup.addGroup(voiceScreenRec);
if (fonts.getIndividualResources() != null) {
otherGroup.addGroup(fontScreen);
}
addGroup(otherGroup);
createHillshadeSRTMGroups();
trimEmptyGroups();
updateLoadedFiles();
return true;
}
public static List<IndexItem> findIndexItemsAt(OsmandApplication app, LatLon latLon, DownloadActivityType type) throws IOException {
List<IndexItem> res = new ArrayList<>();
OsmandRegions regions = app.getRegions();
DownloadIndexesThread downloadThread = app.getDownloadThread();
int point31x = MapUtils.get31TileNumberX(latLon.getLongitude());
int point31y = MapUtils.get31TileNumberY(latLon.getLatitude());
List<BinaryMapDataObject> mapDataObjects;
try {
mapDataObjects = regions.queryBbox(point31x, point31x, point31y, point31y);
} catch (IOException e) {
throw new IOException("Error while calling queryBbox");
}
if (mapDataObjects != null) {
Iterator<BinaryMapDataObject> it = mapDataObjects.iterator();
while (it.hasNext()) {
BinaryMapDataObject o = it.next();
if (o.getTypes() != null) {
boolean isRegion = true;
for (int i = 0; i < o.getTypes().length; i++) {
BinaryMapIndexReader.TagValuePair tp = o.getMapIndex().decodeType(o.getTypes()[i]);
if ("boundary".equals(tp.value)) {
isRegion = false;
break;
}
}
WorldRegion downloadRegion = regions.getRegionData(regions.getFullName(o));
if (downloadRegion != null && isRegion && regions.contain(o, point31x, point31y)) {
if (!isIndexItemDownloaded(downloadThread, type, downloadRegion, res)) {
addIndexItem(downloadThread, type, downloadRegion, res);
}
}
}
}
}
return res;
}
private static boolean isIndexItemDownloaded(DownloadIndexesThread downloadThread, DownloadActivityType type, WorldRegion downloadRegion, List<IndexItem> res) {
List<IndexItem> otherIndexItems = new ArrayList<>(downloadThread.getIndexes().getIndexItems(downloadRegion));
for (IndexItem indexItem : otherIndexItems) {
if (indexItem.getType() == type && indexItem.isDownloaded()) {
return true;
}
}
return downloadRegion.getSuperregion() != null
&& isIndexItemDownloaded(downloadThread, type, downloadRegion.getSuperregion(), res);
}
private static boolean addIndexItem(DownloadIndexesThread downloadThread, DownloadActivityType type, WorldRegion downloadRegion, List<IndexItem> res) {
List<IndexItem> otherIndexItems = new ArrayList<>(downloadThread.getIndexes().getIndexItems(downloadRegion));
for (IndexItem indexItem : otherIndexItems) {
if (indexItem.getType() == type
&& !res.contains(indexItem)) {
res.add(indexItem);
return true;
}
}
return downloadRegion.getSuperregion() != null
&& addIndexItem(downloadThread, type, downloadRegion.getSuperregion(), res);
}
}