├── eyefi-notify.sh ├── eyefi-install.sh ├── eyefi-restart.sh ├── S99EyeFiServer.sh ├── eyefiserver.conf ├── README.md └── eyefiserver.py /eyefi-notify.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ARG_NUM=$1 4 | ARG_SIZE=$2 5 | ARG_NAMES=$3 6 | 7 | sendmail -F "Synology NAS" -f "FROMADDRESS@gmail.com" -t TOADDRESS@gmail.com << EOF 8 | Subject: eye-fi upload complete 9 | Uploaded $ARG_NUM pictures (size: $ARG_SIZE) 10 | 11 | -- files -- 12 | $ARG_NAMES 13 | 14 | EOF 15 | 16 | -------------------------------------------------------------------------------- /eyefi-install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo press any key to install and restart eyefi server 4 | read n 5 | 6 | cp eyefiserver.conf /etc 7 | 8 | cp S99EyeFiServer.sh /usr/local/etc/rc.d 9 | 10 | cp eyefiserver.py /usr/local/bin 11 | cp eyefi-notify.sh /usr/local/bin 12 | cp eyefi-restart.sh /usr/local/bin 13 | 14 | eyefi-restart.sh 15 | -------------------------------------------------------------------------------- /eyefi-restart.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | PID_FILE=/tmp/eyefiserver.pid 4 | 5 | if [ -f $PID_FILE ] 6 | then 7 | PID=`cat $PID_FILE` 8 | echo stopping eyefiserver - pid:$PID 9 | kill -9 $PID 10 | 11 | sleep 1 12 | 13 | rm $PID_FILE 14 | fi 15 | 16 | echo 'starting eyefiserver' 17 | /usr/local/etc/rc.d/S99EyeFiServer.sh start 18 | 19 | PID=`cat $PID_FILE` 20 | echo eyefiserver - pid:$PID 21 | -------------------------------------------------------------------------------- /S99EyeFiServer.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # NOTE: to install as a service, copy to /usr/local/etc/rc.d 4 | # start: /usr/local/etc/rc.d/S99EyeFiServer.sh start 5 | # stop: /usr/local/etc/rc.d/S99EyeFiServer.sh stop 6 | 7 | eyefiserver=/usr/local/bin/eyefiserver.py 8 | case $1 in 9 | start) 10 | echo "Starting EyeFi server..." 11 | python $eyefiserver start /etc/eyefiserver.conf /var/log/eyefiserver.log 12 | ;; 13 | 14 | stop) 15 | echo "Stopping EyeFi server..." 16 | python $eyefiserver stop /etc/eyefiserver.conf /var/log/eyefiserver.log 17 | ;; 18 | 19 | restart) 20 | $0 stop 21 | sleep 1 22 | $0 start 23 | ;; 24 | esac 25 | -------------------------------------------------------------------------------- /eyefiserver.conf: -------------------------------------------------------------------------------- 1 | 2 | [EyeFiServer] 3 | 4 | # host name and port to listen on 5 | # you can leave hostname empty for localhost 6 | 7 | host_name: 8 | host_port:59278 9 | 10 | # To use this script you need to have your Eye-Fi upload key. 11 | # You can find it after configuring the card, 12 | # which you can currently on do on windows or mac 13 | # It is inside C:\Documents and Settings\\Application Data\Eye-Fi\Settings.xml on windows 14 | # or ~/Applications Data/Eye-Fi/Settings.xml on mac 15 | # search for it and paste it here: 16 | 17 | # from Windows: 18 | # 19 | # 11372bdd2cef8dc59d2945694dd550XX 20 | # 30aea110e5cc9216b3d9db46f2c60bXX 21 | # from Mac: 22 | # 23 | # 11372bdd2cef8dc59d2945694dd550XX 24 | # f3908d429695cc83fb8f5cfdd3c318XX 25 | 26 | mac_0:00185624d1XX 27 | upload_key_0:11372bdd2cef8dc59d2945694dd550XX 28 | 29 | mac_1:001856000000 30 | upload_key_1:00000000000000000000000000000000 31 | 32 | # Create XMP file with geolocation information based on access points 33 | # detected by Eye-Fi card for any uploaded JPEG or RAW file 34 | geotag_enable:1 35 | 36 | # Use acces points detected within this period of time 37 | # before or after photo was taken, in seconds 38 | geotag_lag:3600 39 | 40 | # Use acquired geolocation data with accuracy over this value, in meters 41 | geotag_accuracy:140000 42 | 43 | # When connecting, all files are downloaded in one directory 44 | # the name of the directory can be a strftime formatted string like 45 | # /home/myblog/pictures/%%Y-%%m-%%d 46 | # notice the double percent sign to escape % from ini interpolation 47 | # for reference: 48 | # - http://www.foragoodstrftime.com/ 49 | # - http://linux.die.net/man/3/strftime 50 | upload_dir:/volume1/MEDIA/PHOTOS/%%Y/%%m-%%b 51 | 52 | # The UID of the user that you want to own the uploaded images (if commented out, ownership of files will not be changed) 53 | upload_uid:0 54 | # The GID of the group that you want to own the uploaded images (if commented out, group of files will not be changed) 55 | upload_gid:0 56 | # The permissions to use for the uploaded images - see http://unix.stackexchange.com/a/103414 (was: 420) 57 | upload_file_mode:511 58 | # The permissions to use for the directories that are created (was 509) 59 | upload_dir_mode:511 60 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | eyefiserver3 2 | ============ 3 | 4 | A standalone Eye-Fi server in Python, for Linux 5 | 6 | This is basically the same core server and configuration from https://github.com/dgrant/eyefiserver2 with some logging tweaks and a simple script to send a notification e-mail when uploading is complete. 7 | 8 | I'm running this server on my Synology NAS which is a great fit for the Eye-Fi card. I just turn on my camera when home and leave it on while all the pictures I've taken are uploaded. 9 | 10 | For cloud backup, I have a Windows PC with the desktop Google Photos client installed which will detect any new files on the NAS drive and upload them. However, this PC isn't on all the time - hence the reason for this eyefi server!. 11 | 12 | Installation 13 | === 14 | 15 | Here's all the steps I took to get this working on my Synology NAS (DS512j running DSM 5.1): 16 | 17 | **Requirements** 18 | 19 | - ssh enabled 20 | - Installed Packages: 21 | - Python3 22 | - Python Module 23 | - Mail Server 24 | - for configuration help, see https://swisstechiethoughts.wordpress.com/2014/01/20/howto-send-mail-from-synology-nas-commandline-using-google-mail-relay/ 25 | - an Eye-Fi card setup and configured to send pictures to a Mac or PC (which you'll need to get the UPLOAD key from later) 26 | 27 | **Install** 28 | 29 | - Copy all the files in this git repo to the NAS device 30 | - Edit eyefiserver.conf to include YOUR eye-fi card's MAC address and UPLOAD key, which can be found in C:\Documents and Settings\\Application Data\Eye-Fi\Settings.xml (windows) 31 | or ~/Applications Data/Eye-Fi/Settings.xml (mac) 32 | - Edit eyefi-notify.sh script and change the FROMADDRESS@gmail.com and TOADDRESS@gmail.com (from and to addresses for the notification e-mail) 33 | - Make sure the scripts are executable in case these permissions were lost at some point 34 | 35 | ` 36 | chmod 777 * 37 | ` 38 | 39 | - Run eyefi-install.sh which should put everything in place on the device and start (or re-start) it. 40 | 41 | ` 42 | . ./eyefi-install.sh 43 | ` 44 | 45 | NOTE: here's the guide I followed initially for reference (https://github.com/dgrant/eyefiserver2/wiki/Synology-NAS---Installation-procedure) in case anything's not clear 46 | 47 | Support 48 | === 49 | 50 | - The first place to look if anything isn't working is /var/log/eyefiserver.log 51 | 52 | - Make sure you disable the Eye-Fi Helper which is running on your Mac or PC. 53 | 54 | - When I initially set the Eye-Fi Helper software up on my Mac, I first changed the computer's name to my NAS's device name. I read about this here: http://thijs.elenbaas.net/2013/03/installing-an-eye-fi-server-on-a-synology-nas/. I'm not sure it's necessary or not.. my gut says 'yes' since that's how the eye-fi card connects to your server. But, on that same site there's a comment saying it's not.. anyway, something to check if the initial setup doesn't work. -------------------------------------------------------------------------------- /eyefiserver.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ 4 | * Copyright (c) 2009, Jeffrey Tchang 5 | * Additional *pike 6 | * -- additional logging and email notification (JP) 7 | * All rights reserved. 8 | * 9 | * 10 | * THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY 11 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 12 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 13 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY 14 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 15 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 16 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 17 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 18 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 19 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 20 | """ 21 | 22 | 23 | import cgi 24 | import time 25 | from datetime import timedelta 26 | 27 | import random 28 | import sys 29 | import os 30 | import socket 31 | import thread 32 | import StringIO 33 | import traceback 34 | import errno 35 | import tempfile 36 | 37 | import hashlib 38 | import binascii 39 | import select 40 | import tarfile 41 | 42 | import xml.sax 43 | from xml.sax.handler import ContentHandler 44 | import xml.dom.minidom 45 | 46 | from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer 47 | import BaseHTTPServer 48 | import httplib 49 | 50 | import SocketServer 51 | 52 | import logging 53 | import logging.handlers 54 | 55 | import atexit 56 | from signal import SIGTERM 57 | import signal 58 | 59 | #pike 60 | from datetime import datetime 61 | import ConfigParser 62 | 63 | DEFAULTS = {'upload_uid': '-1', 'upload_gid': '-1', 'geotag_enable': '0'} 64 | 65 | import math 66 | 67 | import subprocess 68 | 69 | class Daemon: 70 | """ 71 | A generic daemon class. 72 | 73 | Usage: subclass the Daemon class and override the run() method 74 | """ 75 | def __init__(self, 76 | pidfile, 77 | stdin='/dev/null', 78 | stdout='/dev/null', 79 | stderr='/dev/null', 80 | ): 81 | try: 82 | self.stderr = sys.argv[3] 83 | except: 84 | self.stderr = stderr 85 | self.stdin = stdin 86 | self.stdout = stdout 87 | self.pidfile = pidfile 88 | 89 | def daemonize(self): 90 | """ 91 | do the UNIX double-fork magic, see Stevens' "Advanced 92 | Programming in the UNIX Environment" for details (ISBN 0201563177) 93 | http://www.erlenstar.demon.co.uk/unix/faq_2.html#SEC16 94 | """ 95 | try: 96 | pid = os.fork() 97 | if pid > 0: 98 | # exit first parent 99 | # sys.exit(0) 100 | return 0 101 | except OSError, e: 102 | sys.stderr.write("fork #1 failed: %d (%s)\n" \ 103 | % (e.errno, e.strerror)) 104 | sys.exit(1) 105 | 106 | # decouple from parent environment 107 | os.chdir("/") 108 | os.setsid() 109 | os.umask(0) 110 | 111 | # do second fork 112 | try: 113 | pid = os.fork() 114 | if pid > 0: 115 | # exit from second parent 116 | # sys.exit(0) 117 | return 1 118 | except OSError, e: 119 | sys.stderr.write("fork #2 failed: %d (%s)\n" \ 120 | % (e.errno, e.strerror)) 121 | sys.exit(1) 122 | 123 | # redirect standard file descriptors 124 | sys.stdout.flush() 125 | sys.stderr.flush() 126 | si = file(self.stdin, 'r') 127 | so = file(self.stdout, 'a+') 128 | se = file(self.stderr, 'a+', 0) 129 | os.dup2(si.fileno(), sys.stdin.fileno()) 130 | os.dup2(so.fileno(), sys.stdout.fileno()) 131 | os.dup2(se.fileno(), sys.stderr.fileno()) 132 | 133 | # write pidfile 134 | atexit.register(self.delpid) 135 | pid = str(os.getpid()) 136 | file(self.pidfile,'w+').write("%s\n" % pid) 137 | 138 | def delpid(self): 139 | os.remove(self.pidfile) 140 | 141 | def start(self): 142 | """ 143 | Start the daemon 144 | """ 145 | # Check for a pidfile to see if the daemon already runs 146 | try: 147 | pf = file(self.pidfile,'r') 148 | pid = int(pf.read().strip()) 149 | pf.close() 150 | except IOError: 151 | pid = None 152 | 153 | if pid: 154 | message = "pidfile %s already exist. Daemon already running?\n" 155 | sys.stderr.write(message % self.pidfile) 156 | return 1 157 | 158 | # Start the daemon 159 | forkresult = self.daemonize() 160 | if forkresult==None: 161 | self.run() 162 | return forkresult 163 | 164 | def stop(self): 165 | """ 166 | Stop the daemon 167 | """ 168 | # Get the pid from the pidfile 169 | try: 170 | pf = file(self.pidfile,'r') 171 | pid = int(pf.read().strip()) 172 | pf.close() 173 | except IOError: 174 | pid = None 175 | 176 | if not pid: 177 | message = "pidfile %s does not exist. Daemon not running?\n" 178 | sys.stderr.write(message % self.pidfile) 179 | return 1 180 | 181 | # Try killing the daemon process 182 | try: 183 | while 1: 184 | os.kill(pid, SIGTERM) 185 | time.sleep(0.1) 186 | except OSError, err: 187 | err = str(err) 188 | if err.find("No such process") > 0: 189 | if os.path.exists(self.pidfile): 190 | os.remove(self.pidfile) 191 | else: 192 | print str(err) 193 | sys.exit(1) 194 | 195 | def restart(self): 196 | """ 197 | Restart the daemon 198 | """ 199 | # Get the pid from the pidfile 200 | try: 201 | pf = file(self.pidfile,'r') 202 | pid = int(pf.read().strip()) 203 | pf.close() 204 | except IOError: 205 | pid = None 206 | 207 | if pid: 208 | # Try killing the daemon process 209 | try: 210 | while 1: 211 | os.kill(pid, SIGTERM) 212 | time.sleep(0.1) 213 | except OSError, err: 214 | err = str(err) 215 | if err.find("No such process") > 0: 216 | if os.path.exists(self.pidfile): 217 | os.remove(self.pidfile) 218 | else: 219 | print str(err) 220 | return 1 221 | 222 | # Start the daemon 223 | forkresult = self.daemonize() 224 | if forkresult==None: 225 | self.run() 226 | return forkresult 227 | 228 | def reload(self): 229 | """ 230 | Reload configuration 231 | """ 232 | # Get the pid from the pidfile 233 | try: 234 | pf = file(self.pidfile,'r') 235 | pid = int(pf.read().strip()) 236 | pf.close() 237 | except IOError: 238 | pid = None 239 | 240 | if not pid: 241 | message = "pidfile %s does not exist. Daemon not running?\n" 242 | sys.stderr.write(message % self.pidfile) 243 | return 1 244 | 245 | # Try killing the daemon process 246 | try: 247 | os.kill(pid, signal.SIGUSR1) 248 | except OSError, err: 249 | print str(err) 250 | 251 | def status(self): 252 | """ 253 | Check daemon status 254 | """ 255 | # Get the pid from the pidfile 256 | try: 257 | pf = file(self.pidfile,'r') 258 | pid = int(pf.read().strip()) 259 | pf.close() 260 | except IOError: 261 | pid = None 262 | 263 | if not pid: 264 | message = "pidfile %s does not exist. Daemon not running?\n" 265 | sys.stderr.write(message % self.pidfile) 266 | return 1 267 | 268 | def run(self): 269 | """ 270 | You should override this method when you subclass Daemon. It will be called after the process has been 271 | daemonized by start() or restart(). 272 | """ 273 | 274 | 275 | """ 276 | General architecture notes 277 | 278 | 279 | This is a standalone Eye-Fi Server that is designed to take the place of the Eye-Fi Manager. 280 | 281 | 282 | Starting this server creates a listener on port 59278. I use the BaseHTTPServer class included 283 | with Python. I look for specific POST/GET request URLs and execute functions based on those 284 | URLs. 285 | 286 | 287 | 288 | 289 | """ 290 | 291 | 292 | # Create the main logger 293 | eyeFiLogger = logging.Logger("eyeFiLogger",logging.DEBUG) 294 | 295 | # Create two handlers. One to print to the log and one to print to the console 296 | consoleHandler = logging.StreamHandler(sys.stdout) 297 | 298 | # Set how both handlers will print the pretty log events 299 | eyeFiLoggingFormat = logging.Formatter("[%(asctime)s][%(funcName)s] - %(message)s",'%m/%d/%y %I:%M%p') 300 | consoleHandler.setFormatter(eyeFiLoggingFormat) 301 | 302 | # Append both handlers to the main Eye Fi Server logger 303 | eyeFiLogger.addHandler(consoleHandler) 304 | 305 | 306 | def fix_ownership(path, uid, gid): 307 | if uid != -1 and gid != -1: 308 | os.chown(path, uid, gid) 309 | 310 | 311 | # Eye Fi XML SAX ContentHandler 312 | class EyeFiContentHandler(ContentHandler): 313 | 314 | # These are the element names that I want to parse out of the XML 315 | elementNamesToExtract = ["macaddress","cnonce","transfermode","transfermodetimestamp","fileid","filename","filesize","filesignature"] 316 | 317 | # For each of the element names I create a dictionary with the value to False 318 | elementsToExtract = {} 319 | 320 | # Where to put the extracted values 321 | extractedElements = {} 322 | 323 | 324 | def __init__(self): 325 | 326 | self.extractedElements = {} 327 | 328 | for elementName in self.elementNamesToExtract: 329 | self.elementsToExtract[elementName] = False 330 | 331 | def startElement(self, name, attributes): 332 | 333 | # If the name of the element is a key in the dictionary elementsToExtract 334 | # set the value to True 335 | if name in self.elementsToExtract: 336 | self.elementsToExtract[name] = True 337 | 338 | def endElement(self, name): 339 | 340 | # If the name of the element is a key in the dictionary elementsToExtract 341 | # set the value to False 342 | if name in self.elementsToExtract: 343 | self.elementsToExtract[name] = False 344 | 345 | 346 | def characters(self, content): 347 | 348 | for elementName in self.elementsToExtract: 349 | if self.elementsToExtract[elementName] == True: 350 | self.extractedElements[elementName] = content 351 | 352 | # Implements an EyeFi server 353 | class EyeFiServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer): 354 | 355 | # --------------- 356 | # (JP) track session details (# files, size) 357 | global_counter = 0 358 | session_counter = 0 359 | session_upload_size = 0 360 | session_files = "" 361 | session_start_time = 0 362 | # --------------- 363 | 364 | def serve_forever(self): 365 | while self.run: 366 | try: 367 | self.handle_request() 368 | except select.error, e: 369 | if e[0] != errno.EINTR: 370 | raise e 371 | 372 | def reload_config(self, signum, frame): 373 | try: 374 | configfile = sys.argv[2] 375 | eyeFiLogger.info("Reloading configuration " + configfile) 376 | self.config.read(configfile) 377 | except: 378 | eyeFiLogger.error("Error reloading configuration") 379 | 380 | def stop_server(self, signum, frame): 381 | try: 382 | eyeFiLogger.info("Eye-Fi server stopped ") 383 | self.stop() 384 | except: 385 | eyeFiLogger.error("Error stopping server") 386 | 387 | def server_bind(self): 388 | 389 | BaseHTTPServer.HTTPServer.server_bind(self) 390 | self.socket.settimeout(None) 391 | signal.signal(signal.SIGUSR1, self.reload_config) 392 | signal.signal(signal.SIGTERM, self.stop_server) 393 | signal.signal(signal.SIGINT, self.stop_server) 394 | self.run = True 395 | 396 | def get_request(self): 397 | while self.run: 398 | try: 399 | connection, address = self.socket.accept() 400 | eyeFiLogger.debug("Incoming connection from client %s" % address[0]) 401 | 402 | # --------------- 403 | # (JP) set start time if not set 404 | if (self.session_start_time == 0): 405 | self.session_start_time = time.time() 406 | # --------------- 407 | 408 | connection.settimeout(None) 409 | return (connection, address) 410 | 411 | except socket.timeout: 412 | self.socket.close() 413 | pass 414 | 415 | def stop(self): 416 | self.run = False 417 | 418 | # alt serve_forever method for python <2.6 419 | # because we want a shutdown mech .. 420 | #def serve(self): 421 | # while self.run: 422 | # self.handle_request() 423 | # self.socket.close() 424 | 425 | 426 | 427 | # This class is responsible for handling HTTP requests passed to it. 428 | # It implements the two most common HTTP methods, do_GET() and do_POST() 429 | 430 | class EyeFiRequestHandler(BaseHTTPRequestHandler): 431 | 432 | # pike: these seem unused ? 433 | protocol_version = 'HTTP/1.1' 434 | sys_version = "" 435 | server_version = "Eye-Fi Agent/2.0.4.0 (Windows XP SP2)" 436 | 437 | def do_QUIT (self): 438 | eyeFiLogger.debug("Got StopServer request .. stopping server") 439 | self.send_response(200) 440 | self.end_headers() 441 | self.server.stop() 442 | 443 | def do_GET(self): 444 | try: 445 | #eyeFiLogger.debug(self.command + " " + self.path + " " + self.request_version) 446 | 447 | SOAPAction = "" 448 | # eyeFiLogger.debug("Headers received in GET request:") 449 | for headerName in self.headers.keys(): 450 | for headerValue in self.headers.getheaders(headerName): 451 | # eyeFiLogger.debug(headerName + ": " + headerValue) 452 | if( headerName == "soapaction"): 453 | SOAPAction = headerValue 454 | 455 | self.send_response(200) 456 | self.send_header('Content-type','text/html') 457 | # I should be sending a Content-Length header with HTTP/1.1 but I am being lazy 458 | # self.send_header('Content-length', '123') 459 | self.end_headers() 460 | self.wfile.write(self.client_address) 461 | self.wfile.write(self.headers) 462 | self.close_connection = 0 463 | except: 464 | eyeFiLogger.error("Got an an exception:") 465 | eyeFiLogger.error(traceback.format_exc()) 466 | raise 467 | 468 | def do_POST(self): 469 | try: 470 | # eyeFiLogger.debug(self.command + " " + self.path + " " + self.request_version) 471 | 472 | SOAPAction = "" 473 | contentLength = "" 474 | 475 | # Loop through all the request headers and pick out ones that are relevant 476 | 477 | # eyeFiLogger.debug("Headers received in POST request:") 478 | for headerName in self.headers.keys(): 479 | for headerValue in self.headers.getheaders(headerName): 480 | 481 | if( headerName == "soapaction"): 482 | SOAPAction = headerValue 483 | 484 | if( headerName == "content-length"): 485 | contentLength = int(headerValue) 486 | 487 | # eyeFiLogger.debug(headerName + ": " + headerValue) 488 | 489 | 490 | # Read contentLength bytes worth of data 491 | # eyeFiLogger.debug("Attempting to read " + str(contentLength) + " bytes of data") 492 | # postData = self.rfile.read(contentLength) 493 | try: 494 | from StringIO import StringIO 495 | import tempfile 496 | except ImportError: 497 | eyeFiLogger.debug("No StringIO module") 498 | chunksize = 1048576 # 1MB 499 | mem = StringIO() 500 | while 1: 501 | remain = contentLength - mem.tell() 502 | if remain <= 0: break 503 | chunk = self.rfile.read(min(chunksize, remain)) 504 | if not chunk: break 505 | mem.write(chunk) 506 | print remain 507 | print "Finished" 508 | postData = mem.getvalue() 509 | mem.close() 510 | 511 | # eyeFiLogger.debug("Finished reading " + str(contentLength) + " bytes of data") 512 | 513 | # Perform action based on path and SOAPAction 514 | # A SOAPAction of StartSession indicates the beginning of an EyeFi authentication request 515 | if((self.path == "/api/soap/eyefilm/v1") and (SOAPAction == "\"urn:StartSession\"")): 516 | eyeFiLogger.debug("Got StartSession request") 517 | response = self.startSession(postData) 518 | contentLength = len(response) 519 | 520 | # eyeFiLogger.debug("StartSession response: " + response) 521 | 522 | self.send_response(200) 523 | self.send_header('Date', self.date_time_string()) 524 | self.send_header('Pragma','no-cache') 525 | self.send_header('Server','Eye-Fi Agent/2.0.4.0 (Windows XP SP2)') 526 | self.send_header('Content-Type','text/xml; charset="utf-8"') 527 | self.send_header('Content-Length', contentLength) 528 | self.end_headers() 529 | 530 | self.wfile.write(response) 531 | self.wfile.flush() 532 | self.handle_one_request() 533 | 534 | # GetPhotoStatus allows the card to query if a photo has been uploaded 535 | # to the server yet 536 | if((self.path == "/api/soap/eyefilm/v1") and (SOAPAction == "\"urn:GetPhotoStatus\"")): 537 | eyeFiLogger.debug("Got GetPhotoStatus request") 538 | 539 | response = self.getPhotoStatus(postData) 540 | contentLength = len(response) 541 | 542 | # eyeFiLogger.debug("GetPhotoStatus response: " + response) 543 | 544 | self.send_response(200) 545 | self.send_header('Date', self.date_time_string()) 546 | self.send_header('Pragma','no-cache') 547 | self.send_header('Server','Eye-Fi Agent/2.0.4.0 (Windows XP SP2)') 548 | self.send_header('Content-Type','text/xml; charset="utf-8"') 549 | self.send_header('Content-Length', contentLength) 550 | self.end_headers() 551 | 552 | self.wfile.write(response) 553 | self.wfile.flush() 554 | 555 | 556 | # If the URL is upload and there is no SOAPAction the card is ready to send a picture to me 557 | if((self.path == "/api/soap/eyefilm/v1/upload") and (SOAPAction == "")): 558 | # eyeFiLogger.debug("Got upload request") 559 | response = self.uploadPhoto(postData) 560 | contentLength = len(response) 561 | 562 | # eyeFiLogger.debug("Upload response: " + response) 563 | 564 | self.send_response(200) 565 | self.send_header('Date', self.date_time_string()) 566 | self.send_header('Pragma','no-cache') 567 | self.send_header('Server','Eye-Fi Agent/2.0.4.0 (Windows XP SP2)') 568 | self.send_header('Content-Type','text/xml; charset="utf-8"') 569 | self.send_header('Content-Length', contentLength) 570 | self.end_headers() 571 | 572 | self.wfile.write(response) 573 | self.wfile.flush() 574 | 575 | # If the URL is upload and SOAPAction is MarkLastPhotoInRoll 576 | if((self.path == "/api/soap/eyefilm/v1") and (SOAPAction == "\"urn:MarkLastPhotoInRoll\"")): 577 | # eyeFiLogger.debug("Got MarkLastPhotoInRoll request") 578 | response = self.markLastPhotoInRoll(postData) 579 | contentLength = len(response) 580 | 581 | # eyeFiLogger.debug("MarkLastPhotoInRoll response: " + response) 582 | self.send_response(200) 583 | self.send_header('Date', self.date_time_string()) 584 | self.send_header('Pragma','no-cache') 585 | self.send_header('Server','Eye-Fi Agent/2.0.4.0 (Windows XP SP2)') 586 | self.send_header('Content-Type','text/xml; charset="utf-8"') 587 | self.send_header('Content-Length', contentLength) 588 | self.send_header('Connection', 'Close') 589 | self.end_headers() 590 | 591 | self.wfile.write(response) 592 | self.wfile.flush() 593 | 594 | # --------------- 595 | # format upload size 596 | uploaded_str = human_size(self.server.session_upload_size) 597 | 598 | # elapsed time (secs) 599 | elapsed_time = time.time() - self.server.session_start_time 600 | # formated into HH:MM:SS 601 | elapsed_str = str(timedelta(seconds=elapsed_time)) 602 | 603 | eyeFiLogger.debug("upload complete: duration: " + elapsed_str + ", uploaded: " + str(self.server.session_counter) + ", size: " + uploaded_str + ", total(history): " + str(self.server.global_counter)) 604 | 605 | # (JP) send notification on upload complete 606 | subprocess.call(['/usr/local/bin/eyefi-notify.sh', str(self.server.session_counter), uploaded_str, self.server.session_files]) 607 | 608 | # reset session counters 609 | self.server.session_files = "" 610 | self.server.session_counter = 0 611 | self.server.session_upload_size = 0 612 | self.server.session_start_time = 0; 613 | # --------------- 614 | 615 | except: 616 | eyeFiLogger.error("Got an an exception:") 617 | eyeFiLogger.error(traceback.format_exc()) 618 | raise 619 | 620 | 621 | # Handles MarkLastPhotoInRoll action 622 | def markLastPhotoInRoll(self,postData): 623 | # Create the XML document to send back 624 | doc = xml.dom.minidom.Document() 625 | 626 | SOAPElement = doc.createElementNS("http://schemas.xmlsoap.org/soap/envelope/","SOAP-ENV:Envelope") 627 | SOAPElement.setAttribute("xmlns:SOAP-ENV","http://schemas.xmlsoap.org/soap/envelope/") 628 | SOAPBodyElement = doc.createElement("SOAP-ENV:Body") 629 | 630 | markLastPhotoInRollResponseElement = doc.createElement("MarkLastPhotoInRollResponse") 631 | 632 | SOAPBodyElement.appendChild(markLastPhotoInRollResponseElement) 633 | SOAPElement.appendChild(SOAPBodyElement) 634 | doc.appendChild(SOAPElement) 635 | 636 | return doc.toxml(encoding="UTF-8") 637 | 638 | 639 | # Handles receiving the actual photograph from the card. 640 | # postData will most likely contain multipart binary post data that needs to be parsed 641 | def uploadPhoto(self,postData): 642 | 643 | # Take the postData string and work with it as if it were a file object 644 | postDataInMemoryFile = StringIO.StringIO(postData) 645 | 646 | # Get the content-type header which looks something like this 647 | # content-type: multipart/form-data; boundary=---------------------------02468ace13579bdfcafebabef00d 648 | contentTypeHeader = self.headers.getheaders('content-type').pop() 649 | # eyeFiLogger.debug(contentTypeHeader) 650 | 651 | # Extract the boundary parameter in the content-type header 652 | headerParameters = contentTypeHeader.split(";") 653 | # eyeFiLogger.debug(headerParameters) 654 | 655 | boundary = headerParameters[-1].split("=") 656 | boundary = boundary[1].strip() 657 | # eyeFiLogger.debug("Extracted boundary: " + boundary) 658 | 659 | # eyeFiLogger.debug("uploadPhoto postData: " + postData) 660 | 661 | # Parse the multipart/form-data 662 | form = cgi.parse_multipart(postDataInMemoryFile, {"boundary":boundary,"content-disposition":self.headers.getheaders('content-disposition')}) 663 | # eyeFiLogger.debug("Available multipart/form-data: " + str(form.keys())) 664 | 665 | # Parse the SOAPENVELOPE using the EyeFiContentHandler() 666 | soapEnvelope = form['SOAPENVELOPE'][0] 667 | # eyeFiLogger.debug("SOAPENVELOPE: " + soapEnvelope) 668 | handler = EyeFiContentHandler() 669 | parser = xml.sax.parseString(soapEnvelope,handler) 670 | 671 | # eyeFiLogger.debug("Extracted elements: " + str(handler.extractedElements)) 672 | 673 | imageTarfileName = handler.extractedElements["filename"] 674 | 675 | #pike 676 | uid = self.server.config.getint('EyeFiServer','upload_uid') 677 | gid = self.server.config.getint('EyeFiServer','upload_gid') 678 | file_mode = self.server.config.get('EyeFiServer','upload_file_mode') 679 | dir_mode = self.server.config.get('EyeFiServer','upload_dir_mode') 680 | # eyeFiLogger.debug("Using uid/gid %d/%d"%(uid,gid)) 681 | # eyeFiLogger.debug("Using file_mode " + file_mode) 682 | # eyeFiLogger.debug("Using dir_mode " + dir_mode) 683 | 684 | geotag_enable = int(self.server.config.getint('EyeFiServer','geotag_enable')) 685 | if geotag_enable: 686 | geotag_accuracy = int(self.server.config.get('EyeFiServer','geotag_accuracy')) 687 | 688 | imageTarPath = os.path.join(tempfile.gettempdir(), imageTarfileName) 689 | # eyeFiLogger.debug("Generated path " + imageTarPath) 690 | 691 | fileHandle = open(imageTarPath, 'wb') 692 | # eyeFiLogger.debug("Opened file " + imageTarPath + " for binary writing") 693 | 694 | fileHandle.write(form['FILENAME'][0]) 695 | # eyeFiLogger.debug("Wrote file " + imageTarPath) 696 | 697 | image_size = fileHandle.tell() 698 | 699 | fileHandle.close() 700 | # eyeFiLogger.debug("Closed file " + imageTarPath) 701 | 702 | # eyeFiLogger.debug("Extracting TAR file " + imageTarPath) 703 | try: 704 | imageTarfile = tarfile.open(imageTarPath) 705 | except ReadError, error: 706 | eyeFiLogger.error("Failed to open %s" % imageTarPath) 707 | raise 708 | 709 | for member in imageTarfile.getmembers(): 710 | # If timezone is a daylight savings timezone, and we are 711 | # currently in daylight savings time, then use the altzone 712 | if time.daylight != 0 and time.localtime().tm_isdst != 0: 713 | timeoffset = time.altzone 714 | else: 715 | timeoffset = time.timezone 716 | timezone = timeoffset / 60 / 60 * -1 717 | imageDate = datetime.fromtimestamp(member.mtime) - timedelta(hours=timezone) 718 | uploadDir = imageDate.strftime(self.server.config.get('EyeFiServer','upload_dir')) 719 | # eyeFiLogger.debug("Creating folder " + uploadDir) 720 | if not os.path.isdir(uploadDir): 721 | os.makedirs(uploadDir) 722 | fix_ownership(uploadDir, uid, gid) 723 | if file_mode != "": 724 | os.chmod(uploadDir, int(dir_mode)) 725 | 726 | f=imageTarfile.extract(member, uploadDir) 727 | imagePath = os.path.join(uploadDir, member.name) 728 | # eyeFiLogger.debug("imagePath " + imagePath) 729 | os.utime(imagePath, (member.mtime + timeoffset, member.mtime + timeoffset)) 730 | fix_ownership(imagePath, uid, gid) 731 | if file_mode != "": 732 | os.chmod(imagePath, int(file_mode)) 733 | 734 | if geotag_enable>0 and member.name.lower().endswith(".log"): 735 | eyeFiLogger.debug("Processing LOG file " + imagePath) 736 | try: 737 | imageName = member.name[:-4] 738 | shottime, aps = list(self.parselog(imagePath,imageName)) 739 | aps = self.getphotoaps(shottime, aps) 740 | loc = self.getlocation(aps) 741 | if loc['status']=='OK' and float(loc['accuracy'])<=geotag_accuracy: 742 | xmpName=imageName+".xmp" 743 | xmpPath=os.path.join(uploadDir, xmpName) 744 | eyeFiLogger.debug("Writing XMP file " + xmpPath) 745 | self.writexmp(xmpPath,float(loc['location']['lat']),float(loc['location']['lng'])) 746 | fix_ownership(xmpPath, uid, gid) 747 | if file_mode != "": 748 | os.chmod(xmpPath, int(file_mode)) 749 | except: 750 | eyeFiLogger.error("Error processing LOG file " + imagePath) 751 | 752 | # eyeFiLogger.debug("Closing TAR file " + imageTarPath) 753 | imageTarfile.close() 754 | 755 | # eyeFiLogger.debug("Deleting TAR file " + imageTarPath) 756 | os.remove(imageTarPath) 757 | 758 | # Create the XML document to send back 759 | doc = xml.dom.minidom.Document() 760 | 761 | SOAPElement = doc.createElementNS("http://schemas.xmlsoap.org/soap/envelope/","SOAP-ENV:Envelope") 762 | SOAPElement.setAttribute("xmlns:SOAP-ENV","http://schemas.xmlsoap.org/soap/envelope/") 763 | SOAPBodyElement = doc.createElement("SOAP-ENV:Body") 764 | 765 | uploadPhotoResponseElement = doc.createElement("UploadPhotoResponse") 766 | successElement = doc.createElement("success") 767 | successElementText = doc.createTextNode("true") 768 | 769 | successElement.appendChild(successElementText) 770 | uploadPhotoResponseElement.appendChild(successElement) 771 | 772 | SOAPBodyElement.appendChild(uploadPhotoResponseElement) 773 | SOAPElement.appendChild(SOAPBodyElement) 774 | doc.appendChild(SOAPElement) 775 | 776 | # (JP) track uploaded files during session 777 | self.server.session_counter += 1; 778 | self.server.global_counter += 1; 779 | self.server.session_upload_size += image_size 780 | self.server.session_files = self.server.session_files + "\n" + imagePath 781 | 782 | # format upload size 783 | uploaded_str = human_size(image_size) 784 | 785 | eyeFiLogger.debug("Uploaded #" + str(self.server.session_counter) + ": " + imagePath + ", size: " + uploaded_str) 786 | 787 | return doc.toxml(encoding="UTF-8") 788 | 789 | def parselog(self,logfile,filename): 790 | shottime = 0 791 | aps = {} 792 | for line in open(logfile): 793 | time, timestamp, act = line.strip().split(",", 2) 794 | act = act.split(",") 795 | act, args = act[0], act[1:] 796 | if act in ("AP", "NEWAP"): 797 | aps.setdefault(args[0], []).append({"time": int(time),"pwr": int(args[1])}) 798 | elif act == "NEWPHOTO": 799 | if filename==args[0]: 800 | shottime = int(time) 801 | elif act == "POWERON": 802 | if shottime>0: 803 | return shottime, aps 804 | shottime = 0 805 | aps = {} 806 | if shottime>0: 807 | return shottime, aps 808 | 809 | def getphotoaps(self, time, aps): 810 | geotag_lag = int(self.server.config.get('EyeFiServer','geotag_lag')) 811 | newaps = [] 812 | for mac in aps: 813 | lag = min([(abs(ap["time"] - time), ap["pwr"]) for ap in aps[mac]], key=lambda a: a[0]) 814 | if lag[0] <= geotag_lag: 815 | newaps.append({"mac": mac, "pwr": lag[1]}) 816 | return newaps 817 | 818 | def getlocation(self, aps): 819 | try: 820 | geourl = 'maps.googleapis.com' 821 | headers = {"Host": geourl} 822 | params = "?browser=none&sensor=false" 823 | for ap in aps: 824 | params+='&wifi=mac:'+'-'.join([ap['mac'][2*d:2*d+2] for d in range(6)])+'|ss:'+str(int(math.log10(ap['pwr']/100.0)*10-50)) 825 | conn = httplib.HTTPSConnection(geourl) 826 | conn.request("GET", "/maps/api/browserlocation/json"+params, "", headers) 827 | resp = conn.getresponse() 828 | result = resp.read() 829 | conn.close() 830 | except: 831 | eyeFiLogger.debug("Error connecting to geolocation service") 832 | return None 833 | try: 834 | try: 835 | import simplejson as json 836 | except ImportError: 837 | import json 838 | return json.loads(result) 839 | except: 840 | try: 841 | import re 842 | result=result.replace("\n"," ") 843 | loc={} 844 | loc['location']={} 845 | loc['location']['lat']=float(re.sub(r'.*"lat"\s*:\s*([\d.]+)\s*[,}\n]+.*',r'\1',result)) 846 | loc['location']['lng']=float(re.sub(r'.*"lng"\s*:\s*([\d.]+)\s*[,}\n]+.*',r'\1',result)) 847 | loc['accuracy']=float(re.sub(r'.*"accuracy"\s*:\s*([\d.]+)\s*[,\}\n]+.*',r'\1',result)) 848 | loc['status']=re.sub(r'.*"status"\s*:\s*"(.*?)"\s*[,}\n]+.*',r'\1',result) 849 | return loc 850 | except: 851 | eyeFiLogger.debug("Geolocation service response contains no coordinates: " + result) 852 | return None 853 | 854 | def writexmp(self,name,latitude,longitude): 855 | if latitude>0: 856 | ref="N" 857 | else: 858 | ref="S" 859 | latitude=str(abs(latitude)).split('.') 860 | latitude[1]=str(float('0.'+latitude[1])*60) 861 | latitude=','.join(latitude)+ref 862 | 863 | if longitude>0: 864 | ref="E" 865 | else: 866 | ref="W" 867 | longitude=str(abs(longitude)).split('.') 868 | longitude[1]=str(float('0.'+longitude[1])*60) 869 | longitude=','.join(longitude)+ref 870 | 871 | FILE = open(name,"w") 872 | FILE.write("\n\n\n\n"+latitude+"\n"+longitude+"\n2.2.0.0\n\n\n\n\n") 873 | FILE.close() 874 | 875 | def getPhotoStatus(self,postData): 876 | handler = EyeFiContentHandler() 877 | parser = xml.sax.parseString(postData,handler) 878 | 879 | # Create the XML document to send back 880 | doc = xml.dom.minidom.Document() 881 | 882 | SOAPElement = doc.createElementNS("http://schemas.xmlsoap.org/soap/envelope/","SOAP-ENV:Envelope") 883 | SOAPElement.setAttribute("xmlns:SOAP-ENV","http://schemas.xmlsoap.org/soap/envelope/") 884 | SOAPBodyElement = doc.createElement("SOAP-ENV:Body") 885 | 886 | getPhotoStatusResponseElement = doc.createElement("GetPhotoStatusResponse") 887 | getPhotoStatusResponseElement.setAttribute("xmlns","http://localhost/api/soap/eyefilm") 888 | 889 | fileidElement = doc.createElement("fileid") 890 | fileidElementText = doc.createTextNode("1") 891 | fileidElement.appendChild(fileidElementText) 892 | 893 | offsetElement = doc.createElement("offset") 894 | offsetElementText = doc.createTextNode("0") 895 | offsetElement.appendChild(offsetElementText) 896 | 897 | getPhotoStatusResponseElement.appendChild(fileidElement) 898 | getPhotoStatusResponseElement.appendChild(offsetElement) 899 | 900 | SOAPBodyElement.appendChild(getPhotoStatusResponseElement) 901 | 902 | SOAPElement.appendChild(SOAPBodyElement) 903 | doc.appendChild(SOAPElement) 904 | 905 | return doc.toxml(encoding="UTF-8") 906 | 907 | def _get_mac_uploadkey_dict(self): 908 | macs = {} 909 | upload_keys = {} 910 | for key, value in self.server.config.items('EyeFiServer'): 911 | if key.find('upload_key_') == 0: 912 | index = int(key[11:]) 913 | upload_keys[index] = value 914 | elif key.find('mac_') == 0: 915 | index = int(key[4:]) 916 | macs[index] = value 917 | d = {} 918 | for key in macs.keys(): 919 | d[macs[key]] = upload_keys[key] 920 | return d 921 | 922 | def startSession(self, postData): 923 | # eyeFiLogger.debug("Delegating the XML parsing of startSession postData to EyeFiContentHandler()") 924 | handler = EyeFiContentHandler() 925 | parser = xml.sax.parseString(postData,handler) 926 | 927 | # eyeFiLogger.debug("Extracted elements: " + str(handler.extractedElements)) 928 | 929 | # Retrieve it from C:\Documents and Settings\\Application Data\Eye-Fi\Settings.xml 930 | mac_to_uploadkey_map = self._get_mac_uploadkey_dict() 931 | mac = handler.extractedElements["macaddress"] 932 | upload_key = mac_to_uploadkey_map[mac] 933 | # eyeFiLogger.debug("Got MAC address of " + mac) 934 | # eyeFiLogger.debug("Setting Eye-Fi upload key to " + upload_key) 935 | 936 | credentialString = mac + handler.extractedElements["cnonce"] + upload_key 937 | # eyeFiLogger.debug("Concatenated credential string (pre MD5): " + credentialString) 938 | 939 | # Return the binary data represented by the hexadecimal string 940 | # resulting in something that looks like "\x00\x18V\x03\x04..." 941 | binaryCredentialString = binascii.unhexlify(credentialString) 942 | 943 | # Now MD5 hash the binary string 944 | m = hashlib.md5() 945 | m.update(binaryCredentialString) 946 | 947 | # Hex encode the hash to obtain the final credential string 948 | credential = m.hexdigest() 949 | 950 | # Create the XML document to send back 951 | doc = xml.dom.minidom.Document() 952 | 953 | SOAPElement = doc.createElementNS("http://schemas.xmlsoap.org/soap/envelope/","SOAP-ENV:Envelope") 954 | SOAPElement.setAttribute("xmlns:SOAP-ENV","http://schemas.xmlsoap.org/soap/envelope/") 955 | SOAPBodyElement = doc.createElement("SOAP-ENV:Body") 956 | 957 | 958 | startSessionResponseElement = doc.createElement("StartSessionResponse") 959 | startSessionResponseElement.setAttribute("xmlns","http://localhost/api/soap/eyefilm") 960 | 961 | credentialElement = doc.createElement("credential") 962 | credentialElementText = doc.createTextNode(credential) 963 | credentialElement.appendChild(credentialElementText) 964 | 965 | snonceElement = doc.createElement("snonce") 966 | snonceElementText = doc.createTextNode("%x" % random.getrandbits(128)) 967 | snonceElement.appendChild(snonceElementText) 968 | 969 | transfermodeElement = doc.createElement("transfermode") 970 | transfermodeElementText = doc.createTextNode(handler.extractedElements["transfermode"]) 971 | transfermodeElement.appendChild(transfermodeElementText) 972 | 973 | transfermodetimestampElement = doc.createElement("transfermodetimestamp") 974 | transfermodetimestampElementText = doc.createTextNode(handler.extractedElements["transfermodetimestamp"]) 975 | transfermodetimestampElement.appendChild(transfermodetimestampElementText) 976 | 977 | upsyncallowedElement = doc.createElement("upsyncallowed") 978 | upsyncallowedElementText = doc.createTextNode("true") 979 | upsyncallowedElement.appendChild(upsyncallowedElementText) 980 | 981 | startSessionResponseElement.appendChild(credentialElement) 982 | startSessionResponseElement.appendChild(snonceElement) 983 | startSessionResponseElement.appendChild(transfermodeElement) 984 | startSessionResponseElement.appendChild(transfermodetimestampElement) 985 | startSessionResponseElement.appendChild(upsyncallowedElement) 986 | 987 | SOAPBodyElement.appendChild(startSessionResponseElement) 988 | 989 | SOAPElement.appendChild(SOAPBodyElement) 990 | doc.appendChild(SOAPElement) 991 | 992 | 993 | return doc.toxml(encoding="UTF-8") 994 | 995 | def stopEyeFi(): 996 | configfile = sys.argv[2] 997 | eyeFiLogger.info("Reading config " + configfile) 998 | 999 | config = ConfigParser.SafeConfigParser(defaults=DEFAULTS) 1000 | config.read(configfile) 1001 | 1002 | port = config.getint('EyeFiServer','host_port') 1003 | 1004 | """send QUIT request to http server running on localhost:""" 1005 | conn = httplib.HTTPConnection("127.0.0.1:%d" % port) 1006 | conn.request("QUIT", "/") 1007 | conn.getresponse() 1008 | 1009 | eyeFiServer='' 1010 | 1011 | def runEyeFi(): 1012 | 1013 | configfile = sys.argv[2] 1014 | eyeFiLogger.info("Reading config " + configfile) 1015 | 1016 | config = ConfigParser.SafeConfigParser(defaults=DEFAULTS) 1017 | config.read(configfile) 1018 | 1019 | # open file logging 1020 | logfile = sys.argv[3] 1021 | fileHandler = logging.handlers.TimedRotatingFileHandler(logfile, "D", 7, backupCount=7, encoding=None) 1022 | fileHandler.setFormatter(eyeFiLoggingFormat) 1023 | eyeFiLogger.addHandler(fileHandler) 1024 | 1025 | server_address = (config.get('EyeFiServer','host_name'), config.getint('EyeFiServer','host_port')) 1026 | 1027 | # Create an instance of an HTTP server. Requests will be handled 1028 | # by the class EyeFiRequestHandler 1029 | eyeFiServer = EyeFiServer(server_address, EyeFiRequestHandler) 1030 | eyeFiServer.config = config 1031 | eyeFiLogger.info("Eye-Fi server started listening on port " + str(server_address[1])) 1032 | eyeFiServer.serve_forever() 1033 | 1034 | def human_size(size_bytes): 1035 | """ 1036 | format a size in bytes into a 'human' file size, e.g. bytes, KB, MB, GB, TB, PB 1037 | Note that bytes/KB will be reported in whole numbers but MB and above will have greater precision 1038 | e.g. 1 byte, 43 bytes, 443 KB, 4.3 MB, 4.43 GB, etc 1039 | """ 1040 | if size_bytes == 1: 1041 | # because I really hate unnecessary plurals 1042 | return "1 byte" 1043 | 1044 | suffixes_table = [('bytes',0),('KB',0),('MB',1),('GB',2),('TB',2), ('PB',2)] 1045 | 1046 | num = float(size_bytes) 1047 | for suffix, precision in suffixes_table: 1048 | if num < 1024.0: 1049 | break 1050 | num /= 1024.0 1051 | 1052 | if precision == 0: 1053 | formatted_size = "%d" % num 1054 | else: 1055 | formatted_size = str(round(num, ndigits=precision)) 1056 | 1057 | return "%s %s" % (formatted_size, suffix) 1058 | 1059 | class MyDaemon(Daemon): 1060 | def run(self): 1061 | runEyeFi() 1062 | 1063 | def main(): 1064 | pid_file = '/tmp/eyefiserver.pid' 1065 | result = 0 1066 | if len(sys.argv) > 2: 1067 | if 'start' == sys.argv[1]: 1068 | daemon = MyDaemon(pid_file) 1069 | result = daemon.start() 1070 | if result!=1: 1071 | print "EyeFiServer started" 1072 | elif 'stop' == sys.argv[1]: 1073 | daemon = MyDaemon(pid_file) 1074 | result = daemon.stop() 1075 | if result!=1: 1076 | print "EyeFiServer stopped" 1077 | elif 'restart' == sys.argv[1]: 1078 | daemon = MyDaemon(pid_file) 1079 | result = daemon.restart() 1080 | if result!=1: 1081 | print "EyeFiServer restarted" 1082 | elif 'reload' == sys.argv[1]: 1083 | daemon = MyDaemon(pid_file) 1084 | result = daemon.reload() 1085 | elif 'status' == sys.argv[1]: 1086 | daemon = MyDaemon(pid_file) 1087 | result = daemon.status() 1088 | if result==1: 1089 | print "EyeFiServer is not running" 1090 | else: 1091 | print "EyeFiServer is running" 1092 | elif 'instance' == sys.argv[1]: 1093 | runEyeFi() 1094 | else: 1095 | print "Unknown command" 1096 | sys.exit(2) 1097 | sys.exit(result) 1098 | else: 1099 | print "usage: %s start|stop|restart|reload|status|instance conf_file log_file" % sys.argv[0] 1100 | sys.exit(2) 1101 | 1102 | if __name__ == "__main__": 1103 | main() 1104 | --------------------------------------------------------------------------------