Merge github.com:web2py/web2py
This commit is contained in:
@@ -1 +1 @@
|
|||||||
Version 2.1.0 (2012-10-08 22:09:18) dev
|
Version 2.1.0 (2012-10-10 12:20:21) dev
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ def deploy():
|
|||||||
form = SQLFORM.factory(
|
form = SQLFORM.factory(
|
||||||
Field('appcfg',default=GAE_APPCFG,label=T('Path to appcfg.py'),
|
Field('appcfg',default=GAE_APPCFG,label=T('Path to appcfg.py'),
|
||||||
requires=EXISTS(error_message=T('file not found'))),
|
requires=EXISTS(error_message=T('file not found'))),
|
||||||
Field('google_application_id',requires=IS_ALPHANUMERIC(),label=T('Google Application Id')),
|
Field('google_application_id',requires=IS_MATCH('[\w\-]+'),label=T('Google Application Id')),
|
||||||
Field('applications','list:string',
|
Field('applications','list:string',
|
||||||
requires=IS_IN_SET(apps,multiple=True),
|
requires=IS_IN_SET(apps,multiple=True),
|
||||||
label=T('web2py apps to deploy')),
|
label=T('web2py apps to deploy')),
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+82
-112
@@ -11,7 +11,7 @@ Basic caching classes and methods
|
|||||||
|
|
||||||
- Cache - The generic caching object interfacing with the others
|
- Cache - The generic caching object interfacing with the others
|
||||||
- CacheInRam - providing caching in ram
|
- CacheInRam - providing caching in ram
|
||||||
- CacheInDisk - provides caches on disk
|
- CacheOnDisk - provides caches on disk
|
||||||
|
|
||||||
Memcache is also available via a different module (see gluon.contrib.memcache)
|
Memcache is also available via a different module (see gluon.contrib.memcache)
|
||||||
|
|
||||||
@@ -130,22 +130,27 @@ class CacheInRam(CacheAbstract):
|
|||||||
meta_storage = {}
|
meta_storage = {}
|
||||||
|
|
||||||
def __init__(self, request=None):
|
def __init__(self, request=None):
|
||||||
self.locker.acquire()
|
self.initialized = False
|
||||||
self.request = request
|
self.request = request
|
||||||
|
|
||||||
|
def initialize(self):
|
||||||
|
if self.initialized: return
|
||||||
|
else: self.initialized = True
|
||||||
|
self.locker.acquire()
|
||||||
|
request = self.request
|
||||||
if request:
|
if request:
|
||||||
app = request.application
|
app = request.application
|
||||||
else:
|
else:
|
||||||
app = ''
|
app = ''
|
||||||
if not app in self.meta_storage:
|
if not app in self.meta_storage:
|
||||||
self.storage = self.meta_storage[app] = {CacheAbstract.cache_stats_name: {
|
self.storage = self.meta_storage[app] = {
|
||||||
'hit_total': 0,
|
CacheAbstract.cache_stats_name: {'hit_total': 0, 'misses': 0}}
|
||||||
'misses': 0,
|
|
||||||
}}
|
|
||||||
else:
|
else:
|
||||||
self.storage = self.meta_storage[app]
|
self.storage = self.meta_storage[app]
|
||||||
self.locker.release()
|
self.locker.release()
|
||||||
|
|
||||||
def clear(self, regex=None):
|
def clear(self, regex=None):
|
||||||
|
self.initialize()
|
||||||
self.locker.acquire()
|
self.locker.acquire()
|
||||||
storage = self.storage
|
storage = self.storage
|
||||||
if regex is None:
|
if regex is None:
|
||||||
@@ -154,10 +159,7 @@ class CacheInRam(CacheAbstract):
|
|||||||
self._clear(storage, regex)
|
self._clear(storage, regex)
|
||||||
|
|
||||||
if not CacheAbstract.cache_stats_name in storage.keys():
|
if not CacheAbstract.cache_stats_name in storage.keys():
|
||||||
storage[CacheAbstract.cache_stats_name] = {
|
storage[CacheAbstract.cache_stats_name] = {'hit_total': 0,'misses': 0}
|
||||||
'hit_total': 0,
|
|
||||||
'misses': 0,
|
|
||||||
}
|
|
||||||
|
|
||||||
self.locker.release()
|
self.locker.release()
|
||||||
|
|
||||||
@@ -172,6 +174,7 @@ class CacheInRam(CacheAbstract):
|
|||||||
3) would work unless we deepcopy no storage and retrival which would make things slow.
|
3) would work unless we deepcopy no storage and retrival which would make things slow.
|
||||||
Anyway. You can deepcopy explicitly in the function generating the value to be cached.
|
Anyway. You can deepcopy explicitly in the function generating the value to be cached.
|
||||||
"""
|
"""
|
||||||
|
self.initialize()
|
||||||
|
|
||||||
dt = time_expire
|
dt = time_expire
|
||||||
now = time.time()
|
now = time.time()
|
||||||
@@ -200,6 +203,7 @@ class CacheInRam(CacheAbstract):
|
|||||||
return value
|
return value
|
||||||
|
|
||||||
def increment(self, key, value=1):
|
def increment(self, key, value=1):
|
||||||
|
self.initialize()
|
||||||
self.locker.acquire()
|
self.locker.acquire()
|
||||||
try:
|
try:
|
||||||
if key in self.storage:
|
if key in self.storage:
|
||||||
@@ -226,58 +230,65 @@ class CacheOnDisk(CacheAbstract):
|
|||||||
Values stored in disk cache must be pickable.
|
Values stored in disk cache must be pickable.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
speedup_checks = set()
|
def _close_shelve_and_unlock(self):
|
||||||
|
try:
|
||||||
|
if self.storage:
|
||||||
|
self.storage.close()
|
||||||
|
finally:
|
||||||
|
if self.locker and self.locked:
|
||||||
|
portalocker.unlock(self.locker)
|
||||||
|
self.locker.close()
|
||||||
|
self.locked = False
|
||||||
|
|
||||||
def _open_shelf_with_lock(self):
|
def _open_shelve_and_lock(self):
|
||||||
"""Open and return a shelf object, obtaining an exclusive lock
|
"""Open and return a shelf object, obtaining an exclusive lock
|
||||||
on self.locker first. Replaces the close method of the
|
on self.locker first. Replaces the close method of the
|
||||||
returned shelf instance with one that releases the lock upon
|
returned shelf instance with one that releases the lock upon
|
||||||
closing."""
|
closing."""
|
||||||
def _close(self):
|
|
||||||
try:
|
storage = None
|
||||||
shelve.Shelf.close(self)
|
locker = None
|
||||||
finally:
|
locked = False
|
||||||
portalocker.unlock(self.locker)
|
|
||||||
self.locker.close()
|
|
||||||
|
|
||||||
storage, locker, locker_locked = None, None, False
|
|
||||||
try:
|
try:
|
||||||
locker = open(self.locker_name, 'a')
|
locker = locker = open(self.locker_name, 'a')
|
||||||
portalocker.lock(locker, portalocker.LOCK_EX)
|
portalocker.lock(locker, portalocker.LOCK_EX)
|
||||||
locker_locked = True
|
locked = True
|
||||||
storage = shelve.open(self.shelve_name)
|
try:
|
||||||
storage.close = _close.__get__(storage, shelve.Shelf)
|
storage = shelve.open(self.shelve_name)
|
||||||
storage.locker = locker
|
except:
|
||||||
except Exception:
|
logger.error('corrupted cache file %s, will try rebuild it' \
|
||||||
logger.error('corrupted cache file %s, will try to delete and recreate it!' % (self.shelve_name))
|
% (self.shelve_name))
|
||||||
|
storage = None
|
||||||
|
if not storage and os.path.exists(self.shelve_name):
|
||||||
|
os.unlink(self.shelve_name)
|
||||||
|
storage = shelve.open(self.shelve_name)
|
||||||
|
if not CacheAbstract.cache_stats_name in storage.keys():
|
||||||
|
storage[CacheAbstract.cache_stats_name] = {'hit_total':0, 'misses': 0}
|
||||||
|
storage.sync()
|
||||||
|
except Exception, e:
|
||||||
if storage:
|
if storage:
|
||||||
storage.close()
|
storage.close()
|
||||||
storage = None
|
storage = None
|
||||||
|
if locked:
|
||||||
try:
|
portalocker.unlock(locker)
|
||||||
os.unlink(self.shelve_name)
|
locker.close()
|
||||||
storage = shelve.open(self.shelve_name)
|
locked = False
|
||||||
storage.close = _close.__get__(storage, shelve.Shelf)
|
raise RuntimeError, 'unable to create/re-create cache file %s' % self.shelve_name
|
||||||
storage.locker = locker
|
self.locker = locker
|
||||||
if not CacheAbstract.cache_stats_name in storage.keys():
|
self.locked = locked
|
||||||
storage[CacheAbstract.cache_stats_name] = {
|
self.storage = storage
|
||||||
'hit_total': 0,
|
|
||||||
'misses': 0,
|
|
||||||
}
|
|
||||||
storage.sync()
|
|
||||||
except (IOError, OSError):
|
|
||||||
logger.warn('unable to delete and recreate cache file %s' % self.shelve_name)
|
|
||||||
if storage:
|
|
||||||
storage.close()
|
|
||||||
storage = None
|
|
||||||
if locker_locked:
|
|
||||||
portalocker.unlock(locker)
|
|
||||||
if locker:
|
|
||||||
locker.close()
|
|
||||||
return storage
|
return storage
|
||||||
|
|
||||||
def __init__(self, request, folder=None):
|
def __init__(self, request=None, folder=None):
|
||||||
|
self.initialized = False
|
||||||
self.request = request
|
self.request = request
|
||||||
|
self.folder = folder
|
||||||
|
|
||||||
|
def initialize(self):
|
||||||
|
if self.initialized: return
|
||||||
|
else: self.initialized = True
|
||||||
|
folder = self.folder
|
||||||
|
request = self.request
|
||||||
|
|
||||||
# Lets test if the cache folder exists, if not
|
# Lets test if the cache folder exists, if not
|
||||||
# we are going to create it
|
# we are going to create it
|
||||||
@@ -291,93 +302,54 @@ class CacheOnDisk(CacheAbstract):
|
|||||||
self.locker_name = os.path.join(folder,'cache.lock')
|
self.locker_name = os.path.join(folder,'cache.lock')
|
||||||
self.shelve_name = os.path.join(folder,'cache.shelve')
|
self.shelve_name = os.path.join(folder,'cache.shelve')
|
||||||
|
|
||||||
speedup_key = (folder,CacheAbstract.cache_stats_name)
|
|
||||||
if not speedup_key in self.speedup_checks or \
|
|
||||||
not os.path.exists(self.shelve_name):
|
|
||||||
try:
|
|
||||||
storage = self._open_shelf_with_lock()
|
|
||||||
try:
|
|
||||||
if not CacheAbstract.cache_stats_name in storage:
|
|
||||||
storage[CacheAbstract.cache_stats_name] = {
|
|
||||||
'hit_total': 0,
|
|
||||||
'misses': 0,
|
|
||||||
}
|
|
||||||
storage.sync()
|
|
||||||
finally:
|
|
||||||
storage.close()
|
|
||||||
self.speedup_checks.add(speedup_key)
|
|
||||||
except ImportError:
|
|
||||||
pass # no module _bsddb, ignoring exception now so it makes a ticket only if used
|
|
||||||
|
|
||||||
def clear(self, regex=None):
|
def clear(self, regex=None):
|
||||||
storage = self._open_shelf_with_lock()
|
self.initialize()
|
||||||
|
storage = self._open_shelve_and_lock()
|
||||||
try:
|
try:
|
||||||
if regex is None:
|
if regex is None:
|
||||||
storage.clear()
|
storage.clear()
|
||||||
else:
|
else:
|
||||||
self._clear(storage, regex)
|
self._clear(storage, regex)
|
||||||
if not CacheAbstract.cache_stats_name in storage.keys():
|
|
||||||
storage[CacheAbstract.cache_stats_name] = {
|
|
||||||
'hit_total': 0,
|
|
||||||
'misses': 0,
|
|
||||||
}
|
|
||||||
storage.sync()
|
storage.sync()
|
||||||
finally:
|
finally:
|
||||||
storage.close()
|
self._close_shelve_and_unlock()
|
||||||
|
|
||||||
def __call__(self, key, f,
|
def __call__(self, key, f,
|
||||||
time_expire = DEFAULT_TIME_EXPIRE):
|
time_expire = DEFAULT_TIME_EXPIRE):
|
||||||
|
self.initialize()
|
||||||
dt = time_expire
|
dt = time_expire
|
||||||
|
storage = self._open_shelve_and_lock()
|
||||||
storage = self._open_shelf_with_lock()
|
|
||||||
try:
|
try:
|
||||||
item = storage.get(key, None)
|
item = storage.get(key, None)
|
||||||
|
storage[CacheAbstract.cache_stats_name]['hit_total'] += 1
|
||||||
if item and f is None:
|
if item and f is None:
|
||||||
del storage[key]
|
del storage[key]
|
||||||
|
storage.sync()
|
||||||
storage[CacheAbstract.cache_stats_name] = {
|
now = time.time()
|
||||||
'hit_total': storage[CacheAbstract.cache_stats_name]['hit_total'] + 1,
|
if f is None:
|
||||||
'misses': storage[CacheAbstract.cache_stats_name]['misses']
|
value = None
|
||||||
}
|
elif item and (dt is None or item[0] > now - dt):
|
||||||
|
value = item[1]
|
||||||
storage.sync()
|
else:
|
||||||
|
value = f()
|
||||||
|
storage[key] = (now, value)
|
||||||
|
storage[CacheAbstract.cache_stats_name]['misses']+=1
|
||||||
|
storage.sync()
|
||||||
finally:
|
finally:
|
||||||
if storage:
|
self._close_shelve_and_unlock()
|
||||||
storage.close()
|
|
||||||
|
|
||||||
now = time.time()
|
|
||||||
if f is None:
|
|
||||||
return None
|
|
||||||
if item and (dt is None or item[0] > now - dt):
|
|
||||||
return item[1]
|
|
||||||
value = f()
|
|
||||||
|
|
||||||
storage = self._open_shelf_with_lock()
|
|
||||||
try:
|
|
||||||
storage[key] = (now, value)
|
|
||||||
|
|
||||||
storage[CacheAbstract.cache_stats_name] = {
|
|
||||||
'hit_total': storage[CacheAbstract.cache_stats_name]['hit_total'],
|
|
||||||
'misses': storage[CacheAbstract.cache_stats_name]['misses'] + 1
|
|
||||||
}
|
|
||||||
|
|
||||||
storage.sync()
|
|
||||||
finally:
|
|
||||||
if storage:
|
|
||||||
storage.close()
|
|
||||||
|
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def increment(self, key, value=1):
|
def increment(self, key, value=1):
|
||||||
storage = self._open_shelf_with_lock()
|
self.initialize()
|
||||||
|
storage = self._open_shelve_and_lock()
|
||||||
try:
|
try:
|
||||||
if key in storage:
|
if key in storage:
|
||||||
value = storage[key][1] + value
|
value = storage[key][1] + value
|
||||||
storage[key] = (time.time(), value)
|
storage[key] = (time.time(), value)
|
||||||
storage.sync()
|
storage.sync()
|
||||||
finally:
|
finally:
|
||||||
if storage:
|
self._close_shelve_and_unlock()
|
||||||
storage.close()
|
|
||||||
return value
|
return value
|
||||||
|
|
||||||
class CacheAction(object):
|
class CacheAction(object):
|
||||||
@@ -423,10 +395,9 @@ class Cache(object):
|
|||||||
the global request object
|
the global request object
|
||||||
"""
|
"""
|
||||||
# GAE will have a special caching
|
# GAE will have a special caching
|
||||||
|
|
||||||
if have_settings and settings.global_settings.web2py_runtime_gae:
|
if have_settings and settings.global_settings.web2py_runtime_gae:
|
||||||
from contrib.gae_memcache import MemcacheClient
|
from contrib.gae_memcache import MemcacheClient
|
||||||
self.ram=self.disk=MemcacheClient(request)
|
self.ram = self.disk = MemcacheClient(request)
|
||||||
else:
|
else:
|
||||||
# Otherwise use ram (and try also disk)
|
# Otherwise use ram (and try also disk)
|
||||||
self.ram = CacheInRam(request)
|
self.ram = CacheInRam(request)
|
||||||
@@ -489,7 +460,6 @@ class Cache(object):
|
|||||||
cache_model(prefix + key, f, time_expire)
|
cache_model(prefix + key, f, time_expire)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def lazy_cache(key=None,time_expire=None,cache_model='ram'):
|
def lazy_cache(key=None,time_expire=None,cache_model='ram'):
|
||||||
"""
|
"""
|
||||||
can be used to cache any function including in modules,
|
can be used to cache any function including in modules,
|
||||||
|
|||||||
@@ -387,6 +387,7 @@ def build_environment(request, response, session, store_current=True):
|
|||||||
|
|
||||||
t = environment['T'] = translator(request)
|
t = environment['T'] = translator(request)
|
||||||
c = environment['cache'] = Cache(request)
|
c = environment['cache'] = Cache(request)
|
||||||
|
|
||||||
if store_current:
|
if store_current:
|
||||||
current.globalenv = environment
|
current.globalenv = environment
|
||||||
current.request = request
|
current.request = request
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ class MemcacheClient(Client):
|
|||||||
obj = self.get(key)
|
obj = self.get(key)
|
||||||
if obj:
|
if obj:
|
||||||
value = obj[1] + value
|
value = obj[1] + value
|
||||||
self.set((time.time(), value))
|
self.set(key, (time.time(), value))
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def clear(self, key = None):
|
def clear(self, key = None):
|
||||||
|
|||||||
@@ -0,0 +1,198 @@
|
|||||||
|
"""
|
||||||
|
Developed by niphlod@gmail.com
|
||||||
|
"""
|
||||||
|
|
||||||
|
import redis
|
||||||
|
from redis.exceptions import ConnectionError
|
||||||
|
from gluon import current
|
||||||
|
from gluon.storage import Storage
|
||||||
|
import cPickle as pickle
|
||||||
|
import time
|
||||||
|
import re
|
||||||
|
import logging
|
||||||
|
import thread
|
||||||
|
|
||||||
|
logger = logging.getLogger("web2py.session.redis")
|
||||||
|
|
||||||
|
locker = thread.allocate_lock()
|
||||||
|
|
||||||
|
def RedisSession(*args, **vars):
|
||||||
|
"""
|
||||||
|
Usage example: put in models
|
||||||
|
|
||||||
|
sessiondb = RedisSession('localhost:6379',db=0, debug=True)
|
||||||
|
session.connect(request, response, db = sessiondb)
|
||||||
|
|
||||||
|
Simple slip-in storage for session
|
||||||
|
"""
|
||||||
|
|
||||||
|
locker.acquire()
|
||||||
|
try:
|
||||||
|
if not hasattr(RedisSession, 'redis_instance'):
|
||||||
|
RedisSession.redis_instance = RedisClient(*args, **vars)
|
||||||
|
finally:
|
||||||
|
locker.release()
|
||||||
|
return RedisSession.redis_instance
|
||||||
|
|
||||||
|
|
||||||
|
class RedisClient(object):
|
||||||
|
|
||||||
|
meta_storage = {}
|
||||||
|
MAX_RETRIES = 5
|
||||||
|
RETRIES = 0
|
||||||
|
|
||||||
|
def __init__(self, server='localhost:6379', db=None, debug=False, session_expiry=False):
|
||||||
|
"""session_expiry can be an integer, in seconds, to set the default expiration
|
||||||
|
of sessions. The corresponding record will be deleted from the redis instance,
|
||||||
|
and there's virtually no need to run sessions2trash.py
|
||||||
|
"""
|
||||||
|
self.server = server
|
||||||
|
self.db = db or 0
|
||||||
|
host,port = (self.server.split(':')+['6379'])[:2]
|
||||||
|
port = int(port)
|
||||||
|
self.debug = debug
|
||||||
|
if current and current.request:
|
||||||
|
self.app = current.request.application
|
||||||
|
else:
|
||||||
|
self.app = ''
|
||||||
|
self.r_server = redis.Redis(host=host, port=port, db=self.db)
|
||||||
|
self.tablename = None
|
||||||
|
self.session_expiry = session_expiry
|
||||||
|
|
||||||
|
def get(self, what, default):
|
||||||
|
return self.tablename
|
||||||
|
|
||||||
|
def Field(self, fieldname, type='string', length=None, default=None,
|
||||||
|
required=False,requires=None):
|
||||||
|
return None
|
||||||
|
|
||||||
|
def define_table(self,tablename,*fields,**args):
|
||||||
|
if not self.tablename:
|
||||||
|
self.tablename = MockTable(self, self.r_server, tablename, self.session_expiry)
|
||||||
|
return self.tablename
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
return self.tablename
|
||||||
|
|
||||||
|
def __call__(self, where=''):
|
||||||
|
q = self.tablename.query
|
||||||
|
return q
|
||||||
|
|
||||||
|
def commit(self):
|
||||||
|
#this is only called by session2trash.py
|
||||||
|
pass
|
||||||
|
|
||||||
|
class MockTable(object):
|
||||||
|
|
||||||
|
def __init__(self, db, r_server, tablename, session_expiry):
|
||||||
|
self.db = db
|
||||||
|
self.r_server = r_server
|
||||||
|
self.tablename = tablename
|
||||||
|
#set the namespace for sessions of this app
|
||||||
|
self.keyprefix = 'w2p:sess:%s' % tablename.replace('web2py_session_', '')
|
||||||
|
#fast auto-increment id (needed for session handling)
|
||||||
|
self.serial = "%s:serial" % self.keyprefix
|
||||||
|
#index of all the session keys of this app
|
||||||
|
self.id_idx = "%s:id_idx" % self.keyprefix
|
||||||
|
#remember the session_expiry setting
|
||||||
|
self.session_expiry = session_expiry
|
||||||
|
|
||||||
|
def getserial(self):
|
||||||
|
#return an auto-increment id
|
||||||
|
return "%s" % self.r_server.incr(self.serial, 1)
|
||||||
|
|
||||||
|
def __getattr__(self, key):
|
||||||
|
if key == 'id':
|
||||||
|
#return a fake query. We need to query it just by id for normal operations
|
||||||
|
self.query = MockQuery(field='id', db=self.r_server, prefix=self.keyprefix, session_expiry=self.session_expiry)
|
||||||
|
return self.query
|
||||||
|
elif key == '_db':
|
||||||
|
#needed because of the calls in sessions2trash.py and globals.py
|
||||||
|
return self.db
|
||||||
|
|
||||||
|
def insert(self, **kwargs):
|
||||||
|
#usually kwargs would be a Storage with several keys:
|
||||||
|
#'locked', 'client_ip','created_datetime','modified_datetime'
|
||||||
|
#'unique_key', 'session_data'
|
||||||
|
#retrieve a new key
|
||||||
|
newid = self.getserial()
|
||||||
|
key = "%s:%s" % (self.keyprefix, newid)
|
||||||
|
#add it to the index
|
||||||
|
self.r_server.sadd(self.id_idx, key)
|
||||||
|
#set a hash key with the Storage
|
||||||
|
self.r_server.hmset(key, kwargs)
|
||||||
|
if self.session_expiry:
|
||||||
|
self.r_server.expire(key, self.session_expiry)
|
||||||
|
return newid
|
||||||
|
|
||||||
|
class MockQuery(object):
|
||||||
|
"""a fake Query object that supports querying by id
|
||||||
|
and listing all keys. No other operation is supported
|
||||||
|
"""
|
||||||
|
def __init__(self, field=None, db=None, prefix=None, session_expiry=False):
|
||||||
|
self.field = field
|
||||||
|
self.value = None
|
||||||
|
self.db = db
|
||||||
|
self.keyprefix = prefix
|
||||||
|
self.op = None
|
||||||
|
self.session_expiry = session_expiry
|
||||||
|
|
||||||
|
def __eq__(self, value, op='eq'):
|
||||||
|
self.value = value
|
||||||
|
self.op = op
|
||||||
|
|
||||||
|
def __gt__(self, value, op='ge'):
|
||||||
|
self.value = value
|
||||||
|
self.op = op
|
||||||
|
|
||||||
|
def select(self):
|
||||||
|
if self.op == 'eq' and self.field == 'id' and self.value:
|
||||||
|
#means that someone wants to retrieve the key self.value
|
||||||
|
rtn = self.db.hgetall("%s:%s" % (self.keyprefix, self.value))
|
||||||
|
if rtn == dict():
|
||||||
|
#return an empty resultset for non existing key
|
||||||
|
return []
|
||||||
|
else:
|
||||||
|
return [Storage(rtn)]
|
||||||
|
elif self.op == 'ge' and self.field == 'id' and self.value == 0:
|
||||||
|
#means that someone wants the complete list
|
||||||
|
rtn = []
|
||||||
|
id_idx = "%s:id_idx" % self.keyprefix
|
||||||
|
#find all session keys of this app
|
||||||
|
allkeys = self.db.smembers(id_idx)
|
||||||
|
for sess in allkeys:
|
||||||
|
val = self.db.hgetall(sess)
|
||||||
|
if val == dict():
|
||||||
|
if self.session_expiry:
|
||||||
|
#clean up the idx, because the key expired
|
||||||
|
self.db.srem(id_idx, sess)
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
continue
|
||||||
|
val = Storage(val)
|
||||||
|
#add a delete_record method (necessary for sessions2trash.py)
|
||||||
|
val.delete_record = RecordDeleter(self.db, sess, self.keyprefix)
|
||||||
|
rtn.append(val)
|
||||||
|
return rtn
|
||||||
|
else:
|
||||||
|
raise Exception("Operation not supported")
|
||||||
|
|
||||||
|
def update(self, **kwargs):
|
||||||
|
#means that the session has been found and needs an update
|
||||||
|
if self.op == 'eq' and self.field == 'id' and self.value:
|
||||||
|
return self.db.hmset("%s:%s" % (self.keyprefix, self.value), kwargs)
|
||||||
|
|
||||||
|
class RecordDeleter(object):
|
||||||
|
"""Dumb record deleter to support sessions2trash.py"""
|
||||||
|
|
||||||
|
def __init__(self, db, key, keyprefix):
|
||||||
|
self.db, self.key, self.keyprefix = db, key, keyprefix
|
||||||
|
|
||||||
|
def __call__(self):
|
||||||
|
id_idx = "%s:id_idx" % self.keyprefix
|
||||||
|
#remove from the index
|
||||||
|
self.db.srem(id_idx, self.key)
|
||||||
|
#remove the key itself
|
||||||
|
self.db.delete(self.key)
|
||||||
|
|
||||||
|
|
||||||
+3
-3
@@ -3981,8 +3981,8 @@ class DatabaseStoredFile:
|
|||||||
self.data += data
|
self.data += data
|
||||||
|
|
||||||
def close_connection(self):
|
def close_connection(self):
|
||||||
self.db.executesql("DELETE FROM web2py_filesystem WHERE path='%s'" \
|
self.db.executesql(
|
||||||
% self.filename)
|
"DELETE FROM web2py_filesystem WHERE path='%s'" % self.filename)
|
||||||
query = "INSERT INTO web2py_filesystem(path,content) VALUES ('%s','%s')"\
|
query = "INSERT INTO web2py_filesystem(path,content) VALUES ('%s','%s')"\
|
||||||
% (self.filename, self.data.replace("'","''"))
|
% (self.filename, self.data.replace("'","''"))
|
||||||
self.db.executesql(query)
|
self.db.executesql(query)
|
||||||
@@ -4007,7 +4007,7 @@ class UseDatabaseStoredFile:
|
|||||||
return DatabaseStoredFile(self.db,filename,mode)
|
return DatabaseStoredFile(self.db,filename,mode)
|
||||||
|
|
||||||
def file_close(self, fileobj):
|
def file_close(self, fileobj):
|
||||||
fileobj.close()
|
fileobj.close_connection()
|
||||||
|
|
||||||
def file_delete(self,filename):
|
def file_delete(self,filename):
|
||||||
query = "DELETE FROM web2py_filesystem WHERE path='%s'" % filename
|
query = "DELETE FROM web2py_filesystem WHERE path='%s'" % filename
|
||||||
|
|||||||
+10
-8
@@ -417,7 +417,7 @@ class Response(Storage):
|
|||||||
return handler(request, self, methods)
|
return handler(request, self, methods)
|
||||||
|
|
||||||
def toolbar(self):
|
def toolbar(self):
|
||||||
from html import DIV, SCRIPT, BEAUTIFY, TAG, URL
|
from html import DIV, SCRIPT, BEAUTIFY, TAG, URL, A
|
||||||
BUTTON = TAG.button
|
BUTTON = TAG.button
|
||||||
admin = URL("admin","default","design",
|
admin = URL("admin","default","design",
|
||||||
args=current.request.application)
|
args=current.request.application)
|
||||||
@@ -438,19 +438,21 @@ class Response(Storage):
|
|||||||
dbstats = [] # if no db or on GAE
|
dbstats = [] # if no db or on GAE
|
||||||
dbtables = {}
|
dbtables = {}
|
||||||
u = web2py_uuid()
|
u = web2py_uuid()
|
||||||
|
backtotop = A('Back to top', _href="#totop-%s" % u)
|
||||||
return DIV(
|
return DIV(
|
||||||
BUTTON('design',_onclick="document.location='%s'" % admin),
|
BUTTON('design',_onclick="document.location='%s'" % admin),
|
||||||
BUTTON('request',_onclick="jQuery('#request-%s').slideToggle()"%u),
|
BUTTON('request',_onclick="jQuery('#request-%s').slideToggle()"%u),
|
||||||
DIV(BEAUTIFY(current.request),_class="hidden",_id="request-%s"%u),
|
|
||||||
BUTTON('session',_onclick="jQuery('#session-%s').slideToggle()"%u),
|
|
||||||
DIV(BEAUTIFY(current.session),_class="hidden",_id="session-%s"%u),
|
|
||||||
BUTTON('response',_onclick="jQuery('#response-%s').slideToggle()"%u),
|
BUTTON('response',_onclick="jQuery('#response-%s').slideToggle()"%u),
|
||||||
DIV(BEAUTIFY(current.response),_class="hidden",_id="response-%s"%u),
|
BUTTON('session',_onclick="jQuery('#session-%s').slideToggle()"%u),
|
||||||
BUTTON('db tables',_onclick="jQuery('#db-tables-%s').slideToggle()"%u),
|
BUTTON('db tables',_onclick="jQuery('#db-tables-%s').slideToggle()"%u),
|
||||||
DIV(BEAUTIFY(dbtables),_class="hidden",_id="db-tables-%s"%u),
|
|
||||||
BUTTON('db stats',_onclick="jQuery('#db-stats-%s').slideToggle()"%u),
|
BUTTON('db stats',_onclick="jQuery('#db-stats-%s').slideToggle()"%u),
|
||||||
DIV(BEAUTIFY(dbstats),_class="hidden",_id="db-stats-%s"%u),
|
DIV(BEAUTIFY(current.request), backtotop,_class="hidden",_id="request-%s"%u),
|
||||||
|
DIV(BEAUTIFY(current.session), backtotop, _class="hidden",_id="session-%s"%u),
|
||||||
|
DIV(BEAUTIFY(current.response), backtotop, _class="hidden",_id="response-%s"%u),
|
||||||
|
DIV(BEAUTIFY(dbtables), backtotop, _class="hidden",_id="db-tables-%s"%u),
|
||||||
|
DIV(BEAUTIFY(dbstats), backtotop, _class="hidden",_id="db-stats-%s"%u),
|
||||||
SCRIPT("jQuery('.hidden').hide()")
|
SCRIPT("jQuery('.hidden').hide()")
|
||||||
|
,_id="totop-%s" % u
|
||||||
)
|
)
|
||||||
|
|
||||||
class Session(Storage):
|
class Session(Storage):
|
||||||
@@ -482,7 +484,7 @@ class Session(Storage):
|
|||||||
if not masterapp:
|
if not masterapp:
|
||||||
masterapp = request.application
|
masterapp = request.application
|
||||||
response.session_id_name = 'session_id_%s' % masterapp.lower()
|
response.session_id_name = 'session_id_%s' % masterapp.lower()
|
||||||
|
|
||||||
# Load session data from cookie
|
# Load session data from cookie
|
||||||
cookies = request.cookies
|
cookies = request.cookies
|
||||||
|
|
||||||
|
|||||||
+26
-22
@@ -88,7 +88,7 @@ from contenttype import contenttype
|
|||||||
from dal import BaseAdapter
|
from dal import BaseAdapter
|
||||||
from settings import global_settings
|
from settings import global_settings
|
||||||
from validators import CRYPT
|
from validators import CRYPT
|
||||||
from cache import Cache
|
from cache import CacheInRam
|
||||||
from html import URL, xmlescape
|
from html import URL, xmlescape
|
||||||
from utils import is_valid_ip_address
|
from utils import is_valid_ip_address
|
||||||
from rewrite import load, url_in, thread as rwthread, \
|
from rewrite import load, url_in, thread as rwthread, \
|
||||||
@@ -148,7 +148,7 @@ def get_client(env):
|
|||||||
def copystream_progress(request, chunk_size= 10**5):
|
def copystream_progress(request, chunk_size= 10**5):
|
||||||
"""
|
"""
|
||||||
copies request.env.wsgi_input into request.body
|
copies request.env.wsgi_input into request.body
|
||||||
and stores progress upload status in cache.ram
|
and stores progress upload status in cache_ram
|
||||||
X-Progress-ID:length and X-Progress-ID:uploaded
|
X-Progress-ID:length and X-Progress-ID:uploaded
|
||||||
"""
|
"""
|
||||||
env = request.env
|
env = request.env
|
||||||
@@ -164,16 +164,16 @@ def copystream_progress(request, chunk_size= 10**5):
|
|||||||
copystream(source, dest, size, chunk_size)
|
copystream(source, dest, size, chunk_size)
|
||||||
return dest
|
return dest
|
||||||
cache_key = 'X-Progress-ID:'+request.vars['X-Progress-ID']
|
cache_key = 'X-Progress-ID:'+request.vars['X-Progress-ID']
|
||||||
cache = Cache(request)
|
cache_ram = CacheInRam(request) # same as cache.ram because meta_storage
|
||||||
cache.ram(cache_key+':length', lambda: size, 0)
|
cache_ram(cache_key+':length', lambda: size, 0)
|
||||||
cache.ram(cache_key+':uploaded', lambda: 0, 0)
|
cache_ram(cache_key+':uploaded', lambda: 0, 0)
|
||||||
while size > 0:
|
while size > 0:
|
||||||
if size < chunk_size:
|
if size < chunk_size:
|
||||||
data = source.read(size)
|
data = source.read(size)
|
||||||
cache.ram.increment(cache_key+':uploaded', size)
|
cache_ram.increment(cache_key+':uploaded', size)
|
||||||
else:
|
else:
|
||||||
data = source.read(chunk_size)
|
data = source.read(chunk_size)
|
||||||
cache.ram.increment(cache_key+':uploaded', chunk_size)
|
cache_ram.increment(cache_key+':uploaded', chunk_size)
|
||||||
length = len(data)
|
length = len(data)
|
||||||
if length > size:
|
if length > size:
|
||||||
(data, length) = (data[:size], size)
|
(data, length) = (data[:size], size)
|
||||||
@@ -184,8 +184,8 @@ def copystream_progress(request, chunk_size= 10**5):
|
|||||||
if length < chunk_size:
|
if length < chunk_size:
|
||||||
break
|
break
|
||||||
dest.seek(0)
|
dest.seek(0)
|
||||||
cache.ram(cache_key+':length', None)
|
cache_ram(cache_key+':length', None)
|
||||||
cache.ram(cache_key+':uploaded', None)
|
cache_ram(cache_key+':uploaded', None)
|
||||||
return dest
|
return dest
|
||||||
|
|
||||||
|
|
||||||
@@ -425,19 +425,23 @@ def wsgibase(environ, responder):
|
|||||||
# ##################################################
|
# ##################################################
|
||||||
app = request.application ## must go after url_in!
|
app = request.application ## must go after url_in!
|
||||||
|
|
||||||
http_host = env.http_host.split(':',1)[0]
|
if not global_settings.local_hosts:
|
||||||
local_hosts = [http_host,'::1','127.0.0.1',
|
local_hosts = ['127.0.0.1','::ffff:127.0.0.1']
|
||||||
'::ffff:127.0.0.1']
|
if not global_settings.web2py_runtime_gae:
|
||||||
if not global_settings.web2py_runtime_gae:
|
try:
|
||||||
try:
|
local_hosts.append(socket.gethostname())
|
||||||
local_hosts.append(socket.gethostname())
|
except TypeError:
|
||||||
except TypeError:
|
pass
|
||||||
pass
|
try:
|
||||||
try:
|
if env.server_name:
|
||||||
local_hosts.append(
|
local_hosts += [
|
||||||
socket.gethostbyname(http_host))
|
env.server_name,
|
||||||
except (socket.gaierror,TypeError):
|
socket.gethostbyname(env.server_name)]
|
||||||
pass
|
except (socket.gaierror,TypeError):
|
||||||
|
pass
|
||||||
|
global_settings.local_hosts = local_hosts
|
||||||
|
else:
|
||||||
|
local_hosts = global_settings.local_hosts
|
||||||
client = get_client(env)
|
client = get_client(env)
|
||||||
x_req_with = str(env.http_x_requested_with).lower()
|
x_req_with = str(env.http_x_requested_with).lower()
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1466,7 +1466,7 @@ class Worker(Thread):
|
|||||||
except UnicodeDecodeError:
|
except UnicodeDecodeError:
|
||||||
self.err_log.warning('Invalid request header: '+repr(l))
|
self.err_log.warning('Invalid request header: '+repr(l))
|
||||||
|
|
||||||
if l.strip() == '':
|
if l.strip().replace('\0','') == '':
|
||||||
break
|
break
|
||||||
elif l[0] in ' \t' and lname:
|
elif l[0] in ' \t' and lname:
|
||||||
# Some headers take more than one line
|
# Some headers take more than one line
|
||||||
|
|||||||
+1
-1
@@ -6,6 +6,7 @@ License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
import socket
|
||||||
import platform
|
import platform
|
||||||
from storage import Storage
|
from storage import Storage
|
||||||
|
|
||||||
@@ -37,4 +38,3 @@ global_settings.is_jython = \
|
|||||||
str(sys.copyright).find('Jython') > 0
|
str(sys.copyright).find('Jython') > 0
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1059,7 +1059,7 @@ class SQLFORM(FORM):
|
|||||||
|
|
||||||
xfields.append((row_id,label,inp,comment))
|
xfields.append((row_id,label,inp,comment))
|
||||||
self.custom.dspval[fieldname] = dspval or nbsp
|
self.custom.dspval[fieldname] = dspval or nbsp
|
||||||
self.custom.inpval[fieldname] = inpval or ''
|
self.custom.inpval[fieldname] = inpval if not inpval is None else ''
|
||||||
self.custom.widget[fieldname] = inp
|
self.custom.widget[fieldname] = inp
|
||||||
|
|
||||||
# if a record is provided and found, as is linkto
|
# if a record is provided and found, as is linkto
|
||||||
|
|||||||
+29
-1
@@ -4576,6 +4576,7 @@ class Wiki(object):
|
|||||||
self.env['component'] = Wiki.component
|
self.env['component'] = Wiki.component
|
||||||
if render == 'markmin': render=self.markmin_render
|
if render == 'markmin': render=self.markmin_render
|
||||||
elif render == 'html': render=self.html_render
|
elif render == 'html': render=self.html_render
|
||||||
|
self.render = render
|
||||||
self.auth = auth
|
self.auth = auth
|
||||||
if self.auth.user:
|
if self.auth.user:
|
||||||
self.force_prefix = force_prefix % self.auth.user
|
self.force_prefix = force_prefix % self.auth.user
|
||||||
@@ -4702,6 +4703,8 @@ class Wiki(object):
|
|||||||
)
|
)
|
||||||
elif zero=='_cloud':
|
elif zero=='_cloud':
|
||||||
return self.cloud()
|
return self.cloud()
|
||||||
|
elif zero == '_preview':
|
||||||
|
return self.preview(self.render)
|
||||||
|
|
||||||
def first_paragraph(self,page):
|
def first_paragraph(self,page):
|
||||||
if not self.can_read(page):
|
if not self.can_read(page):
|
||||||
@@ -4782,7 +4785,29 @@ class Wiki(object):
|
|||||||
elif form.accepted:
|
elif form.accepted:
|
||||||
current.session.flash = 'page created'
|
current.session.flash = 'page created'
|
||||||
redirect(URL(args=slug))
|
redirect(URL(args=slug))
|
||||||
return dict(content=form)
|
script = """
|
||||||
|
$(function() {
|
||||||
|
if (!$('#wiki_page_body').length) return;
|
||||||
|
var pagecontent = $('#wiki_page_body');
|
||||||
|
var prevbutton = $('<button class="btn nopreview">Preview</button>');
|
||||||
|
var preview = $('<div id="preview"></div>').hide();
|
||||||
|
var table = $('form');
|
||||||
|
prevbutton.insertBefore(table);
|
||||||
|
prevbutton.on('click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (prevbutton.hasClass('nopreview')) {
|
||||||
|
prevbutton.addClass('preview').removeClass('nopreview').html('Edit Source');
|
||||||
|
preview.insertBefore(table);
|
||||||
|
web2py_ajax_page('post', '%(url)s', {body : $('#wiki_page_body').val()}, 'preview');
|
||||||
|
table.fadeOut('medium', function() {preview.fadeIn()});
|
||||||
|
} else {
|
||||||
|
prevbutton.addClass('nopreview').removeClass('preview').html('Preview');
|
||||||
|
preview.fadeOut('medium', function() {table.fadeIn()});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
""" % dict(url=URL(args=('_preview')))
|
||||||
|
return dict(content=TAG[''](form, SCRIPT(script)))
|
||||||
|
|
||||||
def editmedia(self,slug):
|
def editmedia(self,slug):
|
||||||
auth = self.auth
|
auth = self.auth
|
||||||
@@ -4977,6 +5002,9 @@ class Wiki(object):
|
|||||||
vars=dict(q=item.wiki_tag.name))))
|
vars=dict(q=item.wiki_tag.name))))
|
||||||
items.append(' ')
|
items.append(' ')
|
||||||
return dict(content = DIV(_class='w2p_cloud',*items))
|
return dict(content = DIV(_class='w2p_cloud',*items))
|
||||||
|
def preview(self, render):
|
||||||
|
request = current.request
|
||||||
|
return render(request.post_vars)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
import doctest
|
import doctest
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
import os
|
|
||||||
import sys
|
|
||||||
paths = [sys.argv[1]]
|
|
||||||
paths1 = []
|
|
||||||
paths2 = []
|
|
||||||
while paths:
|
|
||||||
path = paths.pop()
|
|
||||||
for filename in os.listdir(path):
|
|
||||||
fullname = os.path.join(path,filename)
|
|
||||||
if os.path.isdir(fullname):
|
|
||||||
paths.append(fullname)
|
|
||||||
else:
|
|
||||||
extension = filename.split('.')[-1]
|
|
||||||
if extension.lower() in ('png','gif','jpg','jpeg','js','css'):
|
|
||||||
paths1.append((filename,fullname))
|
|
||||||
if extension.lower() in ('css','js','py','html'):
|
|
||||||
paths2.append(fullname)
|
|
||||||
for filename,fullname in paths1:
|
|
||||||
for otherfullname in paths2:
|
|
||||||
if open(otherfullname).read().find(filename)>=0:
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
print fullname
|
|
||||||
# os.system('hg rm '+fullname)
|
|
||||||
# os.system('rm '+fullname)
|
|
||||||
@@ -94,11 +94,11 @@ class SessionSetDb(SessionSet):
|
|||||||
"""Return list of SessionDb instances for existing sessions."""
|
"""Return list of SessionDb instances for existing sessions."""
|
||||||
sessions = []
|
sessions = []
|
||||||
tablename = 'web2py_session'
|
tablename = 'web2py_session'
|
||||||
if request.application:
|
from gluon import current
|
||||||
tablename = 'web2py_session_' + request.application
|
(record_id_name, table, record_id, unique_key) = \
|
||||||
if tablename in db:
|
current.response._dbtable_and_field
|
||||||
for row in db(db[tablename].id > 0).select():
|
for row in table._db(table.id > 0).select():
|
||||||
sessions.append(SessionDb(row))
|
sessions.append(SessionDb(row))
|
||||||
return sessions
|
return sessions
|
||||||
|
|
||||||
|
|
||||||
@@ -121,8 +121,11 @@ class SessionDb(object):
|
|||||||
self.row = row
|
self.row = row
|
||||||
|
|
||||||
def delete(self):
|
def delete(self):
|
||||||
|
from gluon import current
|
||||||
|
(record_id_name, table, record_id, unique_key) = \
|
||||||
|
current.response._dbtable_and_field
|
||||||
self.row.delete_record()
|
self.row.delete_record()
|
||||||
db.commit()
|
table._db.commit()
|
||||||
|
|
||||||
def get(self):
|
def get(self):
|
||||||
session = Storage()
|
session = Storage()
|
||||||
@@ -130,7 +133,13 @@ class SessionDb(object):
|
|||||||
return session
|
return session
|
||||||
|
|
||||||
def last_visit_default(self):
|
def last_visit_default(self):
|
||||||
return self.row.modified_datetime
|
if isinstance(self.row.modified_datetime, datetime.datetime):
|
||||||
|
return self.row.modified_datetime
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
return datetime.datetime.strptime(self.row.modified_datetime, '%Y-%m-%d %H:%M:%S.%f')
|
||||||
|
except:
|
||||||
|
print 'failed to retrieve last modified time (value: %s)' % self.row.modified_datetime
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.row.unique_key
|
return self.row.unique_key
|
||||||
|
|||||||
Reference in New Issue
Block a user