package net.osmand.plus.resources;
import net.osmand.IndexConstants;
import net.osmand.PlatformUtil;
import net.osmand.binary.BinaryMapIndexReader;
import net.osmand.osm.io.NetworkUtils;
import net.osmand.plus.R;
import net.osmand.util.Algorithms;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.File;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
public class IncrementalChangesManager {
private static final String URL = "http://download.osmand.net/check_live.php";
private static final org.apache.commons.logging.Log log = PlatformUtil.getLog(IncrementalChangesManager.class);
private ResourceManager resourceManager;
private final Map<String, RegionUpdateFiles> regions = new ConcurrentHashMap<String, IncrementalChangesManager.RegionUpdateFiles>();
public IncrementalChangesManager(ResourceManager resourceManager) {
this.resourceManager = resourceManager;
}
public List<File> collectChangesFiles(File dir, String ext, List<File> files) {
if (dir.exists() && dir.canRead()) {
File[] lf = dir.listFiles();
if (lf == null || lf.length == 0) {
return files;
}
Set<String> existingFiles = new HashSet<String>();
for (File f : files) {
if(!f.getName().endsWith(IndexConstants.BINARY_WIKI_MAP_INDEX_EXT) &&
!f.getName().endsWith(IndexConstants.BINARY_SRTM_MAP_INDEX_EXT)) {
existingFiles.add(Algorithms.getFileNameWithoutExtension(f));
}
}
for (File f : lf) {
if (f.getName().endsWith(ext)) {
String index = Algorithms.getFileNameWithoutExtension(f);
if (index.length() >= 9 || index.charAt(index.length() - 9) != '_') {
String nm = index.substring(0, index.length() - 9);
if (existingFiles.contains(nm)) {
files.add(f);
}
}
}
}
}
return files;
}
public void indexMainMap(File f, long dateCreated) {
String nm = Algorithms.getFileNameWithoutExtension(f).toLowerCase();
if(!regions.containsKey(nm)) {
regions.put(nm, new RegionUpdateFiles(nm));
}
RegionUpdateFiles regionUpdateFiles = regions.get(nm);
regionUpdateFiles.mainFile = f;
regionUpdateFiles.mainFileInit = dateCreated;
if (!regionUpdateFiles.monthUpdates.isEmpty()) {
List<String> list = new ArrayList<String>(regionUpdateFiles.monthUpdates.keySet());
for (String month : list) {
RegionUpdate ru = regionUpdateFiles.monthUpdates.get(month);
if (ru.obfCreated < dateCreated) {
log.info("Delete overlapping month update " + ru.file.getName());
resourceManager.closeFile(ru.file.getName());
regionUpdateFiles.monthUpdates.remove(month);
ru.file.delete();
log.info("Delete overlapping month update " + ru.file.getName());
}
}
}
if (!regionUpdateFiles.dayUpdates.isEmpty()) {
ArrayList<String> list = new ArrayList<String>(regionUpdateFiles.dayUpdates.keySet());
for (String month : list) {
Iterator<RegionUpdate> it = regionUpdateFiles.dayUpdates.get(month).iterator();
RegionUpdate monthRu = regionUpdateFiles.monthUpdates.get(month);
while (it.hasNext()) {
RegionUpdate ru = it.next();
if (ru.obfCreated < dateCreated || (monthRu != null && ru.obfCreated < monthRu.obfCreated)) {
log.info("Delete overlapping day update " + ru.file.getName());
resourceManager.closeFile(ru.file.getName());
it.remove();
ru.file.delete();
log.info("Delete overlapping day update " + ru.file.getName());
}
}
}
}
}
public boolean index(File f, long dateCreated, BinaryMapIndexReader mapReader) {
String index = Algorithms.getFileNameWithoutExtension(f).toLowerCase();
if(index.length() <= 9 || index.charAt(index.length() - 9) != '_'){
return false;
}
String nm = index.substring(0, index.length() - 9);
String date = index.substring(index.length() - 9 + 1);
if(!regions.containsKey(nm)) {
regions.put(nm, new RegionUpdateFiles(nm));
}
RegionUpdateFiles regionUpdateFiles = regions.get(nm);
return regionUpdateFiles.addUpdate(date, f, dateCreated);
}
protected static String formatSize(long vl) {
return (vl * 1000 / (1 << 20l)) / 1000.0f + "";
}
public static long calculateSize(List<IncrementalUpdate> list) {
long l = 0;
for(IncrementalUpdate iu : list) {
l += iu.containerSize;
}
return l;
}
protected class RegionUpdate {
protected File file;
protected String date;
protected long obfCreated;
}
protected class RegionUpdateFiles {
protected String nm;
protected File mainFile;
protected long mainFileInit;
TreeMap<String, List<RegionUpdate>> dayUpdates = new TreeMap<String, List<RegionUpdate>>();
TreeMap<String, RegionUpdate> monthUpdates = new TreeMap<String, RegionUpdate>();
public RegionUpdateFiles(String nm) {
this.nm = nm;
}
public boolean addUpdate(String date, File file, long dateCreated) {
String monthYear = date.substring(0, 5);
RegionUpdate ru = new RegionUpdate();
ru.date = date;
ru.file = file;
ru.obfCreated = dateCreated;
if(date.endsWith("00")) {
monthUpdates.put(monthYear, ru);
} else {
if (!dayUpdates.containsKey(monthYear)) {
dayUpdates.put(monthYear, new ArrayList<IncrementalChangesManager.RegionUpdate>());
}
dayUpdates.get(monthYear).add(ru);
}
return true;
}
}
public class IncrementalUpdateList {
public TreeMap<String, IncrementalUpdateGroupByMonth> updateByMonth =
new TreeMap<String, IncrementalUpdateGroupByMonth>();
public String errorMessage;
public RegionUpdateFiles updateFiles;
public boolean isPreferrableLimitForDayUpdates(String monthYearPart, List<IncrementalUpdate> dayUpdates) {
List<RegionUpdate> lst = updateFiles.dayUpdates.get(monthYearPart);
if(lst == null || lst.size() < 10) {
return true;
}
return false;
}
public List<IncrementalUpdate> getItemsForUpdate() {
Iterator<IncrementalUpdateGroupByMonth> it = updateByMonth.values().iterator();
List<IncrementalUpdate> ll = new ArrayList<IncrementalUpdate>();
while(it.hasNext()) {
IncrementalUpdateGroupByMonth n = it.next();
if(it.hasNext()) {
if(!n.isMonthUpdateApplicable()) {
return null;
}
ll.addAll(n.getMonthUpdate());
} else {
if(n.isDayUpdateApplicable() && isPreferrableLimitForDayUpdates(n.monthYearPart, n.getDayUpdates())) {
ll.addAll(n.getDayUpdates());
} else if(n.isMonthUpdateApplicable()) {
ll.addAll(n.getMonthUpdate());
} else {
return null;
}
}
}
return ll;
}
public void addUpdate(IncrementalUpdate iu) {
String dtMonth = iu.date.substring(0, 5);
if(!updateByMonth.containsKey(dtMonth)) {
IncrementalUpdateGroupByMonth iubm = new IncrementalUpdateGroupByMonth(dtMonth);
updateByMonth.put(dtMonth, iubm);
}
IncrementalUpdateGroupByMonth mm = updateByMonth.get(dtMonth);
if(iu.isMonth()) {
mm.monthUpdate = iu;
} else {
mm.dayUpdates.add(iu);
}
}
}
protected static class IncrementalUpdateGroupByMonth {
public final String monthYearPart ;
public List<IncrementalUpdate> dayUpdates = new ArrayList<IncrementalUpdate>();
public IncrementalUpdate monthUpdate;
public long calculateSizeMonthUpdates() {
return calculateSize(getMonthUpdate());
}
public long calculateSizeDayUpdates() {
return calculateSize(getDayUpdates());
}
public boolean isMonthUpdateApplicable() {
return monthUpdate != null;
}
public boolean isDayUpdateApplicable() {
boolean inLimits = dayUpdates.size() > 0 && dayUpdates.size() < 4;
if(!inLimits) {
return false;
}
return true;
}
public List<IncrementalUpdate> getMonthUpdate() {
List<IncrementalUpdate> ll = new ArrayList<IncrementalUpdate>();
if(monthUpdate == null) {
return ll;
}
ll.add(monthUpdate);
for(IncrementalUpdate iu : dayUpdates) {
if(iu.timestamp > monthUpdate.timestamp) {
ll.add(iu);
}
}
return ll;
}
public List<IncrementalUpdate> getDayUpdates() {
return dayUpdates;
}
public IncrementalUpdateGroupByMonth(String monthYearPart ) {
this.monthYearPart = monthYearPart;
}
}
public static class IncrementalUpdate {
String date = "";
public long timestamp;
public String sizeText = "";
public long containerSize;
public long contentSize;
public String fileName;
public boolean isMonth() {
return date.endsWith("00");
}
@Override
public String toString() {
return "Update " + fileName + " " + sizeText + " MB " + date;
}
}
private List<IncrementalUpdate> getIncrementalUpdates(String file, long timestamp) throws IOException,
XmlPullParserException {
String url = URL + "?aosmc=true×tamp=" + timestamp + "&file=" + URLEncoder.encode(file);
HttpURLConnection conn = NetworkUtils.getHttpURLConnection(url);
XmlPullParser parser = PlatformUtil.newXMLPullParser();
parser.setInput(conn.getInputStream(), "UTF-8");
List<IncrementalUpdate> lst = new ArrayList<IncrementalUpdate>();
while (parser.next() != XmlPullParser.END_DOCUMENT) {
if (parser.getEventType() == XmlPullParser.START_TAG) {
if (parser.getName().equals("update")) {
IncrementalUpdate dt = new IncrementalUpdate();
dt.date = parser.getAttributeValue("", "updateDate");
dt.containerSize = Long.parseLong(parser.getAttributeValue("", "containerSize"));
dt.contentSize = Long.parseLong(parser.getAttributeValue("", "contentSize"));
dt.sizeText = parser.getAttributeValue("", "size");
dt.timestamp = Long.parseLong(parser.getAttributeValue("", "timestamp"));
dt.fileName = parser.getAttributeValue("", "name");
lst.add(dt);
}
}
}
return lst;
}
public IncrementalUpdateList getUpdatesByMonth(String fileName) {
IncrementalUpdateList iul = new IncrementalUpdateList();
RegionUpdateFiles ruf = regions.get(fileName.toLowerCase());
iul.updateFiles = ruf;
if(ruf == null) {
iul.errorMessage = resourceManager.getContext().getString(R.string.no_updates_available);
return iul;
}
long timestamp = getTimestamp(ruf);
try {
List<IncrementalUpdate> lst = getIncrementalUpdates(fileName, timestamp);
for(IncrementalUpdate iu : lst) {
iul.addUpdate(iu);
}
} catch (Exception e) {
iul.errorMessage = e.getMessage();
e.printStackTrace();
log.error(e.getMessage(), e);
}
return iul;
}
public long getUpdatesSize(String fileName){
RegionUpdateFiles ruf = regions.get(fileName.toLowerCase());
if(ruf == null) {
return 0;
}
long size = 0;
for (List<RegionUpdate> regionUpdates : ruf.dayUpdates.values()) {
for (RegionUpdate regionUpdate : regionUpdates) {
size += regionUpdate.file.length();
}
}
for (RegionUpdate regionUpdate : ruf.monthUpdates.values()) {
size += regionUpdate.file.length();
}
return size;
}
public void deleteUpdates(String fileName){
RegionUpdateFiles ruf = regions.get(fileName.toLowerCase());
if(ruf == null) {
return;
}
for (List<RegionUpdate> regionUpdates : ruf.dayUpdates.values()) {
for (RegionUpdate regionUpdate : regionUpdates) {
boolean successful = Algorithms.removeAllFiles(regionUpdate.file);
if (successful) {
resourceManager.closeFile(regionUpdate.file.getName());
}
}
}
for (RegionUpdate regionUpdate : ruf.monthUpdates.values()) {
boolean successful = Algorithms.removeAllFiles(regionUpdate.file);
if (successful) {
resourceManager.closeFile(regionUpdate.file.getName());
}
}
}
public long getTimestamp(String fileName) {
RegionUpdateFiles ruf = regions.get(fileName.toLowerCase());
if(ruf == null) {
return System.currentTimeMillis();
}
return getTimestamp(ruf);
}
public long getMapTimestamp(String fileName) {
RegionUpdateFiles ruf = regions.get(fileName.toLowerCase());
if(ruf == null) {
return System.currentTimeMillis();
}
return ruf.mainFileInit;
}
private long getTimestamp(RegionUpdateFiles ruf) {
long timestamp = ruf.mainFileInit;
for (RegionUpdate ru : ruf.monthUpdates.values()) {
timestamp = Math.max(ru.obfCreated, timestamp);
}
for (List<RegionUpdate> l : ruf.dayUpdates.values()) {
for (RegionUpdate ru : l) {
timestamp = Math.max(ru.obfCreated, timestamp);
}
}
return timestamp;
}
}