1d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck/* 2d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck * Copyright (C) 2011 The Android Open Source Project 3d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck * 4d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck * Licensed under the Apache License, Version 2.0 (the "License"); 5d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck * you may not use this file except in compliance with the License. 6d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck * You may obtain a copy of the License at 7d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck * 8d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck * http://www.apache.org/licenses/LICENSE-2.0 9d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck * 10d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck * Unless required by applicable law or agreed to in writing, software 11d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck * distributed under the License is distributed on an "AS IS" BASIS, 12d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck * See the License for the specific language governing permissions and 14d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck * limitations under the License. 15d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck */ 16d8c7452a7d8975a2d60414c5a33842b4a743e631John Reckpackage com.android.browser; 17d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck 18d8c7452a7d8975a2d60414c5a33842b4a743e631John Reckimport android.content.ContentResolver; 19d8c7452a7d8975a2d60414c5a33842b4a743e631John Reckimport android.content.ContentUris; 20d8c7452a7d8975a2d60414c5a33842b4a743e631John Reckimport android.content.ContentValues; 212b71d6dad1cbdc84da3eed140429a102971a1106John Reckimport android.content.Context; 22d8c7452a7d8975a2d60414c5a33842b4a743e631John Reckimport android.database.Cursor; 23d8c7452a7d8975a2d60414c5a33842b4a743e631John Reckimport android.graphics.BitmapFactory; 24d8c7452a7d8975a2d60414c5a33842b4a743e631John Reckimport android.net.Uri; 25d8c7452a7d8975a2d60414c5a33842b4a743e631John Reckimport android.os.AsyncTask; 261cf4b79a0020bc18c83ca8bde0e318ecd5252bc2John Reckimport android.os.Bundle; 272b71d6dad1cbdc84da3eed140429a102971a1106John Reckimport android.text.TextUtils; 288cc9235816ac9b3f1b3551d6234684f0455746dcJohn Reckimport android.util.Log; 29d8c7452a7d8975a2d60414c5a33842b4a743e631John Reckimport android.webkit.WebView; 304d2fcaba7fb8eb1723943ac9a10e76d509330bd1Jonathan Dixonimport android.webkit.WebViewClassic; 31d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck 328cc9235816ac9b3f1b3551d6234684f0455746dcJohn Reckimport com.android.browser.provider.SnapshotProvider.Snapshots; 33d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck 34d8c7452a7d8975a2d60414c5a33842b4a743e631John Reckimport java.io.ByteArrayInputStream; 352b71d6dad1cbdc84da3eed140429a102971a1106John Reckimport java.io.FileNotFoundException; 362b71d6dad1cbdc84da3eed140429a102971a1106John Reckimport java.io.InputStream; 37ef654f1dd80f55609060005342fc4f2cde62990cJohn Reckimport java.util.Map; 388cc9235816ac9b3f1b3551d6234684f0455746dcJohn Reckimport java.util.zip.GZIPInputStream; 39d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck 40d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck 41d8c7452a7d8975a2d60414c5a33842b4a743e631John Reckpublic class SnapshotTab extends Tab { 42d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck 438cc9235816ac9b3f1b3551d6234684f0455746dcJohn Reck private static final String LOGTAG = "SnapshotTab"; 448cc9235816ac9b3f1b3551d6234684f0455746dcJohn Reck 45d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck private long mSnapshotId; 46d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck private LoadData mLoadTask; 47d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck private WebViewFactory mWebViewFactory; 48d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck private int mBackgroundColor; 49ef654f1dd80f55609060005342fc4f2cde62990cJohn Reck private long mDateCreated; 50ef654f1dd80f55609060005342fc4f2cde62990cJohn Reck private boolean mIsLive; 51d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck 52d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck public SnapshotTab(WebViewController wvcontroller, long snapshotId) { 531cf4b79a0020bc18c83ca8bde0e318ecd5252bc2John Reck super(wvcontroller, null, null); 54d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck mSnapshotId = snapshotId; 55d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck mWebViewFactory = mWebViewController.getWebViewFactory(); 56ef654f1dd80f55609060005342fc4f2cde62990cJohn Reck WebView web = mWebViewFactory.createWebView(false); 57ef654f1dd80f55609060005342fc4f2cde62990cJohn Reck setWebView(web); 58d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck loadData(); 59d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck } 60d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck 61d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck @Override 62d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck void putInForeground() { 63d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck if (getWebView() == null) { 64d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck WebView web = mWebViewFactory.createWebView(false); 65d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck if (mBackgroundColor != 0) { 66d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck web.setBackgroundColor(mBackgroundColor); 67d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck } 68d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck setWebView(web); 69d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck loadData(); 70d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck } 71d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck super.putInForeground(); 72d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck } 73d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck 74d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck @Override 75d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck void putInBackground() { 76d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck if (getWebView() == null) return; 77d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck super.putInBackground(); 78d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck } 79d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck 80d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck void loadData() { 81d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck if (mLoadTask == null) { 822b71d6dad1cbdc84da3eed140429a102971a1106John Reck mLoadTask = new LoadData(this, mContext); 83d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck mLoadTask.execute(); 84d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck } 85d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck } 86d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck 87d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck @Override 88d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck void addChildTab(Tab child) { 89e91c74859a4e5b319f52da4f61b0f5e6f60d9bd6John Reck if (mIsLive) { 90e91c74859a4e5b319f52da4f61b0f5e6f60d9bd6John Reck super.addChildTab(child); 91e91c74859a4e5b319f52da4f61b0f5e6f60d9bd6John Reck } else { 92e91c74859a4e5b319f52da4f61b0f5e6f60d9bd6John Reck throw new IllegalStateException("Snapshot tabs cannot have child tabs!"); 93e91c74859a4e5b319f52da4f61b0f5e6f60d9bd6John Reck } 94d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck } 95d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck 96d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck @Override 97d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck public boolean isSnapshot() { 98ef654f1dd80f55609060005342fc4f2cde62990cJohn Reck return !mIsLive; 99d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck } 100d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck 101d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck public long getSnapshotId() { 102d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck return mSnapshotId; 103d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck } 104d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck 105d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck @Override 106d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck public ContentValues createSnapshotValues() { 107e91c74859a4e5b319f52da4f61b0f5e6f60d9bd6John Reck if (mIsLive) { 108e91c74859a4e5b319f52da4f61b0f5e6f60d9bd6John Reck return super.createSnapshotValues(); 109e91c74859a4e5b319f52da4f61b0f5e6f60d9bd6John Reck } 110d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck return null; 111d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck } 112d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck 113d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck @Override 11480a5fbb2965a859e0ccc814c02b30434645044e7John Reck public Bundle saveState() { 115e91c74859a4e5b319f52da4f61b0f5e6f60d9bd6John Reck if (mIsLive) { 116e91c74859a4e5b319f52da4f61b0f5e6f60d9bd6John Reck return super.saveState(); 117e91c74859a4e5b319f52da4f61b0f5e6f60d9bd6John Reck } 1181cf4b79a0020bc18c83ca8bde0e318ecd5252bc2John Reck return null; 119d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck } 120d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck 121ef654f1dd80f55609060005342fc4f2cde62990cJohn Reck public long getDateCreated() { 122ef654f1dd80f55609060005342fc4f2cde62990cJohn Reck return mDateCreated; 123ef654f1dd80f55609060005342fc4f2cde62990cJohn Reck } 124ef654f1dd80f55609060005342fc4f2cde62990cJohn Reck 125ef654f1dd80f55609060005342fc4f2cde62990cJohn Reck @Override 126ef654f1dd80f55609060005342fc4f2cde62990cJohn Reck public void loadUrl(String url, Map<String, String> headers) { 127ef654f1dd80f55609060005342fc4f2cde62990cJohn Reck if (!mIsLive) { 128ef654f1dd80f55609060005342fc4f2cde62990cJohn Reck mIsLive = true; 1294d2fcaba7fb8eb1723943ac9a10e76d509330bd1Jonathan Dixon getWebViewClassic().clearViewState(); 130ef654f1dd80f55609060005342fc4f2cde62990cJohn Reck } 131ef654f1dd80f55609060005342fc4f2cde62990cJohn Reck super.loadUrl(url, headers); 132ef654f1dd80f55609060005342fc4f2cde62990cJohn Reck } 133ef654f1dd80f55609060005342fc4f2cde62990cJohn Reck 134ef654f1dd80f55609060005342fc4f2cde62990cJohn Reck @Override 135ef654f1dd80f55609060005342fc4f2cde62990cJohn Reck public boolean canGoBack() { 136ef654f1dd80f55609060005342fc4f2cde62990cJohn Reck return super.canGoBack() || mIsLive; 137ef654f1dd80f55609060005342fc4f2cde62990cJohn Reck } 138ef654f1dd80f55609060005342fc4f2cde62990cJohn Reck 139ef654f1dd80f55609060005342fc4f2cde62990cJohn Reck @Override 140ef654f1dd80f55609060005342fc4f2cde62990cJohn Reck public boolean canGoForward() { 141ef654f1dd80f55609060005342fc4f2cde62990cJohn Reck return mIsLive && super.canGoForward(); 142ef654f1dd80f55609060005342fc4f2cde62990cJohn Reck } 143ef654f1dd80f55609060005342fc4f2cde62990cJohn Reck 144ef654f1dd80f55609060005342fc4f2cde62990cJohn Reck @Override 145ef654f1dd80f55609060005342fc4f2cde62990cJohn Reck public void goBack() { 146ef654f1dd80f55609060005342fc4f2cde62990cJohn Reck if (super.canGoBack()) { 147ef654f1dd80f55609060005342fc4f2cde62990cJohn Reck super.goBack(); 148ef654f1dd80f55609060005342fc4f2cde62990cJohn Reck } else { 149ef654f1dd80f55609060005342fc4f2cde62990cJohn Reck mIsLive = false; 150ef654f1dd80f55609060005342fc4f2cde62990cJohn Reck getWebView().stopLoading(); 151ef654f1dd80f55609060005342fc4f2cde62990cJohn Reck loadData(); 152ef654f1dd80f55609060005342fc4f2cde62990cJohn Reck } 153ef654f1dd80f55609060005342fc4f2cde62990cJohn Reck } 154ef654f1dd80f55609060005342fc4f2cde62990cJohn Reck 155d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck static class LoadData extends AsyncTask<Void, Void, Cursor> { 156d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck 157d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck static final String[] PROJECTION = new String[] { 158d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck Snapshots._ID, // 0 1592b71d6dad1cbdc84da3eed140429a102971a1106John Reck Snapshots.URL, // 1 1602b71d6dad1cbdc84da3eed140429a102971a1106John Reck Snapshots.TITLE, // 2 161d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck Snapshots.FAVICON, // 3 162d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck Snapshots.VIEWSTATE, // 4 163d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck Snapshots.BACKGROUND, // 5 164ef654f1dd80f55609060005342fc4f2cde62990cJohn Reck Snapshots.DATE_CREATED, // 6 1652b71d6dad1cbdc84da3eed140429a102971a1106John Reck Snapshots.VIEWSTATE_PATH, // 7 166d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck }; 1672b71d6dad1cbdc84da3eed140429a102971a1106John Reck static final int SNAPSHOT_ID = 0; 1682b71d6dad1cbdc84da3eed140429a102971a1106John Reck static final int SNAPSHOT_URL = 1; 1692b71d6dad1cbdc84da3eed140429a102971a1106John Reck static final int SNAPSHOT_TITLE = 2; 1702b71d6dad1cbdc84da3eed140429a102971a1106John Reck static final int SNAPSHOT_FAVICON = 3; 1712b71d6dad1cbdc84da3eed140429a102971a1106John Reck static final int SNAPSHOT_VIEWSTATE = 4; 1722b71d6dad1cbdc84da3eed140429a102971a1106John Reck static final int SNAPSHOT_BACKGROUND = 5; 1732b71d6dad1cbdc84da3eed140429a102971a1106John Reck static final int SNAPSHOT_DATE_CREATED = 6; 1742b71d6dad1cbdc84da3eed140429a102971a1106John Reck static final int SNAPSHOT_VIEWSTATE_PATH = 7; 175d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck 176d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck private SnapshotTab mTab; 177d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck private ContentResolver mContentResolver; 1782b71d6dad1cbdc84da3eed140429a102971a1106John Reck private Context mContext; 179d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck 1802b71d6dad1cbdc84da3eed140429a102971a1106John Reck public LoadData(SnapshotTab t, Context context) { 181d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck mTab = t; 1822b71d6dad1cbdc84da3eed140429a102971a1106John Reck mContentResolver = context.getContentResolver(); 1832b71d6dad1cbdc84da3eed140429a102971a1106John Reck mContext = context; 184d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck } 185d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck 186d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck @Override 187d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck protected Cursor doInBackground(Void... params) { 188d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck long id = mTab.mSnapshotId; 189d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck Uri uri = ContentUris.withAppendedId(Snapshots.CONTENT_URI, id); 190d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck return mContentResolver.query(uri, PROJECTION, null, null, null); 191d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck } 192d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck 1932b71d6dad1cbdc84da3eed140429a102971a1106John Reck private InputStream getInputStream(Cursor c) throws FileNotFoundException { 1942b71d6dad1cbdc84da3eed140429a102971a1106John Reck String path = c.getString(SNAPSHOT_VIEWSTATE_PATH); 1952b71d6dad1cbdc84da3eed140429a102971a1106John Reck if (!TextUtils.isEmpty(path)) { 1962b71d6dad1cbdc84da3eed140429a102971a1106John Reck return mContext.openFileInput(path); 1972b71d6dad1cbdc84da3eed140429a102971a1106John Reck } 1982b71d6dad1cbdc84da3eed140429a102971a1106John Reck byte[] data = c.getBlob(SNAPSHOT_VIEWSTATE); 1992b71d6dad1cbdc84da3eed140429a102971a1106John Reck ByteArrayInputStream bis = new ByteArrayInputStream(data); 2002b71d6dad1cbdc84da3eed140429a102971a1106John Reck return bis; 2012b71d6dad1cbdc84da3eed140429a102971a1106John Reck } 2022b71d6dad1cbdc84da3eed140429a102971a1106John Reck 203d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck @Override 204d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck protected void onPostExecute(Cursor result) { 205d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck try { 206d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck if (result.moveToFirst()) { 2072b71d6dad1cbdc84da3eed140429a102971a1106John Reck mTab.mCurrentState.mTitle = result.getString(SNAPSHOT_TITLE); 2082b71d6dad1cbdc84da3eed140429a102971a1106John Reck mTab.mCurrentState.mUrl = result.getString(SNAPSHOT_URL); 2092b71d6dad1cbdc84da3eed140429a102971a1106John Reck byte[] favicon = result.getBlob(SNAPSHOT_FAVICON); 210d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck if (favicon != null) { 211d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck mTab.mCurrentState.mFavicon = BitmapFactory 212d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck .decodeByteArray(favicon, 0, favicon.length); 213d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck } 2144d2fcaba7fb8eb1723943ac9a10e76d509330bd1Jonathan Dixon WebViewClassic web = mTab.getWebViewClassic(); 215d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck if (web != null) { 2162b71d6dad1cbdc84da3eed140429a102971a1106John Reck InputStream ins = getInputStream(result); 2172b71d6dad1cbdc84da3eed140429a102971a1106John Reck GZIPInputStream stream = new GZIPInputStream(ins); 218282637771aaffd93f29fd72bf5cd49dd82d24d0dJohn Reck web.loadViewState(stream); 219d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck } 2202b71d6dad1cbdc84da3eed140429a102971a1106John Reck mTab.mBackgroundColor = result.getInt(SNAPSHOT_BACKGROUND); 2212b71d6dad1cbdc84da3eed140429a102971a1106John Reck mTab.mDateCreated = result.getLong(SNAPSHOT_DATE_CREATED); 222d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck mTab.mWebViewController.onPageFinished(mTab); 223d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck } 224282637771aaffd93f29fd72bf5cd49dd82d24d0dJohn Reck } catch (Exception e) { 225282637771aaffd93f29fd72bf5cd49dd82d24d0dJohn Reck Log.w(LOGTAG, "Failed to load view state, closing tab", e); 226282637771aaffd93f29fd72bf5cd49dd82d24d0dJohn Reck mTab.mWebViewController.closeTab(mTab); 227d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck } finally { 228d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck if (result != null) { 229d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck result.close(); 230d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck } 231d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck mTab.mLoadTask = null; 232d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck } 233d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck } 234d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck 235d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck } 2361cf4b79a0020bc18c83ca8bde0e318ecd5252bc2John Reck 2371cf4b79a0020bc18c83ca8bde0e318ecd5252bc2John Reck @Override 2381cf4b79a0020bc18c83ca8bde0e318ecd5252bc2John Reck protected void persistThumbnail() { 239e91c74859a4e5b319f52da4f61b0f5e6f60d9bd6John Reck if (mIsLive) { 240e91c74859a4e5b319f52da4f61b0f5e6f60d9bd6John Reck super.persistThumbnail(); 241e91c74859a4e5b319f52da4f61b0f5e6f60d9bd6John Reck } 2421cf4b79a0020bc18c83ca8bde0e318ecd5252bc2John Reck } 243d8c7452a7d8975a2d60414c5a33842b4a743e631John Reck} 244