协议: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.
    List<DavResource> remotelyAdded = new ArrayList<DavResource> ();
    List<FavoritePoi> remotelyUpdated = new ArrayList<FavoritePoi>();
    List<DavResource> 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值