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 if(--u->ref > 0){
136 unlock(&u->lk);
137 return;
139 if(u->ref < 0)
140 abort();
141 if(u->fd >= 0){
142 close(u->fd);
143 u->fd = -1;
145 if(u->name){
146 free(u->name);
147 u->name = nil;
149 if(u->file){
150 remove(u->file);
151 free(u->file);
152 u->file = nil;
154 u->album = 0;
155 if(u->albumkey){
156 free(u->albumkey);
157 u->albumkey = nil;
159 u->size = 0;
160 u->ready = 0;
161 u->nwriters = 0;
162 u->uploaded = 0;
163 u->uploading = 0;
164 u->length = 0;
165 unlock(&u->lk);
168 Upload*
169 getuploadindex(SmugFid *sf, int *index)
171 int i;
172 Upload *u;
174 qlock(&uploadlock);
175 for(i=0; i<nup; i++){
176 u = up[i];
177 lock(&u->lk);
178 if(u->ref > 0 && !u->uploaded && u->album == sf->album && (*index)-- == 0){
179 qunlock(&uploadlock);
180 u->ref++;
181 unlock(&u->lk);
182 return u;
184 unlock(&u->lk);
186 qunlock(&uploadlock);
187 return nil;
190 Upload*
191 getuploadname(SmugFid *sf, char *name)
193 int i;
194 Upload *u;
196 qlock(&uploadlock);
197 for(i=0; i<nup; i++){
198 u = up[i];
199 lock(&u->lk);
200 if(u->ref > 0 && !u->uploaded && u->album == sf->album && strcmp(name, u->name) == 0){
201 qunlock(&uploadlock);
202 u->ref++;
203 unlock(&u->lk);
204 return u;
206 unlock(&u->lk);
208 qunlock(&uploadlock);
209 return nil;
212 void doupload(Upload*);
214 void
215 uploader(void *v)
217 int i, did;
218 Upload *u;
220 qlock(&uploadlock);
221 for(;;){
222 did = 0;
223 for(i=0; i<nup; i++){
224 u = up[i];
225 lock(&u->lk);
226 if(u->ref > 0 && u->ready && !u->uploading && !u->uploaded){
227 u->uploading = 1;
228 unlock(&u->lk);
229 qunlock(&uploadlock);
230 doupload(u);
231 closeupload(u);
232 did = 1;
233 qlock(&uploadlock);
234 }else
235 unlock(&u->lk);
237 if(!did)
238 rsleep(&uploadrendez);
242 void
243 kickupload(Upload *u)
245 Dir *d;
247 lock(&u->lk);
248 if((d = dirfstat(u->fd)) != nil)
249 u->length = d->length;
250 close(u->fd);
251 u->fd = -1;
252 u->ref++;
253 u->ready = 1;
254 unlock(&u->lk);
255 qlock(&uploadlock);
256 rwakeup(&uploadrendez);
257 qunlock(&uploadlock);
260 void
261 doupload(Upload *u)
263 Dir *d;
264 vlong datalen;
265 Fmt fmt;
266 char *req;
267 char buf[8192];
268 int n, total;
269 uchar digest[MD5dlen];
270 DigestState ds;
271 Json *jv;
273 if((u->fd = open(u->file, OREAD)) < 0){
274 fprint(2, "cannot reopen temporary file %s: %r\n", u->file);
275 return;
277 if((d = dirfstat(u->fd)) == nil){
278 fprint(2, "fstat: %r\n");
279 return;
281 datalen = d->length;
282 free(d);
284 memset(&ds, 0, sizeof ds);
285 seek(u->fd, 0, 0);
286 total = 0;
287 while((n = read(u->fd, buf, sizeof buf)) > 0){
288 md5((uchar*)buf, n, nil, &ds);
289 total += n;
291 if(total != datalen){
292 fprint(2, "bad total: %lld %lld\n", total, datalen);
293 return;
295 md5(nil, 0, digest, &ds);
297 fmtstrinit(&fmt);
298 fmtprint(&fmt, "PUT /%s HTTP/1.0\r\n", u->name);
299 fmtprint(&fmt, "Content-Length: %lld\r\n", datalen);
300 fmtprint(&fmt, "Content-MD5: %.16lH\r\n", digest);
301 fmtprint(&fmt, "X-Smug-SessionID: %s\r\n", sessid);
302 fmtprint(&fmt, "X-Smug-Version: %s\r\n", API_VERSION);
303 fmtprint(&fmt, "X-Smug-ResponseType: JSON\r\n");
304 // Can send X-Smug-ImageID instead to replace existing files.
305 fmtprint(&fmt, "X-Smug-AlbumID: %lld\r\n", u->album);
306 fmtprint(&fmt, "X-Smug-FileName: %s\r\n", u->name);
307 fmtprint(&fmt, "\r\n");
308 req = fmtstrflush(&fmt);
310 seek(u->fd, 0, 0);
311 jv = jsonupload(&http, UPLOAD_HOST, req, u->fd, datalen);
312 free(req);
313 if(jv == nil){
314 fprint(2, "upload: %r\n");
315 return;
318 close(u->fd);
319 remove(u->file);
320 free(u->file);
321 u->file = nil;
322 u->fd = -1;
323 u->uploaded = 1;
324 rpclog("uploaded: %J", jv);
325 jclose(jv);
328 int
329 nickindex(char *name)
331 int i;
332 Json *v;
334 for(i=0; i<nnick; i++)
335 if(strcmp(nick[i], name) == 0)
336 return i;
337 v = smug("smugmug.users.getTree", "NickName", name, nil);
338 if(v == nil)
339 return -1;
340 nick = erealloc(nick, (nnick+1)*sizeof nick[0]);
341 nick[nnick] = estrdup(name);
342 return nnick++;
345 char*
346 nickname(int i)
348 if(i < 0 || i >= nnick)
349 return nil;
350 return nick[i];
353 void
354 responderrstr(Req *r)
356 char err[ERRMAX];
358 rerrstr(err, sizeof err);
359 respond(r, err);
362 static char*
363 xclone(Fid *oldfid, Fid *newfid)
365 SmugFid *sf;
367 if(oldfid->aux == nil)
368 return nil;
370 sf = emalloc(sizeof *sf);
371 *sf = *(SmugFid*)oldfid->aux;
372 sf->upload = nil;
373 sf->upwriter = 0;
374 if(sf->albumkey)
375 sf->albumkey = estrdup(sf->albumkey);
376 if(sf->imagekey)
377 sf->imagekey = estrdup(sf->imagekey);
378 newfid->aux = sf;
379 return nil;
382 static void
383 xdestroyfid(Fid *fid)
385 SmugFid *sf;
387 sf = fid->aux;
388 free(sf->albumkey);
389 free(sf->imagekey);
390 if(sf->upload){
391 if(sf->upwriter && --sf->upload->nwriters == 0){
392 fprint(2, "should upload %s\n", sf->upload->name);
393 kickupload(sf->upload);
395 closeupload(sf->upload);
396 sf->upload = nil;
398 free(sf);
401 static Json*
402 getcategories(SmugFid *sf)
404 Json *v, *w;
406 v = smug("smugmug.categories.get", "NickName", nickname(sf->nickid), nil);
407 w = jincref(jwalk(v, "Categories"));
408 jclose(v);
409 return w;
412 static Json*
413 getcategorytree(SmugFid *sf)
415 Json *v, *w;
417 v = smug("smugmug.users.getTree", "NickName", nickname(sf->nickid), nil);
418 w = jincref(jwalk(v, "Categories"));
419 jclose(v);
420 return w;
423 static Json*
424 getcategory(SmugFid *sf, vlong id)
426 int i;
427 Json *v, *w;
429 v = getcategorytree(sf);
430 if(v == nil)
431 return nil;
432 for(i=0; i<v->len; i++){
433 if(jint(jwalk(v->value[i], "id")) == id){
434 w = jincref(v->value[i]);
435 jclose(v);
436 return w;
439 jclose(v);
440 return nil;
443 static vlong
444 getcategoryid(SmugFid *sf, char *name)
446 int i;
447 vlong id;
448 Json *v;
450 v = getcategories(sf);
451 if(v == nil)
452 return -1;
453 for(i=0; i<v->len; i++){
454 if(jstrcmp(jwalk(v->value[i], "Name"), name) == 0){
455 id = jint(jwalk(v->value[i], "id"));
456 if(id < 0){
457 jclose(v);
458 return -1;
460 jclose(v);
461 return id;
464 jclose(v);
465 return -1;
468 static vlong
469 getcategoryindex(SmugFid *sf, int i)
471 Json *v;
472 vlong id;
474 v = getcategories(sf);
475 if(v == nil)
476 return -1;
477 if(i < 0 || i >= v->len){
478 jclose(v);
479 return -1;
481 id = jint(jwalk(v->value[i], "id"));
482 jclose(v);
483 return id;
486 static Json*
487 getalbum(SmugFid *sf, vlong albumid, char *albumkey)
489 char id[50];
490 Json *v, *w;
492 snprint(id, sizeof id, "%lld", albumid);
493 v = smug("smugmug.albums.getInfo",
494 "AlbumID", id, "AlbumKey", albumkey,
495 "NickName", nickname(sf->nickid), nil);
496 w = jincref(jwalk(v, "Album"));
497 jclose(v);
498 return w;
501 static Json*
502 getalbums(SmugFid *sf)
504 Json *v, *w;
506 if(sf->category >= 0)
507 v = getcategory(sf, sf->category);
508 else
509 v = smug("smugmug.albums.get",
510 "NickName", nickname(sf->nickid), nil);
511 w = jincref(jwalk(v, "Albums"));
512 jclose(v);
513 return w;
516 static vlong
517 getalbumid(SmugFid *sf, char *name, char **keyp)
519 int i;
520 vlong id;
521 Json *v;
522 char *key;
524 v = getalbums(sf);
525 if(v == nil)
526 return -1;
527 for(i=0; i<v->len; i++){
528 if(jstrcmp(jwalk(v->value[i], "Title"), name) == 0){
529 id = jint(jwalk(v->value[i], "id"));
530 key = jstring(jwalk(v->value[i], "Key"));
531 if(id < 0 || key == nil){
532 jclose(v);
533 return -1;
535 if(keyp)
536 *keyp = estrdup(key);
537 jclose(v);
538 return id;
541 jclose(v);
542 return -1;
545 static vlong
546 getalbumindex(SmugFid *sf, int i, char **keyp)
548 vlong id;
549 Json *v;
550 char *key;
552 v = getalbums(sf);
553 if(v == nil)
554 return -1;
555 if(i < 0 || i >= v->len){
556 jclose(v);
557 return -1;
559 id = jint(jwalk(v->value[i], "id"));
560 key = jstring(jwalk(v->value[i], "Key"));
561 if(id < 0 || key == nil){
562 jclose(v);
563 return -1;
565 if(keyp)
566 *keyp = estrdup(key);
567 jclose(v);
568 return id;
571 static Json*
572 getimages(SmugFid *sf, vlong albumid, char *albumkey)
574 char id[50];
575 Json *v, *w;
577 snprint(id, sizeof id, "%lld", albumid);
578 v = smug("smugmug.images.get",
579 "AlbumID", id, "AlbumKey", albumkey,
580 "NickName", nickname(sf->nickid), nil);
581 w = jincref(jwalk(v, "Images"));
582 jclose(v);
583 return w;
586 static vlong
587 getimageid(SmugFid *sf, char *name, char **keyp)
589 int i;
590 vlong id;
591 Json *v;
592 char *p;
593 char *key;
595 id = strtol(name, &p, 10);
596 if(*p != 0 || *name == 0)
597 return -1;
599 v = getimages(sf, sf->album, sf->albumkey);
600 if(v == nil)
601 return -1;
602 for(i=0; i<v->len; i++){
603 if(jint(jwalk(v->value[i], "id")) == id){
604 key = jstring(jwalk(v->value[i], "Key"));
605 if(key == nil){
606 jclose(v);
607 return -1;
609 if(keyp)
610 *keyp = estrdup(key);
611 jclose(v);
612 return id;
615 jclose(v);
616 return -1;
619 static Json*
620 getimageinfo(SmugFid *sf, vlong imageid, char *imagekey)
622 char id[50];
623 Json *v, *w;
625 snprint(id, sizeof id, "%lld", imageid);
626 v = smug("smugmug.images.getInfo",
627 "ImageID", id, "ImageKey", imagekey,
628 "NickName", nickname(sf->nickid), nil);
629 w = jincref(jwalk(v, "Image"));
630 jclose(v);
631 return w;
634 static Json*
635 getimageexif(SmugFid *sf, vlong imageid, char *imagekey)
637 char id[50];
638 Json *v, *w;
640 snprint(id, sizeof id, "%lld", imageid);
641 v = smug("smugmug.images.getEXIF",
642 "ImageID", id, "ImageKey", imagekey,
643 "NickName", nickname(sf->nickid), nil);
644 w = jincref(jwalk(v, "Image"));
645 jclose(v);
646 return w;
649 static vlong
650 getimageindex(SmugFid *sf, int i, char **keyp)
652 vlong id;
653 Json *v;
654 char *key;
656 v = getimages(sf, sf->album, sf->albumkey);
657 if(v == nil)
658 return -1;
659 if(i < 0 || i >= v->len){
660 jclose(v);
661 return -1;
663 id = jint(jwalk(v->value[i], "id"));
664 key = jstring(jwalk(v->value[i], "Key"));
665 if(id < 0 || key == nil){
666 jclose(v);
667 return -1;
669 if(keyp)
670 *keyp = estrdup(key);
671 jclose(v);
672 return id;
675 static char*
676 categoryname(SmugFid *sf)
678 Json *v;
679 char *s;
681 v = getcategory(sf, sf->category);
682 s = jstring(jwalk(v, "Name"));
683 if(s)
684 s = estrdup(s);
685 jclose(v);
686 return s;
689 static char*
690 albumname(SmugFid *sf)
692 Json *v;
693 char *s;
695 v = getalbum(sf, sf->album, sf->albumkey);
696 s = jstring(jwalk(v, "Title"));
697 if(s)
698 s = estrdup(s);
699 jclose(v);
700 return s;
703 static char*
704 imagename(SmugFid *sf)
706 char *s;
707 Json *v;
709 v = getimageinfo(sf, sf->image, sf->imagekey);
710 s = jstring(jwalk(v, "FileName"));
711 if(s && s[0])
712 s = estrdup(s);
713 else
714 s = smprint("%lld.jpg", sf->image); // TODO: use Format
715 jclose(v);
716 return s;
719 static vlong
720 imagelength(SmugFid *sf)
722 vlong length;
723 Json *v;
725 v = getimageinfo(sf, sf->image, sf->imagekey);
726 length = jint(jwalk(v, "Size"));
727 jclose(v);
728 return length;
731 static struct {
732 char *key;
733 char *name;
734 } urls[] = {
735 "AlbumURL", "album",
736 "TinyURL", "tiny",
737 "ThumbURL", "thumb",
738 "SmallURL", "small",
739 "MediumURL", "medium",
740 "LargeURL", "large",
741 "XLargeURL", "xlarge",
742 "X2LargeURL", "xxlarge",
743 "X3LargeURL", "xxxlarge",
744 "OriginalURL", "original",
745 };
747 static char*
748 imageurl(SmugFid *sf)
750 Json *v;
751 char *s;
752 int i;
754 v = getimageinfo(sf, sf->image, sf->imagekey);
755 for(i=nelem(urls)-1; i>=0; i--){
756 if((s = jstring(jwalk(v, urls[i].key))) != nil){
757 s = estrdup(s);
758 jclose(v);
759 return s;
762 jclose(v);
763 return nil;
766 static char* imagestrings[] =
768 "Caption",
769 "LastUpdated",
770 "FileName",
771 "MD5Sum",
772 "Watermark",
773 "Format",
774 "Keywords",
775 "Date",
776 "AlbumURL",
777 "TinyURL",
778 "ThumbURL",
779 "SmallURL",
780 "MediumURL",
781 "LargeURL",
782 "XLargeURL",
783 "X2LargeURL",
784 "X3LargeURL",
785 "OriginalURL",
786 "Album",
787 };
789 static char* albumbools[] =
791 "Public",
792 "Printable",
793 "Filenames",
794 "Comments",
795 "External",
796 "Originals",
797 "EXIF",
798 "Share",
799 "SortDirection",
800 "FamilyEdit",
801 "FriendEdit",
802 "HideOwner",
803 "CanRank",
804 "Clean",
805 "Geography",
806 "SmugSearchable",
807 "WorldSearchable",
808 "SquareThumbs",
809 "X2Larges",
810 "X3Larges",
811 };
813 static char* albumstrings[] =
815 "Description"
816 "Keywords",
817 "Password",
818 "PasswordHint",
819 "SortMethod",
820 "LastUpdated",
821 };
823 static char*
824 readctl(SmugFid *sf)
826 int i;
827 Upload *u;
828 char *s;
829 Json *v, *vv;
830 Fmt fmt;
832 v = nil;
833 switch(sf->type){
834 case Qctl:
835 return smprint("%#J\n", userinfo);
837 case Quploads:
838 fmtstrinit(&fmt);
839 qlock(&uploadlock);
840 for(i=0; i<nup; i++){
841 u = up[i];
842 lock(&u->lk);
843 if(u->ready && !u->uploaded && u->ref > 0)
844 fmtprint(&fmt, "%s %s%s\n", u->name, u->file, u->uploading ? " [uploading]" : "");
845 unlock(&u->lk);
847 qunlock(&uploadlock);
848 return fmtstrflush(&fmt);
850 case Qnickctl:
851 v = getcategories(sf);
852 break;
854 case Qcategoryctl:
855 v = getcategory(sf, sf->category);
856 break;
858 case Qalbumctl:
859 v = getimages(sf, sf->album, sf->albumkey);
860 break;
862 case Qalbumsctl:
863 v = getalbums(sf);
864 break;
866 case Qimagectl:
867 v = getimageinfo(sf, sf->image, sf->imagekey);
868 break;
870 case Qimageurl:
871 v = getimageinfo(sf, sf->image, sf->imagekey);
872 fmtstrinit(&fmt);
873 for(i=0; i<nelem(urls); i++)
874 if((s = jstring(jwalk(v, urls[i].key))) != nil)
875 fmtprint(&fmt, "%s %s\n", urls[i].name, s);
876 jclose(v);
877 return fmtstrflush(&fmt);
879 case Qimageexif:
880 v = getimageexif(sf, sf->image, sf->imagekey);
881 break;
883 case Qalbumsettings:
884 v = getalbum(sf, sf->album, sf->albumkey);
885 fmtstrinit(&fmt);
886 fmtprint(&fmt, "id\t%lld\n", jint(jwalk(v, "id")));
887 // TODO: Category/id
888 // TODO: SubCategory/id
889 // TODO: Community/id
890 // TODO: Template/id
891 fmtprint(&fmt, "Highlight\t%lld\n", jint(jwalk(v, "Highlight/id")));
892 fmtprint(&fmt, "Position\t%lld\n", jint(jwalk(v, "Position")));
893 fmtprint(&fmt, "ImageCount\t%lld\n", jint(jwalk(v, "ImageCount")));
894 for(i=0; i<nelem(albumbools); i++){
895 vv = jwalk(v, albumbools[i]);
896 if(vv)
897 fmtprint(&fmt, "%s\t%J\n", albumbools[i], vv);
899 for(i=0; i<nelem(albumstrings); i++){
900 s = jstring(jwalk(v, albumstrings[i]));
901 if(s)
902 fmtprint(&fmt, "%s\t%s\n", albumstrings[i], s);
904 s = fmtstrflush(&fmt);
905 jclose(v);
906 return s;
908 case Qimagesettings:
909 v = getimageinfo(sf, sf->image, sf->imagekey);
910 fmtstrinit(&fmt);
911 fmtprint(&fmt, "id\t%lld\n", jint(jwalk(v, "id")));
912 fmtprint(&fmt, "Position\t%lld\n", jint(jwalk(v, "Position")));
913 fmtprint(&fmt, "Serial\t%lld\n", jint(jwalk(v, "Serial")));
914 fmtprint(&fmt, "Size\t%lld\t%lldx%lld\n",
915 jint(jwalk(v, "Size")),
916 jint(jwalk(v, "Width")),
917 jint(jwalk(v, "Height")));
918 vv = jwalk(v, "Hidden");
919 fmtprint(&fmt, "Hidden\t%J\n", vv);
920 // TODO: Album/id
921 for(i=0; i<nelem(imagestrings); i++){
922 s = jstring(jwalk(v, imagestrings[i]));
923 if(s)
924 fmtprint(&fmt, "%s\t%s\n", imagestrings[i], s);
926 s = fmtstrflush(&fmt);
927 jclose(v);
928 return s;
931 if(v == nil)
932 return estrdup("");
933 s = smprint("%#J\n", v);
934 jclose(v);
935 return s;
939 static void
940 dostat(SmugFid *sf, Qid *qid, Dir *dir)
942 Qid q;
943 char *name;
944 int freename;
945 ulong mode;
946 char *uid;
947 char *s;
948 vlong length;
950 memset(&q, 0, sizeof q);
951 name = nil;
952 freename = 0;
953 uid = "smugfs";
954 q.type = 0;
955 q.vers = 0;
956 q.path = QPATH(sf->type, sf->nickid);
957 length = 0;
958 mode = 0444;
960 switch(sf->type){
961 case Qroot:
962 name = "/";
963 q.type = QTDIR;
964 break;
965 case Qctl:
966 name = "ctl";
967 mode |= 0222;
968 break;
969 case Quploads:
970 name = "uploads";
971 s = readctl(sf);
972 if(s){
973 length = strlen(s);
974 free(s);
976 break;
977 case Qrpclog:
978 name = "rpclog";
979 break;
980 case Qnick:
981 name = nickname(sf->nickid);
982 q.type = QTDIR;
983 break;
984 case Qnickctl:
985 name = "ctl";
986 mode |= 0222;
987 break;
988 case Qalbums:
989 name = "albums";
990 q.type = QTDIR;
991 break;
992 case Qalbumsctl:
993 name = "ctl";
994 mode |= 0222;
995 break;
996 case Qcategory:
997 name = categoryname(sf);
998 freename = 1;
999 q.path |= QPATH(0, sf->category << 8);
1000 q.type = QTDIR;
1001 break;
1002 case Qcategoryctl:
1003 name = "ctl";
1004 mode |= 0222;
1005 q.path |= QPATH(0, sf->category << 8);
1006 break;
1007 case Qalbum:
1008 name = albumname(sf);
1009 freename = 1;
1010 q.path |= QPATH(0, sf->album << 8);
1011 q.type = QTDIR;
1012 break;
1013 case Qalbumctl:
1014 name = "ctl";
1015 mode |= 0222;
1016 q.path |= QPATH(0, sf->album << 8);
1017 break;
1018 case Qalbumsettings:
1019 name = "settings";
1020 mode |= 0222;
1021 q.path |= QPATH(0, sf->album << 8);
1022 break;
1023 case Quploadfile:
1024 q.path |= QPATH(0, (uintptr)sf->upload << 8);
1025 if(sf->upload){
1026 Dir *dd;
1027 name = sf->upload->name;
1028 if(sf->upload->fd >= 0){
1029 dd = dirfstat(sf->upload->fd);
1030 if(dd){
1031 length = dd->length;
1032 free(dd);
1034 }else
1035 length = sf->upload->length;
1036 if(!sf->upload->ready)
1037 mode |= 0222;
1039 break;
1040 case Qimage:
1041 name = smprint("%lld", sf->image);
1042 freename = 1;
1043 q.path |= QPATH(0, sf->image << 8);
1044 q.type = QTDIR;
1045 break;
1046 case Qimagectl:
1047 name = "ctl";
1048 mode |= 0222;
1049 q.path |= QPATH(0, sf->image << 8);
1050 break;
1051 case Qimagesettings:
1052 name = "settings";
1053 mode |= 0222;
1054 q.path |= QPATH(0, sf->image << 8);
1055 break;
1056 case Qimageexif:
1057 name = "exif";
1058 q.path |= QPATH(0, sf->image << 8);
1059 break;
1060 case Qimageurl:
1061 name = "url";
1062 q.path |= QPATH(0, sf->image << 8);
1063 break;
1064 case Qimagefile:
1065 name = imagename(sf);
1066 freename = 1;
1067 q.path |= QPATH(0, sf->image << 8);
1068 length = imagelength(sf);
1069 break;
1070 default:
1071 name = "?egreg";
1072 q.path = 0;
1073 break;
1076 if(name == nil){
1077 name = "???";
1078 freename = 0;
1081 if(qid)
1082 *qid = q;
1083 if(dir){
1084 memset(dir, 0, sizeof *dir);
1085 dir->name = estrdup9p(name);
1086 dir->muid = estrdup9p("muid");
1087 mode |= q.type<<24;
1088 if(mode & DMDIR)
1089 mode |= 0755;
1090 dir->mode = mode;
1091 dir->uid = estrdup9p(uid);
1092 dir->gid = estrdup9p("smugfs");
1093 dir->qid = q;
1094 dir->length = length;
1096 if(freename)
1097 free(name);
1100 static char*
1101 xwalk1(Fid *fid, char *name, Qid *qid)
1103 int dotdot, i;
1104 vlong id;
1105 char *key;
1106 SmugFid *sf;
1107 char *x;
1108 Upload *u;
1110 dotdot = strcmp(name, "..") == 0;
1111 sf = fid->aux;
1112 switch(sf->type){
1113 default:
1114 NotFound:
1115 return "file not found";
1117 case Qroot:
1118 if(dotdot)
1119 break;
1120 if(strcmp(name, "ctl") == 0){
1121 sf->type = Qctl;
1122 break;
1124 if(strcmp(name, "uploads") == 0){
1125 sf->type = Quploads;
1126 break;
1128 if(strcmp(name, "rpclog") == 0){
1129 sf->type = Qrpclog;
1130 break;
1132 if((i = nickindex(name)) >= 0){
1133 sf->nickid = i;
1134 sf->type = Qnick;
1135 break;
1137 goto NotFound;
1139 case Qnick:
1140 if(dotdot){
1141 sf->type = Qroot;
1142 sf->nickid = 0;
1143 break;
1145 if(strcmp(name, "ctl") == 0){
1146 sf->type = Qnickctl;
1147 break;
1149 if(strcmp(name, "albums") == 0){
1150 sf->category = -1;
1151 sf->type = Qalbums;
1152 break;
1154 if((id = getcategoryid(sf, name)) >= 0){
1155 sf->category = id;
1156 sf->type = Qcategory;
1157 break;
1159 goto NotFound;
1161 case Qalbums:
1162 case Qcategory:
1163 if(dotdot){
1164 sf->category = 0;
1165 sf->type = Qnick;
1166 break;
1168 if(strcmp(name, "ctl") == 0){
1169 sf->type++;
1170 break;
1172 if((id = getalbumid(sf, name, &key)) >= 0){
1173 sf->album = id;
1174 sf->albumkey = key;
1175 sf->type = Qalbum;
1176 break;
1178 goto NotFound;
1180 case Qalbum:
1181 if(dotdot){
1182 free(sf->albumkey);
1183 sf->albumkey = nil;
1184 sf->album = 0;
1185 if(sf->category == -1)
1186 sf->type = Qalbums;
1187 else
1188 sf->type = Qcategory;
1189 break;
1191 if(strcmp(name, "ctl") == 0){
1192 sf->type = Qalbumctl;
1193 break;
1195 if(strcmp(name, "settings") == 0){
1196 sf->type = Qalbumsettings;
1197 break;
1199 if((id = getimageid(sf, name, &key)) >= 0){
1200 sf->image = id;
1201 sf->imagekey = key;
1202 sf->type = Qimage;
1203 break;
1205 if((u = getuploadname(sf, name)) != nil){
1206 sf->upload = u;
1207 sf->type = Quploadfile;
1208 break;
1210 goto NotFound;
1212 case Qimage:
1213 if(dotdot){
1214 free(sf->imagekey);
1215 sf->imagekey = nil;
1216 sf->image = 0;
1217 sf->type = Qalbum;
1218 break;
1220 if(strcmp(name, "ctl") == 0){
1221 sf->type = Qimagectl;
1222 break;
1224 if(strcmp(name, "url") == 0){
1225 sf->type = Qimageurl;
1226 break;
1228 if(strcmp(name, "settings") == 0){
1229 sf->type = Qimagesettings;
1230 break;
1232 if(strcmp(name, "exif") == 0){
1233 sf->type = Qimageexif;
1234 break;
1236 x = imagename(sf);
1237 if(x && strcmp(name, x) == 0){
1238 free(x);
1239 sf->type = Qimagefile;
1240 break;
1242 free(x);
1243 goto NotFound;
1245 dostat(sf, qid, nil);
1246 fid->qid = *qid;
1247 return nil;
1250 static int
1251 dodirgen(int i, Dir *d, void *v)
1253 SmugFid *sf, xsf;
1254 char *key;
1255 vlong id;
1256 Upload *u;
1258 sf = v;
1259 xsf = *sf;
1260 if(i-- == 0){
1261 xsf.type++; // ctl in every directory
1262 dostat(&xsf, nil, d);
1263 return 0;
1266 switch(sf->type){
1267 default:
1268 return -1;
1270 case Qroot:
1271 if(i-- == 0){
1272 xsf.type = Qrpclog;
1273 dostat(&xsf, nil, d);
1274 return 0;
1276 if(i < 0 || i >= nnick)
1277 return -1;
1278 xsf.type = Qnick;
1279 xsf.nickid = i;
1280 dostat(&xsf, nil, d);
1281 return 0;
1283 case Qnick:
1284 if(i-- == 0){
1285 xsf.type = Qalbums;
1286 dostat(&xsf, nil, d);
1287 return 0;
1289 if((id = getcategoryindex(sf, i)) < 0)
1290 return -1;
1291 xsf.type = Qcategory;
1292 xsf.category = id;
1293 dostat(&xsf, nil, d);
1294 return 0;
1296 case Qalbums:
1297 case Qcategory:
1298 if((id = getalbumindex(sf, i, &key)) < 0)
1299 return -1;
1300 xsf.type = Qalbum;
1301 xsf.album = id;
1302 xsf.albumkey = key;
1303 dostat(&xsf, nil, d);
1304 free(key);
1305 return 0;
1307 case Qalbum:
1308 if(i-- == 0){
1309 xsf.type = Qalbumsettings;
1310 dostat(&xsf, nil, d);
1311 return 0;
1313 if((u = getuploadindex(sf, &i)) != nil){
1314 xsf.upload = u;
1315 xsf.type = Quploadfile;
1316 dostat(&xsf, nil, d);
1317 closeupload(u);
1318 return 0;
1320 if((id = getimageindex(sf, i, &key)) < 0)
1321 return -1;
1322 xsf.type = Qimage;
1323 xsf.image = id;
1324 xsf.imagekey = key;
1325 dostat(&xsf, nil, d);
1326 free(key);
1327 return 0;
1329 case Qimage:
1330 if(i-- == 0){
1331 xsf.type = Qimagefile;
1332 dostat(&xsf, nil, d);
1333 return 0;
1335 if(i-- == 0){
1336 xsf.type = Qimageexif;
1337 dostat(&xsf, nil, d);
1338 return 0;
1340 if(i-- == 0){
1341 xsf.type = Qimagesettings;
1342 dostat(&xsf, nil, d);
1343 return 0;
1345 if(i-- == 0){
1346 xsf.type = Qimageurl;
1347 dostat(&xsf, nil, d);
1348 return 0;
1350 return -1;
1354 static void
1355 xstat(Req *r)
1357 dostat(r->fid->aux, nil, &r->d);
1358 respond(r, nil);
1361 static void
1362 xwstat(Req *r)
1364 SmugFid *sf;
1365 Json *v;
1366 char *s;
1367 char strid[50];
1369 sf = r->fid->aux;
1370 if(r->d.uid[0] || r->d.gid[0] || r->d.muid[0] || ~r->d.mode != 0
1371 || ~r->d.atime != 0 || ~r->d.mtime != 0 || ~r->d.length != 0){
1372 respond(r, "invalid wstat");
1373 return;
1375 if(r->d.name[0]){
1376 switch(sf->type){
1377 default:
1378 respond(r, "invalid wstat");
1379 return;
1380 // TODO: rename category
1381 case Qalbum:
1382 snprint(strid, sizeof strid, "%lld", sf->album);
1383 v = ncsmug("smugmug.albums.changeSettings",
1384 "AlbumID", strid, "Title", r->d.name, nil);
1385 if(v == nil)
1386 responderrstr(r);
1387 else
1388 respond(r, nil);
1389 s = smprint("&AlbumID=%lld&", sf->album);
1390 jcacheflush(s);
1391 free(s);
1392 jcacheflush("smugmug.albums.get&");
1393 return;
1396 respond(r, "invalid wstat");
1399 static void
1400 xattach(Req *r)
1402 SmugFid *sf;
1404 sf = emalloc(sizeof *sf);
1405 r->fid->aux = sf;
1406 sf->type = Qroot;
1407 dostat(sf, &r->ofcall.qid, nil);
1408 r->fid->qid = r->ofcall.qid;
1409 respond(r, nil);
1412 void
1413 xopen(Req *r)
1415 SmugFid *sf;
1417 if((r->ifcall.mode&~OTRUNC) > 2){
1418 respond(r, "permission denied");
1419 return;
1422 sf = r->fid->aux;
1423 switch(sf->type){
1424 case Qctl:
1425 case Qnickctl:
1426 case Qalbumsctl:
1427 case Qcategoryctl:
1428 case Qalbumctl:
1429 case Qimagectl:
1430 case Qalbumsettings:
1431 case Qimagesettings:
1432 break;
1434 case Quploadfile:
1435 if(r->ifcall.mode != OREAD){
1436 lock(&sf->upload->lk);
1437 if(sf->upload->ready){
1438 unlock(&sf->upload->lk);
1439 respond(r, "permission denied");
1440 return;
1442 sf->upwriter = 1;
1443 sf->upload->nwriters++;
1444 unlock(&sf->upload->lk);
1446 break;
1448 default:
1449 if(r->ifcall.mode != OREAD){
1450 respond(r, "permission denied");
1451 return;
1453 break;
1456 r->ofcall.qid = r->fid->qid;
1457 respond(r, nil);
1460 void
1461 xcreate(Req *r)
1463 SmugFid *sf;
1464 Json *v;
1465 vlong id;
1466 char strid[50], *key;
1467 Upload *u;
1469 sf = r->fid->aux;
1470 switch(sf->type){
1471 case Qnick:
1472 // Create new category.
1473 if(!(r->ifcall.perm&DMDIR))
1474 break;
1475 v = ncsmug("smugmug.categories.create",
1476 "Name", r->ifcall.name, nil);
1477 if(v == nil){
1478 responderrstr(r);
1479 return;
1481 id = jint(jwalk(v, "Category/id"));
1482 if(id < 0){
1483 fprint(2, "Create category: %J\n", v);
1484 jclose(v);
1485 responderrstr(r);
1486 return;
1488 sf->type = Qcategory;
1489 sf->category = id;
1490 jcacheflush("method=smugmug.users.getTree&");
1491 jcacheflush("method=smugmug.categories.get&");
1492 dostat(sf, &r->ofcall.qid, nil);
1493 respond(r, nil);
1494 return;
1496 case Qcategory:
1497 // Create new album.
1498 if(!(r->ifcall.perm&DMDIR))
1499 break;
1500 snprint(strid, sizeof strid, "%lld", sf->category);
1501 // Start with most restrictive settings.
1502 v = ncsmug("smugmug.albums.create",
1503 "Title", r->ifcall.name,
1504 "CategoryID", strid,
1505 "Public", "0",
1506 "WorldSearchable", "0",
1507 "SmugSearchable", "0",
1508 nil);
1509 if(v == nil){
1510 responderrstr(r);
1511 return;
1513 id = jint(jwalk(v, "Album/id"));
1514 key = jstring(jwalk(v, "Album/Key"));
1515 if(id < 0 || key == nil){
1516 fprint(2, "Create album: %J\n", v);
1517 jclose(v);
1518 responderrstr(r);
1519 return;
1521 sf->type = Qalbum;
1522 sf->album = id;
1523 sf->albumkey = estrdup(key);
1524 jclose(v);
1525 jcacheflush("method=smugmug.users.getTree&");
1526 dostat(sf, &r->ofcall.qid, nil);
1527 respond(r, nil);
1528 return;
1530 case Qalbum:
1531 // Upload image to album.
1532 if(r->ifcall.perm&DMDIR)
1533 break;
1534 u = newupload(sf, r->ifcall.name);
1535 if(u == nil){
1536 responderrstr(r);
1537 return;
1539 sf->upload = u;
1540 sf->upwriter = 1;
1541 sf->type = Quploadfile;
1542 dostat(sf, &r->ofcall.qid, nil);
1543 respond(r, nil);
1544 return;
1546 respond(r, "permission denied");
1549 static int
1550 writetofd(Req *r, int fd)
1552 int total, n;
1554 total = 0;
1555 while(total < r->ifcall.count){
1556 n = pwrite(fd, (char*)r->ifcall.data+total, r->ifcall.count-total, r->ifcall.offset+total);
1557 if(n <= 0)
1558 return -1;
1559 total += n;
1561 r->ofcall.count = r->ifcall.count;
1562 return 0;
1565 static void
1566 readfromfd(Req *r, int fd)
1568 int n;
1569 n = pread(fd, r->ofcall.data, r->ifcall.count, r->ifcall.offset);
1570 if(n < 0)
1571 n = 0;
1572 r->ofcall.count = n;
1575 void
1576 xread(Req *r)
1578 SmugFid *sf;
1579 char *data;
1580 int fd;
1581 HTTPHeader hdr;
1582 char *url;
1584 sf = r->fid->aux;
1585 r->ofcall.count = 0;
1586 switch(sf->type){
1587 default:
1588 respond(r, "not implemented");
1589 return;
1590 case Qroot:
1591 case Qnick:
1592 case Qalbums:
1593 case Qcategory:
1594 case Qalbum:
1595 case Qimage:
1596 dirread9p(r, dodirgen, sf);
1597 break;
1598 case Qrpclog:
1599 rpclogread(r);
1600 return;
1601 case Qctl:
1602 case Qnickctl:
1603 case Qalbumsctl:
1604 case Qcategoryctl:
1605 case Qalbumctl:
1606 case Qimagectl:
1607 case Qimageurl:
1608 case Qimageexif:
1609 case Quploads:
1610 case Qimagesettings:
1611 case Qalbumsettings:
1612 data = readctl(sf);
1613 readstr(r, data);
1614 free(data);
1615 break;
1616 case Qimagefile:
1617 url = imageurl(sf);
1618 if(url == nil || (fd = download(url, &hdr)) < 0){
1619 free(url);
1620 responderrstr(r);
1621 return;
1623 readfromfd(r, fd);
1624 free(url);
1625 close(fd);
1626 break;
1627 case Quploadfile:
1628 if(sf->upload)
1629 readfromfd(r, sf->upload->fd);
1630 break;
1632 respond(r, nil);
1635 void
1636 xwrite(Req *r)
1638 int sync;
1639 char *s, *t, *p;
1640 Json *v;
1641 char strid[50];
1642 SmugFid *sf;
1644 sf = r->fid->aux;
1645 r->ofcall.count = r->ifcall.count;
1646 sync = (r->ifcall.count==4 && memcmp(r->ifcall.data, "sync", 4) == 0);
1647 switch(sf->type){
1648 case Qctl:
1649 if(sync){
1650 jcacheflush(nil);
1651 respond(r, nil);
1652 return;
1654 break;
1655 case Qnickctl:
1656 if(sync){
1657 s = smprint("&NickName=%s&", nickname(sf->nickid));
1658 jcacheflush(s);
1659 free(s);
1660 respond(r, nil);
1661 return;
1663 break;
1664 case Qalbumsctl:
1665 case Qcategoryctl:
1666 jcacheflush("smugmug.categories.get");
1667 break;
1668 case Qalbumctl:
1669 if(sync){
1670 s = smprint("&AlbumID=%lld&", sf->album);
1671 jcacheflush(s);
1672 free(s);
1673 respond(r, nil);
1674 return;
1676 break;
1677 case Qimagectl:
1678 if(sync){
1679 s = smprint("&ImageID=%lld&", sf->image);
1680 jcacheflush(s);
1681 free(s);
1682 respond(r, nil);
1683 return;
1685 break;
1686 case Quploadfile:
1687 if(sf->upload){
1688 if(writetofd(r, sf->upload->fd) < 0){
1689 responderrstr(r);
1690 return;
1692 respond(r, nil);
1693 return;
1695 break;
1696 case Qimagesettings:
1697 case Qalbumsettings:
1698 s = (char*)r->ifcall.data; // lib9p nul-terminated it
1699 t = strpbrk(s, " \r\t\n");
1700 if(t == nil)
1701 t = "";
1702 else{
1703 *t++ = 0;
1704 while(*t == ' ' || *t == '\r' || *t == '\t' || *t == '\n')
1705 t++;
1707 p = strchr(t, '\n');
1708 if(p && p[1] == 0)
1709 *p = 0;
1710 else if(p){
1711 respond(r, "newline in argument");
1712 return;
1714 if(sf->type == Qalbumsettings)
1715 goto Albumsettings;
1716 snprint(strid, sizeof strid, "%lld", sf->image);
1717 v = ncsmug("smugmug.images.changeSettings",
1718 "ImageID", strid,
1719 s, t, nil);
1720 if(v == nil)
1721 responderrstr(r);
1722 else
1723 respond(r, nil);
1724 s = smprint("&ImageID=%lld&", sf->image);
1725 jcacheflush(s);
1726 free(s);
1727 return;
1728 Albumsettings:
1729 snprint(strid, sizeof strid, "%lld", sf->album);
1730 v = ncsmug("smugmug.albums.changeSettings",
1731 "AlbumID", strid, s, t, nil);
1732 if(v == nil)
1733 responderrstr(r);
1734 else
1735 respond(r, nil);
1736 s = smprint("&AlbumID=%lld&", sf->album);
1737 jcacheflush(s);
1738 free(s);
1739 return;
1741 respond(r, "invalid control message");
1742 return;
1745 void
1746 xremove(Req *r)
1748 char id[100];
1749 SmugFid *sf;
1750 Json *v;
1752 sf = r->fid->aux;
1753 switch(sf->type){
1754 default:
1755 respond(r, "permission denied");
1756 return;
1757 case Qcategoryctl:
1758 case Qalbumctl:
1759 case Qalbumsettings:
1760 case Qimagectl:
1761 case Qimagesettings:
1762 case Qimageexif:
1763 case Qimageurl:
1764 case Qimagefile:
1765 /* ignore remove request, but no error, so rm -r works */
1766 /* you can pretend they get removed and immediately grow back! */
1767 respond(r, nil);
1768 return;
1769 case Qcategory:
1770 v = getalbums(sf);
1771 if(v && v->len > 0){
1772 respond(r, "directory not empty");
1773 return;
1775 snprint(id, sizeof id, "%lld", sf->category);
1776 v = ncsmug("smugmug.categories.delete",
1777 "CategoryID", id, nil);
1778 if(v == nil)
1779 responderrstr(r);
1780 else{
1781 jclose(v);
1782 jcacheflush("smugmug.users.getTree");
1783 jcacheflush("smugmug.categories.get");
1784 respond(r, nil);
1786 return;
1787 case Qalbum:
1788 v = getimages(sf, sf->album, sf->albumkey);
1789 if(v && v->len > 0){
1790 respond(r, "directory not empty");
1791 return;
1793 snprint(id, sizeof id, "%lld", sf->album);
1794 v = ncsmug("smugmug.albums.delete",
1795 "AlbumID", id, nil);
1796 if(v == nil)
1797 responderrstr(r);
1798 else{
1799 jclose(v);
1800 jcacheflush("smugmug.users.getTree");
1801 jcacheflush("smugmug.categories.get");
1802 jcacheflush("smugmug.albums.get");
1803 respond(r, nil);
1805 return;
1807 case Qimage:
1808 snprint(id, sizeof id, "%lld", sf->image);
1809 v = ncsmug("smugmug.images.delete",
1810 "ImageID", id, nil);
1811 if(v == nil)
1812 responderrstr(r);
1813 else{
1814 jclose(v);
1815 snprint(id, sizeof id, "ImageID=%lld&", sf->image);
1816 jcacheflush(id);
1817 jcacheflush("smugmug.images.get&");
1818 respond(r, nil);
1820 return;
1824 void
1825 xflush(Req *r)
1827 rpclogflush(r->oldreq);
1828 respond(r, nil);
1831 Srv xsrv;
1833 void
1834 xinit(void)
1836 xsrv.attach = xattach;
1837 xsrv.open = xopen;
1838 xsrv.create = xcreate;
1839 xsrv.read = xread;
1840 xsrv.stat = xstat;
1841 xsrv.walk1 = xwalk1;
1842 xsrv.clone = xclone;
1843 xsrv.destroyfid = xdestroyfid;
1844 xsrv.remove = xremove;
1845 xsrv.write = xwrite;
1846 xsrv.flush = xflush;
1847 xsrv.wstat = xwstat;