package com.emop.client.provider; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.OutputStream; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; import org.json.JSONArray; import org.json.JSONObject; import android.content.ContentProvider; import android.content.Context; import android.database.Cursor; import android.net.Uri; import android.support.v4.util.LruCache; import android.util.Log; import com.emop.client.Constants; import com.emop.client.io.ApiResult; import com.emop.client.io.FmeiClient; import com.emop.client.io.TaodianApi; import com.emop.client.provider.model.Rebate; import com.emop.client.provider.model.Shop; import com.emop.client.provider.model.Topic; public class DataUpdateService { private final static long MAX_CACHE_TIME = 1000 * 60 * 60 * 24 * 3; private Context context = null; private ContentProvider provider = null; private TaodianApi api = null; private LruCache<String, Lock> lockCache; private File dataPath = null; private boolean hastDirty = false; private Timer syncTimer = null; private Map<String, CacheItem> cache = new HashMap<String, CacheItem>(); public DataUpdateService(Context context, ContentProvider provider){ this.context = context; this.provider = provider; lockCache = new LruCache<String, Lock>(200) { @Override protected int sizeOf(String key, Lock obj) { return 1; } }; dataPath = context.getDatabasePath("lastUpdate.db"); loadLastUpadte(); syncTimer = new Timer(); syncTimer.scheduleAtFixedRate(new TimerTask(){ @Override public void run() { saveLastUpdate(); }}, 1000, 1000 * 60); } /** * 检查是否过期,或许要重新加载。如果数据需要重新从网络服务器加载,回调用API加载 * 后生成一个新的Cursor替换原来的Cursor返回。 * * @param uri * @param c * @return */ public Cursor syncCheck(final Uri uri, Cursor c){ boolean isForceRefresh = isForceRefresh(uri); if(c.getCount() == 0 || isForceRefresh){ if((isExpried(uri, 60 * 1000) || isForceRefresh) && !allowEmpty(uri)){ ApiResult r = refreshDataByUri(uri); if(r != null && r.isOK){ try{ JSONObject data = r.json.getJSONObject("data"); String[] names = c.getColumnNames(); c.close(); c = new JSONCursor(data.getJSONArray("items"), names); }catch(Exception e){ Log.w("emop", "Result parsse error:" + e.toString(), e); } } }else { Log.w("emop", "Ignore refresh empty uri:" + uri.toString()); } }else if(isExpried(uri, 1000 * 60 * 60 * 3)){ new Thread(){ public void run(){ Log.e(Constants.TAG_EMOP, "refresh expired uri:" + uri.toString()); refreshDataByUri(uri); context.getContentResolver().notifyChange(uri, null); } }.start(); } return c; } public ApiResult refreshDataByUri(Uri uri){ String ck = getCacheKey(uri); Lock lock = getLock(uri.toString()); ApiResult r = null; if(lock.tryLock()){ Log.e(Constants.TAG_EMOP, "Start to refresh uri:" + uri.toString()); /* * 避免网络通信期间,导致次刷新请求. */ CacheItem item = new CacheItem(); item.cacheTime = System.currentTimeMillis(); cache.put(ck, item); hastDirty = true; try{ switch (Schema.FmeiUriMatcher.match(uri)) { case Schema.TYPE_SHOPS: case Schema.TYPE_SHOPS_CATE: r = refreshShopList(uri); break; case Schema.TYPE_SHOP_TAOKE_LSIT: r = refreshShopTaokeList(uri); break; case Schema.TYPE_REBATES: case Schema.TYPE_REBATES_CATE: r = refreshRebateList(uri); break; case Schema.TYPE_SHOP_ID: r = refreshOneShop(uri); break; case Schema.TYPE_REBATE_CATES: r = this.refreshRebateCateList(uri); break; case Schema.TYPE_TOPIC_ITEM_LIST: r = this.refreshTopicItemList(uri); break; case Schema.TYPE_TOPICS: r = this.refreshTopicList(uri); break; case Schema.TYPE_CATES: r = this.refreshCateList(uri); break; case Schema.TYPE_HOTS: r = this.refreshHotCateList(uri); break; default: Log.e(Constants.TAG_EMOP, "unkown in data update uri:" + uri.toString()); } }finally{ lock.unlock(); } }else { Log.e(Constants.TAG_EMOP, "Failed to get refresh lock:" + uri.toString()); } return r; } public boolean isExpried(Uri uri){ return isExpried(uri, 1000 * 60 * 30); } public boolean isExpried(Uri uri, int timeOut){ String ck = getCacheKey(uri); CacheItem item = cache.get(ck); if(item == null || System.currentTimeMillis() - item.cacheTime > timeOut){ return true; } return false; } private boolean isForceRefresh(Uri uri){ String q = uri.getQueryParameter("force_refresh"); return q != null && q.equals("y"); } private boolean allowEmpty(Uri uri){ String q = uri.getQueryParameter("empty"); return q != null && q.equals("y"); } public String getCacheKey(Uri u){ return u.toString(); } public synchronized Lock getLock(String url){ Lock o = lockCache.get(url); if(o == null){ o = new ReentrantLock(); lockCache.put(url, o); } return o; } public TaodianApi taoDianApi(){ if(api == null){ api = new TaodianApi(); api.connect(null); } return api; } public ApiResult refreshShopList(final Uri shopList){ String cate = shopList.getQueryParameter("cate"); FmeiClient client = FmeiClient.getInstance(null); int pageSize = this.getIntParamter(shopList, "page_size", 40); int pageNo = this.getIntParamter(shopList, "page_no", 0); final ApiResult r = taoDianApi().getShopList(client.trackUserId, cate, pageSize, pageNo); if(r.isOK){ new Thread(){ public void run(){ try { JSONObject json = r.json.getJSONObject("data"); JSONArray jarray = json.getJSONArray("items"); for(int i = 0; i < jarray.length(); i++){ provider.update(Schema.SHOP_LIST, Shop.convertJson(jarray.getJSONObject(i)), null, null); } } catch (Exception e) { Log.w("emop", "JSON:" + r.json.toString()); Log.w("emop", "Refresh shop listerror:" + e.toString(), e); } } }.start(); } return r; } public ApiResult refreshOneShop(final Uri shopList){ String cate = shopList.getQueryParameter("cate"); FmeiClient client = FmeiClient.getInstance(null); int pageSize = this.getIntParamter(shopList, "page_size", 40); int pageNo = this.getIntParamter(shopList, "page_no", 0); List<String> seg = shopList.getPathSegments(); String shopId = seg.get(1); final ApiResult r = taoDianApi().getOneShop(client.trackUserId, shopId, pageSize, pageNo); if(r.isOK){ new Thread(){ public void run(){ try { JSONObject json = r.json.getJSONObject("data"); JSONArray jarray = json.getJSONArray("items"); for(int i = 0; i < jarray.length(); i++){ provider.update(Schema.SHOP_LIST, Shop.convertJson(jarray.getJSONObject(i)), null, null); } } catch (Exception e) { Log.w("emop", "JSON:" + r.json.toString()); Log.w("emop", "Refresh shop listerror:" + e.toString(), e); } } }.start(); } return r; } public ApiResult refreshRebateList(final Uri rebateList){ String cate = rebateList.getQueryParameter("cate"); FmeiClient client = FmeiClient.getInstance(null); int pageSize = this.getIntParamter(rebateList, QueryParam.PAGE_SIZE, 40); int pageNo = this.getIntParamter(rebateList, QueryParam.PAGE_NO, 0); Log.w("emop", "Refresh rebate listerror:..."); final ApiResult r = taoDianApi().getRebateList(client.trackUserId, cate, pageSize, pageNo); if(r.isOK){ new Thread(){ public void run(){ try { JSONObject json = r.json.getJSONObject("data"); JSONArray jarray = json.getJSONArray("items"); for(int i = 0; i < jarray.length(); i++){ provider.update(Schema.REBATE_LIST, Rebate.convertJson(jarray.getJSONObject(i)), null, null); } } catch (Exception e) { Log.w("emop", "JSON:" + r.json.toString()); Log.w("emop", "Refresh rebate listerror:" + e.toString(), e); } } }.start(); } return r; } public ApiResult refreshRebateCateList(final Uri rebateList){ Log.w("emop", "Refresh rebate cate listerror:..."); final ApiResult r = taoDianApi().getRebateCateList(50, 1001); if(r.isOK){ new Thread(){ public void run(){ try { JSONObject json = r.json.getJSONObject("data"); JSONArray jarray = json.getJSONArray("items"); for(int i = 0; i < jarray.length(); i++){ provider.update(Schema.REBATE_CATE_LIST, Topic.convertJson(jarray.getJSONObject(i)), null, null); } } catch (Exception e) { Log.w("emop", "JSON:" + r.json.toString()); Log.w("emop", "Refresh rebate listerror:" + e.toString(), e); } } }.start(); } return r; } public ApiResult refreshShopTaokeList(final Uri shopList){ FmeiClient client = FmeiClient.getInstance(null); String force = isForceRefresh(shopList) ? "y": "n"; return client.refreshTopicItemList(context.getContentResolver(), shopList, true, force); } public ApiResult refreshTopicItemList(final Uri topic){ FmeiClient client = FmeiClient.getInstance(null); String force = isForceRefresh(topic) ? "y": "n"; return client.refreshTopicItemList(context.getContentResolver(), topic, true, force); } public ApiResult refreshTopicList(final Uri topic){ FmeiClient client = FmeiClient.getInstance(null); String force = isForceRefresh(topic) ? "y": "n"; return client.refreshTopicList(context.getContentResolver(), TaodianApi.STATUS_NORMAL, force); } public ApiResult refreshHotCateList(final Uri topic){ FmeiClient client = FmeiClient.getInstance(null); return client.refreshHotCatList(context.getContentResolver(), TaodianApi.STATUS_NORMAL); } public ApiResult refreshCateList(final Uri topic){ FmeiClient client = FmeiClient.getInstance(null); return client.refreshCateList(context.getContentResolver(), TaodianApi.STATUS_NORMAL); } protected void loadLastUpadte(){ if(dataPath.isFile() && dataPath.canRead()){ InputStream in = null; try { in = new FileInputStream(dataPath); ObjectInputStream obj2 = null; obj2 = new ObjectInputStream(new GZIPInputStream(in)); Object data = obj2.readObject(); if(data instanceof Map){ Map<String, CacheItem> tmp = (Map<String, CacheItem>)data; cache.putAll(tmp); Log.i("emop", "Read last udpate data from cache, size:" + tmp.size()); } } catch (Exception e) { Log.d("emop", "read data error:" + e.toString(), e); }finally{ if(in != null){ try { in.close(); } catch (IOException e) { } } } }else { Log.d("emop", "cant read data file:" + dataPath.getAbsolutePath()); } } protected void saveLastUpdate(){ if(!this.hastDirty) return; hastDirty = false; OutputStream outs = null; try { outs = new FileOutputStream(dataPath); ObjectOutputStream obj2 = new ObjectOutputStream(new GZIPOutputStream(outs)); Map<String, CacheItem> tmp = new HashMap<String, CacheItem>(); Collection<String> keys = new ArrayList<String>(); keys.addAll(cache.keySet()); for(String k: keys){ CacheItem item = cache.get(k); if(item != null && System.currentTimeMillis() - item.cacheTime < MAX_CACHE_TIME){ tmp.put(k, item); } } obj2.writeObject(tmp); obj2.flush(); Log.d("emop", "Write cache item, size:" + tmp.size()); } catch (Exception e) { Log.d("emop", "read data error:" + e.toString(), e); }finally{ if(outs != null){ try { outs.close(); } catch (IOException e) { } } } } private int getIntParamter(final Uri uri, String name, int def){ int i = def; String val = uri.getQueryParameter(name); if(val != null && val.length() > 0){ try{ i = Integer.parseInt(val); }catch(Throwable e){} } return i; } static class CacheItem implements Serializable{ private static final long serialVersionUID = 2541899275403465619L; public long cacheTime = 0; public boolean lastStatus = false; } }