1 // This file is part of Golly.
2 // See docs/License.html for the copyright notice.
3 
4 #include <sys/time.h>   // for gettimeofday
5 #include <algorithm>    // for std::transform
6 #include <ctype.h>      // for tolower
7 
8 #include "lifepoll.h"   // for lifepoll
9 #include "util.h"       // for linereader
10 
11 #include "prefs.h"      // for allowbeep, tempdir
12 #include "utils.h"
13 
14 #ifdef ANDROID_GUI
15     #include "jnicalls.h"    // for AndroidWarning, AndroidBeep, UpdateStatus, etc
16 #endif
17 
18 #ifdef WEB_GUI
19     #include "webcalls.h"   // for WebWarning, WebBeep, UpdateStatus, etc
20 #endif
21 
22 #ifdef IOS_GUI
23     #import <AudioToolbox/AudioToolbox.h>   // for AudioServicesPlaySystemSound, etc
24     #import "PatternViewController.h"       // for UpdateStatus, etc
25 #endif
26 
27 // -----------------------------------------------------------------------------
28 
29 int event_checker = 0;      // if > 0 then we're in gollypoller.checkevents()
30 
31 // -----------------------------------------------------------------------------
32 
SetColor(gColor & color,unsigned char red,unsigned char green,unsigned char blue)33 void SetColor(gColor& color, unsigned char red, unsigned char green, unsigned char blue)
34 {
35     color.r = red;
36     color.g = green;
37     color.b = blue;
38 }
39 
40 // -----------------------------------------------------------------------------
41 
SetRect(gRect & rect,int x,int y,int width,int height)42 void SetRect(gRect& rect, int x, int y, int width, int height)
43 {
44     rect.x = x;
45     rect.y = y;
46     rect.width = width;
47     rect.height = height;
48 }
49 
50 // -----------------------------------------------------------------------------
51 
52 #ifdef IOS_GUI
53 
54 // need the following to make YesNo/Warning/Fatal dialogs modal:
55 
56 @interface ModalAlertDelegate : NSObject <UIAlertViewDelegate>
57 {
58     NSInteger returnButt;
59 }
60 
61 @property () NSInteger returnButt;
62 
63 - (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex;
64 
65 @end
66 
67 @implementation ModalAlertDelegate
68 
69 @synthesize returnButt;
70 
71 - (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
72 {
73     returnButt = buttonIndex;
74 }
75 
76 @end
77 
78 #endif // IOS_GUI
79 
80 // -----------------------------------------------------------------------------
81 
YesNo(const char * msg)82 bool YesNo(const char* msg)
83 {
84     Beep();
85 
86 #ifdef ANDROID_GUI
87     return AndroidYesNo(msg);
88 #endif
89 
90 #ifdef WEB_GUI
91     return WebYesNo(msg);
92 #endif
93 
94 #ifdef IOS_GUI
95     ModalAlertDelegate *md = [[ModalAlertDelegate alloc] init];
96     md.returnButt = -1;
97 
98     UIAlertView *a = [[UIAlertView alloc] initWithTitle:@"Warning"
99                                                 message:[NSString stringWithCString:msg encoding:NSUTF8StringEncoding]
100                                                delegate:md
101                                       cancelButtonTitle:@"No"
102                                       otherButtonTitles:@"Yes", nil];
103     [a show];
104 
105     // wait for user to hit button
106     while (md.returnButt == -1) {
107         event_checker++;
108         [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
109         event_checker--;
110     }
111 
112     return md.returnButt != 0;
113 #endif // IOS_GUI
114 }
115 
116 // -----------------------------------------------------------------------------
117 
Warning(const char * msg)118 void Warning(const char* msg)
119 {
120     Beep();
121 
122 #ifdef ANDROID_GUI
123     AndroidWarning(msg);
124 #endif
125 
126 #ifdef WEB_GUI
127     WebWarning(msg);
128 #endif
129 
130 #ifdef IOS_GUI
131     ModalAlertDelegate *md = [[ModalAlertDelegate alloc] init];
132     md.returnButt = -1;
133 
134     UIAlertView *a = [[UIAlertView alloc] initWithTitle:@"Warning"
135                                                 message:[NSString stringWithCString:msg encoding:NSUTF8StringEncoding]
136                                                delegate:md
137                                       cancelButtonTitle:@"OK"
138                                       otherButtonTitles:nil];
139     [a show];
140 
141     // wait for user to hit button
142     while (md.returnButt == -1) {
143         event_checker++;
144         [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
145         event_checker--;
146     }
147 #endif // IOS_GUI
148 }
149 
150 // -----------------------------------------------------------------------------
151 
Fatal(const char * msg)152 void Fatal(const char* msg)
153 {
154     Beep();
155 
156 #ifdef ANDROID_GUI
157     AndroidFatal(msg);
158 #endif
159 
160 #ifdef WEB_GUI
161     WebFatal(msg);
162 #endif
163 
164 #ifdef IOS_GUI
165     ModalAlertDelegate *md = [[ModalAlertDelegate alloc] init];
166     md.returnButt = -1;
167 
168     UIAlertView *a = [[UIAlertView alloc] initWithTitle:@"Fatal Error"
169                                                 message:[NSString stringWithCString:msg encoding:NSUTF8StringEncoding]
170                                                delegate:md
171                                       cancelButtonTitle:@"Quit"
172                                       otherButtonTitles:nil];
173     [a show];
174 
175     // wait for user to hit button
176     while (md.returnButt == -1) {
177         event_checker++;
178         [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
179         event_checker--;
180     }
181 
182     exit(1);
183 #endif // IOS_GUI
184 }
185 
186 // -----------------------------------------------------------------------------
187 
Beep()188 void Beep()
189 {
190     if (!allowbeep) return;
191 
192 #ifdef ANDROID_GUI
193     AndroidBeep();
194 #endif
195 
196 #ifdef WEB_GUI
197     WebBeep();
198 #endif
199 
200 #ifdef IOS_GUI
201     static SystemSoundID beepID = 0;
202     if (beepID == 0) {
203         // get the path to the sound file
204         NSString* path = [[NSBundle mainBundle] pathForResource:@"beep" ofType:@"aiff"];
205         if (path) {
206             NSURL* url = [NSURL fileURLWithPath:path];
207             OSStatus err = AudioServicesCreateSystemSoundID((__bridge CFURLRef)url, &beepID);
208             if (err == kAudioServicesNoError && beepID > 0) {
209                 // play the sound
210                 AudioServicesPlaySystemSound(beepID);
211             }
212         }
213     } else {
214         // assume we got the sound
215         AudioServicesPlaySystemSound(beepID);
216     }
217 #endif // IOS_GUI
218 }
219 
220 // -----------------------------------------------------------------------------
221 
TimeInSeconds()222 double TimeInSeconds()
223 {
224     struct timeval trec;
225     gettimeofday(&trec, 0);
226     return double(trec.tv_sec) + double(trec.tv_usec) / 1.0e6;
227 }
228 
229 // -----------------------------------------------------------------------------
230 
CreateTempFileName(const char * prefix)231 std::string CreateTempFileName(const char* prefix)
232 {
233     /*
234     std::string tmplate = tempdir;
235     tmplate += prefix;
236     tmplate += ".XXXXXX";
237     std::string path = mktemp((char*)tmplate.c_str());
238     */
239 
240     // simpler to ignore prefix and create /tmp/0, /tmp/1, /tmp/2, etc
241     char n[32];
242     static int nextname = 0;
243     sprintf(n, "%d", nextname++);
244     std::string path = tempdir + n;
245 
246     return path;
247 }
248 
249 // -----------------------------------------------------------------------------
250 
FileExists(const std::string & filepath)251 bool FileExists(const std::string& filepath)
252 {
253     FILE* f = fopen(filepath.c_str(), "r");
254     if (f) {
255         fclose(f);
256         return true;
257     } else {
258         return false;
259     }
260 }
261 
262 // -----------------------------------------------------------------------------
263 
RemoveFile(const std::string & filepath)264 void RemoveFile(const std::string& filepath)
265 {
266 #ifdef ANDROID_GUI
267     AndroidRemoveFile(filepath);
268 #endif
269 
270 #ifdef WEB_GUI
271     WebRemoveFile(filepath);
272 #endif
273 
274 #ifdef IOS_GUI
275     if ([[NSFileManager defaultManager] removeItemAtPath:[NSString stringWithCString:filepath.c_str() encoding:NSUTF8StringEncoding]
276                                                    error:NULL] == NO) {
277         // should never happen
278         Warning("RemoveFile failed!");
279     };
280 #endif
281 }
282 
283 // -----------------------------------------------------------------------------
284 
CopyFile(const std::string & inpath,const std::string & outpath)285 bool CopyFile(const std::string& inpath, const std::string& outpath)
286 {
287 #if defined(ANDROID_GUI) || defined(WEB_GUI)
288     FILE* infile = fopen(inpath.c_str(), "r");
289     if (infile) {
290         // read entire file into contents
291         std::string contents;
292         const int MAXLINELEN = 4095;
293         char linebuf[MAXLINELEN + 1];
294         linereader reader(infile);
295         while (true) {
296             if (reader.fgets(linebuf, MAXLINELEN) == 0) break;
297             contents += linebuf;
298             contents += "\n";
299         }
300         reader.close();
301         // fclose(infile) has been called
302 
303         // write contents to outpath
304         FILE* outfile = fopen(outpath.c_str(), "w");
305         if (outfile) {
306             if (fputs(contents.c_str(), outfile) == EOF) {
307                 fclose(outfile);
308                 Warning("CopyFile failed to copy contents to output file!");
309                 return false;
310             }
311             fclose(outfile);
312         } else {
313             Warning("CopyFile failed to open output file!");
314             return false;
315         }
316 
317         return true;
318     } else {
319         Warning("CopyFile failed to open input file!");
320         return false;
321     }
322 #endif // ANDROID_GUI or WEB_GUI
323 
324 #ifdef IOS_GUI
325     if (FileExists(outpath)) {
326         RemoveFile(outpath);
327     }
328     return [[NSFileManager defaultManager] copyItemAtPath:[NSString stringWithCString:inpath.c_str() encoding:NSUTF8StringEncoding]
329                                                    toPath:[NSString stringWithCString:outpath.c_str() encoding:NSUTF8StringEncoding]
330                                                     error:NULL];
331 #endif // IOS_GUI
332 }
333 
334 // -----------------------------------------------------------------------------
335 
MoveFile(const std::string & inpath,const std::string & outpath)336 bool MoveFile(const std::string& inpath, const std::string& outpath)
337 {
338 #ifdef ANDROID_GUI
339     return AndroidMoveFile(inpath, outpath);
340 #endif
341 
342 #ifdef WEB_GUI
343     return WebMoveFile(inpath, outpath);
344 #endif
345 
346 #ifdef IOS_GUI
347     if (FileExists(outpath)) {
348         RemoveFile(outpath);
349     }
350     return [[NSFileManager defaultManager] moveItemAtPath:[NSString stringWithCString:inpath.c_str() encoding:NSUTF8StringEncoding]
351                                                    toPath:[NSString stringWithCString:outpath.c_str() encoding:NSUTF8StringEncoding]
352                                                     error:NULL];
353 #endif
354 }
355 
356 // -----------------------------------------------------------------------------
357 
FixURLPath(std::string & path)358 void FixURLPath(std::string& path)
359 {
360     // replace "%..." with suitable chars for a file path (eg. %20 is changed to space)
361 
362 #ifdef ANDROID_GUI
363     AndroidFixURLPath(path);
364 #endif
365 
366 #ifdef WEB_GUI
367     WebFixURLPath(path);
368 #endif
369 
370 #ifdef IOS_GUI
371     NSString* newpath = [[NSString stringWithCString:path.c_str() encoding:NSUTF8StringEncoding]
372                          stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
373     if (newpath) path = [newpath cStringUsingEncoding:NSUTF8StringEncoding];
374 #endif
375 }
376 
377 // -----------------------------------------------------------------------------
378 
IsHTMLFile(const std::string & filename)379 bool IsHTMLFile(const std::string& filename)
380 {
381     size_t dotpos = filename.rfind('.');
382     if (dotpos == std::string::npos) return false;
383 
384     std::string ext = filename.substr(dotpos+1);
385     return ( strcasecmp(ext.c_str(),"htm") == 0 ||
386              strcasecmp(ext.c_str(),"html") == 0 );
387 }
388 
389 // -----------------------------------------------------------------------------
390 
IsTextFile(const std::string & filename)391 bool IsTextFile(const std::string& filename)
392 {
393     if (!IsHTMLFile(filename)) {
394         // if non-html file name contains "readme" then assume it's a text file
395         std::string basename = filename;
396         size_t lastsep = basename.rfind('/');
397         if (lastsep != std::string::npos) {
398             basename = basename.substr(lastsep+1);
399         }
400         std::transform(basename.begin(), basename.end(), basename.begin(), tolower);
401         if (basename.find("readme") != std::string::npos) return true;
402     }
403     size_t dotpos = filename.rfind('.');
404     if (dotpos == std::string::npos) return false;
405 
406     std::string ext = filename.substr(dotpos+1);
407     return ( strcasecmp(ext.c_str(),"txt") == 0 ||
408              strcasecmp(ext.c_str(),"doc") == 0 );
409 }
410 
411 // -----------------------------------------------------------------------------
412 
IsZipFile(const std::string & filename)413 bool IsZipFile(const std::string& filename)
414 {
415     size_t dotpos = filename.rfind('.');
416     if (dotpos == std::string::npos) return false;
417 
418     std::string ext = filename.substr(dotpos+1);
419     return ( strcasecmp(ext.c_str(),"zip") == 0 ||
420              strcasecmp(ext.c_str(),"gar") == 0 );
421 }
422 
423 // -----------------------------------------------------------------------------
424 
IsRuleFile(const std::string & filename)425 bool IsRuleFile(const std::string& filename)
426 {
427     size_t dotpos = filename.rfind('.');
428     if (dotpos == std::string::npos) return false;
429 
430     std::string ext = filename.substr(dotpos+1);
431     return ( strcasecmp(ext.c_str(),"rule") == 0 ||
432              strcasecmp(ext.c_str(),"table") == 0 ||
433              strcasecmp(ext.c_str(),"tree") == 0 ||
434              strcasecmp(ext.c_str(),"colors") == 0 ||
435              strcasecmp(ext.c_str(),"icons") == 0 );
436 }
437 
438 // -----------------------------------------------------------------------------
439 
IsScriptFile(const std::string & filename)440 bool IsScriptFile(const std::string& filename)
441 {
442     size_t dotpos = filename.rfind('.');
443     if (dotpos == std::string::npos) return false;
444 
445     std::string ext = filename.substr(dotpos+1);
446     return ( strcasecmp(ext.c_str(),"lua") == 0 ||
447              strcasecmp(ext.c_str(),"pl") == 0 ||
448              strcasecmp(ext.c_str(),"py") == 0 );
449 }
450 
451 // -----------------------------------------------------------------------------
452 
EndsWith(const std::string & str,const std::string & suffix)453 bool EndsWith(const std::string& str, const std::string& suffix)
454 {
455     // return true if str ends with suffix
456     size_t strlen = str.length();
457     size_t sufflen = suffix.length();
458     return (strlen >= sufflen) && (str.rfind(suffix) == strlen - sufflen);
459 }
460 
461 // -----------------------------------------------------------------------------
462 
463 // let gollybase modules process events
464 
465 class golly_poll : public lifepoll
466 {
467 public:
468     virtual int checkevents();
469     virtual void updatePop();
470 };
471 
checkevents()472 int golly_poll::checkevents()
473 {
474     if (event_checker > 0) return isInterrupted();
475     event_checker++;
476 
477 #ifdef ANDROID_GUI
478     AndroidCheckEvents();
479 #endif
480 
481 #ifdef WEB_GUI
482     WebCheckEvents();
483 #endif
484 
485 #ifdef IOS_GUI
486     [[NSRunLoop currentRunLoop] runUntilDate:[NSDate date]];
487 #endif
488 
489     event_checker--;
490     return isInterrupted();
491 }
492 
updatePop()493 void golly_poll::updatePop()
494 {
495     UpdateStatus();
496 }
497 
498 // -----------------------------------------------------------------------------
499 
500 golly_poll gollypoller;    // create instance
501 
Poller()502 lifepoll* Poller()
503 {
504     return &gollypoller;
505 }
506 
PollerReset()507 void PollerReset()
508 {
509     gollypoller.resetInterrupted();
510 }
511 
PollerInterrupt()512 void PollerInterrupt()
513 {
514     gollypoller.setInterrupted();
515 }
516