Skip to content
This repository
Newer
Older
100644 964 lines (777 sloc) 44.981 kb
b10fbeab »
2010-09-19 Working on README file.
1 The appengine-magic library attempts to abstract away the infrastructural nuts
2 and bolts of writing a Clojure application for the Google App Engine platform.
b81b8a90 »
2010-09-19 Added barebones steps to the README file.
3
b10fbeab »
2010-09-19 Working on README file.
4 The development environment of Google App Engine for Java expects pre-compiled
5 classes, and generally does not fit well with Clojure's interactive development
6 model. appengine-magic attempts to make REPL-based development of App Engine
7 applications as natural as any other Clojure program.
b81b8a90 »
2010-09-19 Added barebones steps to the README file.
8
b10fbeab »
2010-09-19 Working on README file.
9 1. Programs using appengine-magic just need to include appengine-magic as a
10 Leiningen dev-dependency.
11 2. appengine-magic takes a Ring handler and makes it available as a servlet for
12 App Engine deployment.
13 3. appengine-magic is also a Leiningen plugin, and adds several tasks which
14 simplify preparing for App Engine deployment.
b81b8a90 »
2010-09-19 Added barebones steps to the README file.
15
7efad8a3 »
2010-10-04 Updated documentation for the new datastore support.
16 Using appengine-magic still requires familiarity with Google App Engine. This
17 README file tries to describe everything you need to know to use App Engine with
18 Clojure, but does not explain the details of App Engine semantics. Please refer
19 to Google's official documentation for details.
20
36a69060 »
2011-03-14 Added the optional install-artifacts.sh script. Updated documentation.
21 Please read the project's HISTORY file to learn what changed in recent releases.
6ac52add »
2010-10-23 Updated documentation.
22
b81b8a90 »
2010-09-19 Added barebones steps to the README file.
23
24
b10fbeab »
2010-09-19 Working on README file.
25 ## Dependencies
b81b8a90 »
2010-09-19 Added barebones steps to the README file.
26
f775862b »
2011-03-27 Version 0.4.0.
27 * Clojure 1.2.1
524cf87c »
2011-06-21 Documentation updates.
28 * Leiningen 1.5.2
29 * Google App Engine SDK 1.5.1
30 * swank-clojure 1.3.1 (optional)
b10fbeab »
2010-09-19 Working on README file.
31
32
33
f3bdc91b »
2010-09-23 Documentation updates.
34 ## Overview
35
36 To use appengine-magic effectively, you need the following:
37
38 1. The `appengine-magic` jar available on the project classpath.
39 2. A Ring handler for your main application. You may use any Ring-compatible
40 framework to make it. If your application does not yet have a `core.clj`
41 file, then the `lein appengine-new` task creates one for you with a simple
42 "hello world" Ring handler.
43 3. A var defined by passing the Ring handler to the
44 `appengine-magic.core/def-appengine-app` macro. This makes the application
45 available both to interactive REPL development, and to App Engine itself.
46 4. An entry point servlet. REPL development does not use it, but the standard
47 App Engine SDK `dev_appserver.sh` mode and production deployment both
48 do. This servlet must be AOT-compiled into a class file. This servlet
49 defaults to the name `app_servlet.clj`, and the `lein appengine-new` task
50 creates one for your project. The servlet must refer to the var defined by
51 `def-appengine-app`.
52 5. Web application resources. This primarily includes web application
53 descriptors. `lein appengine-new` generates those and places them in the
931e199b »
2010-12-14 Updated documentation for the breaking change.
54 `war/WEB-INF/` directory. You should also place all static files that your
55 application uses in `war/`.
f3bdc91b »
2010-09-23 Documentation updates.
56
36a69060 »
2011-03-14 Added the optional install-artifacts.sh script. Updated documentation.
57 Here is a sample `core.clj`, using
58 [Compojure](https://github.com/weavejester/compojure) (other Ring-compatible
59 frameworks, such as [Moustache](https://github.com/cgrand/moustache), also
60 work):
fad69988 »
2010-11-15 Added Compojure example.
61
62 (ns simple-example.core
63 (:use compojure.core)
64 (:require [appengine-magic.core :as ae]))
65
66 (defroutes simple-example-app-handler
67 (GET "/" req
68 {:status 200
69 :headers {"Content-Type" "text/plain"}
70 :body "Hello, world!"})
71 (GET "/hello/:name" [name]
72 {:status 200
73 :headers {"Content-Type" "text/plain"}
74 :body (format "Hello, %s!" name)})
75 (ANY "*" _
76 {:status 200
77 :headers {"Content-Type" "text/plain"}
78 :body "not found"}))
79
80 (ae/def-appengine-app simple-example-app #'simple-example-app-handler)
81
36a69060 »
2011-03-14 Added the optional install-artifacts.sh script. Updated documentation.
82 If you wish to emit HTML or XML from your application, you should use a
83 specialized Clojure server-side templating library, e.g.,
84 [Enlive](https://github.com/cgrand/enlive) or
85 [Hiccup](https://github.com/weavejester/hiccup). None of the appengine-magic
86 examples rely on these libraries.
87
f3bdc91b »
2010-09-23 Documentation updates.
88
89
b10fbeab »
2010-09-19 Working on README file.
90 ## Getting Started
91
f3bdc91b »
2010-09-23 Documentation updates.
92
b10fbeab »
2010-09-19 Working on README file.
93 ### Project setup
94
95 You need a copy of the Google App Engine SDK installed
96 somewhere. appengine-magic cannot replace its `dev_appserver.sh` and `appcfg.sh`
97 functionality.
98
99 1. `lein new` <project-name>
f3bdc91b »
2010-09-23 Documentation updates.
100 2. Optional: `rm src/<project-name>/core.clj` to clean out the default
101 `core.clj` file created by Leiningen. You need to do this so that
102 appengine-magic can create a default file which correctly invokes the
103 `def-appengine-app` macro.
524cf87c »
2011-06-21 Documentation updates.
104 3. Edit `project.clj`: add `[appengine-magic "0.4.2"]` to your
56a3c790 »
2010-11-19 Documentation updates.
105 `:dev-dependencies`.
f3bdc91b »
2010-09-23 Documentation updates.
106 4. `lein deps`. This fetches appengine-magic, and makes its Leiningen plugin
36a69060 »
2011-03-14 Added the optional install-artifacts.sh script. Updated documentation.
107 tasks available. If you already have the App Engine SDK installed locally,
108 and do not wish to wait for Maven to download it again as a dependency, you
109 may optionally run the provided `install-artifacts.sh` script first.
f3bdc91b »
2010-09-23 Documentation updates.
110 5. `lein appengine-new`. This sets up four files for your project: `core.clj`
111 (which has a sample Ring handler and uses the `def-appengine-app` macro),
b10fbeab »
2010-09-19 Working on README file.
112 `app_servlet.clj` (the entry point for the application),
931e199b »
2010-12-14 Updated documentation for the breaking change.
113 `war/WEB-INF/web.xml` (a servlet descriptor), and
114 `war/WEB-INF/appengine-web.xml` (an App Engine application descriptor). These
115 files should contain reasonable starting defaults for your application.
b10fbeab »
2010-09-19 Working on README file.
116
56a3c790 »
2010-11-19 Documentation updates.
117 With regard to AOT-compilation, if your project needs it, then you must include
118 `<project>.app_servlet` in Leiningen's `:aot` directive. Otherwise, omit the
119 `:aot` directive altogether. The `lein appengine-prepare` task will take care of
120 AOT-compiling the entry point servlet and cleaning up afterwards.
121
b10fbeab »
2010-09-19 Working on README file.
122 The default `.gitignore` file produced by Leiningen works well with the
f3bdc91b »
2010-09-23 Documentation updates.
123 resulting project, but do take a careful look at it. In particular, you should
931e199b »
2010-12-14 Updated documentation for the breaking change.
124 avoid checking in `war/WEB-INF/lib/` or `war/WEB-INF/classes/`: let Leiningen
125 take care of managing those directories.
b10fbeab »
2010-09-19 Working on README file.
126
6d695830 »
2010-11-09 Documented a couple of pain points.
127 NB: When editing the Leiningen `project.clj` file, do not point `:compile-path`
931e199b »
2010-12-14 Updated documentation for the breaking change.
128 or `:library-path` to `war/WEB-INF/classes/` and `war/WEB-INF/lib/`. This will
129 interfere with deployment.
6d695830 »
2010-11-09 Documented a couple of pain points.
130
b10fbeab »
2010-09-19 Working on README file.
131
132 ### Development process
133
134 Launch `lein swank` or `lein repl`, whichever you normally use. Once you have a
f3bdc91b »
2010-09-23 Documentation updates.
135 working REPL, compile your application's `core.clj` (or whatever other entry
136 point file you use).
b10fbeab »
2010-09-19 Working on README file.
137
138 The key construct provided by appengine-magic is the
139 `appengine-magic.core/def-appengine-app` macro. It takes a Ring handler and
f3bdc91b »
2010-09-23 Documentation updates.
140 defines a new `<project-name>-app` var. If you want to rename this var, remember
141 to update `app_servlet.clj`. That's it: you may now write your application using
142 any framework which produces a Ring-compatible handler. Then, just pass the
143 resulting Ring handler to `def-appengine-app`.
b10fbeab »
2010-09-19 Working on README file.
144
145 To test your work interactively, you can control a Jetty instance from the REPL
82fbe9c3 »
2010-11-28 Documentation updates.
146 using `appengine-magic.core/start` and `appengine-magic.core/stop`. In addition,
3cb947a2 »
2010-11-28 fixed minor typos
147 a convenience function, `appengine-magic.core/serve`, will either start or restart
82fbe9c3 »
2010-11-28 Documentation updates.
148 a running instance. Examples (assuming you are in your application's `core`
149 namespace and your application is named `foo`):
36c7502b »
2010-11-11 Removed all the "suggested alias" nonsense from the documentation.
150
151 (require '[appengine-magic.core :as ae])
b10fbeab »
2010-09-19 Working on README file.
152
82fbe9c3 »
2010-11-28 Documentation updates.
153 ;; recommended: use this to start or restart an app
154 (ae/serve foo-app)
155
156 ;; or use these lower-level functions
885faf93 »
2010-09-22 Changed the new preferred alias for the appengine-magic library to "ae".
157 (ae/start foo-app)
158 (ae/stop)
159 (ae/start foo-app :port 8095)
160 (ae/stop)
b10fbeab »
2010-09-19 Working on README file.
161
162 Recompiling the functions which make up your Ring handler should produce
163 instantaneous results.
164
82fbe9c3 »
2010-11-28 Documentation updates.
165 If you use SLIME, then the `swank.core/break` function works even inside a Ring
166 handler.
167
b10fbeab »
2010-09-19 Working on README file.
168
81d01b7d »
2010-09-22 Updated the README file.
169 ### Testing with dev_appserver.sh
b10fbeab »
2010-09-19 Working on README file.
170
506555c0 »
2010-11-01 Documented resource duplication in jar files.
171 1. `lein appengine-prepare`. This AOT-compiles the entry point servlet, makes a
172 jar of your application, and copies it, along with all your library
931e199b »
2010-12-14 Updated documentation for the breaking change.
173 dependencies, to your application's `war/WEB-INF/lib/` directories.
174 2. Run `dev_appserver.sh` with a path to your application's `war/` directory.
b10fbeab »
2010-09-19 Working on README file.
175
176
931e199b »
2010-12-14 Updated documentation for the breaking change.
177 ### Static files
b10fbeab »
2010-09-19 Working on README file.
178
931e199b »
2010-12-14 Updated documentation for the breaking change.
179 Just put all static files into your application's `war/` directory. If you put a
180 file called `index.html` there, it will become a default welcome file.
6ac52add »
2010-10-23 Updated documentation.
181
182
183 ### Classpath resources
184
185 Put all classpath resources you expect to need at runtime in `resources/`. You
012292fe »
2010-11-20 Updated documentation.
186 can then access them using the `appengine-magic.core/open-resource-stream`,
931e199b »
2010-12-14 Updated documentation for the breaking change.
187 which returns a `java.io.BufferedInputStream` instance.
b10fbeab »
2010-09-19 Working on README file.
188
46c03171 »
2010-11-01 Emphasized the correct way to access classpath resources.
189 Do not use direct methods like `java.io.File` or
190 `ClassLoader/getSystemClassLoader` to access classpath resources; they do not
191 work consistently across all App Engine environments.
192
b10fbeab »
2010-09-19 Working on README file.
193
194 ### Deployment to App Engine
195
fc15ca65 »
2010-09-20 Updated README file.
196 1. First of all, be careful. You must manually maintain the version field in
f3bdc91b »
2010-09-23 Documentation updates.
197 `appengine-web.xml` and you should understand its implications. Refer to
198 Google App Engine documentation for more information.
931e199b »
2010-12-14 Updated documentation for the breaking change.
199 2. `lein appengine-prepare` prepares the `war/` directory with the latest
b10fbeab »
2010-09-19 Working on README file.
200 classes and libraries for deployment.
201 3. When you are ready to deploy, just run `appcfg.sh update` with a path to your
931e199b »
2010-12-14 Updated documentation for the breaking change.
202 application's `war/` directory.
6ac52add »
2010-10-23 Updated documentation.
203
204
012292fe »
2010-11-20 Updated documentation.
205 ### Checking the runtime environment
206
ab580a9a »
2010-12-17 Documentation updates.
207 - `appengine-magic.core/appengine-environment-type`: returns a keyword
208 corresponding to the current environment: `:production`, `:dev-appserver`, and
209 `:interactive`. Useful if you want to, e.g., return more detailed error
210 messages and stack traces in non-production mode.
211 - `appengine-magic.core/appengine-app-id`: returns the ID of the running
212 application.
213 - `appengine-magic.core/appengine-app-version`: returns the current deployed
214 version string.
215 - `appengine-magic.core/appengine-base-url`: returns a string with the base
216 hostname of the current application, e.g., `http://my-app.appspot.com`. In
217 production, this always points to the `appspot.com` domain. In interactive
218 mode, this always points to `localhost`, but also includes the correct
219 port. The `:https?` keyword determines if the schema in the URL should be
220 `https://`, but is ignored in interactive mode. This function does not work in
221 `dev_appserver.sh` at all (it is difficult from within the application
222 environment to determine the correct port).
9cfc1e4b »
2010-12-06 Updated README for appengine-magic.core/appengine-base-url.
223
012292fe »
2010-11-20 Updated documentation.
224
6ac52add »
2010-10-23 Updated documentation.
225 ### Automatic testing code
226
227 The `clojure.test` system works well for testing `appengine-magic` applications,
228 but all tests must bootstrap App Engine services in order to run. The
229 `appengine-magic.testing` namespace provides several functions usable as
230 `clojure.test` fixtures to help you do so. The easiest way to get started is:
231
232 (use 'clojure.test)
233 (require '[appengine-magic.testing :as ae-testing])
234
235 (use-fixtures :each (ae-testing/local-services :all))
236
237 Then, write `deftest` forms normally; you can use App Engine services just as you
238 would in application code.
b10fbeab »
2010-09-19 Working on README file.
239
240
91248c2d »
2010-11-26 Updated documentation.
241 ### File uploads and multipart forms
242
243 A Ring application requires the use of middleware to convert the request body
244 into something useful in the request map. Ring comes with
245 `ring.middleware.multipart-params/wrap-multipart-params` which does this;
246 unfortunately, this middleware uses classes restricted in App Engine. To deal
247 with this, `appengine-magic` has its own middleware.
248
249 `appengine-magic.multipart-params/wrap-multipart-params` works just like the
250 Ring equivalent, except file upload parameters become maps with a `:bytes` key
251 (instead of `:tempfile`). This key contains a byte array with the upload data.
252
253 A full Compojure example (includes features from the Datastore service):
254
255 (use 'compojure.core
256 '[appengine-magic.multipart-params :only [wrap-multipart-params]])
257
258 (require '[appengine-magic.core :as ae]
259 '[appengine-magic.services.datastore :as ds])
260
261 (ds/defentity Image [^:key name, content-type, data])
262
263 (defroutes upload-images-demo-app-handler
264 ;; HTML upload form
265 (GET "/upload" _
266 {:status 200
267 :headers {"Content-Type" "text/html"}
268 :body (str "<html><body>"
269 "<form action=\"/done\" "
270 "method=\"post\" enctype=\"multipart/form-data\">"
271 "<input type=\"file\" name=\"file-upload\">"
272 "<input type=\"submit\" value=\"Submit\">"
273 "</form>"
274 "</body></html>")})
275 ;; handles the uploaded data
276 (POST "/done" _
277 (wrap-multipart-params
278 (fn [req]
279 (let [img (get (:params req) "file-upload")
280 img-entity (Image. (:filename img)
281 (:content-type img)
282 (ds/as-blob (:bytes img)))]
283 (ds/save! img-entity)
284 {:status 200
285 :headers {"Content-Type" "text/plain"}
286 :body (with-out-str
287 (println (:params req)))}))))
288 ;; hit this route to retrieve an uploaded file
289 (GET ["/img/:name", :name #".*"] [name]
290 (let [img (ds/retrieve Image name)]
291 (if (nil? img)
292 {:status 404}
293 {:status 200
294 :headers {"Content-Type" (:content-type img)}
295 :body (.getBytes (:data img))}))))
296
297 (ae/def-appengine-app upload-images-demo-app #'upload-images-demo-app-handler)
298
299 Please note that you do not need to use this middleware with the Blobstore
300 service. App Engine takes care decoding the upload in its internal handlers, and
301 the upload callbacks do not contain multipart data.
302
303
823e44aa »
2010-11-27 Documentation updates.
304 ### Managing multiple environments
305
306 Most web applications use several environments internally: production, plus
307 various staging and development installations. App Engine supports multiple
308 versions in its `appengine-web.xml` file, but does nothing to help deal with
309 installing to different full environments. Since different versions of App
310 Engine applications share the same blobstore and datastore, distinguishing
311 between production and staging using only versions is dangerous.
312
313 `appengine-magic` has a mechanism to help deal with multiple environments. The
314 Leiningen `appengine-update` task replaces the use of `appcfg.sh update`, and a
315 new entry in `project.clj` manages applications and versions.
316
317 1. Rename your `WEB-INF/application-web.xml` file to
318 `WEB-INF/application-web.xml.tmpl`. For safety reasons, `appengine-update`
319 will not run if a normal `application-web.xml` exists. For clarity, you
320 should blank out the contents of the `<application>` and `<version>` tags of
321 the template file (but leave the tags in place).
322 2. Add a new entry to `project.clj`: `:appengine-app-versions`. This entry is a
323 map from application name to application version. Example:
0ad3237c »
2010-11-28 Documentation updates.
324
e7b68f30 »
2010-11-28 Markdown?
325 :appengine-app-versions {"myapp-production" "2010-11-25 11:15"
326 "myapp-staging" "2010-11-27 22:05"
327 "myapp-dev1" "2830"
328 "myapp-dev2" "2893"}
0ad3237c »
2010-11-28 Documentation updates.
329
823e44aa »
2010-11-27 Documentation updates.
330 The `myapp-` key strings correspond to App Engine applications, registered
331 and managed through the App Engine console. The value strings are the
332 versions `appengine-update` will install if invoked on that application.
333 3. Add a new entry to `project.clj`: `:appengine-sdk`. The App Engine SDK
3cb947a2 »
2010-11-28 fixed minor typos
334 location is necessary to execute the actual production deployment. This value
823e44aa »
2010-11-27 Documentation updates.
335 can be just a string, representing a path. Alternatively, for teams whose
336 members keep the App Engine SDK in different locations, this value can be a
337 map from username to path string. Examples:
0ad3237c »
2010-11-28 Documentation updates.
338
e7b68f30 »
2010-11-28 Markdown?
339 :appengine-sdk "/opt/appengine-java-sdk"
340 :appengine-sdk {"alice" "/opt/appengine-java-sdk"
341 "bob" "/Users/bob/lib/appengine-java-sdk"
342 "charlie" "/home/charlie/appengine/sdk/current"}
0ad3237c »
2010-11-28 Documentation updates.
343
0f69545e »
2011-06-13 Use value of $APPENGINE_HOME as a fallback for SDK location
344 If the APPENGINE_HOME environment variable is set, its value will
345 be used if no :appengine-sdk entry is found in the project.clj
346 file.
823e44aa »
2010-11-27 Documentation updates.
347 4. Run `lein appengine-update <application>`, where the argument is an
348 application name from the `:appengine-app-versions` map.
349
0ba18caa »
2010-11-27 Documentation updates.
350 If you use this mechanism, be aware that `dev_appserver.sh` will no longer work
351 (since your project no longer defines a simple `appengine-web.xml` file). To run
352 that process, use `lein appengine-dev-appserver <application>`.
353
1cbb540b »
2010-12-18 Documentation update.
354 You may also force a specific version string if you pass it as an optional
355 argument to `appengine-update`: `lein appengine-update <application> <version>`.
356
823e44aa »
2010-11-27 Documentation updates.
357
b10fbeab »
2010-09-19 Working on README file.
358
81d01b7d »
2010-09-22 Updated the README file.
359 ## App Engine Services
360
361 appengine-magic provides convenience wrappers for using App Engine services from
0ad3237c »
2010-11-28 Documentation updates.
362 Clojure. Most of these API calls will work when invoked from the REPL, but only
363 if an application is running — that is, it was launched using
364 `appengine-magic.core/start`.
81d01b7d »
2010-09-22 Updated the README file.
365
366
367 ### User service
368
36c7502b »
2010-11-11 Removed all the "suggested alias" nonsense from the documentation.
369 The `appengine-magic.services.user` namespace provides the following functions
370 for handling users.
b10fbeab »
2010-09-19 Working on README file.
371
81d01b7d »
2010-09-22 Updated the README file.
372 - `current-user`: returns the `com.google.appengine.api.users.User` for the
373 currently logged-in user.
38de8f58 »
2010-10-20 Updated the README.
374 - `user-logged-in?`
375 - `user-admin?`
81d01b7d »
2010-09-22 Updated the README file.
376 - `login-url` (optional keyword: `:destination`): returns the Google
377 authentication servlet URL, and forwards the user to the optional destination.
378 - `logout-url` (optional keyword: `:destination`): performs logout, and forwards
379 the user to the optional destination.
380
381
7dee3d5e »
2010-09-24 Updated the README file with memcache service documentation.
382 ### Memcache service
383
36c7502b »
2010-11-11 Removed all the "suggested alias" nonsense from the documentation.
384 The `appengine-magic.services.memcache` namespace provides the following
385 functions for the App Engine memcache. See App Engine documentation for detailed
386 explanations of the underlying Java API.
7dee3d5e »
2010-09-24 Updated the README file with memcache service documentation.
387
388 - `statistics`: returns the current memcache statistics.
77559bef »
2010-10-12 Renamed destructive memcache interface functions to use trailing ! ch…
389 - `clear-all!`: wipes the entire cache for all namespaces.
7dee3d5e »
2010-09-24 Updated the README file with memcache service documentation.
390 - `contains? <key>` (optional keyword: `:namespace`): checks if the given key
391 exists in the cache.
77559bef »
2010-10-12 Renamed destructive memcache interface functions to use trailing ! ch…
392 - `delete! <key>` (optional keywords: `:namespace`, `:millis-no-readd`): removes
7dee3d5e »
2010-09-24 Updated the README file with memcache service documentation.
393 the key from the cache, optionally refraining from adding it for the given
394 number of milliseconds. If the key argument is sequential, deletes all the
395 named keys.
396 - `get <key>` (optional keyword: `:namespace`): returns the value for the given
397 key, but if the key argument is sequential, returns a map of key-value pairs
398 for each supplied key.
6ac52add »
2010-10-23 Updated documentation.
399 - `put! <key> <value>` (optional keywords: `:namespace`, `:expiration`,
7dee3d5e »
2010-09-24 Updated the README file with memcache service documentation.
400 `:policy`): saves the given value under the given key; expiration is an
401 instance of `com.google.appengine.api.memcache.Expiration`; policy is one of
402 `:always` (the default), `:add-if-not-present`, or `:replace-only`.
6ac52add »
2010-10-23 Updated documentation.
403 - `put-map! <key-value-map>` (optional keywords: `:namespace`, `:expiration`,
7dee3d5e »
2010-09-24 Updated the README file with memcache service documentation.
404 `:policy`): writes the key-value-map into the cache. Other keywords same as
405 for `put`.
6ac52add »
2010-10-23 Updated documentation.
406 - `increment! <key> <delta>` (optional keywords: `:namespace`, `:initial`):
7dee3d5e »
2010-09-24 Updated the README file with memcache service documentation.
407 atomically increments long integer values in the cache; if key is sequential,
408 it increments all keys by the given delta.
6ac52add »
2010-10-23 Updated documentation.
409 - `increment-map! <key-delta-map>` (optional keywords: `:namespace`, `:initial`):
7dee3d5e »
2010-09-24 Updated the README file with memcache service documentation.
410 atomically increments long integer values by deltas given in the argument map.
411
81d01b7d »
2010-09-22 Updated the README file.
412
7efad8a3 »
2010-10-04 Updated documentation for the new datastore support.
413 ### Datastore
414
36c7502b »
2010-11-11 Removed all the "suggested alias" nonsense from the documentation.
415 The `appengine-magic.services.datastore` namespace provides a fairly complete
416 interface for the App Engine datastore.
7efad8a3 »
2010-10-04 Updated documentation for the new datastore support.
417
98b75dc9 »
2010-10-04 Tweaked the README file.
418 A few simple examples:
7efad8a3 »
2010-10-04 Updated documentation for the new datastore support.
419
36c7502b »
2010-11-11 Removed all the "suggested alias" nonsense from the documentation.
420 (require '[appengine-magic.services.datastore :as ds])
421
422 (ds/defentity Author [^:key name, birthday])
423 (ds/defentity Book [^:key isbn, title, author])
7efad8a3 »
2010-10-04 Updated documentation for the new datastore support.
424
425 ;; Writes three authors to the datastore.
426 (let [will (Author. "Shakespeare, William" nil)
427 geoff (Author. "Chaucer, Geoffrey" "1343")
428 oscar (Author. "Wilde, Oscar" "1854-10-16")]
429 ;; First, just write Will, without a birthday.
36c7502b »
2010-11-11 Removed all the "suggested alias" nonsense from the documentation.
430 (ds/save! will)
7efad8a3 »
2010-10-04 Updated documentation for the new datastore support.
431 ;; Now overwrite Will with an entity containing a birthday, and also
432 ;; write the other two authors.
36c7502b »
2010-11-11 Removed all the "suggested alias" nonsense from the documentation.
433 (ds/save! [(assoc will :birthday "1564"), geoff, oscar]))
7efad8a3 »
2010-10-04 Updated documentation for the new datastore support.
434
435 ;; Retrieves two authors and writes book entites.
36c7502b »
2010-11-11 Removed all the "suggested alias" nonsense from the documentation.
436 (let [will (first (ds/query :kind Author :filter (= :name "Shakespeare, William")))
437 geoff (first (ds/query :kind Author :filter [(= :name "Chaucer, Geoffrey")
438 (= :birthday "1343")]))]
439 (ds/save! (Book. "0393925870" "The Canterbury Tales" geoff))
440 (ds/save! (Book. "143851557X" "Troilus and Criseyde" geoff))
441 (ds/save! (Book. "0393039854" "The First Folio" will)))
7efad8a3 »
2010-10-04 Updated documentation for the new datastore support.
442
443 ;; Retrieves all Chaucer books in the datastore, sorting by descending title and
444 ;; then by ISBN.
36c7502b »
2010-11-11 Removed all the "suggested alias" nonsense from the documentation.
445 (let [geoff (ds/retrieve Author "Chaucer, Geoffrey")]
446 (ds/query :kind Book
447 :filter (= :author geoff)
448 :sort [[title :dsc] :isbn]))
7efad8a3 »
2010-10-04 Updated documentation for the new datastore support.
449
450 ;; Deletes all books by Chaucer.
36c7502b »
2010-11-11 Removed all the "suggested alias" nonsense from the documentation.
451 (let [geoff (ds/retrieve Author "Chaucer, Geoffrey")]
452 (ds/delete! (ds/query :kind Book :filter (= :author geoff))))
7efad8a3 »
2010-10-04 Updated documentation for the new datastore support.
453
b20fccab »
2011-02-04 Updated the README. Credited a new contributor. Included an example f…
454 The next example (which uses Compojure) demonstrates the use of entity groups
455 and transactions.
456
457 (use '[clojure.pprint :only [pprint]]
458 'compojure.core)
459 (require '[appengine-magic.core :as ae]
460 '[appengine-magic.services.datastore :as ds])
461
462 (ds/defentity Parent [^:key name, children])
463 (ds/defentity Child [^:key name])
464
465 (defroutes entity-group-example-app-handler
466 (GET "/" [] {:headers {"Content-Type" "text/plain"} :body "started"})
467 (POST "/new/:parent-name/:child-name" [parent-name child-name]
468 (let [parent (or (ds/retrieve Parent parent-name)
469 ;; Note the use of ds/save! here. Unless an entity has
470 ;; been saved to the datastore, children cannot join
471 ;; the entity group.
472 (ds/save! (Parent. parent-name [])))
473 ;; Note the use of ds/new* here: it is required so that a :parent
474 ;; entity may be specified in the instantiation of a child entity.
475 child (ds/new* Child [child-name] :parent parent)]
476 ;; Updating the parent and the child together occurs in a transaction.
477 (ds/with-transaction
478 (ds/save! (assoc parent
479 :members (conj (:children parent) child-name)))
480 (ds/save! child))
481 {:headers {"Content-Type" "text/plain"}
482 :body "done"}))
483 (GET "/parents" []
484 (let [parents (ds/query :kind Parent)]
485 {:headers {"Content-Type" "text/plain"}
486 :body (str (with-out-str (pprint parents))
487 "\n"
488 (with-out-str (pprint (map ds/get-key-object parents))))}))
489 (GET "/children" []
490 (let [children (ds/query :kind Child)]
491 {:headers {"Content-Type" "text/plain"}
492 :body (str (with-out-str (pprint children))
493 "\n"
494 (with-out-str (pprint (map ds/get-key-object children))))}))
495 (ANY "*" [] {:status 404 :body "not found" :headers {"Content-Type" "text/plain"}}))
496
7efad8a3 »
2010-10-04 Updated documentation for the new datastore support.
497 - `defentity` (optional keyword: `:kind`): defines an entity record type
498 suitable for storing in the App Engine datastore. These entities work just
499 like Clojure records. Internally, they implement an additional protocol,
500 EntityProtocol, which provides the `save!` method. When defining an entity,
501 you may specify `^:key` metadata on any one field of the record, and the
502 datastore will use this as the primary key. Omitting the key will make the
503 datastore assign an automatic primary key to the entity. Specifying the
504 optional `:kind` keyword (a string value) causes App Engine to save the entity
505 under the given "kind" name — like a datastore table. This allows kinds to
506 remain disjoint from entity record types.
507 - `new*`: instantiates a datastore entity record. You may also use standard
508 Clojure conventions to instantiate entity records, but creating entities
509 destined for entity groups requires using `new*`. To put the new entity into a
510 group, use the `:parent` keyword with the parent entity. Instantiating an
7fd9bbcc »
2011-02-10 Documentation update.
511 entity does not automatically write it to the datastore. `new*` accepts either
512 a vector of slot values or a map of slots.
7efad8a3 »
2010-10-04 Updated documentation for the new datastore support.
513 - `get-key-object`: this returns the primary Key object of the given entity. For
514 a newly-instantiated entity lacking an explicit primary key, this method
515 returns nil. Entities properly brought under entity groups using `new*` will
516 have hierarchical keys. You should rarely need to use this explicitly.
b71e187e »
2010-12-13 Added documentation for key-str.
517 - `key-str`: this utility function returns the string representation of a Key
518 object. The Key object may be given directly, or as the encoded result of call
519 to `com.google.appengine.api.datastore.KeyFactory/keyToString`. In addition,
520 the Key may be constructed by passing `key-str` the type (or kind string) of
521 the object and its ID. This function is probably most useful for generating
522 human-readable keys for storing entities in maps or memcache.
040ddca6 »
2011-01-03 Documentation updates.
523 - `key-id`: this utility function returns the numeric identifier of the numeric
524 key of the given entity.
525 - `key-name`: this utility function returns the string identifier of the string
526 key of the given entity.
527 - `key-kind`: this utility function returns the kind, as a string, of the given
528 entity.
7efad8a3 »
2010-10-04 Updated documentation for the new datastore support.
529 - `save!`: calling this method on an entity writes it to the datastore, using
530 the primary key returned by calling `get-key-object` on the entity. May be
531 called on a sequence of entities.
532 - `delete!`: removes an entity. May be called on a sequence of entities.
533 - `retrieve <entity-record-type> <primary-key>` (optional keywords: `:parent`,
534 `:kind`): this is a low-level entity retrieval function. It returns a record
535 of the given type with the given primary key value. If the target entity
536 belongs to an entity group, specify the parent using the optional keyword. If
537 the target entity was stored with a different kind from the entity record
bf996617 »
2010-11-19 Documentation update.
538 type, specify the actual kind using the optional keyword. This function
539 returns `nil` if the given key of the given kind does not exist.
c070f521 »
2010-11-18 Documentation updates for the addition of exists? to the Datastore API.
540 - `exists? <entity-record-type> <primary-key>` (optional keywords the same as
541 for `retrieve`): used exactly like `retrieve`, but returns `true` if the given
542 entity exists and `false` otherwise.
7efad8a3 »
2010-10-04 Updated documentation for the new datastore support.
543 - `query` (optional keywords: `:kind`, `:ancestor`, `:filter`, `:sort`,
544 `:keys-only?`, `:count-only?`, `:in-transaction?`, `:limit`, `:offset`,
545 `:prefetch-size`, `:chunk-size`, `:entity-record-type`): runs a query with the
546 given parameters.
547 * `:kind`: primarily identifies the App Engine entity kind. If given as an
548 entity record type (recommended), the query returns a sequence of entity
549 records of that type. If given as a string, it then checks to see if
550 `:entity-record-type` is given, and uses that type if so; otherwise, the
551 query returns generic `EntityBase` records.
552 * `:filter`: one filter clause, or a list of clauses. Each consists of a
553 symbol specifying the filter operation, a property name, and a target
98b75dc9 »
2010-10-04 Tweaked the README file.
554 property value. See example.
7efad8a3 »
2010-10-04 Updated documentation for the new datastore support.
555 * `:sort`: one sort criterion, or a list of criteria. Each specified criterion
556 defaults to ascending sort order, but may also sort in descending order.
557 - `with-transaction <body>`: wraps the body in a transaction. Can be
558 nested. (Keep the limitations of App Engine's transaction system in mind when
559 using this.)
560 - `init-datastore-service`: not normally needed. Only use this method if you
561 want to modify the the read consistency and implicit transaction policies of
562 the datastore service.
91248c2d »
2010-11-26 Updated documentation.
563 - Type conversion functions: these help cast your data into a Java type which
564 receives special treatment from App Engine.
565 * `as-blob`: casts a byte array to `com.google.appengine.api.datastore.Blob`.
566 * `as-short-blob`: casts a byte array to
567 `com.google.appengine.api.datastore.ShortBlob`.
568 * `as-blob-key`: casts a string to
569 `com.google.appengine.api.blobstore.BlobKey`.
570 * `as-text`: casts a string to `com.google.appengine.api.datastore.Text`.
571 * `as-link`: casts a string to `com.google.appengine.api.datastore.Link`.
7efad8a3 »
2010-10-04 Updated documentation for the new datastore support.
572
c90a79d0 »
2010-12-04 Documentation updates for the datastore type storage enhancement.
573 The Clojure interface to the Datastore has an additional feature: any entity
574 field may be marked with the `^:clj` metadata tag:
575
576 (ds/defentity TestEntity [^:key test-id, ^:clj some-table])
577
578 The values of fields marked with the `^:clj` tag will go into the datastore as
579 strings produced by Clojure's `prn-str` function, and they will be retrieved as
580 Clojure objects read by `read-string`. In other words, `^:clj` fields will be
581 serialized and retrieved using Clojure's reader. This is quite helpful for
582 dealing with types which the datastore does not support: specifically maps (not
583 even `java.util.HashMap` works) and sets (not even `java.util.HashSet`
584 works). Keep in mind, however, that these fields are stored as instances of
585 `com.google.appengine.api.datastore.Text`, which the datastore does not index.
586
7efad8a3 »
2010-10-04 Updated documentation for the new datastore support.
587
c9fa431d »
2010-11-09 Added preliminary Blobstore documentation.
588 ### Blobstore
589
36c7502b »
2010-11-11 Removed all the "suggested alias" nonsense from the documentation.
590 The `appengine-magic.services.blobstore` namespace helps with the App Engine
591 Blobstore service, designed for hosting large files. Note that the production
592 App Engine only enables the Blobstore service for applications with billing
593 enabled.
c9fa431d »
2010-11-09 Added preliminary Blobstore documentation.
594
595 Using the Blobstore generally requires three components: an upload session, an
596 HTTP `multipart/form-data` file upload (usually initiated through an HTML form),
597 and an upload callback.
598
599 1. Your application must first initiate an upload session; this gives it a URL
600 to use for the corresponding HTTP POST request.
601 2. Your application must provide a proper upload form, with the `action`
602 pointing to the URL of the upload session, the `method` set to `post`, and
603 `enctype` set to `multipart/form-data`; each uploaded file must have a `name`
604 attribute.
605 3. Your application must provide an upload callback URL. App Engine will make an
606 HTTP POST request to that URL once the file upload completes. This callback's
607 request will contain information about the uploaded files. The callback
608 should save this data in some way that makes sense for the application. The
609 callback implementation must end with an invocation of the
610 `callback-complete` function. Do not attempt to return a Ring response map
611 from an upload handler.
612 4. A Ring handler which serves up a blob must end with an invocation of the
613 `serve` function. Do not attempt to return a Ring response map from a
614 blob-serving handler.
615
616 NB: In the REPL environment and in `dev_appserver.sh`, using the Blobstore
617 writes entities into the datastore: `__BlobInfo__` and
618 `__BlobUploadSession__`. This does not happen in the production environment.
619
620 - `upload-url <success-path>`: initializes an upload session and returns its
621 URL. `success-path` is the URL of the upload callback.
622 - `delete! <blob-keys>`: deletes the given blobs by their keys.
623 - `serve <ring-request-map> <blob-key>`: modifies the given Ring request map to
624 serve up the given blob.
625 - `callback-complete <ring-request-map> <destination>`: redirects the uploading
626 HTTP client to the given destination.
1a11063e »
2010-11-26 Documentation updates.
627 - `uploaded-blobs <ring-request-map>`: returns a map of form upload name fields
628 to blob keys.
c9fa431d »
2010-11-09 Added preliminary Blobstore documentation.
629
6c25ea17 »
2010-11-09 Added Blobstore example.
630 This is confusing, but a Compojure example will help.
631
632 (use 'compojure.core)
633
634 (require '[appengine-magic.core :as ae]
36c7502b »
2010-11-11 Removed all the "suggested alias" nonsense from the documentation.
635 '[appengine-magic.services.datastore :as ds]
636 '[appengine-magic.services.blobstore :as blobs])
6c25ea17 »
2010-11-09 Added Blobstore example.
637
36c7502b »
2010-11-11 Removed all the "suggested alias" nonsense from the documentation.
638 (ds/defentity UploadedFile [^:key blob-key])
6c25ea17 »
2010-11-09 Added Blobstore example.
639
640 (defroutes upload-demo-app-handler
641 ;; HTML upload form; note the upload-url call
642 (GET "/upload" _
643 {:status 200
644 :headers {"Content-Type" "text/html"}
645 :body (str "<html><body>"
646 "<form action=\""
36c7502b »
2010-11-11 Removed all the "suggested alias" nonsense from the documentation.
647 (blobs/upload-url "/done")
6c25ea17 »
2010-11-09 Added Blobstore example.
648 "\" method=\"post\" enctype=\"multipart/form-data\">"
649 "<input type=\"file\" name=\"file1\">"
650 "<input type=\"file\" name=\"file2\">"
651 "<input type=\"file\" name=\"file3\">"
652 "<input type=\"submit\" value=\"Submit\">"
653 "</form>"
654 "</body></html>")})
655 ;; success callback
656 (POST "/done" req
1a11063e »
2010-11-26 Documentation updates.
657 (let [blob-map (blobs/uploaded-blobs req)]
36c7502b »
2010-11-11 Removed all the "suggested alias" nonsense from the documentation.
658 (ds/save! [(UploadedFile. (.getKeyString (blob-map "file1")))
659 (UploadedFile. (.getKeyString (blob-map "file2")))
660 (UploadedFile. (.getKeyString (blob-map "file3")))])
661 (blobs/callback-complete req "/list")))
6c25ea17 »
2010-11-09 Added Blobstore example.
662 ;; a list of all uploaded files with links
663 (GET "/list" _
664 {:status 200
665 :headers {"Content-Type" "text/html"}
666 :body (apply str `["<html><body>"
667 ~@(map #(format " <a href=\"/serve/%s\">file</a>"
668 (:blob-key %))
36c7502b »
2010-11-11 Removed all the "suggested alias" nonsense from the documentation.
669 (ds/query :kind UploadedFile))
6c25ea17 »
2010-11-09 Added Blobstore example.
670 "</body></html>"])})
671 ;; serves the given blob by key
672 (GET "/serve/:blob-key" {{:strs [blob-key]} :params :as req}
36c7502b »
2010-11-11 Removed all the "suggested alias" nonsense from the documentation.
673 (blobs/serve req blob-key)))
6c25ea17 »
2010-11-09 Added Blobstore example.
674
675 (ae/def-appengine-app upload-demo-app #'upload-demo-app-handler)
676
fcce0b71 »
2010-12-06 Added documentation for the blobstore upload-hack.
677 Note that the Blobstore API primarily allows for browser-driven file
678 uploads. appengine-magic includes a hack which allows an application to upload a
679 blob without a browser.
680
681 - `upload-hack <contents> <success-path>`: upload contents into the
682 blobstore. When the upload completes, App Engine will make a request to the
683 `<success-path>` URL, just like in a regular blobstore upload. This callback
684 should record the blob key of the uploaded data. `<contents>` is either a
685 single map, or a vector of maps, each with the following keys:
686 * `:field`: the name of the imitation form field; used as keys in the result
687 of `uploaded-blobs`.
688 * `:filename`
689 * `:bytes`: byte array of the uploaded data.
690
c9fa431d »
2010-11-09 Added preliminary Blobstore documentation.
691
54ee717a »
2010-11-16 Added documentation for task queues.
692 ### Mail service
b79d57b7 »
2010-11-14 Updated documentation for the Mail service.
693
694 The `appengine-magic.services.mail` namespace provides helper functions for
695 sending and receiving mail in an App Engine application.
696
697 To send an mail message, construct it using `make-message` and `make-attachment`
698 functions, and send it using the `send` function.
699
700 To receive incoming mail, first read and understand the relevant section in
701 (Google's official
702 documentation)[http://code.google.com/appengine/docs/java/mail/receiving.html]. You
703 need to modify your application's `appengine-web.xml`, and you should add a
704 security constraint for `/_ah/mail/*` URLs in your `web.xml`. In your
705 application add a Ring handler for POST methods for URLs which begin with
706 `/_ah/mail`.
707
708 - `make-attachment <filename> <bytes>`: constructs an attachment object for a
709 file with the given filename and consisting of the given bytes.
710 - `make-message`: this function has many keyword parameters, and constructs a
711 message object. The parameters are self-explanatory: `:from`, `:to` (takes a
712 string or a vector), `:subject`, `:cc` (takes a string or a vector), `:bcc`
713 (takes a string or a vector), `:reply-to` (takes a string or a vector),
714 `:text-body`, `:html-body`, and `:attachments` (takes a vector).
715 - `send <msg>`: sends the given message.
1a11063e »
2010-11-26 Documentation updates.
716 - `parse-message <ring-request-map>`: returns a Clojure record of type
b79d57b7 »
2010-11-14 Updated documentation for the Mail service.
717 `appengine-magic.services.mail.MailMessage`. Call this function inside the
718 POST handler for `/_ah/mail/*`, and it will return the message sent in the
719 given HTTP request.
720
721 NB: With Compojure, the only route which seems to work in the production App
722 Engine for handling mail is `/_ah/mail/*`.
723
724 (use 'compojure.core)
725
726 (require '[appengine-magic.core :as ae]
727 '[appengine-magic.services.mail :as mail])
728
729 (defroutes mail-demo-app-handler
730 ;; sending
731 (GET "/mail" _
732 (let [att1 (mail/make-attachment "hello.txt" (.getBytes "hello world"))
733 att2 (mail/make-attachment "jk.txt" (.getBytes "just kidding"))
734 msg (mail/make-message :from "one@example.com"
735 :to "two@example.com"
736 :cc ["three@example.com" "four@example.com"]
737 :subject "Test message."
738 :text-body "Sent from appengine-magic."
739 :attachments [att1 att2])]
740 (mail/send msg)
741 {:status 200
742 :headers {"Content-Type" "text/plain"}
743 :body "sent"}))
744 ;; receiving
745 (POST "/_ah/mail/*" req
746 (let [msg (mail/parse-message req)]
747 ;; use the resulting MailMessage object
748 {:status 200})))
749
750 (ae/def-appengine-app mail-demo-app #'mail-demo-app-handler)
751
752
54ee717a »
2010-11-16 Added documentation for task queues.
753 ### Task Queues service
754
755 The `appengine-magic.services.task-queues` namespace has helper functions for
756 using task queues. As always, read [Google's documentation on task
757 queues](http://code.google.com/appengine/docs/java/taskqueue/overview.html), in
758 particular the sections on configuring `queue.xml`, and on securing task URLs in
759 `web.xml`. In addition, [the section on scheduled
760 tasks](http://code.google.com/appengine/docs/java/config/cron.html) (`cron.xml`)
761 is useful.
762
763 Use the `add!` function to add a new task to a queue, and provide a callback URL
764 which implements the actual work performed by the task. The current App Engine
765 SDK does not seem to have any API calls for removing tasks from a queue, but
766 does support this from the administration console.
767
768 - `add! :url <callback-url>` (optional keywords: `:queue`,
769 `:join-current-transaction?`, `:params`, `:headers`, `:payload`, `:method`,
770 `:countdown-ms`, `:eta-ms`, `:eta`). The `:url` keyword is required.
771 * `:queue`: name of the queue to use; if omitted, uses the system default
772 queue. If provided, the queue must be defined in `queue.xml`.
773 * `:join-current-transaction?`: defaults to false. If true, and if this occurs
774 inside a datastore transaction context, then only adds this task to the
775 queue if the transaction commits successfully.
776 * `:params`: a map of form parameter key-value pairs for the callback. Do not
777 combine with the `:payload` keyword.
778 * `:headers`: a map of extra HTTP headers sent to the callback.
779 * `:payload`: provides data for the callback. Can be a string, a vector of the
780 form `[<string> <charset>]`, or a vector of the form `[<byte-array>
781 <content-type>]`.
782 * `:method`: supports `:post`, `:delete`, `:get`, `:head`, and `:put`. Default
783 is `:post`.
784 * `:countdown-ms`, `:eta-ms`, and `:eta`: scheduling parameters. Only one of
785 these may be used at a time. `:countdown-ms` schedules a task for the given
786 number of milliseconds from the time the `add!` function ran. `:eta-ms`
787 schedules a task for the given number of milliseconds from the beginning of
788 the epoch. `:eta` schedules execution for the time given by the a
789 `java.util.Date` object.
790
791
105c989a »
2010-11-22 Added documentation for URL Fetch.
792 ### URL Fetch service
793
7538a185 »
2010-11-22 Renamed urlfetch to url-fetch, for consistency with other service names.
794 `appengine-magic.services.url-fetch` lets App Engine applications send arbitrary
105c989a »
2010-11-22 Added documentation for URL Fetch.
795 HTTP requests to external services.
796
797 - `fetch <url>` (optional keywords: `:method`, `:headers`, `:payload`,
798 `:allow-truncate`, `:follow-redirects`, `:deadline`).
799 * `:method`: `:get` (default), `:post`, `:delete`, `:head`, or `:put`.
800 * `:headers`: a map from header name (string) to value (string).
801 * `:payload`: a Java byte array.
802 * `:allow-truncate`: if true, allow App Engine to truncate a large response
803 without an error; if false, throws an exception instead.
804 * `:follow-redirects`: if true (default), follows request redirects.
805 * `:deadline`: deadline for the requst, in seconds, expressed as a double.
154792ec »
2010-11-30 Documentation updates for the changes to URL Fetch.
806 * `:async?`: if true, returns a future-like object. May block when derefed if
807 it has not yet finished loading.
105c989a »
2010-11-22 Added documentation for URL Fetch.
808
809
e202e536 »
2010-11-30 Documentation for the Images API.
810 ### Images service
811
812 With `appengine-magic.services.images`, an application can (1) apply simple
813 transformations to images, either in the blobstore or saved in byte arrays, and
814 (2) access blobstore images through a CDN, with limited resizing capability.
815
816 - `get-image <image-arg>`: if `image-arg` is a string or a blob key, returns an
817 image reference to this blob; if `image-arg` is a byte array, returns an image
818 corresponding to this byte array.
819 - `serving-url <blob-key>`: returns a URL pointing directly at a blob image in a
820 Google content delivery network.
821 * `:size`: some resized versions of the given blob are available.
822 * `:crop?`: some sizes can be cropped instead of resized.
823 - `transform <image-arg> <transforms>`: applies one or more transformations to
824 an image and returns the result as an instance of
825 `com.google.appengine.api.images.Image`. `Image/getImageData` returns an array
826 of bytes, useful as a response body. The `image-arg` argument can be an
827 instance of `Image`, or a string blob key reference, or a byte array. The
828 `transforms` argument is a vector of transformation objects, created using the
829 transformation functions below.Keyword arguments:
830 * `:async?`: if true, makes the `transform` function return a future-like
831 object.
832 * `:quality`: a value from 1 to 100.
833 * `:format`: the output format, either `:jpeg` (alternatively `:jpg`) or
834 `:png`.
835 - Transformation functions:
836 * `crop* <left-x> <top-y> <right-x> <bottom-y>`: crops an image, each argument
837 is a fractional value from 0.0 to 1.0.
838 * `im-feeling-lucky*`: tries to automatically correct color and contrast; does
839 nothing in the development environment.
840 * `resize* <width> <height>`
841 * `rotate* <degrees-clockwise>`
842 * `horizontal-flip*`
843 * `vertical-flip*`
844
845
5e928860 »
2010-11-30 Documentation updates for the Channel service.
846 ### Channel service
847
848 App Engine has an implementation of server push through its Channel service
849 (`appengine-magic.services.channel`). Using it requires a combination of
850 client-side JavaScript event callbacks, and channel management on the
851 server.
852
ed541025 »
2010-12-06 Documentation updates.
853 Conceptually, the server maintains one or more channels associated with a client
854 ID (this is a small number; it is probably safest to assume only one channel per
855 ID). The server opens a channel, which generates a channel token. This token
856 must be passed to the connecting client; the client then uses the token to
857 receive messages from the server.
858
859 - `create-channel <client-id>`: creates a new channel and returns a token;
860 JavaScript code will use this token to connect to the server.
861 - `make-message <client-id> <message-string>`: makes a message object destined
862 for all channels associated with the given client ID.
5e928860 »
2010-11-30 Documentation updates for the Channel service.
863 - `send <message-object>`: sends the given message object.
ed541025 »
2010-12-06 Documentation updates.
864 - `send <client-id> <message-string>`: sends the given string to the given
865 client.
524cf87c »
2011-06-21 Documentation updates.
866 - `parse-presence <ring-request-map>`: returns a ClientStatus record, containing
867 two fields: `:status` and `:id`. If the client just connected, the `:status`
868 is `:connected`; otherwise `:disconnected`.
5e928860 »
2010-11-30 Documentation updates for the Channel service.
869
870 NB: The current version of the Channel service does not help with channel
871 bookkeeping. It probably cleans up idle channels internally, but does not inform
872 the application of this. The application is responsible for keeping track of
873 active channels.
874
875 The client needs to load the JavaScript code at `/_ah/channel/jsapi`:
876
877 <script src="/_ah/channel/jsapi" type="text/javascript"></script>
878
879 Once this library loads, the client must initiate a request in which the server
880 can return the channel ID. Once this is done, the rest of the client API looks
881 like this:
882
883 // read this from a normal server response
ed541025 »
2010-12-06 Documentation updates.
884 var channel_token = ...;
5e928860 »
2010-11-30 Documentation updates for the Channel service.
885
886 // open a "socket" to the server
ed541025 »
2010-12-06 Documentation updates.
887 var channel = new goog.appengine.Channel(channel_token);
5e928860 »
2010-11-30 Documentation updates for the Channel service.
888 var socket = channel.open();
889
890 // implement these callbacks to take action when an event occurs
891 socket.onopen = function(evt) { var data = evt.data; ... };
892 socket.onmessage = function(evt) { var data = evt.data; ... };
893 socket.onerror = function(evt) { var data = evt.data; ... };
894 socket.onclose = function(evt) { var data = evt.data; ... };
895
896 NB: The development implementations of the Channel service just poll the server
897 for updates, and merely emulate server push. If you watch a browser request
898 console, you'll see the polling requests.
899
900
506555c0 »
2010-11-01 Documented resource duplication in jar files.
901
81d01b7d »
2010-09-22 Updated the README file.
902 ## Limitations
b10fbeab »
2010-09-19 Working on README file.
903
506555c0 »
2010-11-01 Documented resource duplication in jar files.
904
905 ### Incomplete features
906
f3bdc91b »
2010-09-23 Documentation updates.
907 The following Google services are not yet tested in the REPL environment:
81d01b7d »
2010-09-22 Updated the README file.
908
dba7487e »
2011-06-01 Small documentation updates.
909 - Pull queues (from App Engine SDK 1.5.0)
36329366 »
2011-04-03 Version 0.4.1, with support for App Engine 1.4.3.
910 - Deferred API (from App Engine SDK 1.4.3)
911 - Remote API (from App Engine SDK 1.4.3)
912 - Files API (from App Engine SDK 1.4.3)
931e199b »
2010-12-14 Updated documentation for the breaking change.
913 - Datastore async queries
914 - Datastore cursors
e202e536 »
2010-11-30 Documentation for the Images API.
915 - Compositing in the Images API
c0952301 »
2010-12-05 Documentation updates; clarified missing features.
916 - Multitenancy (namespaces)
917 - Metadata queries (in the datastore API)
0ad3237c »
2010-11-28 Documentation updates.
918 - Capabilities
81d01b7d »
2010-09-22 Updated the README file.
919 - OAuth
920 - XMPP
b10fbeab »
2010-09-19 Working on README file.
921
f3bdc91b »
2010-09-23 Documentation updates.
922 They may still work, but appengine-magic does not provide convenient Clojure
923 interfaces for them, and may lack mappings for any necessary supporting URLs.
924
b10fbeab »
2010-09-19 Working on README file.
925
926
fc15ca65 »
2010-09-20 Updated README file.
927 ## Warning
928
f3bdc91b »
2010-09-23 Documentation updates.
929 Google App Engine maintains a whitelist of permitted classes in Java's standard
930 library. Other classes will cause your application to fail to deploy. Examples
931 include threads and sockets. If you use those in your application, it will not
932 work. This means that you cannot use Clojure's agents or futures. In addition,
933 if one of your dependencies uses those, your application will also not work. For
934 example, `clojure.java.io` (and its fore-runner, duck-streams from
935 `clojure-contrib`), uses `java.net.Socket`, a forbidden class.
936
937 Whenever you add a new dependency, no matter how innocuous, you should make sure
938 your app still works. `dev_appserver.sh` is a good place to start, but you must
939 also test in the main App Engine. The two do not always load classes the same
940 way.
fc15ca65 »
2010-09-20 Updated README file.
941
942
943
1a11063e »
2010-11-26 Documentation updates.
944 ## Contributors
945
946 Many thanks to:
947
948 * Brian Gruber
949 * Marko Kocić
950 * Conrad Barski
0ad3237c »
2010-11-28 Documentation updates.
951 * Yuri Niyazov
2f799fe6 »
2010-12-29 Documentation updates for version 0.3.2.
952 * Alex Bolodurin
b20fccab »
2011-02-04 Updated the README. Credited a new contributor. Included an example f…
953 * Stefan Kamphausen
437e6f74 »
2011-02-10 Version 0.3.3.
954 * Masashi Iizuka
98924a07 »
2011-06-21 Updated contributor list.
955 * Dave Lambert
956 * Brian Rowe
957 * Tobias Raeder
1a11063e »
2010-11-26 Documentation updates.
958
959
960
b10fbeab »
2010-09-19 Working on README file.
961 ## License
962
963 appengine-magic is distributed under the MIT license.
Something went wrong with that request. Please try again.