1 /** 2 * Authors: Szabo Bogdan <szabobogdan@yahoo.com> 3 * Date: 4 23, 2015 4 * License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file. 5 * Copyright: Public Domain 6 */ 7 module vibedav.plugins.syncdav; 8 9 import std.datetime; 10 11 import vibedav.base; 12 import vibedav.davresource; 13 import vibe.core.file; 14 import std.conv; 15 16 import vibe.http.server; 17 18 19 interface ISyncDavProperties { 20 @property { 21 22 /// rfc6578 - 4 23 @ResourceProperty("sync-token", "DAV:") 24 string syncToken(DavResource resource); 25 } 26 } 27 28 interface ISyncDavReports { 29 30 /// rfc6578 - 3.2 31 @DavReport("sync-collection", "DAV:") 32 void syncCollection(DavRequest request, DavResponse response); 33 } 34 35 class SyncDavDataPlugin : BaseDavResourcePlugin, ISyncDavProperties { 36 37 private SyncDavPlugin _syncPlugin; 38 39 this(SyncDavPlugin syncPlugin) { 40 _syncPlugin = syncPlugin; 41 } 42 43 string syncToken(DavResource resource) { 44 return SyncDavPlugin.prefix ~ _syncPlugin.currentChangeNr.to!string; 45 } 46 47 override { 48 bool canGetProperty(DavResource resource, string name) { 49 if(hasDavInterfaceProperty!ISyncDavProperties(name)) 50 return true; 51 52 return false; 53 } 54 55 DavProp property(DavResource resource, string name) { 56 if(hasDavInterfaceProperty!ISyncDavProperties(name)) 57 return getDavInterfaceProperty!ISyncDavProperties(name, this, resource); 58 59 throw new DavException(HTTPStatus.internalServerError, "Can't get property."); 60 } 61 } 62 63 @property 64 string name() { 65 return "SyncDavDataPlugin"; 66 } 67 } 68 69 // todo: add ISyncDavReports 70 class SyncDavPlugin : BaseDavPlugin, ISyncDavReports { 71 enum string prefix = "http://vibedav/ns/sync/"; 72 73 struct Change { 74 Path path; 75 NoticeAction type; 76 SysTime time; 77 } 78 79 private { 80 Change[] log; 81 ulong changeNr = 1; 82 } 83 84 this(IDav dav) { 85 super(dav); 86 } 87 88 protected { 89 90 ulong getToken(DavProp[] syncTokenList) { 91 if(syncTokenList.length == 0) 92 return 0; 93 94 if(syncTokenList[0].tagName != "sync-token") 95 return 0; 96 97 string value = syncTokenList[0].value; 98 99 if(value.length == 0) 100 return 0; 101 102 if(value.length <= prefix.length) 103 return 0; 104 105 if(value[0..prefix.length] != prefix) 106 return 0; 107 108 value = value[prefix.length..$]; 109 110 try { 111 return value.to!ulong; 112 } catch(Exception e) { 113 throw new DavException(HTTPStatus.internalServerError, "invalid sync-token"); 114 } 115 } 116 117 ulong getLevel(DavProp[] syncLevelList) { 118 if(syncLevelList.length == 0) 119 return 0; 120 121 if(syncLevelList[0].name != "sync-level") 122 return 0; 123 124 try { 125 return syncLevelList[0].value.to!ulong; 126 } catch(Exception e) { 127 throw new DavException(HTTPStatus.internalServerError, "invalid sync-level"); 128 } 129 } 130 131 bool[string] getChangesFrom(ulong token) { 132 if(token > changeNr) 133 throw new DavException(HTTPStatus.forbidden, "Invalid token."); 134 135 bool[string] wasRemoved; 136 137 foreach(i; token..changeNr-1) { 138 auto change = log[i]; 139 140 wasRemoved[change.path.toString] = (change.type == NoticeAction.deleted); 141 } 142 143 return wasRemoved; 144 } 145 } 146 147 void syncCollection(DavRequest request, DavResponse response) { 148 response.mimeType = "application/xml"; 149 response.statusCode = HTTPStatus.multiStatus; 150 auto reportData = request.content; 151 152 bool[string] requestedProperties; 153 HTTPStatus[string] responseCodes; 154 155 foreach(name, p; reportData["sync-collection"]["prop"]) 156 requestedProperties[name] = true; 157 158 auto syncTokenList = [ reportData["sync-collection"] ].getTagChilds("sync-token"); 159 auto syncLevelList = [ reportData["sync-collection"] ].getTagChilds("sync-level"); 160 161 ulong token = getToken(syncTokenList); 162 ulong level = getLevel(syncLevelList); 163 164 DavResource[] list; 165 auto changes = getChangesFrom(token); 166 167 foreach(string path, bool wasRemoved; changes) { 168 if(wasRemoved) { 169 responseCodes[path] = HTTPStatus.notFound; 170 } else { 171 list ~= _dav.getResource(URL(path), request.username); 172 } 173 } 174 175 response.setPropContent(list, requestedProperties, responseCodes); 176 response.flush; 177 } 178 179 override { 180 bool hasReport(URL url, string username, string name) { 181 182 if(hasDavReport!ISyncDavReports(name)) 183 return true; 184 185 return false; 186 } 187 188 void report(DavRequest request, DavResponse response) { 189 if(!hasDavReport!ISyncDavReports(request.content.reportName)) 190 throw new DavException(HTTPStatus.internalServerError, "Can't get report."); 191 192 getDavReport!ISyncDavReports(this, request, response); 193 } 194 195 void bindResourcePlugins(DavResource resource) { 196 if(resource.isCollection) 197 resource.registerPlugin(new SyncDavDataPlugin(this)); 198 } 199 200 void notice(NoticeAction action, DavResource resource) { 201 changeNr++; 202 log ~= Change(resource.url.path, action, Clock.currTime); 203 } 204 } 205 206 @property { 207 ulong currentChangeNr() { 208 return changeNr; 209 } 210 211 string name() { 212 return "SyncDavPlugin"; 213 } 214 } 215 }