anonymous group photoblog software
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

236 lines
8.1 KiB

  1. import os
  2. import sys
  3. import time
  4. import errno
  5. import imp
  6. import Cookie
  7. import threading
  8. import mimetypes
  9. import functools
  10. import strings
  11. class DefaultLocal(threading.local):
  12. environ = {}
  13. # shortcuts
  14. board = property(lambda self: self.environ['waka.board'])
  15. request = property(lambda self: self.environ['werkzeug.request'])
  16. local = DefaultLocal()
  17. class WakaError(Exception):
  18. '''Error to be reported to the user'''
  19. def __init__(self, message, fromwindow=False, plain=False):
  20. self.message = message
  21. self.fromwindow = fromwindow
  22. self.plain = plain
  23. def __str__(self):
  24. return self.message
  25. class SpamError(WakaError):
  26. '''Specialized spam-catch error for potential catching.'''
  27. def __init__(self, message=None, fromwindow=False):
  28. message = message or strings.SPAM
  29. super(SpamError, self).__init__(message, fromwindow)
  30. def wrap_static(application, *app_paths, **kwds):
  31. '''Application used in the development server to serve static files
  32. (i.e. everything except the CGI filename). DO NOT USE IN PRODUCTION'''
  33. @functools.wraps(application)
  34. def wrapper(environ, start_response):
  35. filename = environ['PATH_INFO'].strip('/')
  36. environ['DOCUMENT_ROOT'] = os.getcwd()
  37. if filename in app_paths or not filename:
  38. environ['SCRIPT_NAME'] = '/' + filename
  39. return application(environ, start_response)
  40. if os.path.isdir(filename):
  41. index = kwds.get('index', 'index.html')
  42. filename = os.path.join(filename, index)
  43. if os.path.exists(filename):
  44. content, encoding = mimetypes.guess_type(filename)
  45. headers = [('Content-Type', content),
  46. ('Content-Encoding', encoding)]
  47. start_response('200 OK', [x for x in headers if x[1]])
  48. return open(filename)
  49. else:
  50. handler = kwds.get('not_found_handler', None)
  51. if handler:
  52. return handler(environ, start_response)
  53. start_response('404 Not found', [('Content-Type', 'text/plain')])
  54. return ['404 Not found: %s' % filename]
  55. return wrapper
  56. def module_default(modulename, defaults):
  57. '''Set default values to modulename
  58. Keys which start with underscores are ignored'''
  59. module = __import__(modulename)
  60. for key in defaults:
  61. if not key.startswith('_') and not hasattr(module, key):
  62. setattr(module, key, defaults[key])
  63. def import2(name, path):
  64. '''Imports a module from path without requiring a __init__.py file'''
  65. fullname = '%s.%s' % (path, name)
  66. if fullname in sys.modules:
  67. return sys.modules[fullname]
  68. else:
  69. modinfo = imp.find_module(name, [path])
  70. module = imp.load_module(fullname, *modinfo)
  71. modinfo[0].close() # the docs say i must close this :(
  72. return module
  73. def headers(f):
  74. '''Decorator that allows sending output without calling start_response
  75. It replaces start_response with a backwards-compatible version that
  76. doesn't do anything if called more than once.'''
  77. @functools.wraps(f)
  78. def wrapper(environ, start_response):
  79. environ['waka.status'] = '200 OK'
  80. environ['waka.headers'] = {'Content-Type': 'text/html'}
  81. environ['waka.response_sent'] = False
  82. environ['waka.cookies'] = Cookie.BaseCookie()
  83. def new_start_response(status, headers):
  84. if environ['waka.response_sent']:
  85. return
  86. environ['waka.response_sent'] = True
  87. # merge parameter headers with the environ ones
  88. environ['waka.headers'].update(dict(headers))
  89. # Set-cookie can be repeated, so it's handled separately
  90. headerlist = environ['waka.headers'].items()
  91. for cookie in environ['waka.cookies'].itervalues():
  92. headerlist.append(tuple(cookie.output().split(": ", 1)))
  93. start_response(status, headerlist)
  94. appiter = f(environ, new_start_response)
  95. new_start_response(environ['waka.status'], environ['waka.headers'])
  96. return appiter
  97. return wrapper
  98. def cleanup(application, cleanup_function):
  99. '''Pseudo-decorator that calls a cleanup function always after an app
  100. is run. This is needed because the apps may return the iterator before
  101. execution is done'''
  102. @functools.wraps(application)
  103. def wrapper(environ, start_response):
  104. try:
  105. appiter = application(environ, start_response)
  106. for item in appiter:
  107. yield item
  108. finally:
  109. cleanup_function(environ, start_response)
  110. return wrapper
  111. def make_http_forward(location, alternate_method=False):
  112. '''Pseudo-application to redirect to another location. The location
  113. parameter is assumed to be properly decoded and escaped.'''
  114. if alternate_method:
  115. return [str('<html><head>'
  116. '<meta http-equiv="refresh" content="0; url=%s" />'
  117. '<script type="text/javascript">document.location="%s";'
  118. '</script></head><body><a href="%s">%s</a></body></html>' %\
  119. ((location, ) * 4))]
  120. else:
  121. local.environ['waka.status'] = '303 Go West'
  122. local.environ['waka.headers']['Location'] = location
  123. return [str('<html><body><a href="%s">%s</a></body></html>' %\
  124. ((location, ) * 2))]
  125. # The following code was ripped from
  126. # http://www.evanfosmark.com/2009/01/
  127. # cross-platform-file-locking-support-in-python/
  128. # I can't really improve on this to my knowledge.
  129. class FileLockException(Exception):
  130. pass
  131. class FileLock(object):
  132. """ A file locking mechanism that has context-manager support so
  133. you can use it in a with statement. This should be relatively cross
  134. compatible as it doesn't rely on msvcrt or fcntl for the locking.
  135. """
  136. def __init__(self, file_name, timeout=10, delay=.05):
  137. """ Prepare the file locker. Specify the file to lock and optionally
  138. the maximum timeout and the delay between each attempt to lock.
  139. """
  140. self.is_locked = False
  141. self.lockfile = os.path.join(os.getcwd(), "%s.lock" % file_name)
  142. self.file_name = file_name
  143. self.timeout = timeout
  144. self.delay = delay
  145. def acquire(self):
  146. """ Acquire the lock, if possible. If the lock is in use, it check again
  147. every `wait` seconds. It does this until it either gets the lock or
  148. exceeds `timeout` number of seconds, in which case it throws
  149. an exception.
  150. """
  151. start_time = time.time()
  152. while True:
  153. try:
  154. self.fd = os.open(self.lockfile, os.O_CREAT|os.O_EXCL|os.O_RDWR)
  155. break;
  156. except OSError as e:
  157. if e.errno != errno.EEXIST:
  158. raise
  159. if (time.time() - start_time) >= self.timeout:
  160. raise FileLockException("Timeout occured.")
  161. time.sleep(self.delay)
  162. self.is_locked = True
  163. def release(self):
  164. """ Get rid of the lock by deleting the lockfile.
  165. When working in a `with` statement, this gets automatically
  166. called at the end.
  167. """
  168. if self.is_locked:
  169. os.close(self.fd)
  170. os.unlink(self.lockfile)
  171. self.is_locked = False
  172. def __enter__(self):
  173. """ Activated when used in the with statement.
  174. Should automatically acquire a lock to be used in the with block.
  175. """
  176. if not self.is_locked:
  177. self.acquire()
  178. return self
  179. def __exit__(self, type, value, traceback):
  180. """ Activated at the end of the with statement.
  181. It automatically releases the lock if it isn't locked.
  182. """
  183. if self.is_locked:
  184. self.release()
  185. def __del__(self):
  186. """ Make sure that the FileLock instance doesn't leave a lockfile
  187. lying around.
  188. """
  189. self.release()
  190. def proxy_environ():
  191. """Returns a cleaned up version of the environment for proxied calls"""
  192. environ = {}
  193. for key, value in local.environ.iteritems():
  194. if isinstance(value, basestring):
  195. environ[key] = value
  196. return environ