Special URL for storing the gamer key
[louyapi.git] / src / main / java / de / cweiske / ouya / louyapi / HttpServer.java
1 package de.cweiske.ouya.louyapi;
2
3 import static de.cweiske.ouya.louyapi.HttpService.TAG;
4
5 import android.content.res.AssetManager;
6 import android.util.Log;
7
8 import java.io.IOException;
9 import java.io.InputStream;
10
11 import fi.iki.elonen.NanoHTTPD;
12
13 public class HttpServer extends NanoHTTPD {
14
15     private final AssetManager assetManager;
16     protected String prefix = "stouyapi-www";
17
18     public HttpServer(int port, AssetManager assetManager) {
19         super(port);
20         this.assetManager = assetManager;
21     }
22
23     /**
24      * This is basically a re-implementation of the stouyapi .htaccess file
25      *
26      * @param session The HTTP session
27      * @return HTTP response
28      */
29     public Response serve(IHTTPSession session) {
30         String path = session.getUri();
31         Log.d(TAG, "serve: " + path);
32         //this happens with "//agreements/marketplace.html". remove double slash.
33         if (path.startsWith("//")) {
34             path = path.substring(1);
35         }
36
37         InputStream content;
38
39         if (path.equals("/api/v1/status") || path.equals("/generate_204")) {
40             //usage: check if internet connection is working
41             return newFixedLengthResponse(Response.Status.NO_CONTENT, null, "");
42
43         } else if (path.equals("/api/v1/details") && session.getParameters().containsKey("app")) {
44             //usage: detail page for installed games
45             if (null != (content = loadFileContent("/api/v1/details-data/" + session.getParameters().get("app").get(0) + ".json"))) {
46                 return newFixedLengthResponse(Response.Status.OK, "application/json", content);
47             } else {
48                 content = loadFileContent("/api/v1/details-dummy/error-unknown-app-details.json");
49                 return newFixedLengthResponse(Response.Status.OK, "application/json", content);
50             }
51
52         } else if (path.startsWith("/api/v1/apps/") && path.endsWith("/download")) {
53             String appid = path.substring(13, path.length() - 9);
54             if (null != (content = loadFileContent("/api/v1/apps/" + appid + "-download.json"))) {
55
56                 return newFixedLengthResponse(Response.Status.OK, "application/json", content);
57             }
58
59         } else if (path.startsWith("/api/v1/apps/")) {
60             String appid = path.substring(13);
61             if (null != (content = loadFileContent("/api/v1/apps/" + appid + ".json"))) {
62                 return newFixedLengthResponse(Response.Status.OK, "application/json", content);
63             }
64
65         } else if (path.startsWith("/api/v1/developers") && path.endsWith("/products/") && session.getParameters().containsKey("only")) {
66             //usage: product details for a single product
67             if (null != (content = loadFileContent(path + session.getParameters().get("only").get(0) + ".json"))) {
68                 return newFixedLengthResponse(Response.Status.OK, "application/json", content);
69             }
70
71         } else if (path.equals("/api/v1/discover")) {
72             //usage: main store page
73             if (null != (content = loadFileContent("/api/v1/discover-data/discover.json"))) {
74                 return newFixedLengthResponse(Response.Status.OK, "application/json", content);
75             }
76
77         } else if (path.startsWith("/api/v1/discover/")) {
78             //usage: store category
79             if (null != (content = loadFileContent("/api/v1/discover-data/" + path.substring(17) + ".json"))) {
80                 return newFixedLengthResponse(Response.Status.OK, "application/json", content);
81             }
82
83         } else if (path.equals("/api/v1/gamers")) {
84             //usage: register a user
85             if (null != (content = loadFileContent("/api/v1/gamers/register-error.json"))) {
86                 return newFixedLengthResponse(Response.Status.BAD_REQUEST, "application/json", content);
87             }
88
89         } else if (path.equals("/api/v1/gamers/me")) {
90             //usage: fetch user data
91             if (null != (content = loadFileContent("/api/v1/gamers/me.json"))) {
92                 return newFixedLengthResponse(Response.Status.OK, "application/json", content);
93             }
94
95         } else if (path.equals("/api/v1/gamers/key")) {
96             //usage: store gamer ouya public key via PUT
97             return newFixedLengthResponse(Response.Status.CREATED, null, "");
98
99         } else if (path.equals("/api/v1/search") && session.getParameters().containsKey("q")) {
100             //usage: search for games
101             String query = session.getParameters().get("q").get(0);
102             if (null != (content = loadFileContent("/api/v1/search-data/" + query.charAt(0) + ".json"))) {
103                 return newFixedLengthResponse(Response.Status.OK, "application/json", content);
104             }
105
106         } else if (!path.endsWith("/") && null != (content = loadFileContent(path))) {
107             //try if the path exists in the assets/stouyapi-www/ dir
108             return newFixedLengthResponse(Response.Status.OK, mimeTypeFromPath(path), content);
109
110         } else if (null != (content = loadFileContent(path.replaceAll("/$", "") + "/index.htm"))) {
111             //usage: /api/v1/developers/*/products
112             return newFixedLengthResponse(Response.Status.OK, "text/html", content);
113
114         } else if (!path.endsWith("/") && null != (content = loadAssetsFileContent(path))) {
115             //try if the path exists in the assets/ dir (without stouyapi-www)
116             return newFixedLengthResponse(Response.Status.OK, mimeTypeFromPath(path), content);
117
118         } else if (null != (content = loadAssetsFileContent(path.replaceAll("/$", "") + "/index.htm"))) {
119             //try if the path + index.htm exists in the assets/ dir (without stouyapi-www)
120             return newFixedLengthResponse(Response.Status.OK, "text/html", content);
121
122         } else if (path.startsWith("/api/v1/games/") && path.endsWith("/purchases")) {
123             //usage: purchases for non-existing game
124             if (null != (content = loadFileContent("/api/v1/games/purchases-empty.json"))) {
125                 return newFixedLengthResponse(Response.Status.OK, "application/json", content);
126             }
127         }
128
129         return newFixedLengthResponse(Response.Status.NOT_FOUND, "text/plain", "File not found");
130     }
131
132     /**
133      * Automatically determine the length of the input stream
134      */
135     protected Response newFixedLengthResponse(Response.IStatus status, String mimeType, InputStream data) {
136         int length = 0;
137         try {
138             length = streamLength(data);
139             data.reset();
140         } catch (IOException e) {
141             Log.e(TAG, e.getMessage());
142             return newFixedLengthResponse(
143                 Response.Status.INTERNAL_ERROR, "text/plain",
144                 "Error: " + e.getMessage()
145             );
146         }
147         return newFixedLengthResponse(status, mimeType, data, length);
148     }
149
150     protected InputStream loadFileContent(String path)
151     {
152         return loadAssetsFileContent(prefix + path);
153     }
154
155     protected InputStream loadAssetsFileContent(String path)
156     {
157         path = path.replaceAll("^/", "");
158         try {
159             Log.d(TAG, "loadFileContent: " + path);
160             return assetManager.open(path, AssetManager.ACCESS_BUFFER);
161         } catch (IOException e) {
162             //file does not exist
163             Log.d(TAG, "loadFileContent: fail");
164             return null;
165         }
166     }
167
168     protected static int streamLength(InputStream inputStream) throws IOException {
169         byte[] buffer = new byte[4096];
170         int chunkBytesRead = 0;
171         int length = 0;
172         while((chunkBytesRead = inputStream.read(buffer)) != -1) {
173             length += chunkBytesRead;
174         }
175         return length;
176     }
177
178     protected String mimeTypeFromPath(String path) {
179         if (path.endsWith(".htm")) {
180             return "text/html";
181         } else if (path.endsWith(".ico")) {
182             return "image/vnd.microsoft.icon";
183         } else if (path.endsWith(".jpg")) {
184             return "image/jpeg";
185         } else if (path.endsWith(".png")) {
186             return "image/png";
187         }
188         return "text/plain";
189     }
190
191     protected boolean isBinary(String path) {
192         return path.endsWith(".ico")
193             || path.endsWith(".jpg")
194             || path.endsWith(".png");
195     }
196 }