1 | #!/usr/bin/php
|
2 | <?php
|
3 | // Flash Air Auto Sync, copies all images from flash air card when it is available
|
4 | // and drops them in a local folder.
|
5 | // Default Settings override these by passing arguments at the command line
|
6 | // eg. php FlashAirSync.php --syncto=/home/boffin/Documents/FlashAir
|
7 | $defaults = [
|
8 | 'flashairip' => '192.168.178.16',
|
9 | 'syncfrom' => '/DCIM/110CANON',
|
10 | 'syncto' => getenv('HOME') . '/FlashAir',
|
11 | 'timezone' => 'GMT',
|
12 | 'pingtimeout' => '5'
|
13 | ];
|
14 | $options = getopt('', ['flashairip::', 'syncfrom::', 'syncto::', 'timezone::', 'pingtimeout::']) + $defaults;
|
15 | $FlashAirIP = $options['flashairip']; // IP address of the flashair card, if the card is running in the default host mode you could just use the hostname ("flashair")
|
16 | $SyncFrom = $options['syncfrom']; // Path on the sdcard to copy from (recursive)
|
17 | $SyncTo = $options['syncto']; // Full path where it will copy to (recursive)
|
18 | $TZ = $options['timezone']; // Camera Timezone
|
19 | // If the camera isn't online, it quits. If the camera goes offline during the processing, the next time it
|
20 | // will do a full update.
|
21 | // It only copies a file once.
|
22 | // Deletions on the camera will NOT propogate to the target - camera space is tight so you are free
|
23 | // to delete files there without losing the off-camera copy.
|
24 | // If you delete a file from the target directory, that deletion WILL propogate to the camera
|
25 | // because the assumption is, it's a junk image you don't want, so no need for it on camera either.
|
26 | // Modifications on the camera will NOT propogate to the target - you're not modifying images on the camera
|
27 | // in any meaningful way.
|
28 | // Modifications in the target will NOT propogate to the camera - the camera will always have the "original".
|
29 | // Sample config which puts card in wifi client mode (so it connects to your existing wifi router/access point), with upload enabled
|
30 | // the config file is SD_WLAN/CONFIG on the sdcard, just edit then turn off and on the card to reload.
|
31 | // For more info:
|
32 | // https://flashair-developers.com/en/documents/
|
33 | // https://flashair-developers.com/en/documents/api/
|
34 | // http://www.extrud3d.com/flashair
|
35 | /*
|
36 | [Vendor]
|
37 | CIPATH=/DCIM/100__TSB/FA000001.JPG
|
38 | APPMODE=5
|
39 | APPNETWORKKEY=YOUR_NETWORK_KEY_HERE
|
40 | APPSSID=YOUR_SSID_HERE
|
41 | VERSION=F24A6W3AW1.00.03
|
42 | CID=02544d5357303847075000c0bf00c801
|
43 | PRODUCT=FlashAir
|
44 | VENDOR=TOSHIBA
|
45 | MASTERCODE=00216b97d78a
|
46 | LOCK=1
|
47 | APPNAME=flashair
|
48 | UPLOAD=1
|
49 | */
|
50 | date_default_timezone_set($TZ);
|
51 | if(!file_exists($SyncTo))
|
52 | {
|
53 | mkdir($SyncTo);
|
54 | }
|
55 | $ForceUpdate = (!file_exists($SyncTo . '/.Last_Update'))
|
56 | ||file_exists($SyncTo.'/.Force_Update') ;
|
57 | @unlink($SyncTo.'/.Force_Update');
|
58 | // Files which have been copied in the past, we use this when we see
|
59 | // a file not present on the local, if it was copied in the past it must have
|
60 | // been deleted, so we will propogate that deletion back to the card.
|
61 | function was_deleted($Destination)
|
62 | {
|
63 | if(file_exists($Destination)) return false;
|
64 | if(!file_exists(dirname($Destination) . '/.Manifest'))
|
65 | {
|
66 | mkdir(dirname($Destination) . '/.Manifest');
|
67 | }
|
68 | if(file_exists(dirname($Destination) . '/.Manifest/' . basename($Destination)))
|
69 | {
|
70 | return true;
|
71 | }
|
72 | return false;
|
73 | }
|
74 | function sync_for_file($From, $To, $Time)
|
75 | {
|
76 | global $FlashAirIP;
|
77 | if(was_deleted($To))
|
78 | {
|
79 | // Delete from card
|
80 | echo "Delete {$From}\n";
|
81 | command("upload.cgi", array('DEL' => $From));
|
82 | unlink(dirname($To) . '/.Manifest/'.basename($To));
|
83 | }
|
84 | elseif(!file_exists($To))
|
85 | {
|
86 | if(copy("http://{$FlashAirIP}".$From, $To))
|
87 | {
|
88 | echo "Copy {$From}\n";
|
89 | touch($To, $Time);
|
90 | // Add it to the manifest
|
91 | touch(dirname($To) . '/.Manifest/'.basename($To), $Time);
|
92 | }
|
93 | else
|
94 | {
|
95 | if(!alive())
|
96 | {
|
97 | force_next_update();
|
98 | exit;
|
99 | }
|
100 | }
|
101 | }
|
102 | }
|
103 | // Check to see if the card is on the network
|
104 | function alive()
|
105 | {
|
106 | global $FlashAirIP, $options;
|
107 | $timeout = $options['pingtimeout'];
|
108 | $RC = 1;
|
109 | exec('ping -c 1 -t ' . $timeout . ' ' . $FlashAirIP,$retArr, $RC);
|
110 | return !$RC;
|
111 | }
|
112 | // Write a flag to fo a forced update the next time
|
113 | // this happens if we are interrupted by the camera going
|
114 | // into power down during a sync
|
115 | function force_next_update()
|
116 | {
|
117 | global $SyncTo;
|
118 | touch($SyncTo . '/.Force_Update');
|
119 | echo "Interrupted during processing.\n";
|
120 | }
|
121 | function command($Op, $Args = array())
|
122 | {
|
123 | global $FlashAirIP;
|
124 | if(!alive())
|
125 | {
|
126 | force_next_update();
|
127 | exit;
|
128 | }
|
129 | $Command = is_numeric($Op) ? "http://{$FlashAirIP}/command.cgi?op=$Op" : "http://{$FlashAirIP}/{$Op}?__DUMMY__=1";
|
130 | foreach($Args as $k => $v)
|
131 | {
|
132 | $Command .= "&$k=".rawurlencode($v);
|
133 | }
|
134 | $Contents = file_get_contents($Command);
|
135 | if($Contents === FALSE)
|
136 | {
|
137 | force_next_update();
|
138 | exit;
|
139 | }
|
140 | return $Contents;
|
141 | }
|
142 | if(!alive())
|
143 | {
|
144 | echo "Not Online\n";
|
145 | exit;
|
146 | }
|
147 | if(!$ForceUpdate && command(102) == 0)
|
148 | {
|
149 | echo "No Changes\n";
|
150 | exit;
|
151 | }
|
152 | function sync_dir($Dir, $To)
|
153 | {
|
154 | global $FlashAirIP, $TZ;
|
155 | $List = command(100, array('DIR' => $Dir));
|
156 | $List = preg_split('/\r?\n/', $List);
|
157 | foreach($List as $r)
|
158 | {
|
159 | $r = str_getcsv($r);
|
160 | if(count($r) < 3) continue;
|
161 | if($r[3] & 16) // bit 5 = Directory
|
162 | {
|
163 | if(!file_exists($To . '/'.$r[1])) mkdir($To . '/' . $r[1]);
|
164 | sync_dir($Dir . '/' . $r[1], $To . '/' . $r[1]);
|
165 | continue;
|
166 | }
|
167 | // Else normal file
|
168 | $Day = ($r[4] & 0b0000000000011111);
|
169 | $Month = ($r[4] & 0b0000000111100000) >> 5;
|
170 | $Year = (($r[4] & 0b1111111000000000) >> 9)+1980;
|
171 | $Seconds = ($r[5] & 0b0000000000011111) * 2;
|
172 | $Minutes = ($r[5] & 0b0000011111100000) >> 5;
|
173 | $Hours = ($r[5] & 0b1111100000000000) >> 11;
|
174 | // echo "{$r[1]} {$Year}-{$Month}-{$Day} {$Hours}:{$Minutes}:{$Seconds}\n";
|
175 | // $Time = mktime($Hours, $Minutes, $Seconds, $Month, $Day, $Year);
|
176 | $Time = strtotime("{$Year}-{$Month}-{$Day} {$Hours}:{$Minutes}:{$Seconds} {$TZ}");
|
177 | sync_for_file($Dir . '/' . $r[1], $To . '/' . $r[1], $Time);
|
178 | }
|
179 | }
|
180 | sync_dir($SyncFrom, $SyncTo);
|
181 | touch($SyncTo . '/.Last_Update');
|
182 | ?>
|