#include "a.h" enum { Qroot = 0, // /smug/ Qctl, // /smug/ctl Qrpclog, // /smug/rpclog Quploads, // /smug/uploads Qnick, // /smug/nick/ Qnickctl, // /smug/nick/ctl Qalbums, // /smug/nick/albums/ Qalbumsctl, // /smug/nick/albums/ctl Qcategory, // /smug/nick/Category/ Qcategoryctl, // /smug/nick/Category/ctl Qalbum, // /smug/nick/Category/Album/ Qalbumctl, // /smug/nick/Category/Album/ctl Qalbumsettings, // /smug/nick/Category/Album/settings Quploadfile, // /smug/nick/Category/Album/upload/file.jpg Qimage, // /smug/nick/Category/Album/Image/ Qimagectl, // /smug/nick/Category/Album/Image/ctl Qimageexif, // /smug/nick/Category/Album/Image/exif Qimagesettings, // /smug/nick/Category/Album/Image/settings Qimageurl, // /smug/nick/Category/Album/Image/url Qimagefile, // /smug/nick/Category/Album/Image/file.jpg }; void mylock(Lock *lk) { lock(lk); fprint(2, "locked from %p\n", getcallerpc(&lk)); } void myunlock(Lock *lk) { unlock(lk); fprint(2, "unlocked from %p\n", getcallerpc(&lk)); } //#define lock mylock //#define unlock myunlock typedef struct Upload Upload; typedef struct SmugFid SmugFid; struct SmugFid { int type; int nickid; vlong category; // -1 for "albums" vlong album; char *albumkey; vlong image; char *imagekey; Upload *upload; int upwriter; }; #define QTYPE(p) ((p)&0xFF) #define QARG(p) ((p)>>8) #define QPATH(p, q) ((p)|((q)<<8)) char **nick; int nnick; struct Upload { Lock lk; int fd; char *name; char *file; vlong album; vlong length; char *albumkey; int size; int ready; int nwriters; int uploaded; int ref; int uploading; }; Upload **up; int nup; QLock uploadlock; Rendez uploadrendez; void uploader(void*); Upload* newupload(SmugFid *sf, char *name) { Upload *u; int fd, i; char tmp[] = "/var/tmp/smugfs.XXXXXX"; if((fd = opentemp(tmp, ORDWR)) < 0) return nil; qlock(&uploadlock); for(i=0; ilk); if(u->ref == 0){ u->ref = 1; goto Reuse; } unlock(&u->lk); } if(nup == 0){ uploadrendez.l = &uploadlock; proccreate(uploader, nil, STACKSIZE); } u = emalloc(sizeof *u); lock(&u->lk); u->ref = 1; up = erealloc(up, (nup+1)*sizeof up[0]); up[nup++] = u; Reuse: qunlock(&uploadlock); u->fd = fd; u->name = estrdup(name); u->file = estrdup(tmp); u->album = sf->album; u->albumkey = estrdup(sf->albumkey); u->nwriters = 1; unlock(&u->lk); return u; } void closeupload(Upload *u) { lock(&u->lk); if(--u->ref > 0){ unlock(&u->lk); return; } if(u->ref < 0) abort(); if(u->fd >= 0){ close(u->fd); u->fd = -1; } if(u->name){ free(u->name); u->name = nil; } if(u->file){ remove(u->file); free(u->file); u->file = nil; } u->album = 0; if(u->albumkey){ free(u->albumkey); u->albumkey = nil; } u->size = 0; u->ready = 0; u->nwriters = 0; u->uploaded = 0; u->uploading = 0; u->length = 0; unlock(&u->lk); } Upload* getuploadindex(SmugFid *sf, int *index) { int i; Upload *u; qlock(&uploadlock); for(i=0; ilk); if(u->ref > 0 && !u->uploaded && u->album == sf->album && (*index)-- == 0){ qunlock(&uploadlock); u->ref++; unlock(&u->lk); return u; } unlock(&u->lk); } qunlock(&uploadlock); return nil; } Upload* getuploadname(SmugFid *sf, char *name) { int i; Upload *u; qlock(&uploadlock); for(i=0; ilk); if(u->ref > 0 && !u->uploaded && u->album == sf->album && strcmp(name, u->name) == 0){ qunlock(&uploadlock); u->ref++; unlock(&u->lk); return u; } unlock(&u->lk); } qunlock(&uploadlock); return nil; } void doupload(Upload*); void uploader(void *v) { int i, did; Upload *u; qlock(&uploadlock); for(;;){ did = 0; for(i=0; ilk); if(u->ref > 0 && u->ready && !u->uploading && !u->uploaded){ u->uploading = 1; unlock(&u->lk); qunlock(&uploadlock); doupload(u); closeupload(u); did = 1; qlock(&uploadlock); }else unlock(&u->lk); } if(!did) rsleep(&uploadrendez); } } void kickupload(Upload *u) { Dir *d; lock(&u->lk); if((d = dirfstat(u->fd)) != nil) u->length = d->length; close(u->fd); u->fd = -1; u->ref++; u->ready = 1; unlock(&u->lk); qlock(&uploadlock); rwakeup(&uploadrendez); qunlock(&uploadlock); } void doupload(Upload *u) { Dir *d; vlong datalen; Fmt fmt; char *req; char buf[8192]; int n, total; uchar digest[MD5dlen]; DigestState ds; Json *jv; if((u->fd = open(u->file, OREAD)) < 0){ fprint(2, "cannot reopen temporary file %s: %r\n", u->file); return; } if((d = dirfstat(u->fd)) == nil){ fprint(2, "fstat: %r\n"); return; } datalen = d->length; free(d); memset(&ds, 0, sizeof ds); seek(u->fd, 0, 0); total = 0; while((n = read(u->fd, buf, sizeof buf)) > 0){ md5((uchar*)buf, n, nil, &ds); total += n; } if(total != datalen){ fprint(2, "bad total: %lld %lld\n", total, datalen); return; } md5(nil, 0, digest, &ds); fmtstrinit(&fmt); fmtprint(&fmt, "PUT /%s HTTP/1.0\r\n", u->name); fmtprint(&fmt, "Content-Length: %lld\r\n", datalen); fmtprint(&fmt, "Content-MD5: %.16lH\r\n", digest); fmtprint(&fmt, "X-Smug-SessionID: %s\r\n", sessid); fmtprint(&fmt, "X-Smug-Version: %s\r\n", API_VERSION); fmtprint(&fmt, "X-Smug-ResponseType: JSON\r\n"); // Can send X-Smug-ImageID instead to replace existing files. fmtprint(&fmt, "X-Smug-AlbumID: %lld\r\n", u->album); fmtprint(&fmt, "X-Smug-FileName: %s\r\n", u->name); fmtprint(&fmt, "\r\n"); req = fmtstrflush(&fmt); seek(u->fd, 0, 0); jv = jsonupload(&http, UPLOAD_HOST, req, u->fd, datalen); free(req); if(jv == nil){ fprint(2, "upload: %r\n"); return; } close(u->fd); remove(u->file); free(u->file); u->file = nil; u->fd = -1; u->uploaded = 1; rpclog("uploaded: %J", jv); jclose(jv); } int nickindex(char *name) { int i; Json *v; for(i=0; i= nnick) return nil; return nick[i]; } void responderrstr(Req *r) { char err[ERRMAX]; rerrstr(err, sizeof err); respond(r, err); } static char* xclone(Fid *oldfid, Fid *newfid) { SmugFid *sf; if(oldfid->aux == nil) return nil; sf = emalloc(sizeof *sf); *sf = *(SmugFid*)oldfid->aux; sf->upload = nil; sf->upwriter = 0; if(sf->albumkey) sf->albumkey = estrdup(sf->albumkey); if(sf->imagekey) sf->imagekey = estrdup(sf->imagekey); newfid->aux = sf; return nil; } static void xdestroyfid(Fid *fid) { SmugFid *sf; sf = fid->aux; free(sf->albumkey); free(sf->imagekey); if(sf->upload){ if(sf->upwriter && --sf->upload->nwriters == 0){ fprint(2, "should upload %s\n", sf->upload->name); kickupload(sf->upload); } closeupload(sf->upload); sf->upload = nil; } free(sf); } static Json* getcategories(SmugFid *sf) { Json *v, *w; v = smug("smugmug.categories.get", "NickName", nickname(sf->nickid), nil); w = jincref(jwalk(v, "Categories")); jclose(v); return w; } static Json* getcategorytree(SmugFid *sf) { Json *v, *w; v = smug("smugmug.users.getTree", "NickName", nickname(sf->nickid), nil); w = jincref(jwalk(v, "Categories")); jclose(v); return w; } static Json* getcategory(SmugFid *sf, vlong id) { int i; Json *v, *w; v = getcategorytree(sf); if(v == nil) return nil; for(i=0; ilen; i++){ if(jint(jwalk(v->value[i], "id")) == id){ w = jincref(v->value[i]); jclose(v); return w; } } jclose(v); return nil; } static vlong getcategoryid(SmugFid *sf, char *name) { int i; vlong id; Json *v; v = getcategories(sf); if(v == nil) return -1; for(i=0; ilen; i++){ if(jstrcmp(jwalk(v->value[i], "Name"), name) == 0){ id = jint(jwalk(v->value[i], "id")); if(id < 0){ jclose(v); return -1; } jclose(v); return id; } } jclose(v); return -1; } static vlong getcategoryindex(SmugFid *sf, int i) { Json *v; vlong id; v = getcategories(sf); if(v == nil) return -1; if(i < 0 || i >= v->len){ jclose(v); return -1; } id = jint(jwalk(v->value[i], "id")); jclose(v); return id; } static Json* getalbum(SmugFid *sf, vlong albumid, char *albumkey) { char id[50]; Json *v, *w; snprint(id, sizeof id, "%lld", albumid); v = smug("smugmug.albums.getInfo", "AlbumID", id, "AlbumKey", albumkey, "NickName", nickname(sf->nickid), nil); w = jincref(jwalk(v, "Album")); jclose(v); return w; } static Json* getalbums(SmugFid *sf) { Json *v, *w; if(sf->category >= 0) v = getcategory(sf, sf->category); else v = smug("smugmug.albums.get", "NickName", nickname(sf->nickid), nil); w = jincref(jwalk(v, "Albums")); jclose(v); return w; } static vlong getalbumid(SmugFid *sf, char *name, char **keyp) { int i; vlong id; Json *v; char *key; v = getalbums(sf); if(v == nil) return -1; for(i=0; ilen; i++){ if(jstrcmp(jwalk(v->value[i], "Title"), name) == 0){ id = jint(jwalk(v->value[i], "id")); key = jstring(jwalk(v->value[i], "Key")); if(id < 0 || key == nil){ jclose(v); return -1; } if(keyp) *keyp = estrdup(key); jclose(v); return id; } } jclose(v); return -1; } static vlong getalbumindex(SmugFid *sf, int i, char **keyp) { vlong id; Json *v; char *key; v = getalbums(sf); if(v == nil) return -1; if(i < 0 || i >= v->len){ jclose(v); return -1; } id = jint(jwalk(v->value[i], "id")); key = jstring(jwalk(v->value[i], "Key")); if(id < 0 || key == nil){ jclose(v); return -1; } if(keyp) *keyp = estrdup(key); jclose(v); return id; } static Json* getimages(SmugFid *sf, vlong albumid, char *albumkey) { char id[50]; Json *v, *w; snprint(id, sizeof id, "%lld", albumid); v = smug("smugmug.images.get", "AlbumID", id, "AlbumKey", albumkey, "NickName", nickname(sf->nickid), nil); w = jincref(jwalk(v, "Images")); jclose(v); return w; } static vlong getimageid(SmugFid *sf, char *name, char **keyp) { int i; vlong id; Json *v; char *p; char *key; id = strtol(name, &p, 10); if(*p != 0 || *name == 0) return -1; v = getimages(sf, sf->album, sf->albumkey); if(v == nil) return -1; for(i=0; ilen; i++){ if(jint(jwalk(v->value[i], "id")) == id){ key = jstring(jwalk(v->value[i], "Key")); if(key == nil){ jclose(v); return -1; } if(keyp) *keyp = estrdup(key); jclose(v); return id; } } jclose(v); return -1; } static Json* getimageinfo(SmugFid *sf, vlong imageid, char *imagekey) { char id[50]; Json *v, *w; snprint(id, sizeof id, "%lld", imageid); v = smug("smugmug.images.getInfo", "ImageID", id, "ImageKey", imagekey, "NickName", nickname(sf->nickid), nil); w = jincref(jwalk(v, "Image")); jclose(v); return w; } static Json* getimageexif(SmugFid *sf, vlong imageid, char *imagekey) { char id[50]; Json *v, *w; snprint(id, sizeof id, "%lld", imageid); v = smug("smugmug.images.getEXIF", "ImageID", id, "ImageKey", imagekey, "NickName", nickname(sf->nickid), nil); w = jincref(jwalk(v, "Image")); jclose(v); return w; } static vlong getimageindex(SmugFid *sf, int i, char **keyp) { vlong id; Json *v; char *key; v = getimages(sf, sf->album, sf->albumkey); if(v == nil) return -1; if(i < 0 || i >= v->len){ jclose(v); return -1; } id = jint(jwalk(v->value[i], "id")); key = jstring(jwalk(v->value[i], "Key")); if(id < 0 || key == nil){ jclose(v); return -1; } if(keyp) *keyp = estrdup(key); jclose(v); return id; } static char* categoryname(SmugFid *sf) { Json *v; char *s; v = getcategory(sf, sf->category); s = jstring(jwalk(v, "Name")); if(s) s = estrdup(s); jclose(v); return s; } static char* albumname(SmugFid *sf) { Json *v; char *s; v = getalbum(sf, sf->album, sf->albumkey); s = jstring(jwalk(v, "Title")); if(s) s = estrdup(s); jclose(v); return s; } static char* imagename(SmugFid *sf) { char *s; Json *v; v = getimageinfo(sf, sf->image, sf->imagekey); s = jstring(jwalk(v, "FileName")); if(s && s[0]) s = estrdup(s); else s = smprint("%lld.jpg", sf->image); // TODO: use Format jclose(v); return s; } static vlong imagelength(SmugFid *sf) { vlong length; Json *v; v = getimageinfo(sf, sf->image, sf->imagekey); length = jint(jwalk(v, "Size")); jclose(v); return length; } static struct { char *key; char *name; } urls[] = { "AlbumURL", "album", "TinyURL", "tiny", "ThumbURL", "thumb", "SmallURL", "small", "MediumURL", "medium", "LargeURL", "large", "XLargeURL", "xlarge", "X2LargeURL", "xxlarge", "X3LargeURL", "xxxlarge", "OriginalURL", "original", }; static char* imageurl(SmugFid *sf) { Json *v; char *s; int i; v = getimageinfo(sf, sf->image, sf->imagekey); for(i=nelem(urls)-1; i>=0; i--){ if((s = jstring(jwalk(v, urls[i].key))) != nil){ s = estrdup(s); jclose(v); return s; } } jclose(v); return nil; } static char* imagestrings[] = { "Caption", "LastUpdated", "FileName", "MD5Sum", "Watermark", "Format", "Keywords", "Date", "AlbumURL", "TinyURL", "ThumbURL", "SmallURL", "MediumURL", "LargeURL", "XLargeURL", "X2LargeURL", "X3LargeURL", "OriginalURL", "Album", }; static char* albumbools[] = { "Public", "Printable", "Filenames", "Comments", "External", "Originals", "EXIF", "Share", "SortDirection", "FamilyEdit", "FriendEdit", "HideOwner", "CanRank", "Clean", "Geography", "SmugSearchable", "WorldSearchable", "SquareThumbs", "X2Larges", "X3Larges", }; static char* albumstrings[] = { "Description" "Keywords", "Password", "PasswordHint", "SortMethod", "LastUpdated", }; static char* readctl(SmugFid *sf) { int i; Upload *u; char *s; Json *v, *vv; Fmt fmt; v = nil; switch(sf->type){ case Qctl: return smprint("%#J\n", userinfo); case Quploads: fmtstrinit(&fmt); qlock(&uploadlock); for(i=0; ilk); if(u->ready && !u->uploaded && u->ref > 0) fmtprint(&fmt, "%s %s%s\n", u->name, u->file, u->uploading ? " [uploading]" : ""); unlock(&u->lk); } qunlock(&uploadlock); return fmtstrflush(&fmt); case Qnickctl: v = getcategories(sf); break; case Qcategoryctl: v = getcategory(sf, sf->category); break; case Qalbumctl: v = getimages(sf, sf->album, sf->albumkey); break; case Qalbumsctl: v = getalbums(sf); break; case Qimagectl: v = getimageinfo(sf, sf->image, sf->imagekey); break; case Qimageurl: v = getimageinfo(sf, sf->image, sf->imagekey); fmtstrinit(&fmt); for(i=0; iimage, sf->imagekey); break; case Qalbumsettings: v = getalbum(sf, sf->album, sf->albumkey); fmtstrinit(&fmt); fmtprint(&fmt, "id\t%lld\n", jint(jwalk(v, "id"))); // TODO: Category/id // TODO: SubCategory/id // TODO: Community/id // TODO: Template/id fmtprint(&fmt, "Highlight\t%lld\n", jint(jwalk(v, "Highlight/id"))); fmtprint(&fmt, "Position\t%lld\n", jint(jwalk(v, "Position"))); fmtprint(&fmt, "ImageCount\t%lld\n", jint(jwalk(v, "ImageCount"))); for(i=0; iimage, sf->imagekey); fmtstrinit(&fmt); fmtprint(&fmt, "id\t%lld\n", jint(jwalk(v, "id"))); fmtprint(&fmt, "Position\t%lld\n", jint(jwalk(v, "Position"))); fmtprint(&fmt, "Serial\t%lld\n", jint(jwalk(v, "Serial"))); fmtprint(&fmt, "Size\t%lld\t%lldx%lld\n", jint(jwalk(v, "Size")), jint(jwalk(v, "Width")), jint(jwalk(v, "Height"))); vv = jwalk(v, "Hidden"); fmtprint(&fmt, "Hidden\t%J\n", vv); // TODO: Album/id for(i=0; itype, sf->nickid); length = 0; mode = 0444; switch(sf->type){ case Qroot: name = "/"; q.type = QTDIR; break; case Qctl: name = "ctl"; mode |= 0222; break; case Quploads: name = "uploads"; s = readctl(sf); if(s){ length = strlen(s); free(s); } break; case Qrpclog: name = "rpclog"; break; case Qnick: name = nickname(sf->nickid); q.type = QTDIR; break; case Qnickctl: name = "ctl"; mode |= 0222; break; case Qalbums: name = "albums"; q.type = QTDIR; break; case Qalbumsctl: name = "ctl"; mode |= 0222; break; case Qcategory: name = categoryname(sf); freename = 1; q.path |= QPATH(0, sf->category << 8); q.type = QTDIR; break; case Qcategoryctl: name = "ctl"; mode |= 0222; q.path |= QPATH(0, sf->category << 8); break; case Qalbum: name = albumname(sf); freename = 1; q.path |= QPATH(0, sf->album << 8); q.type = QTDIR; break; case Qalbumctl: name = "ctl"; mode |= 0222; q.path |= QPATH(0, sf->album << 8); break; case Qalbumsettings: name = "settings"; mode |= 0222; q.path |= QPATH(0, sf->album << 8); break; case Quploadfile: q.path |= QPATH(0, (uintptr)sf->upload << 8); if(sf->upload){ Dir *dd; name = sf->upload->name; if(sf->upload->fd >= 0){ dd = dirfstat(sf->upload->fd); if(dd){ length = dd->length; free(dd); } }else length = sf->upload->length; if(!sf->upload->ready) mode |= 0222; } break; case Qimage: name = smprint("%lld", sf->image); freename = 1; q.path |= QPATH(0, sf->image << 8); q.type = QTDIR; break; case Qimagectl: name = "ctl"; mode |= 0222; q.path |= QPATH(0, sf->image << 8); break; case Qimagesettings: name = "settings"; mode |= 0222; q.path |= QPATH(0, sf->image << 8); break; case Qimageexif: name = "exif"; q.path |= QPATH(0, sf->image << 8); break; case Qimageurl: name = "url"; q.path |= QPATH(0, sf->image << 8); break; case Qimagefile: name = imagename(sf); freename = 1; q.path |= QPATH(0, sf->image << 8); length = imagelength(sf); break; default: name = "?egreg"; q.path = 0; break; } if(name == nil){ name = "???"; freename = 0; } if(qid) *qid = q; if(dir){ memset(dir, 0, sizeof *dir); dir->name = estrdup9p(name); dir->muid = estrdup9p("muid"); mode |= q.type<<24; if(mode & DMDIR) mode |= 0755; dir->mode = mode; dir->uid = estrdup9p(uid); dir->gid = estrdup9p("smugfs"); dir->qid = q; dir->length = length; } if(freename) free(name); } static char* xwalk1(Fid *fid, char *name, Qid *qid) { int dotdot, i; vlong id; char *key; SmugFid *sf; char *x; Upload *u; dotdot = strcmp(name, "..") == 0; sf = fid->aux; switch(sf->type){ default: NotFound: return "file not found"; case Qroot: if(dotdot) break; if(strcmp(name, "ctl") == 0){ sf->type = Qctl; break; } if(strcmp(name, "uploads") == 0){ sf->type = Quploads; break; } if(strcmp(name, "rpclog") == 0){ sf->type = Qrpclog; break; } if((i = nickindex(name)) >= 0){ sf->nickid = i; sf->type = Qnick; break; } goto NotFound; case Qnick: if(dotdot){ sf->type = Qroot; sf->nickid = 0; break; } if(strcmp(name, "ctl") == 0){ sf->type = Qnickctl; break; } if(strcmp(name, "albums") == 0){ sf->category = -1; sf->type = Qalbums; break; } if((id = getcategoryid(sf, name)) >= 0){ sf->category = id; sf->type = Qcategory; break; } goto NotFound; case Qalbums: case Qcategory: if(dotdot){ sf->category = 0; sf->type = Qnick; break; } if(strcmp(name, "ctl") == 0){ sf->type++; break; } if((id = getalbumid(sf, name, &key)) >= 0){ sf->album = id; sf->albumkey = key; sf->type = Qalbum; break; } goto NotFound; case Qalbum: if(dotdot){ free(sf->albumkey); sf->albumkey = nil; sf->album = 0; if(sf->category == -1) sf->type = Qalbums; else sf->type = Qcategory; break; } if(strcmp(name, "ctl") == 0){ sf->type = Qalbumctl; break; } if(strcmp(name, "settings") == 0){ sf->type = Qalbumsettings; break; } if((id = getimageid(sf, name, &key)) >= 0){ sf->image = id; sf->imagekey = key; sf->type = Qimage; break; } if((u = getuploadname(sf, name)) != nil){ sf->upload = u; sf->type = Quploadfile; break; } goto NotFound; case Qimage: if(dotdot){ free(sf->imagekey); sf->imagekey = nil; sf->image = 0; sf->type = Qalbum; break; } if(strcmp(name, "ctl") == 0){ sf->type = Qimagectl; break; } if(strcmp(name, "url") == 0){ sf->type = Qimageurl; break; } if(strcmp(name, "settings") == 0){ sf->type = Qimagesettings; break; } if(strcmp(name, "exif") == 0){ sf->type = Qimageexif; break; } x = imagename(sf); if(x && strcmp(name, x) == 0){ free(x); sf->type = Qimagefile; break; } free(x); goto NotFound; } dostat(sf, qid, nil); fid->qid = *qid; return nil; } static int dodirgen(int i, Dir *d, void *v) { SmugFid *sf, xsf; char *key; vlong id; Upload *u; sf = v; xsf = *sf; if(i-- == 0){ xsf.type++; // ctl in every directory dostat(&xsf, nil, d); return 0; } switch(sf->type){ default: return -1; case Qroot: if(i-- == 0){ xsf.type = Qrpclog; dostat(&xsf, nil, d); return 0; } if(i < 0 || i >= nnick) return -1; xsf.type = Qnick; xsf.nickid = i; dostat(&xsf, nil, d); return 0; case Qnick: if(i-- == 0){ xsf.type = Qalbums; dostat(&xsf, nil, d); return 0; } if((id = getcategoryindex(sf, i)) < 0) return -1; xsf.type = Qcategory; xsf.category = id; dostat(&xsf, nil, d); return 0; case Qalbums: case Qcategory: if((id = getalbumindex(sf, i, &key)) < 0) return -1; xsf.type = Qalbum; xsf.album = id; xsf.albumkey = key; dostat(&xsf, nil, d); free(key); return 0; case Qalbum: if(i-- == 0){ xsf.type = Qalbumsettings; dostat(&xsf, nil, d); return 0; } if((u = getuploadindex(sf, &i)) != nil){ xsf.upload = u; xsf.type = Quploadfile; dostat(&xsf, nil, d); closeupload(u); return 0; } if((id = getimageindex(sf, i, &key)) < 0) return -1; xsf.type = Qimage; xsf.image = id; xsf.imagekey = key; dostat(&xsf, nil, d); free(key); return 0; case Qimage: if(i-- == 0){ xsf.type = Qimagefile; dostat(&xsf, nil, d); return 0; } if(i-- == 0){ xsf.type = Qimageexif; dostat(&xsf, nil, d); return 0; } if(i-- == 0){ xsf.type = Qimagesettings; dostat(&xsf, nil, d); return 0; } if(i-- == 0){ xsf.type = Qimageurl; dostat(&xsf, nil, d); return 0; } return -1; } } static void xstat(Req *r) { dostat(r->fid->aux, nil, &r->d); respond(r, nil); } static void xwstat(Req *r) { SmugFid *sf; Json *v; char *s; char strid[50]; sf = r->fid->aux; if(r->d.uid[0] || r->d.gid[0] || r->d.muid[0] || ~r->d.mode != 0 || ~r->d.atime != 0 || ~r->d.mtime != 0 || ~r->d.length != 0){ respond(r, "invalid wstat"); return; } if(r->d.name[0]){ switch(sf->type){ default: respond(r, "invalid wstat"); return; // TODO: rename category case Qalbum: snprint(strid, sizeof strid, "%lld", sf->album); v = ncsmug("smugmug.albums.changeSettings", "AlbumID", strid, "Title", r->d.name, nil); if(v == nil) responderrstr(r); else respond(r, nil); s = smprint("&AlbumID=%lld&", sf->album); jcacheflush(s); free(s); jcacheflush("smugmug.albums.get&"); return; } } respond(r, "invalid wstat"); } static void xattach(Req *r) { SmugFid *sf; sf = emalloc(sizeof *sf); r->fid->aux = sf; sf->type = Qroot; dostat(sf, &r->ofcall.qid, nil); r->fid->qid = r->ofcall.qid; respond(r, nil); } void xopen(Req *r) { SmugFid *sf; if((r->ifcall.mode&~OTRUNC) > 2){ respond(r, "permission denied"); return; } sf = r->fid->aux; switch(sf->type){ case Qctl: case Qnickctl: case Qalbumsctl: case Qcategoryctl: case Qalbumctl: case Qimagectl: case Qalbumsettings: case Qimagesettings: break; case Quploadfile: if(r->ifcall.mode != OREAD){ lock(&sf->upload->lk); if(sf->upload->ready){ unlock(&sf->upload->lk); respond(r, "permission denied"); return; } sf->upwriter = 1; sf->upload->nwriters++; unlock(&sf->upload->lk); } break; default: if(r->ifcall.mode != OREAD){ respond(r, "permission denied"); return; } break; } r->ofcall.qid = r->fid->qid; respond(r, nil); } void xcreate(Req *r) { SmugFid *sf; Json *v; vlong id; char strid[50], *key; Upload *u; sf = r->fid->aux; switch(sf->type){ case Qnick: // Create new category. if(!(r->ifcall.perm&DMDIR)) break; v = ncsmug("smugmug.categories.create", "Name", r->ifcall.name, nil); if(v == nil){ responderrstr(r); return; } id = jint(jwalk(v, "Category/id")); if(id < 0){ fprint(2, "Create category: %J\n", v); jclose(v); responderrstr(r); return; } sf->type = Qcategory; sf->category = id; jcacheflush("method=smugmug.users.getTree&"); jcacheflush("method=smugmug.categories.get&"); dostat(sf, &r->ofcall.qid, nil); respond(r, nil); return; case Qcategory: // Create new album. if(!(r->ifcall.perm&DMDIR)) break; snprint(strid, sizeof strid, "%lld", sf->category); // Start with most restrictive settings. v = ncsmug("smugmug.albums.create", "Title", r->ifcall.name, "CategoryID", strid, "Public", "0", "WorldSearchable", "0", "SmugSearchable", "0", nil); if(v == nil){ responderrstr(r); return; } id = jint(jwalk(v, "Album/id")); key = jstring(jwalk(v, "Album/Key")); if(id < 0 || key == nil){ fprint(2, "Create album: %J\n", v); jclose(v); responderrstr(r); return; } sf->type = Qalbum; sf->album = id; sf->albumkey = estrdup(key); jclose(v); jcacheflush("method=smugmug.users.getTree&"); dostat(sf, &r->ofcall.qid, nil); respond(r, nil); return; case Qalbum: // Upload image to album. if(r->ifcall.perm&DMDIR) break; u = newupload(sf, r->ifcall.name); if(u == nil){ responderrstr(r); return; } sf->upload = u; sf->upwriter = 1; sf->type = Quploadfile; dostat(sf, &r->ofcall.qid, nil); respond(r, nil); return; } respond(r, "permission denied"); } static int writetofd(Req *r, int fd) { int total, n; total = 0; while(total < r->ifcall.count){ n = pwrite(fd, (char*)r->ifcall.data+total, r->ifcall.count-total, r->ifcall.offset+total); if(n <= 0) return -1; total += n; } r->ofcall.count = r->ifcall.count; return 0; } static void readfromfd(Req *r, int fd) { int n; n = pread(fd, r->ofcall.data, r->ifcall.count, r->ifcall.offset); if(n < 0) n = 0; r->ofcall.count = n; } void xread(Req *r) { SmugFid *sf; char *data; int fd; HTTPHeader hdr; char *url; sf = r->fid->aux; r->ofcall.count = 0; switch(sf->type){ default: respond(r, "not implemented"); return; case Qroot: case Qnick: case Qalbums: case Qcategory: case Qalbum: case Qimage: dirread9p(r, dodirgen, sf); break; case Qrpclog: rpclogread(r); return; case Qctl: case Qnickctl: case Qalbumsctl: case Qcategoryctl: case Qalbumctl: case Qimagectl: case Qimageurl: case Qimageexif: case Quploads: case Qimagesettings: case Qalbumsettings: data = readctl(sf); readstr(r, data); free(data); break; case Qimagefile: url = imageurl(sf); if(url == nil || (fd = download(url, &hdr)) < 0){ free(url); responderrstr(r); return; } readfromfd(r, fd); free(url); close(fd); break; case Quploadfile: if(sf->upload) readfromfd(r, sf->upload->fd); break; } respond(r, nil); } void xwrite(Req *r) { int sync; char *s, *t, *p; Json *v; char strid[50]; SmugFid *sf; sf = r->fid->aux; r->ofcall.count = r->ifcall.count; sync = (r->ifcall.count==4 && memcmp(r->ifcall.data, "sync", 4) == 0); switch(sf->type){ case Qctl: if(sync){ jcacheflush(nil); respond(r, nil); return; } break; case Qnickctl: if(sync){ s = smprint("&NickName=%s&", nickname(sf->nickid)); jcacheflush(s); free(s); respond(r, nil); return; } break; case Qalbumsctl: case Qcategoryctl: jcacheflush("smugmug.categories.get"); break; case Qalbumctl: if(sync){ s = smprint("&AlbumID=%lld&", sf->album); jcacheflush(s); free(s); respond(r, nil); return; } break; case Qimagectl: if(sync){ s = smprint("&ImageID=%lld&", sf->image); jcacheflush(s); free(s); respond(r, nil); return; } break; case Quploadfile: if(sf->upload){ if(writetofd(r, sf->upload->fd) < 0){ responderrstr(r); return; } respond(r, nil); return; } break; case Qimagesettings: case Qalbumsettings: s = (char*)r->ifcall.data; // lib9p nul-terminated it t = strpbrk(s, " \r\t\n"); if(t == nil) t = ""; else{ *t++ = 0; while(*t == ' ' || *t == '\r' || *t == '\t' || *t == '\n') t++; } p = strchr(t, '\n'); if(p && p[1] == 0) *p = 0; else if(p){ respond(r, "newline in argument"); return; } if(sf->type == Qalbumsettings) goto Albumsettings; snprint(strid, sizeof strid, "%lld", sf->image); v = ncsmug("smugmug.images.changeSettings", "ImageID", strid, s, t, nil); if(v == nil) responderrstr(r); else respond(r, nil); s = smprint("&ImageID=%lld&", sf->image); jcacheflush(s); free(s); return; Albumsettings: snprint(strid, sizeof strid, "%lld", sf->album); v = ncsmug("smugmug.albums.changeSettings", "AlbumID", strid, s, t, nil); if(v == nil) responderrstr(r); else respond(r, nil); s = smprint("&AlbumID=%lld&", sf->album); jcacheflush(s); free(s); return; } respond(r, "invalid control message"); return; } void xremove(Req *r) { char id[100]; SmugFid *sf; Json *v; sf = r->fid->aux; switch(sf->type){ default: respond(r, "permission denied"); return; case Qcategoryctl: case Qalbumctl: case Qalbumsettings: case Qimagectl: case Qimagesettings: case Qimageexif: case Qimageurl: case Qimagefile: /* ignore remove request, but no error, so rm -r works */ /* you can pretend they get removed and immediately grow back! */ respond(r, nil); return; case Qcategory: v = getalbums(sf); if(v && v->len > 0){ respond(r, "directory not empty"); return; } snprint(id, sizeof id, "%lld", sf->category); v = ncsmug("smugmug.categories.delete", "CategoryID", id, nil); if(v == nil) responderrstr(r); else{ jclose(v); jcacheflush("smugmug.users.getTree"); jcacheflush("smugmug.categories.get"); respond(r, nil); } return; case Qalbum: v = getimages(sf, sf->album, sf->albumkey); if(v && v->len > 0){ respond(r, "directory not empty"); return; } snprint(id, sizeof id, "%lld", sf->album); v = ncsmug("smugmug.albums.delete", "AlbumID", id, nil); if(v == nil) responderrstr(r); else{ jclose(v); jcacheflush("smugmug.users.getTree"); jcacheflush("smugmug.categories.get"); jcacheflush("smugmug.albums.get"); respond(r, nil); } return; case Qimage: snprint(id, sizeof id, "%lld", sf->image); v = ncsmug("smugmug.images.delete", "ImageID", id, nil); if(v == nil) responderrstr(r); else{ jclose(v); snprint(id, sizeof id, "ImageID=%lld&", sf->image); jcacheflush(id); jcacheflush("smugmug.images.get&"); respond(r, nil); } return; } } void xflush(Req *r) { rpclogflush(r->oldreq); respond(r, nil); } Srv xsrv; void xinit(void) { xsrv.attach = xattach; xsrv.open = xopen; xsrv.create = xcreate; xsrv.read = xread; xsrv.stat = xstat; xsrv.walk1 = xwalk1; xsrv.clone = xclone; xsrv.destroyfid = xdestroyfid; xsrv.remove = xremove; xsrv.write = xwrite; xsrv.flush = xflush; xsrv.wstat = xwstat; }