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}