001package votorola.s.gwt.scene.diff;// Copyright 2010-2011. Michael Allan. Christian Weilbach. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Votorola Software"), to deal in the Votorola Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicence, and/or sell copies of the Votorola Software, and to permit persons to whom the Votorola Software is furnished to do so, subject to the following conditions: The preceding copyright notice and this permission notice shall be included in all copies or substantial portions of the Votorola Software. THE VOTOROLA SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE VOTOROLA SOFTWARE OR THE USE OR OTHER DEALINGS IN THE VOTOROLA SOFTWARE. 002 003import com.google.gwt.core.client.*; 004import com.google.gwt.user.cellview.client.*; 005import com.google.gwt.user.client.*; 006import com.google.gwt.json.client.*; 007import com.google.gwt.user.client.rpc.AsyncCallback; 008 009import java.util.ArrayList; 010import java.util.List; 011import java.util.Date; 012 013import votorola.a.web.gwt.*; 014import votorola.s.gwt.stage.Stage; 015import votorola.s.gwt.scene.*; 016import votorola.s.gwt.scene.feed.*; 017import votorola.g.hold.*; 018 019/** 020 * A feed for diff bites. 021 * 022 * @see <a href='http://reluk.ca/y/vw/xf/#c=DG'>Live example of a DiffFeed 023 * (left)</a> 024 */ 025public final class DiffFeed extends Feed implements Hold { 026 027 /** 028 * Constructs a DiffFeed. Call {@linkplain #release release}() when done 029 * with it. 030 */ 031 public DiffFeed() { 032 super(new ArrayList<BiteJS>(/* initial capacity */LIST_MAX_SIZE)); 033 034 new Ticker(); 035 } 036 037 // ```````````````````````````````````````````````````````````````````````````````````` 038 // init for early use 039 040 private final Spool spool = new Spool1(); 041 042 // - H o l d 043 // -------------------------------------------------------------------------- 044 045 public void release() { 046 spool.unwind(); 047 } 048 049 // // P r i v a t e 050 // /////////////////////////////////////////////////////////////////////// 051 052 private static void addTrim(final List<BiteJS> list, final BiteJS bite, 053 final int maxSize) { 054 while (list.size() >= maxSize) 055 list.remove(maxSize - 1); 056 // newest last 057 list.add(bite); 058 } 059 060 private static final int LIST_MAX_SIZE = 200; 061 062 private static final int LISTU_MAX_SIZE = LIST_MAX_SIZE * 100; 063 064 // unlike prefix for this legacy code, change also stripPrefix() and 065 // parameters 066 private static final String REQUEST_URL = App.getServletContextLocation() 067 + "/wap?wCall=dfyDiffFeed"; 068 // callback parameter added automatically by JsonpRequestBuilder 069 070 private final UnScoper scoper = new UnScoper(LIST_MAX_SIZE, 071 new ArrayList<BiteJS>(), spool); 072 073 // ==================================================================================== 074 075 private final class Ticker extends Timer implements 076 AsyncCallback<JavaScriptObject> { 077 078 private Date lastAccess = new Date(0); 079 private int INTERVAL = 600000; // ten mins 080 081 Ticker() { 082 spool.add(new Hold() { 083 public void release() { 084 cancel(); 085 } 086 }); 087 run(); // tick eagerly 088 } 089 090 JsArray<BiteJS> biteBuffer // received from server, not yet displayed in 091 // feed list 092 = JavaScriptObject.createArray().cast(); 093 094 boolean isResponsePending; 095 096 boolean isScrapingBottom; 097 098 public void onFailure(final Throwable throwable) { 099 if (spool.isUnwinding()) 100 return; 101 102 final boolean toRetry = Window.confirm("Feed request failed: " 103 + throwable + ". Ready to retry."); 104 if (toRetry) 105 isResponsePending = false; 106 else 107 cancel(); 108 } 109 110 private final native JsArray<BiteJS> stripPrefix( 111 JavaScriptObject prefixJs) 112 /*-{ 113 return prefixJs["dfy"]; 114 }-*/; 115 116 public void onSuccess(final JavaScriptObject prefixJs) { 117 if (spool.isUnwinding()) 118 return; 119 120 JsArray<BiteJS> moreBites = null; 121 moreBites = stripPrefix(prefixJs); 122 123 if (moreBites == null) { 124 return; 125 } 126 127 if (biteBuffer.length() > 0) { 128 assert false; 129 return; 130 } 131 132 biteBuffer = moreBites; 133 isResponsePending = false; 134 135 run(); 136 } 137 138 private BiteJS pendingBite = null; 139 140 public boolean triggerBite(final BiteJS bite) { 141 final Date now = new Date(); 142 143 final long targetTime = bite.sentDate().getTime() + INTERVAL 144 - now.getTime(); 145 if (targetTime > 0) { // show in INTERVAL delay on all clients 146 pendingBite = bite; 147 schedule((int) targetTime); 148 return false; 149 } else { 150 addBite(bite); 151 return true; 152 } 153 } 154 155 public void addBite(final BiteJS bite) { 156 if (bite == null) 157 return; 158 159 // System.out.println( "Added bite " + bite.message().title() ); 160 addTrim(scoper.unList(), bite, LISTU_MAX_SIZE); 161 if (Scenes.i().scene().inScope(bite)) { 162 addTrim(getList(), bite, LIST_MAX_SIZE); 163 } 164 pendingBite = null; 165 } 166 167 public @Override 168 void run() { 169 if (spool.isUnwinding()) 170 return; // though probably impossible, as ticker would be 171 // cancelled 172 173 final Date now = new Date(); 174 addBite(pendingBite); 175 176 // empty the buffer 177 while (biteBuffer.length() > 0) { 178 if (!triggerBite(biteBuffer.shift())) 179 return; 180 } 181 182 if (!isResponsePending) { 183 if ((now.getTime() - lastAccess.getTime()) < INTERVAL) { 184 final long targetTime = lastAccess.getTime() + INTERVAL 185 - now.getTime(); 186 schedule((int) targetTime); 187 return; 188 } 189 190 final StringBuilder ub = new StringBuilder(); 191 ub.append(REQUEST_URL); 192 if (Stage.i().getActorName() != null) { 193 ub.append("&hUsers=").append(Stage.i().getActorName()); 194 } 195 if (Stage.i().getPollName() != null) { 196 ub.append("&hPoll=").append(Stage.i().getPollName()); 197 } 198 App.i().jsonpWAP().requestObject(ub.toString(), Ticker.this); 199 200 lastAccess = now; 201 isResponsePending = true; 202 } 203 } 204 } 205 206}