Update java library versions
[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; version 2 of the License.
7  *
8  * This program is distributed in the hope that it will be useful, but
9  * WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11  * General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License along
14  * with this program; if not, write to the Free Software Foundation, Inc.,
15  * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
16  */
17
18 package org.altusmetrum.altoslib_9;
19
20 import java.io.*;
21 import java.net.*;
22 import java.util.*;
23
24 public class AltosMapStore {
25         String                                  url;
26         public File                             file;
27         LinkedList<AltosMapStoreListener>       listeners = new LinkedList<AltosMapStoreListener>();
28
29         int                                     status;
30
31         public int status() {
32                 return status;
33         }
34
35         public synchronized void add_listener(AltosMapStoreListener listener) {
36                 if (!listeners.contains(listener))
37                         listeners.add(listener);
38         }
39
40         public synchronized void remove_listener(AltosMapStoreListener listener) {
41                 listeners.remove(listener);
42         }
43
44         private synchronized void notify_listeners(int status) {
45                 this.status = status;
46                 for (AltosMapStoreListener listener : listeners)
47                         listener.notify_store(this, status);
48         }
49
50         static Object   forbidden_lock = new Object();
51         static long     forbidden_time;
52         static boolean  forbidden_set;
53
54         private int fetch_url() {
55                 URL u;
56
57                 try {
58                         u = new URL(url);
59                 } catch (java.net.MalformedURLException e) {
60                         return AltosMapTile.bad_request;
61                 }
62
63                 byte[] data;
64                 URLConnection uc = null;
65                 try {
66                         uc = u.openConnection();
67                         String type = uc.getContentType();
68                         int contentLength = uc.getContentLength();
69                         if (uc instanceof HttpURLConnection) {
70                                 int response = ((HttpURLConnection) uc).getResponseCode();
71                                 switch (response) {
72                                 case HttpURLConnection.HTTP_FORBIDDEN:
73                                 case HttpURLConnection.HTTP_PAYMENT_REQUIRED:
74                                 case HttpURLConnection.HTTP_UNAUTHORIZED:
75                                         synchronized (forbidden_lock) {
76                                                 forbidden_time = System.nanoTime();
77                                                 forbidden_set = true;
78                                                 return AltosMapTile.forbidden;
79                                         }
80                                 }
81                         }
82                         InputStream in = new BufferedInputStream(uc.getInputStream());
83                         int bytesRead = 0;
84                         int offset = 0;
85                         data = new byte[contentLength];
86                         while (offset < contentLength) {
87                                 bytesRead = in.read(data, offset, data.length - offset);
88                                 if (bytesRead == -1)
89                                         break;
90                                 offset += bytesRead;
91                         }
92                         in.close();
93
94                         if (offset != contentLength)
95                                 return AltosMapTile.failed;
96
97                 } catch (IOException e) {
98                         return AltosMapTile.failed;
99                 }
100
101                 try {
102                         FileOutputStream out = new FileOutputStream(file);
103                         out.write(data);
104                         out.flush();
105                         out.close();
106                 } catch (FileNotFoundException e) {
107                         return AltosMapTile.bad_request;
108                 } catch (IOException e) {
109                         if (file.exists())
110                                 file.delete();
111                         return AltosMapTile.bad_request;
112                 }
113                 return AltosMapTile.success;
114         }
115
116         static Object   fetch_lock = new Object();
117
118         static final long       forbidden_interval = 60l * 1000l * 1000l * 1000l;
119         static final long       google_maps_ratelimit_ms = 1200;
120
121         static Object   loader_lock = new Object();
122
123         static LinkedList<AltosMapStore> waiting = new LinkedList<AltosMapStore>();
124         static LinkedList<AltosMapStore> running = new LinkedList<AltosMapStore>();
125
126         static final int concurrent_loaders = 128;
127
128         static void start_loaders() {
129                 while (!waiting.isEmpty() && running.size() < concurrent_loaders) {
130                         AltosMapStore   s = waiting.remove();
131                         running.add(s);
132                         Thread lt = s.make_loader_thread();
133                         lt.start();
134                 }
135         }
136
137         void finish_loader() {
138                 synchronized(loader_lock) {
139                         running.remove(this);
140                         start_loaders();
141                 }
142         }
143
144         void add_loader() {
145                 synchronized(loader_lock) {
146                         waiting.add(this);
147                         start_loaders();
148                 }
149         }
150
151         class loader implements Runnable {
152
153                 public void run() {
154                         try {
155                                 if (file.exists()) {
156                                         notify_listeners(AltosMapTile.success);
157                                         return;
158                                 }
159
160                                 synchronized(forbidden_lock) {
161                                         if (forbidden_set && (System.nanoTime() - forbidden_time) < forbidden_interval) {
162                                                 notify_listeners(AltosMapTile.forbidden);
163                                                 return;
164                                         }
165                                 }
166
167                                 int new_status;
168
169                                 if (!AltosVersion.has_google_maps_api_key()) {
170                                         synchronized (fetch_lock) {
171                                                 long startTime = System.nanoTime();
172                                                 new_status = fetch_url();
173                                                 if (new_status == AltosMapTile.success) {
174                                                         long duration_ms = (System.nanoTime() - startTime) / 1000000;
175                                                         if (duration_ms < google_maps_ratelimit_ms) {
176                                                                 try {
177                                                                         Thread.sleep(google_maps_ratelimit_ms - duration_ms);
178                                                                 } catch (InterruptedException e) {
179                                                                         Thread.currentThread().interrupt();
180                                                                 }
181                                                         }
182                                                 }
183                                         }
184                                 } else {
185                                         new_status = fetch_url();
186                                 }
187                                 notify_listeners(new_status);
188                         } finally {
189                                 finish_loader();
190                         }
191                 }
192         }
193
194         private Thread make_loader_thread() {
195                 return new Thread(new loader());
196         }
197
198         private void load() {
199                 add_loader();
200         }
201
202         private AltosMapStore (String url, File file) {
203                 this.url = url;
204                 this.file = file;
205
206                 if (file.exists())
207                         status = AltosMapTile.success;
208                 else {
209                         status = AltosMapTile.loading;
210                         load();
211                 }
212         }
213
214         public int hashCode() {
215                 return url.hashCode();
216         }
217
218         public boolean equals(Object o) {
219                 if (o == null)
220                         return false;
221
222                 if (!(o instanceof AltosMapStore))
223                         return false;
224
225                 AltosMapStore other = (AltosMapStore) o;
226                 return url.equals(other.url);
227         }
228
229         static HashMap<String,AltosMapStore> stores = new HashMap<String,AltosMapStore>();
230
231         public static AltosMapStore get(String url, File file) {
232                 AltosMapStore   store;
233                 synchronized(stores) {
234                         if (stores.containsKey(url)) {
235                                 store = stores.get(url);
236                         } else {
237                                 store = new AltosMapStore(url, file);
238                                 stores.put(url, store);
239                         }
240                 }
241                 return store;
242         }
243 }