altoslib: Flush settings restoration commands after accel cal
[fw/altos] / altoslib / AltosMapStore.java
1 /*
2  * Copyright © 2014 Keith Packard <keithp@keithp.com>
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License along
15  * with this program; if not, write to the Free Software Foundation, Inc.,
16  * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
17  */
18
19 package org.altusmetrum.altoslib_12;
20
21 import java.io.*;
22 import java.net.*;
23 import java.util.*;
24
25 public class AltosMapStore {
26         String                                  url;
27         public File                             file;
28         LinkedList<AltosMapStoreListener>       listeners = new LinkedList<AltosMapStoreListener>();
29
30         int                                     status;
31
32         private static File map_file(AltosLatLon center, int zoom, int maptype, int px_size, int scale) {
33                 double lat = center.lat;
34                 double lon = center.lon;
35                 char chlat = lat < 0 ? 'S' : 'N';
36                 char chlon = lon < 0 ? 'W' : 'E';
37
38                 if (lat < 0) lat = -lat;
39                 if (lon < 0) lon = -lon;
40                 String maptype_string = String.format("%s-", AltosMap.maptype_names[maptype]);
41                 String format_string;
42                 if (maptype == AltosMap.maptype_hybrid || maptype == AltosMap.maptype_satellite || maptype == AltosMap.maptype_terrain)
43                         format_string = "jpg";
44                 else
45                         format_string = "png";
46                 return new File(AltosPreferences.mapdir(),
47                                 String.format("map-%c%.6f,%c%.6f-%s%d%s.%s",
48                                               chlat, lat, chlon, lon, maptype_string, zoom, scale == 1 ? "" : String.format("-%d", scale), format_string));
49         }
50
51         private static String map_url(AltosLatLon center, int zoom, int maptype, int px_size, int scale) {
52                 String format_string;
53                 int z = zoom;
54
55                 if (maptype == AltosMap.maptype_hybrid || maptype == AltosMap.maptype_satellite || maptype == AltosMap.maptype_terrain)
56                         format_string = "jpg";
57                 else
58                         format_string = "png32";
59
60                 for (int s = 1; s < scale; s <<= 1)
61                         z--;
62
63                 if (AltosVersion.has_google_maps_api_key())
64                         return String.format("http://maps.google.com/maps/api/staticmap?center=%.6f,%.6f&zoom=%d&size=%dx%d&scale=%d&sensor=false&maptype=%s&format=%s&key=%s",
65                                              center.lat, center.lon, z, px_size/scale, px_size/scale, scale, AltosMap.maptype_names[maptype], format_string, AltosVersion.google_maps_api_key);
66                 else
67                         return String.format("http://maps.google.com/maps/api/staticmap?center=%.6f,%.6f&zoom=%d&size=%dx%d&scale=%d&sensor=false&maptype=%s&format=%s",
68                                              center.lat, center.lon, z, px_size/scale, px_size/scale, AltosMap.maptype_names[maptype], format_string);
69         }
70
71         public int status() {
72                 return status;
73         }
74
75         public synchronized void add_listener(AltosMapStoreListener listener) {
76                 if (!listeners.contains(listener))
77                         listeners.add(listener);
78                 listener.notify_store(this, status);
79         }
80
81         public synchronized void remove_listener(AltosMapStoreListener listener) {
82                 listeners.remove(listener);
83         }
84
85         private synchronized void notify_listeners(int status) {
86                 this.status = status;
87                 for (AltosMapStoreListener listener : listeners)
88                         listener.notify_store(this, status);
89         }
90
91         static Object   forbidden_lock = new Object();
92         static long     forbidden_time;
93         static boolean  forbidden_set;
94         public static int forbidden_response;
95
96         private int fetch_url() {
97                 URL u;
98
99                 try {
100                         u = new URL(url);
101                 } catch (java.net.MalformedURLException e) {
102                         return AltosMapTile.bad_request;
103                 }
104
105                 byte[] data;
106                 URLConnection uc = null;
107                 try {
108                         uc = u.openConnection();
109                         String type = uc.getContentType();
110                         int contentLength = uc.getContentLength();
111                         if (uc instanceof HttpURLConnection) {
112                                 int response = ((HttpURLConnection) uc).getResponseCode();
113                                 switch (response) {
114                                 case HttpURLConnection.HTTP_FORBIDDEN:
115                                 case HttpURLConnection.HTTP_PAYMENT_REQUIRED:
116                                 case HttpURLConnection.HTTP_UNAUTHORIZED:
117                                         synchronized (forbidden_lock) {
118                                                 forbidden_time = System.nanoTime();
119                                                 forbidden_set = true;
120                                                 forbidden_response = response;
121                                                 return AltosMapTile.forbidden;
122                                         }
123                                 }
124                         }
125                         InputStream in = new BufferedInputStream(uc.getInputStream());
126                         int bytesRead = 0;
127                         int offset = 0;
128                         data = new byte[contentLength];
129                         while (offset < contentLength) {
130                                 bytesRead = in.read(data, offset, data.length - offset);
131                                 if (bytesRead == -1)
132                                         break;
133                                 offset += bytesRead;
134                         }
135                         in.close();
136
137                         if (offset != contentLength)
138                                 return AltosMapTile.failed;
139
140                 } catch (IOException e) {
141                         return AltosMapTile.failed;
142                 }
143
144                 try {
145                         FileOutputStream out = new FileOutputStream(file);
146                         out.write(data);
147                         out.flush();
148                         out.close();
149                 } catch (FileNotFoundException e) {
150                         return AltosMapTile.bad_request;
151                 } catch (IOException e) {
152                         if (file.exists())
153                                 file.delete();
154                         return AltosMapTile.bad_request;
155                 }
156                 return AltosMapTile.fetched;
157         }
158
159         static Object   fetch_lock = new Object();
160
161         static final long       forbidden_interval = 60l * 1000l * 1000l * 1000l;
162         static final long       google_maps_ratelimit_ms = 1200;
163
164         static Object   fetcher_lock = new Object();
165
166         static LinkedList<AltosMapStore> waiting = new LinkedList<AltosMapStore>();
167         static LinkedList<AltosMapStore> running = new LinkedList<AltosMapStore>();
168
169         static final int concurrent_fetchers = 128;
170
171         static void start_fetchers() {
172                 while (!waiting.isEmpty() && running.size() < concurrent_fetchers) {
173                         AltosMapStore   s = waiting.remove();
174                         running.add(s);
175                         Thread lt = s.make_fetcher_thread();
176                         lt.start();
177                 }
178         }
179
180         void finish_fetcher() {
181                 synchronized(fetcher_lock) {
182                         running.remove(this);
183                         start_fetchers();
184                 }
185         }
186
187         void add_fetcher() {
188                 synchronized(fetcher_lock) {
189                         waiting.add(this);
190                         start_fetchers();
191                 }
192         }
193
194         class fetcher implements Runnable {
195
196                 public void run() {
197                         try {
198                                 if (file.exists()) {
199                                         notify_listeners(AltosMapTile.fetched);
200                                         return;
201                                 }
202
203                                 synchronized(forbidden_lock) {
204                                         if (forbidden_set && (System.nanoTime() - forbidden_time) < forbidden_interval) {
205                                                 notify_listeners(AltosMapTile.forbidden);
206                                                 return;
207                                         }
208                                 }
209
210                                 int new_status;
211
212                                 if (!AltosVersion.has_google_maps_api_key()) {
213                                         synchronized (fetch_lock) {
214                                                 long startTime = System.nanoTime();
215                                                 new_status = fetch_url();
216                                                 if (new_status == AltosMapTile.fetched) {
217                                                         long duration_ms = (System.nanoTime() - startTime) / 1000000;
218                                                         if (duration_ms < google_maps_ratelimit_ms) {
219                                                                 try {
220                                                                         Thread.sleep(google_maps_ratelimit_ms - duration_ms);
221                                                                 } catch (InterruptedException e) {
222                                                                         Thread.currentThread().interrupt();
223                                                                 }
224                                                         }
225                                                 }
226                                         }
227                                 } else {
228                                         new_status = fetch_url();
229                                 }
230                                 notify_listeners(new_status);
231                         } finally {
232                                 finish_fetcher();
233                         }
234                 }
235         }
236
237         private Thread make_fetcher_thread() {
238                 return new Thread(new fetcher());
239         }
240
241         private void fetch() {
242                 add_fetcher();
243         }
244
245         private AltosMapStore (String url, File file) {
246                 this.url = url;
247                 this.file = file;
248
249                 if (file.exists())
250                         status = AltosMapTile.fetched;
251                 else {
252                         status = AltosMapTile.fetching;
253                         fetch();
254                 }
255         }
256
257         public int hashCode() {
258                 return url.hashCode();
259         }
260
261         public boolean equals(Object o) {
262                 if (o == null)
263                         return false;
264
265                 if (!(o instanceof AltosMapStore))
266                         return false;
267
268                 AltosMapStore other = (AltosMapStore) o;
269                 return url.equals(other.url);
270         }
271
272         static HashMap<String,AltosMapStore> stores = new HashMap<String,AltosMapStore>();
273
274         public static AltosMapStore get(AltosLatLon center, int zoom, int maptype, int px_size, int scale) {
275                 String url = map_url(center, zoom, maptype, px_size, scale);
276
277                 AltosMapStore   store;
278                 synchronized(stores) {
279                         if (stores.containsKey(url)) {
280                                 store = stores.get(url);
281                         } else {
282                                 store = new AltosMapStore(url, map_file(center, zoom, maptype, px_size, scale));
283                                 stores.put(url, store);
284                         }
285                 }
286                 return store;
287         }
288
289 }