> I already succeeded in tweaking the simpleappcachelinker class a
> little bit to create a first simple Offline application.
>
> Now I have two question concerning this topic:
>
> 1.) Are there methods inside GWT to read HTML5 Offline Web Appliation
> events? (Online? Offline? Finished Caching? Caching not
> necessary? ...)
I found the Devloper Tools in Chrome to be extremely useful here. It
shows the events and what is in the cache.
this was useful to validate the manifest http://manifest-validator.com/
> 2.) appcache.nocache.manifest contains all HTML files generated by
> GWT. The effect is that a user calling the web app from FireFox will
> cache all HTML files inside this browser. Altough he/she would only
> need the FireFox HTML file. Difficult topic because creating the
> manifest happens during linking, browser detection during runtime.
It's really easier than you think.
The link method gets called for each permutation, so just set it up
make a different manifest for each permutation.
Then on your server when your application.manifest is requested,
detect the useragent and version and serve back the appropriate file
for that permutation. With serlvet filters this is pretty easy.
There is also a linker on the web someplace, can't find the link just
now, that makes a property file with the userAgent and hash (directory
containing that permutation). I don't really use it but just glanced
at it to veryfy things were right. The only trick was from within
each link call figuring out what permutation is being created.
I have this;
Properties props = new Properties();
for (CompilationResult result : artifacts.find(CompilationResult.class)) {
permutation = result.getStrongName();
SortedSet<SortedMap<SelectionProperty, String>> propertiesMap =
result.getPropertyMap();
for (SortedMap<SelectionProperty, String> sm : propertiesMap) {
for (Map.Entry<SelectionProperty, String> e : sm.entrySet()) {
selectionProperty = e.getKey();
if ("locale".equals(selectionProperty.getName())) {
locale = e.getValue();
}
if ("user.agent".equals(selectionProperty.getName())) {
userAgent = e.getValue();
}
}
}
props.setProperty(userAgent + "." + locale, permutation);
}
Shawn
Oh, that I do not know.
> @2: Great idea. Servlet filter, intercepting requests to
> appcache.nocache.manifest and returning the correct one (depending on
> browser version) seems pretty clear to me.
>
> But I am really stuck on how to write the linker class that generates
> different appcache.nocache.manifest files for every permutation.
oops sorry forgot to say use @Shardable
@Shardable @LinkerOrder(LinkerOrder.Order.POST) public class
SimpleAppCacheLinker extends AbstractLinker {
be sure to read the api for the method you'll use to understand the
behavior - it calls link for every permutation and then one final time
(with onePermutation set as true )
> Sorry for getting confused with the API. I did not want to sound
> "rude"...
Yeah no problem at all...
> The only solution I have come up so far is this:
> (It generates an offline manifest for every user.agent. So after
> compiling I have six different manifests in the WAR directory.
Which is what you want right?
> @Override
> public ArtifactSet link(TreeLogger logger, LinkerContext context,
> ArtifactSet artifacts,
> boolean onePermutation)
> throws UnableToCompleteException {
>
> ArtifactSet toReturn = new ArtifactSet(artifacts);
>
> for (CompilationResult compilationResult :
> artifacts.find(CompilationResult.class)) {
> for (SelectionProperty property :
> compilationResult.getPropertyMap().first().keySet()) {
> if (property.getName().equals("user.agent")) {
> String userAgent = compilationResult.getPropertyMap()
> .first().get(property);
> System.out.println(userAgent);
> // Create the general cache-manifest resource for the
> // landing page:
> toReturn.add(emitLandingPageCacheManifest(context, logger,
> artifacts, userAgent));
> }
> }
> }
>
> This calls a method for generating a offline manifest for every
> user.agent. But there gotta be an easier way than to navigate over the
> compilation result?
I don't know one. I need to add in some .css and .gif files, as well
as get the rpc stuff under network so I do the following: (well I
took some other stuff out ... but this is mostly it )
. Your code is probably better than mine.
@Override public ArtifactSet link(TreeLogger logger, LinkerContext
context, ArtifactSet artifacts, boolean onePermutation)
throws UnableToCompleteException {
String permutation = null;
String locale = "";
String userAgent = "";
SelectionProperty selectionProperty = null;
Properties props = new Properties();
for (CompilationResult result : artifacts.find(CompilationResult.class)) {
permutation = result.getStrongName();
SortedSet<SortedMap<SelectionProperty, String>> propertiesMap =
result.getPropertyMap();
for (SortedMap<SelectionProperty, String> sm : propertiesMap) {
for (Map.Entry<SelectionProperty, String> e : sm.entrySet()) {
selectionProperty = e.getKey();
if ("locale".equals(selectionProperty.getName())) {
locale = e.getValue();
}
if ("user.agent".equals(selectionProperty.getName())) {
userAgent = e.getValue();
}
}
}
props.setProperty(userAgent + "." + locale, permutation);
}
StringBuilder buf = new StringBuilder();
Log.info("is Shardable: " + isShardable());
StringBuilder network_buf = new StringBuilder();
network_buf.append("NETWORK:").append(SEP);
buf.append("CACHE MANIFEST" + SEP + WELCOME + FAVICON + S_STYLE +
T_STYLE + WELCOME_STYLE + NO_CACHE_JS + EDIT_STUFF + IMAGES);
buf.append("#").append(System.currentTimeMillis()).append(SEP);
ArtifactSet artifactSet = new ArtifactSet(artifacts);
for (EmittedArtifact artifact : artifacts.find(EmittedArtifact.class)) {
if (!artifact.isPrivate()) {
if (!isRPRCall(artifact)) {
if (artifact.getPartialPath().contains("rpcPolicyManifest") ||
artifact.getPartialPath().contains("symbolMap")) {
Log.info("NOT ADDING :" + (artifact.getPartialPath() + "to cache"));
} else {
addToCache(buf, artifact);
}
} else {
network_buf.append(HOME + artifact.getPartialPath()).append(SEP);
}
}
}
String lang = (locale.equals("default") ? "" : "." + locale);
if (!onePermutation) {
EmittedArtifact artifact = emitString(logger, buf.toString() +
network_buf.toString() + FALLBACK, "/../" + userAgent + lang
+ ".fluency-up.manifest");
artifactSet.add(artifact);
Log.info("return artifactSet " + artifact.toString());
}
return artifactSet;
}
One more thing:
private void addToCache(StringBuilder buf, EmittedArtifact artifact) {
buf.append("/myApp/" + artifact.getPartialPath()).append("\n");
}
No,
1) get the servlet context at init time
private ServletContext context;
@Override public void init(FilterConfig arg0) throws ServletException {
context = arg0.getServletContext();
}
2) use that to forward the file
response.setContentType("text/cache-manifest");
context.getRequestDispatcher("/" +
"appcache.nocache.manifest.gecko1_8").include(request, response);
> Because now I return appcache.nocache.manifest.gecko1_8 for example...
If the request asks for appcache.nocach.manifest, then that is what
will be returned - athough the contexts will be
appcache.nocache.manifest.gecko1_8
Is appcache.nocache.manifest.gecko1_8 OK? It doens't have to end with
.manifest ??? I don't know that.
> Followed this thread, and also stumbled the same problem.
> anyone solved this?
>> DevMode warning: Clobbering appcache.nocache.manifest to allow debugging.
>> Recompile before deploying your app!
>>
>> And the appcache.nocache.manifest_user-agent doesn't contain the js file
>> name!
>>
>> Any idea what is causing this???
Sorry no idea at all. Have never seen it. I don't understand the problem.
If you need to know which js file goes into which user-agent file, you
can figure that out from this:
[actually this makes a separate permutation.properties file. I have
the same login in my linker class to make sure the pro]
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Map;
import java.util.Properties;
import java.util.SortedMap;
import java.util.SortedSet;
import com.google.gwt.core.ext.LinkerContext;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.linker.AbstractLinker;
import com.google.gwt.core.ext.linker.ArtifactSet;
import com.google.gwt.core.ext.linker.CompilationResult;
import com.google.gwt.core.ext.linker.LinkerOrder;
import com.google.gwt.core.ext.linker.SelectionProperty;
import com.google.gwt.core.ext.linker.SyntheticArtifact;
/**
* This GWT linker creates a properties file which can be used to
resolve Permutation Strong name given UserAgent and
* locale.
*
* @author Etienne Lacazedieu
*/
@LinkerOrder(LinkerOrder.Order.PRE) public class
StrongNameOracleLinker extends AbstractLinker {
public static final String STRONGNAME_FILE = "permutation.properties";
@Override public String getDescription() {
return "PermutationStrongName Oracle linker";
}
@Override public ArtifactSet link(TreeLogger logger, LinkerContext
context, ArtifactSet artifacts) throws UnableToCompleteException {
artifacts = new ArtifactSet(artifacts);
ByteArrayOutputStream out = new ByteArrayOutputStream();
String permutation = null;
String locale = null;
String userAgent = null;
SelectionProperty selectionProperty = null;
Properties props = new Properties();
for (CompilationResult result : artifacts.find(CompilationResult.class)) {
permutation = result.getStrongName();
SortedSet<SortedMap<SelectionProperty, String>> propertiesMap =
result.getPropertyMap();
for (SortedMap<SelectionProperty, String> sm : propertiesMap) {
for (Map.Entry<SelectionProperty, String> e : sm.entrySet()) {
selectionProperty = e.getKey();
if ("locale".equals(selectionProperty.getName())) {
locale = e.getValue();
}
if ("user.agent".equals(selectionProperty.getName())) {
userAgent = e.getValue();
}
}
}
props.setProperty(userAgent + "." + locale, permutation);
}
try {
props.store(out, "StrongNameOracle properties file");
} catch (IOException e) { // Should generally not happen
logger.log(TreeLogger.ERROR, "Unable to store deRPC data", e);
throw new UnableToCompleteException();
}
SyntheticArtifact a = emitBytes(logger, out.toByteArray(), STRONGNAME_FILE);
artifacts.add(a);
return artifacts;
}
}
-------------------------------
so in the actual linker class I have I use
...
Properties props = new Properties();
for (CompilationResult result : artifacts.find(CompilationResult.class)) {
permutation = result.getStrongName();
SortedSet<SortedMap<SelectionProperty, String>> propertiesMap =
result.getPropertyMap();
for (SortedMap<SelectionProperty, String> sm : propertiesMap) {
for (Map.Entry<SelectionProperty, String> e : sm.entrySet()) {
selectionProperty = e.getKey();
if ("locale".equals(selectionProperty.getName())) {
locale = e.getValue();
}
if ("user.agent".equals(selectionProperty.getName())) {
userAgent = e.getValue();
}
}
}
props.setProperty(userAgent + "." + locale, permutation);
}
...
that gets the right js file into the right permutation manifest.
Is that what the question is?
shawn