7 Qrpclog, // /smug/rpclog
8 Quploads, // /smug/uploads
10 Qnickctl, // /smug/nick/ctl
11 Qalbums, // /smug/nick/albums/
12 Qalbumsctl, // /smug/nick/albums/ctl
13 Qcategory, // /smug/nick/Category/
14 Qcategoryctl, // /smug/nick/Category/ctl
15 Qalbum, // /smug/nick/Category/Album/
16 Qalbumctl, // /smug/nick/Category/Album/ctl
17 Qalbumsettings, // /smug/nick/Category/Album/settings
18 Quploadfile, // /smug/nick/Category/Album/upload/file.jpg
19 Qimage, // /smug/nick/Category/Album/Image/
20 Qimagectl, // /smug/nick/Category/Album/Image/ctl
21 Qimageexif, // /smug/nick/Category/Album/Image/exif
22 Qimagesettings, // /smug/nick/Category/Album/Image/settings
23 Qimageurl, // /smug/nick/Category/Album/Image/url
24 Qimagefile, // /smug/nick/Category/Album/Image/file.jpg
31 fprint(2, "locked from %p\n", getcallerpc(&lk));
38 fprint(2, "unlocked from %p\n", getcallerpc(&lk));
42 //#define unlock myunlock
44 typedef struct Upload Upload;
46 typedef struct SmugFid SmugFid;
51 vlong category; // -1 for "albums"
60 #define QTYPE(p) ((p)&0xFF)
61 #define QARG(p) ((p)>>8)
62 #define QPATH(p, q) ((p)|((q)<<8))
92 newupload(SmugFid *sf, char *name)
96 char tmp[] = "/var/tmp/smugfs.XXXXXX";
98 if((fd = opentemp(tmp, ORDWR)) < 0)
101 for(i=0; i<nup; i++){
111 uploadrendez.l = &uploadlock;
112 proccreate(uploader, nil, STACKSIZE);
114 u = emalloc(sizeof *u);
117 up = erealloc(up, (nup+1)*sizeof up[0]);
120 qunlock(&uploadlock);
122 u->name = estrdup(name);
123 u->file = estrdup(tmp);
124 u->album = sf->album;
125 u->albumkey = estrdup(sf->albumkey);
132 closeupload(Upload *u)
169 getuploadindex(SmugFid *sf, int *index)
175 for(i=0; i<nup; i++){
178 if(u->ref > 0 && !u->uploaded && u->album == sf->album && (*index)-- == 0){
179 qunlock(&uploadlock);
186 qunlock(&uploadlock);
191 getuploadname(SmugFid *sf, char *name)
197 for(i=0; i<nup; i++){
200 if(u->ref > 0 && !u->uploaded && u->album == sf->album && strcmp(name, u->name) == 0){
201 qunlock(&uploadlock);
208 qunlock(&uploadlock);
212 void doupload(Upload*);
223 for(i=0; i<nup; i++){
226 if(u->ref > 0 && u->ready && !u->uploading && !u->uploaded){
229 qunlock(&uploadlock);
238 rsleep(&uploadrendez);
243 kickupload(Upload *u)
248 if((d = dirfstat(u->fd)) != nil)
249 u->length = d->length;
256 rwakeup(&uploadrendez);
257 qunlock(&uploadlock);
269 uchar digest[MD5dlen];
273 if((u->fd = open(u->file, OREAD)) < 0){
274 fprint(2, "cannot reopen temporary file %s: %r\n", u->file);
277 if((d = dirfstat(u->fd)) == nil){
278 fprint(2, "fstat: %r\n");
284 memset(&ds, 0, sizeof ds);
287 while((n = read(u->fd, buf, sizeof buf)) > 0){
288 md5((uchar*)buf, n, nil, &ds);
291 if(total != datalen){
292 fprint(2, "bad total: %lld %lld\n", total, datalen);
295 md5(nil, 0, digest, &ds);
298 fmtprint(&fmt, "PUT /%s HTTP/1.0\r\n", u->name);
299 fmtprint(&fmt, "Content-Length: %lld\r\n", datalen);
300 fmtprint(&fmt, "Content-MD5: %.16lH\r\n", digest);
301 fmtprint(&fmt, "X-Smug-SessionID: %s\r\n", sessid);
302 fmtprint(&fmt, "X-Smug-Version: %s\r\n", API_VERSION);
303 fmtprint(&fmt, "X-Smug-ResponseType: JSON\r\n");
304 // Can send X-Smug-ImageID instead to replace existing files.
305 fmtprint(&fmt, "X-Smug-AlbumID: %lld\r\n", u->album);
306 fmtprint(&fmt, "X-Smug-FileName: %s\r\n", u->name);
307 fmtprint(&fmt, "\r\n");
308 req = fmtstrflush(&fmt);
311 jv = jsonupload(&http, UPLOAD_HOST, req, u->fd, datalen);
314 fprint(2, "upload: %r\n");
324 rpclog("uploaded: %J", jv);
329 nickindex(char *name)
334 for(i=0; i<nnick; i++)
335 if(strcmp(nick[i], name) == 0)
337 v = smug("smugmug.users.getTree", "NickName", name, nil);
340 nick = erealloc(nick, (nnick+1)*sizeof nick[0]);
341 nick[nnick] = estrdup(name);
348 if(i < 0 || i >= nnick)
354 responderrstr(Req *r)
358 rerrstr(err, sizeof err);
363 xclone(Fid *oldfid, Fid *newfid)
367 if(oldfid->aux == nil)
370 sf = emalloc(sizeof *sf);
371 *sf = *(SmugFid*)oldfid->aux;
375 sf->albumkey = estrdup(sf->albumkey);
377 sf->imagekey = estrdup(sf->imagekey);
383 xdestroyfid(Fid *fid)
391 if(sf->upwriter && --sf->upload->nwriters == 0){
392 fprint(2, "should upload %s\n", sf->upload->name);
393 kickupload(sf->upload);
395 closeupload(sf->upload);
402 getcategories(SmugFid *sf)
406 v = smug("smugmug.categories.get", "NickName", nickname(sf->nickid), nil);
407 w = jincref(jwalk(v, "Categories"));
413 getcategorytree(SmugFid *sf)
417 v = smug("smugmug.users.getTree", "NickName", nickname(sf->nickid), nil);
418 w = jincref(jwalk(v, "Categories"));
424 getcategory(SmugFid *sf, vlong id)
429 v = getcategorytree(sf);
432 for(i=0; i<v->len; i++){
433 if(jint(jwalk(v->value[i], "id")) == id){
434 w = jincref(v->value[i]);
444 getcategoryid(SmugFid *sf, char *name)
450 v = getcategories(sf);
453 for(i=0; i<v->len; i++){
454 if(jstrcmp(jwalk(v->value[i], "Name"), name) == 0){
455 id = jint(jwalk(v->value[i], "id"));
469 getcategoryindex(SmugFid *sf, int i)
474 v = getcategories(sf);
477 if(i < 0 || i >= v->len){
481 id = jint(jwalk(v->value[i], "id"));
487 getalbum(SmugFid *sf, vlong albumid, char *albumkey)
492 snprint(id, sizeof id, "%lld", albumid);
493 v = smug("smugmug.albums.getInfo",
494 "AlbumID", id, "AlbumKey", albumkey,
495 "NickName", nickname(sf->nickid), nil);
496 w = jincref(jwalk(v, "Album"));
502 getalbums(SmugFid *sf)
506 if(sf->category >= 0)
507 v = getcategory(sf, sf->category);
509 v = smug("smugmug.albums.get",
510 "NickName", nickname(sf->nickid), nil);
511 w = jincref(jwalk(v, "Albums"));
517 getalbumid(SmugFid *sf, char *name, char **keyp)
527 for(i=0; i<v->len; i++){
528 if(jstrcmp(jwalk(v->value[i], "Title"), name) == 0){
529 id = jint(jwalk(v->value[i], "id"));
530 key = jstring(jwalk(v->value[i], "Key"));
531 if(id < 0 || key == nil){
536 *keyp = estrdup(key);
546 getalbumindex(SmugFid *sf, int i, char **keyp)
555 if(i < 0 || i >= v->len){
559 id = jint(jwalk(v->value[i], "id"));
560 key = jstring(jwalk(v->value[i], "Key"));
561 if(id < 0 || key == nil){
566 *keyp = estrdup(key);
572 getimages(SmugFid *sf, vlong albumid, char *albumkey)
577 snprint(id, sizeof id, "%lld", albumid);
578 v = smug("smugmug.images.get",
579 "AlbumID", id, "AlbumKey", albumkey,
580 "NickName", nickname(sf->nickid), nil);
581 w = jincref(jwalk(v, "Images"));
587 getimageid(SmugFid *sf, char *name, char **keyp)
595 id = strtol(name, &p, 10);
596 if(*p != 0 || *name == 0)
599 v = getimages(sf, sf->album, sf->albumkey);
602 for(i=0; i<v->len; i++){
603 if(jint(jwalk(v->value[i], "id")) == id){
604 key = jstring(jwalk(v->value[i], "Key"));
610 *keyp = estrdup(key);
620 getimageinfo(SmugFid *sf, vlong imageid, char *imagekey)
625 snprint(id, sizeof id, "%lld", imageid);
626 v = smug("smugmug.images.getInfo",
627 "ImageID", id, "ImageKey", imagekey,
628 "NickName", nickname(sf->nickid), nil);
629 w = jincref(jwalk(v, "Image"));
635 getimageexif(SmugFid *sf, vlong imageid, char *imagekey)
640 snprint(id, sizeof id, "%lld", imageid);
641 v = smug("smugmug.images.getEXIF",
642 "ImageID", id, "ImageKey", imagekey,
643 "NickName", nickname(sf->nickid), nil);
644 w = jincref(jwalk(v, "Image"));
650 getimageindex(SmugFid *sf, int i, char **keyp)
656 v = getimages(sf, sf->album, sf->albumkey);
659 if(i < 0 || i >= v->len){
663 id = jint(jwalk(v->value[i], "id"));
664 key = jstring(jwalk(v->value[i], "Key"));
665 if(id < 0 || key == nil){
670 *keyp = estrdup(key);
676 categoryname(SmugFid *sf)
681 v = getcategory(sf, sf->category);
682 s = jstring(jwalk(v, "Name"));
690 albumname(SmugFid *sf)
695 v = getalbum(sf, sf->album, sf->albumkey);
696 s = jstring(jwalk(v, "Title"));
704 imagename(SmugFid *sf)
709 v = getimageinfo(sf, sf->image, sf->imagekey);
710 s = jstring(jwalk(v, "FileName"));
714 s = smprint("%lld.jpg", sf->image); // TODO: use Format
720 imagelength(SmugFid *sf)
725 v = getimageinfo(sf, sf->image, sf->imagekey);
726 length = jint(jwalk(v, "Size"));
739 "MediumURL", "medium",
741 "XLargeURL", "xlarge",
742 "X2LargeURL", "xxlarge",
743 "X3LargeURL", "xxxlarge",
744 "OriginalURL", "original",
748 imageurl(SmugFid *sf)
754 v = getimageinfo(sf, sf->image, sf->imagekey);
755 for(i=nelem(urls)-1; i>=0; i--){
756 if((s = jstring(jwalk(v, urls[i].key))) != nil){
766 static char* imagestrings[] =
789 static char* albumbools[] =
813 static char* albumstrings[] =
835 return smprint("%#J\n", userinfo);
840 for(i=0; i<nup; i++){
843 if(u->ready && !u->uploaded && u->ref > 0)
844 fmtprint(&fmt, "%s %s%s\n", u->name, u->file, u->uploading ? " [uploading]" : "");
847 qunlock(&uploadlock);
848 return fmtstrflush(&fmt);
851 v = getcategories(sf);
855 v = getcategory(sf, sf->category);
859 v = getimages(sf, sf->album, sf->albumkey);
867 v = getimageinfo(sf, sf->image, sf->imagekey);
871 v = getimageinfo(sf, sf->image, sf->imagekey);
873 for(i=0; i<nelem(urls); i++)
874 if((s = jstring(jwalk(v, urls[i].key))) != nil)
875 fmtprint(&fmt, "%s %s\n", urls[i].name, s);
877 return fmtstrflush(&fmt);
880 v = getimageexif(sf, sf->image, sf->imagekey);
884 v = getalbum(sf, sf->album, sf->albumkey);
886 fmtprint(&fmt, "id\t%lld\n", jint(jwalk(v, "id")));
888 // TODO: SubCategory/id
889 // TODO: Community/id
891 fmtprint(&fmt, "Highlight\t%lld\n", jint(jwalk(v, "Highlight/id")));
892 fmtprint(&fmt, "Position\t%lld\n", jint(jwalk(v, "Position")));
893 fmtprint(&fmt, "ImageCount\t%lld\n", jint(jwalk(v, "ImageCount")));
894 for(i=0; i<nelem(albumbools); i++){
895 vv = jwalk(v, albumbools[i]);
897 fmtprint(&fmt, "%s\t%J\n", albumbools[i], vv);
899 for(i=0; i<nelem(albumstrings); i++){
900 s = jstring(jwalk(v, albumstrings[i]));
902 fmtprint(&fmt, "%s\t%s\n", albumstrings[i], s);
904 s = fmtstrflush(&fmt);
909 v = getimageinfo(sf, sf->image, sf->imagekey);
911 fmtprint(&fmt, "id\t%lld\n", jint(jwalk(v, "id")));
912 fmtprint(&fmt, "Position\t%lld\n", jint(jwalk(v, "Position")));
913 fmtprint(&fmt, "Serial\t%lld\n", jint(jwalk(v, "Serial")));
914 fmtprint(&fmt, "Size\t%lld\t%lldx%lld\n",
915 jint(jwalk(v, "Size")),
916 jint(jwalk(v, "Width")),
917 jint(jwalk(v, "Height")));
918 vv = jwalk(v, "Hidden");
919 fmtprint(&fmt, "Hidden\t%J\n", vv);
921 for(i=0; i<nelem(imagestrings); i++){
922 s = jstring(jwalk(v, imagestrings[i]));
924 fmtprint(&fmt, "%s\t%s\n", imagestrings[i], s);
926 s = fmtstrflush(&fmt);
933 s = smprint("%#J\n", v);
940 dostat(SmugFid *sf, Qid *qid, Dir *dir)
950 memset(&q, 0, sizeof q);
956 q.path = QPATH(sf->type, sf->nickid);
981 name = nickname(sf->nickid);
997 name = categoryname(sf);
999 q.path |= QPATH(0, sf->category << 8);
1005 q.path |= QPATH(0, sf->category << 8);
1008 name = albumname(sf);
1010 q.path |= QPATH(0, sf->album << 8);
1016 q.path |= QPATH(0, sf->album << 8);
1018 case Qalbumsettings:
1021 q.path |= QPATH(0, sf->album << 8);
1024 q.path |= QPATH(0, (uintptr)sf->upload << 8);
1027 name = sf->upload->name;
1028 if(sf->upload->fd >= 0){
1029 dd = dirfstat(sf->upload->fd);
1031 length = dd->length;
1035 length = sf->upload->length;
1036 if(!sf->upload->ready)
1041 name = smprint("%lld", sf->image);
1043 q.path |= QPATH(0, sf->image << 8);
1049 q.path |= QPATH(0, sf->image << 8);
1051 case Qimagesettings:
1054 q.path |= QPATH(0, sf->image << 8);
1058 q.path |= QPATH(0, sf->image << 8);
1062 q.path |= QPATH(0, sf->image << 8);
1065 name = imagename(sf);
1067 q.path |= QPATH(0, sf->image << 8);
1068 length = imagelength(sf);
1084 memset(dir, 0, sizeof *dir);
1085 dir->name = estrdup9p(name);
1086 dir->muid = estrdup9p("muid");
1091 dir->uid = estrdup9p(uid);
1092 dir->gid = estrdup9p("smugfs");
1094 dir->length = length;
1101 xwalk1(Fid *fid, char *name, Qid *qid)
1110 dotdot = strcmp(name, "..") == 0;
1115 return "file not found";
1120 if(strcmp(name, "ctl") == 0){
1124 if(strcmp(name, "uploads") == 0){
1125 sf->type = Quploads;
1128 if(strcmp(name, "rpclog") == 0){
1132 if((i = nickindex(name)) >= 0){
1145 if(strcmp(name, "ctl") == 0){
1146 sf->type = Qnickctl;
1149 if(strcmp(name, "albums") == 0){
1154 if((id = getcategoryid(sf, name)) >= 0){
1156 sf->type = Qcategory;
1168 if(strcmp(name, "ctl") == 0){
1172 if((id = getalbumid(sf, name, &key)) >= 0){
1185 if(sf->category == -1)
1188 sf->type = Qcategory;
1191 if(strcmp(name, "ctl") == 0){
1192 sf->type = Qalbumctl;
1195 if(strcmp(name, "settings") == 0){
1196 sf->type = Qalbumsettings;
1199 if((id = getimageid(sf, name, &key)) >= 0){
1205 if((u = getuploadname(sf, name)) != nil){
1207 sf->type = Quploadfile;
1220 if(strcmp(name, "ctl") == 0){
1221 sf->type = Qimagectl;
1224 if(strcmp(name, "url") == 0){
1225 sf->type = Qimageurl;
1228 if(strcmp(name, "settings") == 0){
1229 sf->type = Qimagesettings;
1232 if(strcmp(name, "exif") == 0){
1233 sf->type = Qimageexif;
1237 if(x && strcmp(name, x) == 0){
1239 sf->type = Qimagefile;
1245 dostat(sf, qid, nil);
1251 dodirgen(int i, Dir *d, void *v)
1261 xsf.type++; // ctl in every directory
1262 dostat(&xsf, nil, d);
1273 dostat(&xsf, nil, d);
1276 if(i < 0 || i >= nnick)
1280 dostat(&xsf, nil, d);
1286 dostat(&xsf, nil, d);
1289 if((id = getcategoryindex(sf, i)) < 0)
1291 xsf.type = Qcategory;
1293 dostat(&xsf, nil, d);
1298 if((id = getalbumindex(sf, i, &key)) < 0)
1303 dostat(&xsf, nil, d);
1309 xsf.type = Qalbumsettings;
1310 dostat(&xsf, nil, d);
1313 if((u = getuploadindex(sf, &i)) != nil){
1315 xsf.type = Quploadfile;
1316 dostat(&xsf, nil, d);
1320 if((id = getimageindex(sf, i, &key)) < 0)
1325 dostat(&xsf, nil, d);
1331 xsf.type = Qimagefile;
1332 dostat(&xsf, nil, d);
1336 xsf.type = Qimageexif;
1337 dostat(&xsf, nil, d);
1341 xsf.type = Qimagesettings;
1342 dostat(&xsf, nil, d);
1346 xsf.type = Qimageurl;
1347 dostat(&xsf, nil, d);
1357 dostat(r->fid->aux, nil, &r->d);
1370 if(r->d.uid[0] || r->d.gid[0] || r->d.muid[0] || ~r->d.mode != 0
1371 || ~r->d.atime != 0 || ~r->d.mtime != 0 || ~r->d.length != 0){
1372 respond(r, "invalid wstat");
1378 respond(r, "invalid wstat");
1380 // TODO: rename category
1382 snprint(strid, sizeof strid, "%lld", sf->album);
1383 v = ncsmug("smugmug.albums.changeSettings",
1384 "AlbumID", strid, "Title", r->d.name, nil);
1389 s = smprint("&AlbumID=%lld&", sf->album);
1392 jcacheflush("smugmug.albums.get&");
1396 respond(r, "invalid wstat");
1404 sf = emalloc(sizeof *sf);
1407 dostat(sf, &r->ofcall.qid, nil);
1408 r->fid->qid = r->ofcall.qid;
1417 if((r->ifcall.mode&~OTRUNC) > 2){
1418 respond(r, "permission denied");
1430 case Qalbumsettings:
1431 case Qimagesettings:
1435 if(r->ifcall.mode != OREAD){
1436 lock(&sf->upload->lk);
1437 if(sf->upload->ready){
1438 unlock(&sf->upload->lk);
1439 respond(r, "permission denied");
1443 sf->upload->nwriters++;
1444 unlock(&sf->upload->lk);
1449 if(r->ifcall.mode != OREAD){
1450 respond(r, "permission denied");
1456 r->ofcall.qid = r->fid->qid;
1466 char strid[50], *key;
1472 // Create new category.
1473 if(!(r->ifcall.perm&DMDIR))
1475 v = ncsmug("smugmug.categories.create",
1476 "Name", r->ifcall.name, nil);
1481 id = jint(jwalk(v, "Category/id"));
1483 fprint(2, "Create category: %J\n", v);
1488 sf->type = Qcategory;
1490 jcacheflush("method=smugmug.users.getTree&");
1491 jcacheflush("method=smugmug.categories.get&");
1492 dostat(sf, &r->ofcall.qid, nil);
1497 // Create new album.
1498 if(!(r->ifcall.perm&DMDIR))
1500 snprint(strid, sizeof strid, "%lld", sf->category);
1501 // Start with most restrictive settings.
1502 v = ncsmug("smugmug.albums.create",
1503 "Title", r->ifcall.name,
1504 "CategoryID", strid,
1506 "WorldSearchable", "0",
1507 "SmugSearchable", "0",
1513 id = jint(jwalk(v, "Album/id"));
1514 key = jstring(jwalk(v, "Album/Key"));
1515 if(id < 0 || key == nil){
1516 fprint(2, "Create album: %J\n", v);
1523 sf->albumkey = estrdup(key);
1525 jcacheflush("method=smugmug.users.getTree&");
1526 dostat(sf, &r->ofcall.qid, nil);
1531 // Upload image to album.
1532 if(r->ifcall.perm&DMDIR)
1534 u = newupload(sf, r->ifcall.name);
1541 sf->type = Quploadfile;
1542 dostat(sf, &r->ofcall.qid, nil);
1546 respond(r, "permission denied");
1550 writetofd(Req *r, int fd)
1555 while(total < r->ifcall.count){
1556 n = pwrite(fd, (char*)r->ifcall.data+total, r->ifcall.count-total, r->ifcall.offset+total);
1561 r->ofcall.count = r->ifcall.count;
1566 readfromfd(Req *r, int fd)
1569 n = pread(fd, r->ofcall.data, r->ifcall.count, r->ifcall.offset);
1572 r->ofcall.count = n;
1585 r->ofcall.count = 0;
1588 respond(r, "not implemented");
1596 dirread9p(r, dodirgen, sf);
1610 case Qimagesettings:
1611 case Qalbumsettings:
1618 if(url == nil || (fd = download(url, &hdr)) < 0){
1629 readfromfd(r, sf->upload->fd);
1645 r->ofcall.count = r->ifcall.count;
1646 sync = (r->ifcall.count==4 && memcmp(r->ifcall.data, "sync", 4) == 0);
1657 s = smprint("&NickName=%s&", nickname(sf->nickid));
1666 jcacheflush("smugmug.categories.get");
1670 s = smprint("&AlbumID=%lld&", sf->album);
1679 s = smprint("&ImageID=%lld&", sf->image);
1688 if(writetofd(r, sf->upload->fd) < 0){
1696 case Qimagesettings:
1697 case Qalbumsettings:
1698 s = (char*)r->ifcall.data; // lib9p nul-terminated it
1699 t = strpbrk(s, " \r\t\n");
1704 while(*t == ' ' || *t == '\r' || *t == '\t' || *t == '\n')
1707 p = strchr(t, '\n');
1711 respond(r, "newline in argument");
1714 if(sf->type == Qalbumsettings)
1716 snprint(strid, sizeof strid, "%lld", sf->image);
1717 v = ncsmug("smugmug.images.changeSettings",
1724 s = smprint("&ImageID=%lld&", sf->image);
1729 snprint(strid, sizeof strid, "%lld", sf->album);
1730 v = ncsmug("smugmug.albums.changeSettings",
1731 "AlbumID", strid, s, t, nil);
1736 s = smprint("&AlbumID=%lld&", sf->album);
1741 respond(r, "invalid control message");
1755 respond(r, "permission denied");
1759 case Qalbumsettings:
1761 case Qimagesettings:
1765 /* ignore remove request, but no error, so rm -r works */
1766 /* you can pretend they get removed and immediately grow back! */
1771 if(v && v->len > 0){
1772 respond(r, "directory not empty");
1775 snprint(id, sizeof id, "%lld", sf->category);
1776 v = ncsmug("smugmug.categories.delete",
1777 "CategoryID", id, nil);
1782 jcacheflush("smugmug.users.getTree");
1783 jcacheflush("smugmug.categories.get");
1788 v = getimages(sf, sf->album, sf->albumkey);
1789 if(v && v->len > 0){
1790 respond(r, "directory not empty");
1793 snprint(id, sizeof id, "%lld", sf->album);
1794 v = ncsmug("smugmug.albums.delete",
1795 "AlbumID", id, nil);
1800 jcacheflush("smugmug.users.getTree");
1801 jcacheflush("smugmug.categories.get");
1802 jcacheflush("smugmug.albums.get");
1808 snprint(id, sizeof id, "%lld", sf->image);
1809 v = ncsmug("smugmug.images.delete",
1810 "ImageID", id, nil);
1815 snprint(id, sizeof id, "ImageID=%lld&", sf->image);
1817 jcacheflush("smugmug.images.get&");
1827 rpclogflush(r->oldreq);
1836 xsrv.attach = xattach;
1838 xsrv.create = xcreate;
1841 xsrv.walk1 = xwalk1;
1842 xsrv.clone = xclone;
1843 xsrv.destroyfid = xdestroyfid;
1844 xsrv.remove = xremove;
1845 xsrv.write = xwrite;
1846 xsrv.flush = xflush;
1847 xsrv.wstat = xwstat;