2019独角兽企业重金招聘Python工程师标准>>>
协议:WebDAV
WebDAV协议比较简单,主要是对服务器端的文件按照PC的文件路径进行组织,通过PUT、DELETE、PropFind等方法进行交互。这里就不再详细阐述协议内容。
一般服务器会暴露一个EndPoint给Client作为该Client的Root Path。后续该Client的资源都会在这个Root Path下进行组织管理。对于服务端的实现而言比较简单,利用现有的一些框架可以很方便的搭建服务。主要的同步逻辑集中在客户端进行控制,下边介绍下客户端的策略实现。
首先,需要对数据库的结构进行改造,新加几个字段
source_id | deleted | etag | ... | ... |
上表中,需要新加三个字段类型,source_id可加可不加,为了提高效率这里还是加上了,source_id主要是作为WebDAV容器中的资源名称存在的,一般是对该条内容(一般将一条记录构造成一个对应的json数据)的hash或者md5值以保证唯一性。在一条数据插入数据库时就生成。deleted字段默认false,表示该条目是否本地被删除,一般我们删除一个条目并不是完全删除这条数据,而是先将该条目的deleted状态进行修改,最终根据与服务器的同步结果来决定是否实际删除这条数据。etag主要是对应服务器容器的etag,主要是作为判断是不是一条新插入数据的依据,默认是null,当与服务器同步成功后将其修改为服务端该资源对应的etag。具体这几个字段的使用后续会进一步说明。我们来先看一下主要的同步策略代码
/* return synchronized favorite poi list */ public void synchronize() throws Exception { //step1: push local changes to server. int addedRemotely = pushNew(); int deletedRemotely = pushDelete(); boolean fetchCollection = (addedRemotely + deletedRemotely) > 0; //step2: detect details of remote changes LetvLog.d(TAG, "Fetching remote resource list"); PropFindResult result = new PropFindResource().listWebDAVResources(ServerConfig.getServerUrl(), mToken); if (!(result.getErrorCode() == BaseWebDAVResult.SUCCESS)) { LetvLog.d(TAG,"errorcode:" + result.getErrorCode()); throw new Exception(new IOException()); } // step3: check if there is a reason to do a sync with remote if(!fetchCollection){ String currentCTag = getRemoteCTag(result.getResources()); String lastCTag = getLocalCTag(); LetvLog.d(TAG, "Last local CTag = " + lastCTag + "; current remote ctag = " + currentCTag); if(currentCTag == null || !currentCTag.equals(lastCTag)){ fetchCollection = true; } } if(!fetchCollection){ LetvLog.d(TAG, "No local changes and CTags match, no need to sync"); return; } // step4. find remote changes. ListremotelyAdded = new ArrayList (); List remotelyUpdated = new ArrayList (); List remoteResources = result.getResources(); LetvLog.d(TAG, "Remote resource list size = " + (null == remoteResources ? "null" : remoteResources.size())); if(null != remoteResources){ for(DavResource res : remoteResources){ if(res.isCollection()){ LetvLog.d(TAG, res.getName() + " is collection, skip it"); continue; } FavoritePoi poi = mAccountPoiDB.findByRemoteName(res.getName()); if(null == poi){ //item not found local that is new added in server LetvLog.d(TAG, res.getName() + " not found local that is new added in server"); remotelyAdded.add(res); }else{ LetvLog.d(TAG, "local poi's etag is:" + poi.etag); if(TextUtils.isEmpty(poi.etag)){ remotelyUpdated.add(poi); }else{ // these are added for home & office, because they are only one. if(FavoritePoi.HOME_SOURCEID.equals(res.getName()) && !res.getEtag().equals(poi.etag)){ remotelyUpdated.add(poi); }else if(FavoritePoi.OFFICE_SOURCEID.equals(res.getName()) && !res.getEtag().equals(poi.etag)){ remotelyUpdated.add(poi); } } } LetvLog.d(TAG,"remote path is:" + res.getName() + " etag = " + res.getEtag()); } } //step5. pull remote changes to local. pullNew(remotelyAdded); pullChanges(remotelyUpdated); //step6. delete other items. deleteAllExceptRemoteNames(remoteResources); //step7. save local CTag saveLocalCTag(result.getResources()); }
以上代码主要分7步:
Step1. Push local changes to server:这一步主要是将本地数据库中新加的资源(etag为null)给put到服务器,故名pushNew,在pushNew中当成功put一条item时,记得更新资源的etag;另外,将本地deleted的为true的资源在服务端进行delete,成功后将记录从本地数据库删除;
Step2. Detect details of remote changes:这一步主要是通过PropFind方法将服务端资源的摘要信息取到,主要是资源的etag列表,不会返回所有资源的详细内容。这一步主要为后续与本地资源进行比较使用;
Step3: Check if there is a reason to do a sync with remote:这一步利用到了一个叫CTag的东西,这个CTag是什么呢?CTag通过step2的PropFind返回,其实CTag主要是为了优化查询设置的一个数值,它主要由服务器来维护,每次与客户端同步一次它就+1,客户端也保存这个值,当客户端检查发现服务器目前维护的CTag值与本地一致,说明双方最新的数据是一致的,不需要进行同步,节省一次同步操作的开销;
Step4. Find remote changes:这一步是利用Step2返回的摘要信息,将服务端的资源与本地资源进行比对,找出哪些是服务端新加的资源,通过,哪些是服务端更新的资源;
Step5. Pull remote changes to local:对于新加的资源从服务器拉取详细内容插入本地数据库,对于更新的资源拉下来更新本地数据库;
Step6. Delete other items:将所有服务端没有而本地有的数据清除;
Step7. Save local CTag:维护本地的CTag值