| #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; i<nup; i++){ | 
 | 		u = up[i]; | 
 | 		lock(&u->lk); | 
 | 		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; i<nup; i++){ | 
 | 		u = up[i]; | 
 | 		lock(&u->lk); | 
 | 		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; i<nup; i++){ | 
 | 		u = up[i]; | 
 | 		lock(&u->lk); | 
 | 		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; i<nup; i++){ | 
 | 			u = up[i]; | 
 | 			lock(&u->lk); | 
 | 			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; i++) | 
 | 		if(strcmp(nick[i], name) == 0) | 
 | 			return i; | 
 | 	v = smug("smugmug.users.getTree", "NickName", name, nil); | 
 | 	if(v == nil) | 
 | 		return -1; | 
 | 	nick = erealloc(nick, (nnick+1)*sizeof nick[0]); | 
 | 	nick[nnick] = estrdup(name); | 
 | 	return nnick++; | 
 | } | 
 |  | 
 | char* | 
 | nickname(int i) | 
 | { | 
 | 	if(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; i<v->len; 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; i<v->len; 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; i<v->len; 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; i<v->len; 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; i<nup; i++){ | 
 | 			u = up[i]; | 
 | 			lock(&u->lk); | 
 | 			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; i<nelem(urls); i++) | 
 | 			if((s = jstring(jwalk(v, urls[i].key))) != nil) | 
 | 				fmtprint(&fmt, "%s %s\n", urls[i].name, s); | 
 | 		jclose(v); | 
 | 		return fmtstrflush(&fmt); | 
 | 	 | 
 | 	case Qimageexif: | 
 | 		v = getimageexif(sf, sf->image, 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; i<nelem(albumbools); i++){ | 
 | 			vv = jwalk(v, albumbools[i]); | 
 | 			if(vv) | 
 | 				fmtprint(&fmt, "%s\t%J\n", albumbools[i], vv); | 
 | 		} | 
 | 		for(i=0; i<nelem(albumstrings); i++){ | 
 | 			s = jstring(jwalk(v, albumstrings[i])); | 
 | 			if(s) | 
 | 				fmtprint(&fmt, "%s\t%s\n", albumstrings[i], s); | 
 | 		} | 
 | 		s = fmtstrflush(&fmt); | 
 | 		jclose(v); | 
 | 		return s; | 
 |  | 
 | 	case Qimagesettings: | 
 | 		v = getimageinfo(sf, sf->image, 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; i<nelem(imagestrings); i++){ | 
 | 			s = jstring(jwalk(v, imagestrings[i])); | 
 | 			if(s) | 
 | 				fmtprint(&fmt, "%s\t%s\n", imagestrings[i], s); | 
 | 		} | 
 | 		s = fmtstrflush(&fmt); | 
 | 		jclose(v); | 
 | 		return s; | 
 | 	} | 
 |  | 
 | 	if(v == nil) | 
 | 		return estrdup(""); | 
 | 	s = smprint("%#J\n", v); | 
 | 	jclose(v); | 
 | 	return s; | 
 | } | 
 |  | 
 |  | 
 | static void | 
 | dostat(SmugFid *sf, Qid *qid, Dir *dir) | 
 | { | 
 | 	Qid q; | 
 | 	char *name; | 
 | 	int freename; | 
 | 	ulong mode; | 
 | 	char *uid; | 
 | 	char *s; | 
 | 	vlong length; | 
 | 	 | 
 | 	memset(&q, 0, sizeof q); | 
 | 	name = nil; | 
 | 	freename = 0; | 
 | 	uid = "smugfs"; | 
 | 	q.type = 0; | 
 | 	q.vers = 0; | 
 | 	q.path = QPATH(sf->type, 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; | 
 | } |