Blob


1 #include "a.h"
3 enum
4 {
5 Qroot = 0, // /smug/
6 Qctl, // /smug/ctl
7 Qrpclog, // /smug/rpclog
8 Quploads, // /smug/uploads
9 Qnick, // /smug/nick/
10 Qnickctl, // /smug/nick/ctl
11 Qalbums, // /smug/nick/albums/
12 Qalbumsctl, // /smug/nick/albums/ctl
13 Qcategory, // /smug/nick/Category/
14 Qcategoryctl, // /smug/nick/Category/ctl
15 Qalbum, // /smug/nick/Category/Album/
16 Qalbumctl, // /smug/nick/Category/Album/ctl
17 Qalbumsettings, // /smug/nick/Category/Album/settings
18 Quploadfile, // /smug/nick/Category/Album/upload/file.jpg
19 Qimage, // /smug/nick/Category/Album/Image/
20 Qimagectl, // /smug/nick/Category/Album/Image/ctl
21 Qimageexif, // /smug/nick/Category/Album/Image/exif
22 Qimagesettings, // /smug/nick/Category/Album/Image/settings
23 Qimageurl, // /smug/nick/Category/Album/Image/url
24 Qimagefile, // /smug/nick/Category/Album/Image/file.jpg
25 };
27 void
28 mylock(Lock *lk)
29 {
30 lock(lk);
31 fprint(2, "locked from %p\n", getcallerpc(&lk));
32 }
34 void
35 myunlock(Lock *lk)
36 {
37 unlock(lk);
38 fprint(2, "unlocked from %p\n", getcallerpc(&lk));
39 }
41 //#define lock mylock
42 //#define unlock myunlock
44 typedef struct Upload Upload;
46 typedef struct SmugFid SmugFid;
47 struct SmugFid
48 {
49 int type;
50 int nickid;
51 vlong category; // -1 for "albums"
52 vlong album;
53 char *albumkey;
54 vlong image;
55 char *imagekey;
56 Upload *upload;
57 int upwriter;
58 };
60 #define QTYPE(p) ((p)&0xFF)
61 #define QARG(p) ((p)>>8)
62 #define QPATH(p, q) ((p)|((q)<<8))
64 char **nick;
65 int nnick;
67 struct Upload
68 {
69 Lock lk;
70 int fd;
71 char *name;
72 char *file;
73 vlong album;
74 vlong length;
75 char *albumkey;
76 int size;
77 int ready;
78 int nwriters;
79 int uploaded;
80 int ref;
81 int uploading;
82 };
84 Upload **up;
85 int nup;
86 QLock uploadlock;
87 Rendez uploadrendez;
89 void uploader(void*);
91 Upload*
92 newupload(SmugFid *sf, char *name)
93 {
94 Upload *u;
95 int fd, i;
96 char tmp[] = "/var/tmp/smugfs.XXXXXX";
98 if((fd = opentemp(tmp, ORDWR)) < 0)
99 return nil;
100 qlock(&uploadlock);
101 for(i=0; i<nup; i++){
102 u = up[i];
103 lock(&u->lk);
104 if(u->ref == 0){
105 u->ref = 1;
106 goto Reuse;
108 unlock(&u->lk);
110 if(nup == 0){
111 uploadrendez.l = &uploadlock;
112 proccreate(uploader, nil, STACKSIZE);
114 u = emalloc(sizeof *u);
115 lock(&u->lk);
116 u->ref = 1;
117 up = erealloc(up, (nup+1)*sizeof up[0]);
118 up[nup++] = u;
119 Reuse:
120 qunlock(&uploadlock);
121 u->fd = fd;
122 u->name = estrdup(name);
123 u->file = estrdup(tmp);
124 u->album = sf->album;
125 u->albumkey = estrdup(sf->albumkey);
126 u->nwriters = 1;
127 unlock(&u->lk);
128 return u;
131 void
132 closeupload(Upload *u)
134 lock(&u->lk);
135 fprint(2, "close %p from %p: %d\n", u, getcallerpc(&u), u->ref);
136 if(--u->ref > 0){
137 unlock(&u->lk);
138 return;
140 if(u->ref < 0)
141 abort();
142 if(u->fd >= 0){
143 close(u->fd);
144 u->fd = -1;
146 if(u->name){
147 free(u->name);
148 u->name = nil;
150 if(u->file){
151 remove(u->file);
152 free(u->file);
153 u->file = nil;
155 u->album = 0;
156 if(u->albumkey){
157 free(u->albumkey);
158 u->albumkey = nil;
160 u->size = 0;
161 u->ready = 0;
162 u->nwriters = 0;
163 u->uploaded = 0;
164 u->uploading = 0;
165 u->length = 0;
166 unlock(&u->lk);
169 Upload*
170 getuploadindex(SmugFid *sf, int *index)
172 int i;
173 Upload *u;
175 qlock(&uploadlock);
176 for(i=0; i<nup; i++){
177 u = up[i];
178 lock(&u->lk);
179 if(u->ref > 0 && !u->uploaded && u->album == sf->album && (*index)-- == 0){
180 qunlock(&uploadlock);
181 u->ref++;
182 fprint(2, "bump %p from %p: %d\n", u, getcallerpc(&sf), u->ref);
183 unlock(&u->lk);
184 return u;
186 unlock(&u->lk);
188 qunlock(&uploadlock);
189 return nil;
192 Upload*
193 getuploadname(SmugFid *sf, char *name)
195 int i;
196 Upload *u;
198 qlock(&uploadlock);
199 for(i=0; i<nup; i++){
200 u = up[i];
201 lock(&u->lk);
202 if(u->ref > 0 && !u->uploaded && u->album == sf->album && strcmp(name, u->name) == 0){
203 qunlock(&uploadlock);
204 u->ref++;
205 fprint(2, "bump %p from %p: %d\n", u, getcallerpc(&sf), u->ref);
206 unlock(&u->lk);
207 return u;
209 unlock(&u->lk);
211 qunlock(&uploadlock);
212 return nil;
215 void doupload(Upload*);
217 void
218 uploader(void *v)
220 int i, did;
221 Upload *u;
223 qlock(&uploadlock);
224 for(;;){
225 did = 0;
226 for(i=0; i<nup; i++){
227 u = up[i];
228 lock(&u->lk);
229 if(u->ref > 0 && u->ready && !u->uploading && !u->uploaded){
230 u->uploading = 1;
231 unlock(&u->lk);
232 qunlock(&uploadlock);
233 doupload(u);
234 closeupload(u);
235 fprint(2, "done %d\n", u->ref);
236 did = 1;
237 qlock(&uploadlock);
238 }else
239 unlock(&u->lk);
241 if(!did)
242 rsleep(&uploadrendez);
246 void
247 kickupload(Upload *u)
249 Dir *d;
251 lock(&u->lk);
252 if((d = dirfstat(u->fd)) != nil)
253 u->length = d->length;
254 close(u->fd);
255 u->fd = -1;
256 u->ref++;
257 fprint(2, "kick %p from %p: %d\n", u, getcallerpc(&u), u->ref);
258 u->ready = 1;
259 unlock(&u->lk);
260 qlock(&uploadlock);
261 rwakeup(&uploadrendez);
262 qunlock(&uploadlock);
265 void
266 doupload(Upload *u)
268 Dir *d;
269 vlong datalen;
270 Fmt fmt;
271 char *req;
272 char buf[8192];
273 int n, total;
274 uchar digest[MD5dlen];
275 DigestState ds;
276 Json *jv;
278 if((u->fd = open(u->file, OREAD)) < 0){
279 fprint(2, "cannot reopen temporary file %s: %r\n", u->file);
280 return;
282 if((d = dirfstat(u->fd)) == nil){
283 fprint(2, "fstat: %r\n");
284 return;
286 datalen = d->length;
287 free(d);
289 memset(&ds, 0, sizeof ds);
290 seek(u->fd, 0, 0);
291 total = 0;
292 while((n = read(u->fd, buf, sizeof buf)) > 0){
293 md5((uchar*)buf, n, nil, &ds);
294 total += n;
296 if(total != datalen){
297 fprint(2, "bad total: %lld %lld\n", total, datalen);
298 return;
300 md5(nil, 0, digest, &ds);
302 fmtstrinit(&fmt);
303 fmtprint(&fmt, "PUT /%s HTTP/1.0\r\n", u->name);
304 fmtprint(&fmt, "Content-Length: %lld\r\n", datalen);
305 fmtprint(&fmt, "Content-MD5: %.16lH\r\n", digest);
306 fmtprint(&fmt, "X-Smug-SessionID: %s\r\n", sessid);
307 fmtprint(&fmt, "X-Smug-Version: %s\r\n", API_VERSION);
308 fmtprint(&fmt, "X-Smug-ResponseType: JSON\r\n");
309 // Can send X-Smug-ImageID instead to replace existing files.
310 fmtprint(&fmt, "X-Smug-AlbumID: %lld\r\n", u->album);
311 fmtprint(&fmt, "X-Smug-FileName: %s\r\n", u->name);
312 fmtprint(&fmt, "\r\n");
313 req = fmtstrflush(&fmt);
315 seek(u->fd, 0, 0);
316 jv = jsonupload(&http, UPLOAD_HOST, req, u->fd, datalen);
317 free(req);
318 if(jv == nil){
319 fprint(2, "upload: %r\n");
320 return;
323 close(u->fd);
324 remove(u->file);
325 free(u->file);
326 u->file = nil;
327 u->fd = -1;
328 u->uploaded = 1;
329 rpclog("uploaded: %J", jv);
330 jclose(jv);
333 int
334 nickindex(char *name)
336 int i;
337 Json *v;
339 for(i=0; i<nnick; i++)
340 if(strcmp(nick[i], name) == 0)
341 return i;
342 v = smug("smugmug.users.getTree", "NickName", name, nil);
343 if(v == nil)
344 return -1;
345 nick = erealloc(nick, (nnick+1)*sizeof nick[0]);
346 nick[nnick] = estrdup(name);
347 return nnick++;
350 char*
351 nickname(int i)
353 if(i < 0 || i >= nnick)
354 return nil;
355 return nick[i];
358 void
359 responderrstr(Req *r)
361 char err[ERRMAX];
363 rerrstr(err, sizeof err);
364 respond(r, err);
367 static char*
368 xclone(Fid *oldfid, Fid *newfid)
370 SmugFid *sf;
372 if(oldfid->aux == nil)
373 return nil;
375 sf = emalloc(sizeof *sf);
376 *sf = *(SmugFid*)oldfid->aux;
377 sf->upload = nil;
378 sf->upwriter = 0;
379 if(sf->albumkey)
380 sf->albumkey = estrdup(sf->albumkey);
381 if(sf->imagekey)
382 sf->imagekey = estrdup(sf->imagekey);
383 newfid->aux = sf;
384 return nil;
387 static void
388 xdestroyfid(Fid *fid)
390 SmugFid *sf;
392 sf = fid->aux;
393 free(sf->albumkey);
394 free(sf->imagekey);
395 if(sf->upload){
396 if(sf->upwriter && --sf->upload->nwriters == 0){
397 fprint(2, "should upload %s\n", sf->upload->name);
398 kickupload(sf->upload);
400 closeupload(sf->upload);
401 sf->upload = nil;
403 free(sf);
406 static Json*
407 getcategories(SmugFid *sf)
409 Json *v, *w;
411 v = smug("smugmug.categories.get", "NickName", nickname(sf->nickid), nil);
412 w = jincref(jwalk(v, "Categories"));
413 jclose(v);
414 return w;
417 static Json*
418 getcategorytree(SmugFid *sf)
420 Json *v, *w;
422 v = smug("smugmug.users.getTree", "NickName", nickname(sf->nickid), nil);
423 w = jincref(jwalk(v, "Categories"));
424 jclose(v);
425 return w;
428 static Json*
429 getcategory(SmugFid *sf, vlong id)
431 int i;
432 Json *v, *w;
434 v = getcategorytree(sf);
435 if(v == nil)
436 return nil;
437 for(i=0; i<v->len; i++){
438 if(jint(jwalk(v->value[i], "id")) == id){
439 w = jincref(v->value[i]);
440 jclose(v);
441 return w;
444 jclose(v);
445 return nil;
448 static vlong
449 getcategoryid(SmugFid *sf, char *name)
451 int i;
452 vlong id;
453 Json *v;
455 v = getcategories(sf);
456 if(v == nil)
457 return -1;
458 for(i=0; i<v->len; i++){
459 if(jstrcmp(jwalk(v->value[i], "Name"), name) == 0){
460 id = jint(jwalk(v->value[i], "id"));
461 if(id < 0){
462 jclose(v);
463 return -1;
465 jclose(v);
466 return id;
469 jclose(v);
470 return -1;
473 static vlong
474 getcategoryindex(SmugFid *sf, int i)
476 Json *v;
477 vlong id;
479 v = getcategories(sf);
480 if(v == nil)
481 return -1;
482 if(i < 0 || i >= v->len){
483 jclose(v);
484 return -1;
486 id = jint(jwalk(v->value[i], "id"));
487 jclose(v);
488 return id;
491 static Json*
492 getalbum(SmugFid *sf, vlong albumid, char *albumkey)
494 char id[50];
495 Json *v, *w;
497 snprint(id, sizeof id, "%lld", albumid);
498 v = smug("smugmug.albums.getInfo",
499 "AlbumID", id, "AlbumKey", albumkey,
500 "NickName", nickname(sf->nickid), nil);
501 w = jincref(jwalk(v, "Album"));
502 jclose(v);
503 return w;
506 static Json*
507 getalbums(SmugFid *sf)
509 Json *v, *w;
511 if(sf->category >= 0)
512 v = getcategory(sf, sf->category);
513 else
514 v = smug("smugmug.albums.get",
515 "NickName", nickname(sf->nickid), nil);
516 w = jincref(jwalk(v, "Albums"));
517 jclose(v);
518 return w;
521 static vlong
522 getalbumid(SmugFid *sf, char *name, char **keyp)
524 int i;
525 vlong id;
526 Json *v;
527 char *key;
529 v = getalbums(sf);
530 if(v == nil)
531 return -1;
532 for(i=0; i<v->len; i++){
533 if(jstrcmp(jwalk(v->value[i], "Title"), name) == 0){
534 id = jint(jwalk(v->value[i], "id"));
535 key = jstring(jwalk(v->value[i], "Key"));
536 if(id < 0 || key == nil){
537 jclose(v);
538 return -1;
540 if(keyp)
541 *keyp = estrdup(key);
542 jclose(v);
543 return id;
546 jclose(v);
547 return -1;
550 static vlong
551 getalbumindex(SmugFid *sf, int i, char **keyp)
553 vlong id;
554 Json *v;
555 char *key;
557 v = getalbums(sf);
558 if(v == nil)
559 return -1;
560 if(i < 0 || i >= v->len){
561 jclose(v);
562 return -1;
564 id = jint(jwalk(v->value[i], "id"));
565 key = jstring(jwalk(v->value[i], "Key"));
566 if(id < 0 || key == nil){
567 jclose(v);
568 return -1;
570 if(keyp)
571 *keyp = estrdup(key);
572 jclose(v);
573 return id;
576 static Json*
577 getimages(SmugFid *sf, vlong albumid, char *albumkey)
579 char id[50];
580 Json *v, *w;
582 snprint(id, sizeof id, "%lld", albumid);
583 v = smug("smugmug.images.get",
584 "AlbumID", id, "AlbumKey", albumkey,
585 "NickName", nickname(sf->nickid), nil);
586 w = jincref(jwalk(v, "Images"));
587 jclose(v);
588 return w;
591 static vlong
592 getimageid(SmugFid *sf, char *name, char **keyp)
594 int i;
595 vlong id;
596 Json *v;
597 char *p;
598 char *key;
600 id = strtol(name, &p, 10);
601 if(*p != 0 || *name == 0)
602 return -1;
604 v = getimages(sf, sf->album, sf->albumkey);
605 if(v == nil)
606 return -1;
607 for(i=0; i<v->len; i++){
608 if(jint(jwalk(v->value[i], "id")) == id){
609 key = jstring(jwalk(v->value[i], "Key"));
610 if(key == nil){
611 jclose(v);
612 return -1;
614 if(keyp)
615 *keyp = estrdup(key);
616 jclose(v);
617 return id;
620 jclose(v);
621 return -1;
624 static Json*
625 getimageinfo(SmugFid *sf, vlong imageid, char *imagekey)
627 char id[50];
628 Json *v, *w;
630 snprint(id, sizeof id, "%lld", imageid);
631 v = smug("smugmug.images.getInfo",
632 "ImageID", id, "ImageKey", imagekey,
633 "NickName", nickname(sf->nickid), nil);
634 w = jincref(jwalk(v, "Image"));
635 jclose(v);
636 return w;
639 static Json*
640 getimageexif(SmugFid *sf, vlong imageid, char *imagekey)
642 char id[50];
643 Json *v, *w;
645 snprint(id, sizeof id, "%lld", imageid);
646 v = smug("smugmug.images.getEXIF",
647 "ImageID", id, "ImageKey", imagekey,
648 "NickName", nickname(sf->nickid), nil);
649 w = jincref(jwalk(v, "Image"));
650 jclose(v);
651 return w;
654 static vlong
655 getimageindex(SmugFid *sf, int i, char **keyp)
657 vlong id;
658 Json *v;
659 char *key;
661 v = getimages(sf, sf->album, sf->albumkey);
662 if(v == nil)
663 return -1;
664 if(i < 0 || i >= v->len){
665 jclose(v);
666 return -1;
668 id = jint(jwalk(v->value[i], "id"));
669 key = jstring(jwalk(v->value[i], "Key"));
670 if(id < 0 || key == nil){
671 jclose(v);
672 return -1;
674 if(keyp)
675 *keyp = estrdup(key);
676 jclose(v);
677 return id;
680 static char*
681 categoryname(SmugFid *sf)
683 Json *v;
684 char *s;
686 v = getcategory(sf, sf->category);
687 s = jstring(jwalk(v, "Name"));
688 if(s)
689 s = estrdup(s);
690 jclose(v);
691 return s;
694 static char*
695 albumname(SmugFid *sf)
697 Json *v;
698 char *s;
700 v = getalbum(sf, sf->album, sf->albumkey);
701 s = jstring(jwalk(v, "Title"));
702 if(s)
703 s = estrdup(s);
704 jclose(v);
705 return s;
708 static char*
709 imagename(SmugFid *sf)
711 char *s;
712 Json *v;
714 v = getimageinfo(sf, sf->image, sf->imagekey);
715 s = jstring(jwalk(v, "FileName"));
716 if(s && s[0])
717 s = estrdup(s);
718 else
719 s = smprint("%lld.jpg", sf->image); // TODO: use Format
720 jclose(v);
721 return s;
724 static vlong
725 imagelength(SmugFid *sf)
727 vlong length;
728 Json *v;
730 v = getimageinfo(sf, sf->image, sf->imagekey);
731 length = jint(jwalk(v, "Size"));
732 jclose(v);
733 return length;
736 static struct {
737 char *key;
738 char *name;
739 } urls[] = {
740 "AlbumURL", "album",
741 "TinyURL", "tiny",
742 "ThumbURL", "thumb",
743 "SmallURL", "small",
744 "MediumURL", "medium",
745 "LargeURL", "large",
746 "XLargeURL", "xlarge",
747 "X2LargeURL", "xxlarge",
748 "X3LargeURL", "xxxlarge",
749 "OriginalURL", "original",
750 };
752 static char*
753 imageurl(SmugFid *sf)
755 Json *v;
756 char *s;
757 int i;
759 v = getimageinfo(sf, sf->image, sf->imagekey);
760 for(i=nelem(urls)-1; i>=0; i--){
761 if((s = jstring(jwalk(v, urls[i].key))) != nil){
762 s = estrdup(s);
763 jclose(v);
764 return s;
767 jclose(v);
768 return nil;
771 static char* imagestrings[] =
773 "Caption",
774 "LastUpdated",
775 "FileName",
776 "MD5Sum",
777 "Watermark",
778 "Format",
779 "Keywords",
780 "Date",
781 "AlbumURL",
782 "TinyURL",
783 "ThumbURL",
784 "SmallURL",
785 "MediumURL",
786 "LargeURL",
787 "XLargeURL",
788 "X2LargeURL",
789 "X3LargeURL",
790 "OriginalURL",
791 "Album",
792 };
794 static char* albumbools[] =
796 "Public",
797 "Printable",
798 "Filenames",
799 "Comments",
800 "External",
801 "Originals",
802 "EXIF",
803 "Share",
804 "SortDirection",
805 "FamilyEdit",
806 "FriendEdit",
807 "HideOwner",
808 "CanRank",
809 "Clean",
810 "Geography",
811 "SmugSearchable",
812 "WorldSearchable",
813 "SquareThumbs",
814 "X2Larges",
815 "X3Larges",
816 };
818 static char* albumstrings[] =
820 "Description"
821 "Keywords",
822 "Password",
823 "PasswordHint",
824 "SortMethod",
825 "LastUpdated",
826 };
828 static char*
829 readctl(SmugFid *sf)
831 int i;
832 Upload *u;
833 char *s;
834 Json *v, *vv;
835 Fmt fmt;
837 v = nil;
838 switch(sf->type){
839 case Qctl:
840 return smprint("%#J\n", userinfo);
842 case Quploads:
843 fmtstrinit(&fmt);
844 qlock(&uploadlock);
845 for(i=0; i<nup; i++){
846 u = up[i];
847 lock(&u->lk);
848 if(u->ready && !u->uploaded && u->ref > 0)
849 fmtprint(&fmt, "%s %s%s\n", u->name, u->file, u->uploading ? " [uploading]" : "");
850 unlock(&u->lk);
852 qunlock(&uploadlock);
853 return fmtstrflush(&fmt);
855 case Qnickctl:
856 v = getcategories(sf);
857 break;
859 case Qcategoryctl:
860 v = getcategory(sf, sf->category);
861 break;
863 case Qalbumctl:
864 v = getimages(sf, sf->album, sf->albumkey);
865 break;
867 case Qalbumsctl:
868 v = getalbums(sf);
869 break;
871 case Qimagectl:
872 v = getimageinfo(sf, sf->image, sf->imagekey);
873 break;
875 case Qimageurl:
876 v = getimageinfo(sf, sf->image, sf->imagekey);
877 fmtstrinit(&fmt);
878 for(i=0; i<nelem(urls); i++)
879 if((s = jstring(jwalk(v, urls[i].key))) != nil)
880 fmtprint(&fmt, "%s %s\n", urls[i].name, s);
881 jclose(v);
882 return fmtstrflush(&fmt);
884 case Qimageexif:
885 v = getimageexif(sf, sf->image, sf->imagekey);
886 break;
888 case Qalbumsettings:
889 v = getalbum(sf, sf->album, sf->albumkey);
890 fmtstrinit(&fmt);
891 fmtprint(&fmt, "id\t%lld\n", jint(jwalk(v, "id")));
892 // TODO: Category/id
893 // TODO: SubCategory/id
894 // TODO: Community/id
895 // TODO: Template/id
896 fmtprint(&fmt, "Highlight\t%lld\n", jint(jwalk(v, "Highlight/id")));
897 fmtprint(&fmt, "Position\t%lld\n", jint(jwalk(v, "Position")));
898 fmtprint(&fmt, "ImageCount\t%lld\n", jint(jwalk(v, "ImageCount")));
899 for(i=0; i<nelem(albumbools); i++){
900 vv = jwalk(v, albumbools[i]);
901 if(vv)
902 fmtprint(&fmt, "%s\t%J\n", albumbools[i], vv);
904 for(i=0; i<nelem(albumstrings); i++){
905 s = jstring(jwalk(v, albumstrings[i]));
906 if(s)
907 fmtprint(&fmt, "%s\t%s\n", albumstrings[i], s);
909 s = fmtstrflush(&fmt);
910 jclose(v);
911 return s;
913 case Qimagesettings:
914 v = getimageinfo(sf, sf->image, sf->imagekey);
915 fmtstrinit(&fmt);
916 fmtprint(&fmt, "id\t%lld\n", jint(jwalk(v, "id")));
917 fmtprint(&fmt, "Position\t%lld\n", jint(jwalk(v, "Position")));
918 fmtprint(&fmt, "Serial\t%lld\n", jint(jwalk(v, "Serial")));
919 fmtprint(&fmt, "Size\t%lld\t%lldx%lld\n",
920 jint(jwalk(v, "Size")),
921 jint(jwalk(v, "Width")),
922 jint(jwalk(v, "Height")));
923 vv = jwalk(v, "Hidden");
924 fmtprint(&fmt, "Hidden\t%J\n", vv);
925 // TODO: Album/id
926 for(i=0; i<nelem(imagestrings); i++){
927 s = jstring(jwalk(v, imagestrings[i]));
928 if(s)
929 fmtprint(&fmt, "%s\t%s\n", imagestrings[i], s);
931 s = fmtstrflush(&fmt);
932 jclose(v);
933 return s;
936 if(v == nil)
937 return estrdup("");
938 s = smprint("%#J\n", v);
939 jclose(v);
940 return s;
944 static void
945 dostat(SmugFid *sf, Qid *qid, Dir *dir)
947 Qid q;
948 char *name;
949 int freename;
950 ulong mode;
951 char *uid;
952 char *s;
953 vlong length;
955 memset(&q, 0, sizeof q);
956 name = nil;
957 freename = 0;
958 uid = "smugfs";
959 q.type = 0;
960 q.vers = 0;
961 q.path = QPATH(sf->type, sf->nickid);
962 length = 0;
963 mode = 0444;
965 switch(sf->type){
966 case Qroot:
967 name = "/";
968 q.type = QTDIR;
969 break;
970 case Qctl:
971 name = "ctl";
972 mode |= 0222;
973 break;
974 case Quploads:
975 name = "uploads";
976 s = readctl(sf);
977 if(s){
978 length = strlen(s);
979 free(s);
981 break;
982 case Qrpclog:
983 name = "rpclog";
984 break;
985 case Qnick:
986 name = nickname(sf->nickid);
987 q.type = QTDIR;
988 break;
989 case Qnickctl:
990 name = "ctl";
991 mode |= 0222;
992 break;
993 case Qalbums:
994 name = "albums";
995 q.type = QTDIR;
996 break;
997 case Qalbumsctl:
998 name = "ctl";
999 mode |= 0222;
1000 break;
1001 case Qcategory:
1002 name = categoryname(sf);
1003 freename = 1;
1004 q.path |= QPATH(0, sf->category << 8);
1005 q.type = QTDIR;
1006 break;
1007 case Qcategoryctl:
1008 name = "ctl";
1009 mode |= 0222;
1010 q.path |= QPATH(0, sf->category << 8);
1011 break;
1012 case Qalbum:
1013 name = albumname(sf);
1014 freename = 1;
1015 q.path |= QPATH(0, sf->album << 8);
1016 q.type = QTDIR;
1017 break;
1018 case Qalbumctl:
1019 name = "ctl";
1020 mode |= 0222;
1021 q.path |= QPATH(0, sf->album << 8);
1022 break;
1023 case Qalbumsettings:
1024 name = "settings";
1025 mode |= 0222;
1026 q.path |= QPATH(0, sf->album << 8);
1027 break;
1028 case Quploadfile:
1029 q.path |= QPATH(0, (uintptr)sf->upload << 8);
1030 if(sf->upload){
1031 Dir *dd;
1032 name = sf->upload->name;
1033 if(sf->upload->fd >= 0){
1034 dd = dirfstat(sf->upload->fd);
1035 if(dd){
1036 length = dd->length;
1037 free(dd);
1039 }else
1040 length = sf->upload->length;
1041 if(!sf->upload->ready)
1042 mode |= 0222;
1044 break;
1045 case Qimage:
1046 name = smprint("%lld", sf->image);
1047 freename = 1;
1048 q.path |= QPATH(0, sf->image << 8);
1049 q.type = QTDIR;
1050 break;
1051 case Qimagectl:
1052 name = "ctl";
1053 mode |= 0222;
1054 q.path |= QPATH(0, sf->image << 8);
1055 break;
1056 case Qimagesettings:
1057 name = "settings";
1058 mode |= 0222;
1059 q.path |= QPATH(0, sf->image << 8);
1060 break;
1061 case Qimageexif:
1062 name = "exif";
1063 q.path |= QPATH(0, sf->image << 8);
1064 break;
1065 case Qimageurl:
1066 name = "url";
1067 q.path |= QPATH(0, sf->image << 8);
1068 break;
1069 case Qimagefile:
1070 name = imagename(sf);
1071 freename = 1;
1072 q.path |= QPATH(0, sf->image << 8);
1073 length = imagelength(sf);
1074 break;
1075 default:
1076 name = "?egreg";
1077 q.path = 0;
1078 break;
1081 if(name == nil){
1082 name = "???";
1083 freename = 0;
1086 if(qid)
1087 *qid = q;
1088 if(dir){
1089 memset(dir, 0, sizeof *dir);
1090 dir->name = estrdup9p(name);
1091 dir->muid = estrdup9p("muid");
1092 mode |= q.type<<24;
1093 if(mode & DMDIR)
1094 mode |= 0555;
1095 dir->mode = mode;
1096 dir->uid = estrdup9p(uid);
1097 dir->gid = estrdup9p("smugfs");
1098 dir->qid = q;
1099 dir->length = length;
1101 if(freename)
1102 free(name);
1105 static char*
1106 xwalk1(Fid *fid, char *name, Qid *qid)
1108 int dotdot, i;
1109 vlong id;
1110 char *key;
1111 SmugFid *sf;
1112 char *x;
1113 Upload *u;
1115 dotdot = strcmp(name, "..") == 0;
1116 sf = fid->aux;
1117 switch(sf->type){
1118 default:
1119 NotFound:
1120 return "file not found";
1122 case Qroot:
1123 if(dotdot)
1124 break;
1125 if(strcmp(name, "ctl") == 0){
1126 sf->type = Qctl;
1127 break;
1129 if(strcmp(name, "uploads") == 0){
1130 sf->type = Quploads;
1131 break;
1133 if(strcmp(name, "rpclog") == 0){
1134 sf->type = Qrpclog;
1135 break;
1137 if((i = nickindex(name)) >= 0){
1138 sf->nickid = i;
1139 sf->type = Qnick;
1140 break;
1142 goto NotFound;
1144 case Qnick:
1145 if(dotdot){
1146 sf->type = Qroot;
1147 sf->nickid = 0;
1148 break;
1150 if(strcmp(name, "ctl") == 0){
1151 sf->type = Qnickctl;
1152 break;
1154 if(strcmp(name, "albums") == 0){
1155 sf->category = -1;
1156 sf->type = Qalbums;
1157 break;
1159 if((id = getcategoryid(sf, name)) >= 0){
1160 sf->category = id;
1161 sf->type = Qcategory;
1162 break;
1164 goto NotFound;
1166 case Qalbums:
1167 case Qcategory:
1168 if(dotdot){
1169 sf->category = 0;
1170 sf->type = Qnick;
1171 break;
1173 if(strcmp(name, "ctl") == 0){
1174 sf->type++;
1175 break;
1177 if((id = getalbumid(sf, name, &key)) >= 0){
1178 sf->album = id;
1179 sf->albumkey = key;
1180 sf->type = Qalbum;
1181 break;
1183 goto NotFound;
1185 case Qalbum:
1186 if(dotdot){
1187 free(sf->albumkey);
1188 sf->albumkey = nil;
1189 sf->album = 0;
1190 if(sf->category == -1)
1191 sf->type = Qalbums;
1192 else
1193 sf->type = Qcategory;
1194 break;
1196 if(strcmp(name, "ctl") == 0){
1197 sf->type = Qalbumctl;
1198 break;
1200 if(strcmp(name, "settings") == 0){
1201 sf->type = Qalbumsettings;
1202 break;
1204 if((id = getimageid(sf, name, &key)) >= 0){
1205 sf->image = id;
1206 sf->imagekey = key;
1207 sf->type = Qimage;
1208 break;
1210 if((u = getuploadname(sf, name)) != nil){
1211 sf->upload = u;
1212 sf->type = Quploadfile;
1213 break;
1215 goto NotFound;
1217 case Qimage:
1218 if(dotdot){
1219 free(sf->imagekey);
1220 sf->imagekey = nil;
1221 sf->image = 0;
1222 sf->type = Qalbum;
1223 break;
1225 if(strcmp(name, "ctl") == 0){
1226 sf->type = Qimagectl;
1227 break;
1229 if(strcmp(name, "url") == 0){
1230 sf->type = Qimageurl;
1231 break;
1233 if(strcmp(name, "settings") == 0){
1234 sf->type = Qimagesettings;
1235 break;
1237 if(strcmp(name, "exif") == 0){
1238 sf->type = Qimageexif;
1239 break;
1241 x = imagename(sf);
1242 if(x && strcmp(name, x) == 0){
1243 free(x);
1244 sf->type = Qimagefile;
1245 break;
1247 free(x);
1248 goto NotFound;
1250 dostat(sf, qid, nil);
1251 fid->qid = *qid;
1252 return nil;
1255 static int
1256 dodirgen(int i, Dir *d, void *v)
1258 SmugFid *sf, xsf;
1259 char *key;
1260 vlong id;
1261 Upload *u;
1263 sf = v;
1264 xsf = *sf;
1265 if(i-- == 0){
1266 xsf.type++; // ctl in every directory
1267 dostat(&xsf, nil, d);
1268 return 0;
1271 switch(sf->type){
1272 default:
1273 return -1;
1275 case Qroot:
1276 if(i-- == 0){
1277 xsf.type = Qrpclog;
1278 dostat(&xsf, nil, d);
1279 return 0;
1281 if(i < 0 || i >= nnick)
1282 return -1;
1283 xsf.type = Qnick;
1284 xsf.nickid = i;
1285 dostat(&xsf, nil, d);
1286 return 0;
1288 case Qnick:
1289 if(i-- == 0){
1290 xsf.type = Qalbums;
1291 dostat(&xsf, nil, d);
1292 return 0;
1294 if((id = getcategoryindex(sf, i)) < 0)
1295 return -1;
1296 xsf.type = Qcategory;
1297 xsf.category = id;
1298 dostat(&xsf, nil, d);
1299 return 0;
1301 case Qalbums:
1302 case Qcategory:
1303 if((id = getalbumindex(sf, i, &key)) < 0)
1304 return -1;
1305 xsf.type = Qalbum;
1306 xsf.album = id;
1307 xsf.albumkey = key;
1308 dostat(&xsf, nil, d);
1309 free(key);
1310 return 0;
1312 case Qalbum:
1313 if(i-- == 0){
1314 xsf.type = Qalbumsettings;
1315 dostat(&xsf, nil, d);
1316 return 0;
1318 if((u = getuploadindex(sf, &i)) != nil){
1319 xsf.upload = u;
1320 xsf.type = Quploadfile;
1321 dostat(&xsf, nil, d);
1322 closeupload(u);
1323 return 0;
1325 if((id = getimageindex(sf, i, &key)) < 0)
1326 return -1;
1327 xsf.type = Qimage;
1328 xsf.image = id;
1329 xsf.imagekey = key;
1330 dostat(&xsf, nil, d);
1331 free(key);
1332 return 0;
1334 case Qimage:
1335 if(i-- == 0){
1336 xsf.type = Qimagefile;
1337 dostat(&xsf, nil, d);
1338 return 0;
1340 if(i-- == 0){
1341 xsf.type = Qimageexif;
1342 dostat(&xsf, nil, d);
1343 return 0;
1345 if(i-- == 0){
1346 xsf.type = Qimagesettings;
1347 dostat(&xsf, nil, d);
1348 return 0;
1350 if(i-- == 0){
1351 xsf.type = Qimageurl;
1352 dostat(&xsf, nil, d);
1353 return 0;
1355 return -1;
1359 static void
1360 xstat(Req *r)
1362 dostat(r->fid->aux, nil, &r->d);
1363 respond(r, nil);
1366 static void
1367 xwstat(Req *r)
1369 SmugFid *sf;
1370 Json *v;
1371 char *s;
1372 char strid[50];
1374 sf = r->fid->aux;
1375 if(r->d.uid[0] || r->d.gid[0] || r->d.muid[0] || ~r->d.mode != 0
1376 || ~r->d.atime != 0 || ~r->d.mtime != 0 || ~r->d.length != 0){
1377 respond(r, "invalid wstat");
1378 return;
1380 if(r->d.name[0]){
1381 switch(sf->type){
1382 default:
1383 respond(r, "invalid wstat");
1384 return;
1385 // TODO: rename category
1386 case Qalbum:
1387 snprint(strid, sizeof strid, "%lld", sf->album);
1388 v = ncsmug("smugmug.albums.changeSettings",
1389 "AlbumID", strid, "Title", r->d.name, nil);
1390 if(v == nil)
1391 responderrstr(r);
1392 else
1393 respond(r, nil);
1394 s = smprint("&AlbumID=%lld&", sf->album);
1395 jcacheflush(s);
1396 free(s);
1397 jcacheflush("smugmug.albums.get&");
1398 return;
1401 respond(r, "invalid wstat");
1404 static void
1405 xattach(Req *r)
1407 SmugFid *sf;
1409 sf = emalloc(sizeof *sf);
1410 r->fid->aux = sf;
1411 sf->type = Qroot;
1412 dostat(sf, &r->ofcall.qid, nil);
1413 r->fid->qid = r->ofcall.qid;
1414 respond(r, nil);
1417 void
1418 xopen(Req *r)
1420 SmugFid *sf;
1422 if((r->ifcall.mode&~OTRUNC) > 2){
1423 respond(r, "permission denied");
1424 return;
1427 sf = r->fid->aux;
1428 switch(sf->type){
1429 case Qctl:
1430 case Qnickctl:
1431 case Qalbumsctl:
1432 case Qcategoryctl:
1433 case Qalbumctl:
1434 case Qimagectl:
1435 case Qalbumsettings:
1436 case Qimagesettings:
1437 break;
1439 case Quploadfile:
1440 if(r->ifcall.mode != OREAD){
1441 lock(&sf->upload->lk);
1442 if(sf->upload->ready){
1443 unlock(&sf->upload->lk);
1444 respond(r, "permission denied");
1445 return;
1447 sf->upwriter = 1;
1448 sf->upload->nwriters++;
1449 unlock(&sf->upload->lk);
1451 break;
1453 default:
1454 if(r->ifcall.mode != OREAD){
1455 respond(r, "permission denied");
1456 return;
1458 break;
1461 r->ofcall.qid = r->fid->qid;
1462 respond(r, nil);
1465 void
1466 xcreate(Req *r)
1468 SmugFid *sf;
1469 Json *v;
1470 vlong id;
1471 char strid[50], *key;
1472 Upload *u;
1474 sf = r->fid->aux;
1475 switch(sf->type){
1476 case Qnick:
1477 // Create new category.
1478 if(!(r->ifcall.perm&DMDIR))
1479 break;
1480 v = ncsmug("smugmug.categories.create",
1481 "Name", r->ifcall.name, nil);
1482 if(v == nil){
1483 responderrstr(r);
1484 return;
1486 id = jint(jwalk(v, "Category/id"));
1487 if(id < 0){
1488 fprint(2, "Create category: %J\n", v);
1489 jclose(v);
1490 responderrstr(r);
1491 return;
1493 sf->type = Qcategory;
1494 sf->category = id;
1495 jcacheflush("method=smugmug.users.getTree&");
1496 jcacheflush("method=smugmug.categories.get&");
1497 dostat(sf, &r->ofcall.qid, nil);
1498 respond(r, nil);
1499 return;
1501 case Qcategory:
1502 // Create new album.
1503 if(!(r->ifcall.perm&DMDIR))
1504 break;
1505 snprint(strid, sizeof strid, "%lld", sf->category);
1506 // Start with most restrictive settings.
1507 v = ncsmug("smugmug.albums.create",
1508 "Title", r->ifcall.name,
1509 "CategoryID", strid,
1510 "Public", "0",
1511 "WorldSearchable", "0",
1512 "SmugSearchable", "0",
1513 nil);
1514 if(v == nil){
1515 responderrstr(r);
1516 return;
1518 id = jint(jwalk(v, "Album/id"));
1519 key = jstring(jwalk(v, "Album/Key"));
1520 if(id < 0 || key == nil){
1521 fprint(2, "Create album: %J\n", v);
1522 jclose(v);
1523 responderrstr(r);
1524 return;
1526 sf->type = Qalbum;
1527 sf->album = id;
1528 sf->albumkey = estrdup(key);
1529 jclose(v);
1530 jcacheflush("method=smugmug.users.getTree&");
1531 dostat(sf, &r->ofcall.qid, nil);
1532 respond(r, nil);
1533 return;
1535 case Qalbum:
1536 // Upload image to album.
1537 if(r->ifcall.perm&DMDIR)
1538 break;
1539 u = newupload(sf, r->ifcall.name);
1540 if(u == nil){
1541 responderrstr(r);
1542 return;
1544 sf->upload = u;
1545 sf->upwriter = 1;
1546 sf->type = Quploadfile;
1547 dostat(sf, &r->ofcall.qid, nil);
1548 respond(r, nil);
1549 return;
1551 respond(r, "permission denied");
1554 static int
1555 writetofd(Req *r, int fd)
1557 int total, n;
1559 total = 0;
1560 while(total < r->ifcall.count){
1561 n = pwrite(fd, (char*)r->ifcall.data+total, r->ifcall.count-total, r->ifcall.offset+total);
1562 if(n <= 0)
1563 return -1;
1564 total += n;
1566 r->ofcall.count = r->ifcall.count;
1567 return 0;
1570 static void
1571 readfromfd(Req *r, int fd)
1573 int n;
1574 n = pread(fd, r->ofcall.data, r->ifcall.count, r->ifcall.offset);
1575 if(n < 0)
1576 n = 0;
1577 r->ofcall.count = n;
1580 void
1581 xread(Req *r)
1583 SmugFid *sf;
1584 char *data;
1585 int fd;
1586 HTTPHeader hdr;
1587 char *url;
1589 sf = r->fid->aux;
1590 r->ofcall.count = 0;
1591 switch(sf->type){
1592 default:
1593 respond(r, "not implemented");
1594 return;
1595 case Qroot:
1596 case Qnick:
1597 case Qalbums:
1598 case Qcategory:
1599 case Qalbum:
1600 case Qimage:
1601 dirread9p(r, dodirgen, sf);
1602 break;
1603 case Qrpclog:
1604 rpclogread(r);
1605 return;
1606 case Qctl:
1607 case Qnickctl:
1608 case Qalbumsctl:
1609 case Qcategoryctl:
1610 case Qalbumctl:
1611 case Qimagectl:
1612 case Qimageurl:
1613 case Qimageexif:
1614 case Quploads:
1615 case Qimagesettings:
1616 case Qalbumsettings:
1617 data = readctl(sf);
1618 readstr(r, data);
1619 free(data);
1620 break;
1621 case Qimagefile:
1622 url = imageurl(sf);
1623 if(url == nil || (fd = download(url, &hdr)) < 0){
1624 free(url);
1625 responderrstr(r);
1626 return;
1628 readfromfd(r, fd);
1629 free(url);
1630 close(fd);
1631 break;
1632 case Quploadfile:
1633 if(sf->upload)
1634 readfromfd(r, sf->upload->fd);
1635 break;
1637 respond(r, nil);
1640 void
1641 xwrite(Req *r)
1643 int sync;
1644 char *s, *t, *p;
1645 Json *v;
1646 char strid[50];
1647 SmugFid *sf;
1649 sf = r->fid->aux;
1650 r->ofcall.count = r->ifcall.count;
1651 sync = (r->ifcall.count==4 && memcmp(r->ifcall.data, "sync", 4) == 0);
1652 switch(sf->type){
1653 case Qctl:
1654 if(sync){
1655 jcacheflush(nil);
1656 respond(r, nil);
1657 return;
1659 break;
1660 case Qnickctl:
1661 if(sync){
1662 s = smprint("&NickName=%s&", nickname(sf->nickid));
1663 jcacheflush(s);
1664 free(s);
1665 respond(r, nil);
1666 return;
1668 break;
1669 case Qalbumsctl:
1670 case Qcategoryctl:
1671 jcacheflush("smugmug.categories.get");
1672 break;
1673 case Qalbumctl:
1674 if(sync){
1675 s = smprint("&AlbumID=%lld&", sf->album);
1676 jcacheflush(s);
1677 free(s);
1678 respond(r, nil);
1679 return;
1681 break;
1682 case Qimagectl:
1683 if(sync){
1684 s = smprint("&ImageID=%lld&", sf->image);
1685 jcacheflush(s);
1686 free(s);
1687 respond(r, nil);
1688 return;
1690 break;
1691 case Quploadfile:
1692 if(sf->upload){
1693 if(writetofd(r, sf->upload->fd) < 0){
1694 responderrstr(r);
1695 return;
1697 respond(r, nil);
1698 return;
1700 break;
1701 case Qimagesettings:
1702 case Qalbumsettings:
1703 s = (char*)r->ifcall.data; // lib9p nul-terminated it
1704 t = strpbrk(s, " \r\t\n");
1705 if(t == nil)
1706 t = "";
1707 else{
1708 *t++ = 0;
1709 while(*t == ' ' || *t == '\r' || *t == '\t' || *t == '\n')
1710 t++;
1712 p = strchr(t, '\n');
1713 if(p && p[1] == 0)
1714 *p = 0;
1715 else if(p){
1716 respond(r, "newline in argument");
1717 return;
1719 if(sf->type == Qalbumsettings)
1720 goto Albumsettings;
1721 snprint(strid, sizeof strid, "%lld", sf->image);
1722 v = ncsmug("smugmug.images.changeSettings",
1723 "ImageID", strid,
1724 s, t, nil);
1725 if(v == nil)
1726 responderrstr(r);
1727 else
1728 respond(r, nil);
1729 s = smprint("&ImageID=%lld&", sf->image);
1730 jcacheflush(s);
1731 free(s);
1732 return;
1733 Albumsettings:
1734 snprint(strid, sizeof strid, "%lld", sf->album);
1735 v = ncsmug("smugmug.albums.changeSettings",
1736 "AlbumID", strid, s, t, nil);
1737 if(v == nil)
1738 responderrstr(r);
1739 else
1740 respond(r, nil);
1741 s = smprint("&AlbumID=%lld&", sf->album);
1742 jcacheflush(s);
1743 free(s);
1744 return;
1746 respond(r, "invalid control message");
1747 return;
1750 void
1751 xremove(Req *r)
1753 char id[100];
1754 SmugFid *sf;
1755 Json *v;
1757 sf = r->fid->aux;
1758 switch(sf->type){
1759 default:
1760 respond(r, "permission denied");
1761 return;
1762 case Qcategoryctl:
1763 case Qalbumctl:
1764 case Qalbumsettings:
1765 case Qimagectl:
1766 case Qimagesettings:
1767 case Qimageexif:
1768 case Qimageurl:
1769 case Qimagefile:
1770 /* ignore remove request, but no error, so rm -r works */
1771 /* you can pretend they get removed and immediately grow back! */
1772 respond(r, nil);
1773 return;
1774 case Qcategory:
1775 v = getalbums(sf);
1776 if(v && v->len > 0){
1777 respond(r, "directory not empty");
1778 return;
1780 snprint(id, sizeof id, "%lld", sf->category);
1781 v = ncsmug("smugmug.categories.delete",
1782 "CategoryID", id, nil);
1783 if(v == nil)
1784 responderrstr(r);
1785 else{
1786 jclose(v);
1787 jcacheflush("smugmug.users.getTree");
1788 jcacheflush("smugmug.categories.get");
1789 respond(r, nil);
1791 return;
1792 case Qalbum:
1793 v = getimages(sf, sf->album, sf->albumkey);
1794 if(v && v->len > 0){
1795 respond(r, "directory not empty");
1796 return;
1798 snprint(id, sizeof id, "%lld", sf->album);
1799 v = ncsmug("smugmug.albums.delete",
1800 "AlbumID", id, nil);
1801 if(v == nil)
1802 responderrstr(r);
1803 else{
1804 jclose(v);
1805 jcacheflush("smugmug.users.getTree");
1806 jcacheflush("smugmug.categories.get");
1807 jcacheflush("smugmug.albums.get");
1808 respond(r, nil);
1810 return;
1812 case Qimage:
1813 snprint(id, sizeof id, "%lld", sf->image);
1814 v = ncsmug("smugmug.images.delete",
1815 "ImageID", id, nil);
1816 if(v == nil)
1817 responderrstr(r);
1818 else{
1819 jclose(v);
1820 snprint(id, sizeof id, "ImageID=%lld&", sf->image);
1821 jcacheflush(id);
1822 jcacheflush("smugmug.images.get&");
1823 respond(r, nil);
1825 return;
1829 void
1830 xflush(Req *r)
1832 rpclogflush(r->oldreq);
1833 respond(r, nil);
1836 Srv xsrv;
1838 void
1839 xinit(void)
1841 xsrv.attach = xattach;
1842 xsrv.open = xopen;
1843 xsrv.create = xcreate;
1844 xsrv.read = xread;
1845 xsrv.stat = xstat;
1846 xsrv.walk1 = xwalk1;
1847 xsrv.clone = xclone;
1848 xsrv.destroyfid = xdestroyfid;
1849 xsrv.remove = xremove;
1850 xsrv.write = xwrite;
1851 xsrv.flush = xflush;
1852 xsrv.wstat = xwstat;