blob: a57b96d11d58938578d99dfa505394a27cc7ffa0 [file] [log] [blame]
#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;
}