1 /*
2  * imagenote.c: save info about images. For pho, an image viewer.
3  *
4  * Copyright 2002,2007 by Akkana Peck.
5  * You are free to use or modify this code under the Gnu Public License.
6  */
7 
8 #include "pho.h"
9 
10 #include <string.h>
11 #include <stdio.h>
12 #include <stdlib.h>       /* for malloc() */
13 #include <glib.h>
14 #include <ctype.h>
15 #include <fcntl.h>
16 #include <unistd.h>    /* for write() */
17 
18 static char *sFlagFileList[NUM_NOTES];
19 
InitNotes()20 void InitNotes()
21 {
22     int i;
23     for (i=0; i<NUM_NOTES; ++i)
24         sFlagFileList[i] = 0;
25 }
26 
ToggleNoteFlag(PhoImage * img,int note)27 void ToggleNoteFlag(PhoImage* img, int note)
28 {
29     int bit = (1 << note);
30     if (img->noteFlags & bit)
31         img->noteFlags &= ~bit;
32     else
33         img->noteFlags |= bit;
34 
35     /* Update any dialogs which might be showing toggles */
36     SetInfoDialogToggle(note, (img->noteFlags & bit) != 0);
37     SetKeywordsDialogToggle(note, (img->noteFlags & bit) != 0);
38 }
39 
40 /* Guard against filenames which contain odd characters, like
41  * spaces or quotes. Returns allocated memory.
42  */
QuoteString(char * str)43 char *QuoteString(char *str)
44 {
45     int i;
46     char *newstr;
47 
48     /* look for a space or quote in str */
49     for (i = 0; str[i] != '\0'; i++)
50         if (isspace(str[i]) || (str[i] == '\"') || (str[i] == '\'')) {
51             GString *gstr = g_string_new("\"");
52             for (i = 0; str[i] != '\0'; i++) {
53                 if (str[i] == '\"')
54                     g_string_append(gstr, (gchar *)"\\\"");
55                 /*else if (str[i] == '\'')
56                   g_string_append(gstr, (gchar *)"\\\'");*/
57                 else
58                     g_string_append_c(gstr, (gchar)str[i]);
59             }
60 
61             g_string_append_c(gstr, '\"');
62 
63             newstr = gstr->str;
64             g_string_free(gstr, FALSE);
65             return newstr;
66         }
67 
68     /* If there are no spaces or quotes in str, return a copy of str */
69     return (char *)g_strdup((gchar *)str);
70 }
71 
AddImgToList(char ** strp,char * str)72 void AddImgToList(char** strp, char* str)
73 {
74     str = QuoteString(str);
75 
76     if (*strp)
77     {
78         int newsize = strlen(*strp) + strlen(str) + 2;
79         char* newstr = malloc(newsize);
80         if (!newstr) {
81             free(str);
82             *strp = 0;
83             return;
84         }
85         snprintf(newstr, newsize, "%s %s", *strp, str);
86         free(*strp);
87         *strp = newstr;
88     }
89     else
90         *strp = strdup(str);
91 
92     /* QuoteString allocated a copy, so free that now: */
93     free(str);
94 }
95 
96 
97 /* Captions:
98  * captions may be stored in one file per image, or they may
99  * all be in one file in lines like
100  * imgname: caption
101  * This is controlled by gCapFileFormat: if it has a %s in it,
102  * that will be replaced by the current image file name.
103  */
104 
GlobalCaptionFile()105 static int GlobalCaptionFile()
106 {
107     char* cp;
108 
109     /* For captions, are we going to put them all in one file,
110      * or in individual files? That's controlled by whether
111      * gCapFileFormat includes a '%s" in it, i.e., will it
112      * substitute the name of the image file inside caption filenames.
113      */
114     for (cp = gCapFileFormat; *cp != 0; ++cp) {
115         if (cp[0] == '%' && cp[1] == 's')
116             return 0;
117     }
118 
119     /* If we got throught he loop without seeing %s, then the
120      * caption file is global.
121      */
122     return 1;
123 }
124 
125 /* Return the appropriate caption file name for this image */
CapFileName(PhoImage * img)126 static char* CapFileName(PhoImage* img)
127 {
128     /* How much ram do we need for the caption filename? */
129     static char buf[BUFSIZ];
130     int caplength = snprintf(buf, BUFSIZ, gCapFileFormat, img->filename);
131     if (strncmp(img->filename, buf, caplength) == 0) {
132         fprintf(stderr,
133            "Caption filename expanded to same as image filename. Bailing!\n");
134         return 0;
135     }
136     return buf;
137 }
138 
139 /* Read any caption that might be in the caption file.
140  * If the caption file is global, though, we read the file once
141  * for the first image and cache them.
142  */
ReadCaption(PhoImage * img)143 void ReadCaption(PhoImage* img)
144 {
145     int i;
146     int capfile;
147     char* capfilename;
148 
149     static int sFirstTime = 1;
150     static int sGlobalCaptions = 0;
151     static char** sCaptionFileList = 0;
152     static char** sCaptionList = 0;
153     static int sNumCaptions = 0;
154 
155     if (sFirstTime) {
156         sFirstTime = 0;
157         sGlobalCaptions = GlobalCaptionFile();
158         if (sGlobalCaptions) {
159             /* Read the global file */
160             char line[10000];
161             int numlines = 0;
162 
163             FILE* capfile = fopen(gCapFileFormat, "r");
164 
165             if (!capfile)    /* No captions to read, nothing to do */
166                 return;
167 
168             /* Make a first pass through the file just to count lines */
169             while (fgets(line, sizeof line, capfile)) {
170                 char* colon = strchr(line, ':');
171                 if (colon)
172                     ++numlines;
173             }
174 
175             /* Now we can allocate space for the lists of files/captions */
176             sCaptionFileList = malloc(numlines * (sizeof (char*)));
177             sCaptionList = malloc(numlines * (sizeof (char*)));
178 
179             /* and loop through again to actually read the captions */
180             rewind(capfile);
181             while (fgets(line, sizeof line, capfile)) {
182                 char* cp;
183 
184                 /* Line should look like: imagename: blah blah */
185                 char* colon = strchr(line, ':');
186                 if (!colon) continue;
187 
188                 /* terminate the filename string */
189                 *colon = '\0';
190                 sCaptionFileList[sNumCaptions] = strdup(line);
191 
192                 /* Skip the colon and any spaces immediately after it */
193                 ++colon;
194                 while (*colon == ' ')
195                     ++colon;
196                 /* strip off the terminal newline */
197                 for (cp = colon; *cp != '\0'; ++cp)
198                     if (*cp == '\n' || *cp == '\r') {
199                         *cp = '\0';
200                         break;
201                     }
202 
203                 /* Now colon points to the beginning of the caption */
204                 sCaptionList[sNumCaptions] = strdup(colon);
205 
206                 ++sNumCaptions;
207             }
208             fclose(capfile);
209         }
210     }
211 
212     /* Now we've done the first-time reading of the file, if needed. */
213     if (sGlobalCaptions) {
214         for (i=0; i < sNumCaptions; ++i)
215             if (!strcmp(sCaptionFileList[i], img->filename)) {
216                 img->caption = sCaptionList[i];
217                 return;
218             }
219         img->caption = 0;
220         return;
221     }
222 
223     /* If we get here, caption files are per-image.
224      * So look for the appropriate caption file.
225      */
226 
227     capfilename = CapFileName(img);
228     if (!capfilename)
229         return;
230 
231     capfile = open(capfilename, O_RDONLY);
232     if (capfile < 0)
233         return;
234     printf("Reading caption from %s\n", capfilename);
235 
236 #define MAX_CAPTION 1023
237     img->caption = calloc(1, MAX_CAPTION);
238     if (!(img->caption)) {
239         perror("Couldn't allocate memory for caption");
240         return;
241     }
242     read(capfile, img->caption, MAX_CAPTION-1);
243     for (i=0; i < MAX_CAPTION && img->caption[i] != 0; ++i) {
244         if (img->caption[i] == '\n') {
245             img->caption[i] = ' ';
246         }
247     }
248     close(capfile);
249     if (gDebug)
250         fprintf(stderr, "Read caption file %s\n", capfilename);
251 }
252 
253 /* Finally, the routine that prints a summary to a file or stdout */
PrintNotes()254 void PrintNotes()
255 {
256     int i;
257     char *rot90=0, *rot180=0, *rot270=0, *rot0=0, *unmatchExif=0;
258     PhoImage *img;
259     FILE *capfile = 0;
260     int useGlobalCaptionFile = GlobalCaptionFile();
261 
262     /* Should free up memory here, e.g. for sFlagFileList,
263      * but since this is only called right before exit,
264      * it's never been allocated so it doesn't matter.
265      * If PrintNotes ever gets called except at exit,
266      * this will leak.
267      */
268 
269     img = gFirstImage;
270     while (img)
271     {
272         if (img->caption && img->caption[0] && gCapFileFormat) {
273             if (gDebug)
274                 printf("Caption %s: %s\n", img->filename, img->caption);
275 
276             /* If we have a global captions file and we haven't
277              * opened it yet, do so now.
278              */
279             if (useGlobalCaptionFile && gCapFileFormat && !capfile) {
280                 capfile = fopen(gCapFileFormat, "w");
281                 if (!capfile) {
282                     perror(gCapFileFormat);
283                     gCapFileFormat = 0;  /* don't try again to write to it */
284                 }
285             }
286 
287             if (capfile) {    /* One shared caption file */
288                 /* capfile is already open, so append to it: */
289                 fprintf(capfile, "%s: %s\n\n", img->filename, img->caption);
290 
291             } else if (gCapFileFormat) {   /* need individual caption files */
292                 char* capname = CapFileName(img);
293                 if (capname) {
294                     capfile = fopen(capname, "w");
295                     if (capfile) {
296                         fputs(img->caption, capfile);
297                         fclose(capfile);
298                         capfile = 0;
299                     }
300                     else
301                         perror(capname);
302                 }
303             }
304 	}
305         if (img->noteFlags)
306         {
307             int flag, j;
308             for (j=0, flag=1; j<NUM_NOTES; ++j, flag <<= 1)
309                 if (img->noteFlags & flag)
310                     AddImgToList(sFlagFileList+j, img->filename);
311         }
312 
313         switch (img->curRot)
314         {
315           case 90:
316               AddImgToList(&rot90, img->filename);
317               break;
318           case 180:
319               AddImgToList(&rot180, img->filename);
320               break;
321           case 270:
322           case -90:
323               AddImgToList(&rot270, img->filename);
324               break;
325           case 0:
326               /* If the image is rotated at zero but the EXIF says otherwise,
327                * we need to list that too:
328                */
329               if (img->exifRot != 0)
330                   AddImgToList(&rot0, img->filename);
331               break;
332           default:
333               break;
334         }
335 
336         /* If the user-chosen rotation doesn't match the EXIF,
337          * we need to know that too.
338          */
339         if (img->curRot != img->exifRot)
340             AddImgToList(&unmatchExif, img->filename);
341 
342         img = img->next;
343         /* Have we looped back to the beginning?*/
344         if (img == gFirstImage) break;
345     }
346 
347     if (capfile)
348         fclose(capfile);
349 
350     /* Now we've looped over all the structs, so we can print out
351      * the tables of rotation and notes.
352      */
353     if (rot90)
354         printf("\nRotate 90 (CW): %s\n", rot90);
355     if (rot270)
356         printf("\nRotate -90 (CCW): %s\n", rot270);
357     if (rot180)
358         printf("\nRotate 180: %s\n", rot180);
359     if (rot0)
360         printf("\nRotate 0 (wrong EXIF): %s\n", rot0);
361     if (unmatchExif)
362         printf("\nWrong EXIF: %s\n", unmatchExif);
363     for (i=0; i < NUM_NOTES; ++i)
364         if (sFlagFileList[i])
365         {
366             char* keyword = KeywordString(i);
367             if (keyword && *keyword)
368                 printf("\n%s: ", keyword);
369             else
370                 printf("\nNote %d: ", i);
371             printf("%s\n", sFlagFileList[i]);
372         }
373     printf("\n");
374 }
375