Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
99087ab37a | ||
|
|
4bcd905f4f | ||
|
|
83bda542ad | ||
|
|
1ea27f7f15 | ||
|
|
95b54857a3 | ||
|
|
327b1cbfdd | ||
|
|
3bd44d4d84 | ||
|
|
7e50bd6050 | ||
|
|
c1c3621bf3 | ||
|
|
2d9f0fafdc | ||
|
|
9fd827c561 | ||
|
|
6ba9f450b2 | ||
|
|
d1d85e9614 | ||
|
|
6649721a7d | ||
|
|
a51007949f | ||
|
|
8c5422d2d6 | ||
|
|
b8a29a67aa | ||
|
|
3902cb0b27 | ||
|
|
d744a99e13 | ||
|
|
1456c0da1e | ||
|
|
fa5100cb2a | ||
|
|
9b9a5034ad | ||
|
|
d1e4ede9b3 | ||
|
|
f1ab50fb91 | ||
|
|
52fac63b9e | ||
|
|
d73c668f2d |
@@ -1,10 +1,13 @@
|
|||||||
## 2.9.6
|
## 2.9.6 - 2.9.8
|
||||||
|
|
||||||
|
- fixed support of GAE + SQL
|
||||||
- fixed a typo in the license of some login_methods code. It is now LGPL consistently with the rest of the web2py code. This change applied to all previous web2py versions.
|
- fixed a typo in the license of some login_methods code. It is now LGPL consistently with the rest of the web2py code. This change applied to all previous web2py versions.
|
||||||
- support for SAML2 (with pysaml2)
|
- support for SAML2 (with pysaml2)
|
||||||
- Sphinx documentation (thanks Niphlod)
|
- Sphinx documentation (thanks Niphlod)
|
||||||
- improved scheduler (thanks Niphlod)
|
- improved scheduler (thanks Niphlod)
|
||||||
- increased security
|
- increased security
|
||||||
|
- better cache.dick (thanks Leonel)
|
||||||
|
- sessions are stored in subfolders for speed
|
||||||
- postgres support for "INSERT ... RETURING ..."
|
- postgres support for "INSERT ... RETURING ..."
|
||||||
- ldap support for Certificate Authority (thanks Maggs and Shane)
|
- ldap support for Certificate Authority (thanks Maggs and Shane)
|
||||||
- improved support for S/Mime X.509 (thanks Gyuris)
|
- improved support for S/Mime X.509 (thanks Gyuris)
|
||||||
|
|||||||
@@ -30,20 +30,20 @@ update:
|
|||||||
echo "remember that pymysql was tweaked"
|
echo "remember that pymysql was tweaked"
|
||||||
src:
|
src:
|
||||||
### Use semantic versioning
|
### Use semantic versioning
|
||||||
echo 'Version 2.9.6-stable+timestamp.'`date +%Y.%m.%d.%H.%M.%S` > VERSION
|
echo 'Version 2.9.8-stable+timestamp.'`date +%Y.%m.%d.%H.%M.%S` > VERSION
|
||||||
### rm -f all junk files
|
### rm -f all junk files
|
||||||
make clean
|
make clean
|
||||||
### clean up baisc apps
|
### clean up baisc apps
|
||||||
rm -f routes.py
|
rm -f routes.py
|
||||||
rm -f applications/*/sessions/*
|
rm -rf applications/*/sessions/*
|
||||||
rm -f applications/*/errors/* | echo 'too many files'
|
rm -rf applications/*/errors/* | echo 'too many files'
|
||||||
rm -f applications/*/cache/*
|
rm -rf applications/*/cache/*
|
||||||
rm -f applications/admin/databases/*
|
rm -rf applications/admin/databases/*
|
||||||
rm -f applications/welcome/databases/*
|
rm -rf applications/welcome/databases/*
|
||||||
rm -f applications/examples/databases/*
|
rm -rf applications/examples/databases/*
|
||||||
rm -f applications/admin/uploads/*
|
rm -rf applications/admin/uploads/*
|
||||||
rm -f applications/welcome/uploads/*
|
rm -rf applications/welcome/uploads/*
|
||||||
rm -f applications/examples/uploads/*
|
rm -rf applications/examples/uploads/*
|
||||||
### NO MORE make epydoc
|
### NO MORE make epydoc
|
||||||
# make epydoc
|
# make epydoc
|
||||||
### make welcome layout and appadmin the default
|
### make welcome layout and appadmin the default
|
||||||
|
|||||||
+4
-1
@@ -6,7 +6,6 @@ It is written and programmable in Python. LGPLv3 License
|
|||||||
|
|
||||||
Learn more at http://web2py.com
|
Learn more at http://web2py.com
|
||||||
|
|
||||||
|
|
||||||
## Google App Engine deployment
|
## Google App Engine deployment
|
||||||
|
|
||||||
cp examples/app.yaml ./
|
cp examples/app.yaml ./
|
||||||
@@ -14,6 +13,10 @@ Learn more at http://web2py.com
|
|||||||
|
|
||||||
Then edit ./app.yaml and replace "yourappname" with yourappname.
|
Then edit ./app.yaml and replace "yourappname" with yourappname.
|
||||||
|
|
||||||
|
## Documentation (readthedocs.org)
|
||||||
|
|
||||||
|
[](http://web2py.rtfd.org/)
|
||||||
|
|
||||||
## Tests
|
## Tests
|
||||||
|
|
||||||
[](https://travis-ci.org/web2py/web2py)
|
[](https://travis-ci.org/web2py/web2py)
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
Version 2.9.6-stable+timestamp.2014.09.01.20.55.31
|
Version 2.9.8-stable+timestamp.2014.09.06.23.09.41
|
||||||
|
|||||||
+126
-99
@@ -20,20 +20,27 @@ caching will be provided by the GAE memcache
|
|||||||
(see gluon.contrib.gae_memcache)
|
(see gluon.contrib.gae_memcache)
|
||||||
"""
|
"""
|
||||||
import time
|
import time
|
||||||
import portalocker
|
import shutil
|
||||||
import shelve
|
|
||||||
import thread
|
import thread
|
||||||
import os
|
import os
|
||||||
|
import sys
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
import hashlib
|
import hashlib
|
||||||
import datetime
|
import datetime
|
||||||
|
import tempfile
|
||||||
|
from gluon import recfile
|
||||||
try:
|
try:
|
||||||
from gluon import settings
|
from gluon import settings
|
||||||
have_settings = True
|
have_settings = True
|
||||||
except ImportError:
|
except ImportError:
|
||||||
have_settings = False
|
have_settings = False
|
||||||
|
|
||||||
|
try:
|
||||||
|
import cPickle as pickle
|
||||||
|
except:
|
||||||
|
import pickle
|
||||||
|
|
||||||
logger = logging.getLogger("web2py.cache")
|
logger = logging.getLogger("web2py.cache")
|
||||||
|
|
||||||
__all__ = ['Cache', 'lazy_cache']
|
__all__ = ['Cache', 'lazy_cache']
|
||||||
@@ -122,7 +129,7 @@ class CacheAbstract(object):
|
|||||||
Auxiliary function called by `clear` to search and clear cache entries
|
Auxiliary function called by `clear` to search and clear cache entries
|
||||||
"""
|
"""
|
||||||
r = re.compile(regex)
|
r = re.compile(regex)
|
||||||
for (key, value) in storage.items():
|
for key in storage:
|
||||||
if r.match(str(key)):
|
if r.match(str(key)):
|
||||||
del storage[key]
|
del storage[key]
|
||||||
break
|
break
|
||||||
@@ -242,7 +249,6 @@ class CacheOnDisk(CacheAbstract):
|
|||||||
|
|
||||||
This is implemented as a shelve object and it is shared by multiple web2py
|
This is implemented as a shelve object and it is shared by multiple web2py
|
||||||
processes (and threads) as long as they share the same filesystem.
|
processes (and threads) as long as they share the same filesystem.
|
||||||
The file is locked when accessed.
|
|
||||||
|
|
||||||
Disk cache provides persistance when web2py is started/stopped but it slower
|
Disk cache provides persistance when web2py is started/stopped but it slower
|
||||||
than `CacheInRam`
|
than `CacheInRam`
|
||||||
@@ -250,137 +256,158 @@ class CacheOnDisk(CacheAbstract):
|
|||||||
Values stored in disk cache must be pickable.
|
Values stored in disk cache must be pickable.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def _close_shelve_and_unlock(self):
|
class PersistentStorage(object):
|
||||||
try:
|
"""
|
||||||
if self.storage:
|
Implements a key based storage in disk.
|
||||||
self.storage.close()
|
"""
|
||||||
except ValueError:
|
def __init__(self, folder):
|
||||||
pass
|
self.folder = folder
|
||||||
finally:
|
# Check the best way to do atomic file replacement.
|
||||||
self.storage = None
|
if sys.version_info >= (3, 3):
|
||||||
if self.locker and self.locked:
|
self.replace = os.replace
|
||||||
portalocker.unlock(self.locker)
|
elif sys.platform == "win32":
|
||||||
self.locker.close()
|
import ctypes
|
||||||
self.locked = False
|
from ctypes import wintypes
|
||||||
|
ReplaceFile = ctypes.windll.kernel32.ReplaceFileW
|
||||||
|
ReplaceFile.restype = wintypes.BOOL
|
||||||
|
ReplaceFile.argtypes = [
|
||||||
|
wintypes.LPWSTR,
|
||||||
|
wintypes.LPWSTR,
|
||||||
|
wintypes.LPWSTR,
|
||||||
|
wintypes.DWORD,
|
||||||
|
wintypes.LPVOID,
|
||||||
|
wintypes.LPVOID,
|
||||||
|
]
|
||||||
|
|
||||||
def _open_shelve_and_lock(self):
|
def replace_windows(src, dst):
|
||||||
"""Open and return a shelf object, obtaining an exclusive lock
|
if not ReplaceFile(dst, src, None, 0, 0, 0):
|
||||||
on self.locker first. Replaces the close method of the
|
os.rename(src, dst)
|
||||||
returned shelf instance with one that releases the lock upon
|
|
||||||
closing."""
|
|
||||||
|
|
||||||
storage = None
|
self.replace = replace_windows
|
||||||
locker = None
|
else:
|
||||||
locked = False
|
# POSIX rename() is always atomic
|
||||||
try:
|
self.replace = os.rename
|
||||||
locker = locker = open(self.locker_name, 'a')
|
|
||||||
portalocker.lock(locker, portalocker.LOCK_EX)
|
|
||||||
locked = True
|
def __setitem__(self, key, value):
|
||||||
|
tmp_name, tmp_path = tempfile.mkstemp(dir=self.folder)
|
||||||
|
tmp = os.fdopen(tmp_name, 'wb')
|
||||||
try:
|
try:
|
||||||
storage = shelve.open(self.shelve_name)
|
pickle.dump((time.time(), value), tmp, pickle.HIGHEST_PROTOCOL)
|
||||||
except:
|
finally:
|
||||||
logger.error('corrupted cache file %s, will try rebuild it'
|
tmp.close()
|
||||||
% self.shelve_name)
|
fullfilename = os.path.join(self.folder, recfile.generate(key))
|
||||||
storage = None
|
if not os.path.exists(os.path.dirname(fullfilename)):
|
||||||
if storage is None:
|
os.makedirs(os.path.dirname(fullfilename))
|
||||||
if os.path.exists(self.shelve_name):
|
self.replace(tmp_path, fullfilename)
|
||||||
os.unlink(self.shelve_name)
|
|
||||||
storage = shelve.open(self.shelve_name)
|
|
||||||
if not CacheAbstract.cache_stats_name in storage.keys():
|
def __getitem__(self, key):
|
||||||
storage[CacheAbstract.cache_stats_name] = {
|
if recfile.exists(key, path=self.folder):
|
||||||
'hit_total': 0, 'misses': 0}
|
timestamp, value = pickle.load(recfile.open(key, 'rb', path=self.folder))
|
||||||
storage.sync()
|
return value
|
||||||
except Exception, e:
|
else:
|
||||||
if storage:
|
raise KeyError
|
||||||
storage.close()
|
|
||||||
storage = None
|
def __contains__(self, key):
|
||||||
if locked:
|
return recfile.exists(key, path=self.folder)
|
||||||
portalocker.unlock(locker)
|
|
||||||
locker.close()
|
|
||||||
locked = False
|
def __delitem__(self, key):
|
||||||
raise RuntimeError(
|
recfile.remove(key, path=self.folder)
|
||||||
'unable to create/re-create cache file %s' % self.shelve_name)
|
|
||||||
self.locker = locker
|
|
||||||
self.locked = locked
|
def __iter__(self):
|
||||||
self.storage = storage
|
for dirpath, dirnames, filenames in os.walk(self.folder):
|
||||||
return storage
|
for filename in filenames:
|
||||||
|
yield filename
|
||||||
|
|
||||||
|
|
||||||
|
def get(self, key, default=None):
|
||||||
|
try:
|
||||||
|
return self[key]
|
||||||
|
except KeyError:
|
||||||
|
return default
|
||||||
|
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
for key in self:
|
||||||
|
del self[key]
|
||||||
|
|
||||||
def __init__(self, request=None, folder=None):
|
def __init__(self, request=None, folder=None):
|
||||||
self.initialized = False
|
self.initialized = False
|
||||||
self.request = request
|
self.request = request
|
||||||
self.folder = folder
|
self.folder = folder
|
||||||
self.storage = {}
|
self.storage = None
|
||||||
|
|
||||||
|
|
||||||
def initialize(self):
|
def initialize(self):
|
||||||
if self.initialized:
|
if self.initialized:
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
self.initialized = True
|
self.initialized = True
|
||||||
|
|
||||||
folder = self.folder
|
folder = self.folder
|
||||||
request = self.request
|
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
|
||||||
folder = folder or os.path.join(request.folder, 'cache')
|
folder = os.path.join(folder or request.folder, 'cache')
|
||||||
|
|
||||||
if not os.path.exists(folder):
|
if not os.path.exists(folder):
|
||||||
os.mkdir(folder)
|
os.mkdir(folder)
|
||||||
|
|
||||||
### we need this because of a possible bug in shelve that may
|
self.storage = CacheOnDisk.PersistentStorage(folder)
|
||||||
### or may not lock
|
|
||||||
self.locker_name = os.path.join(folder, 'cache.lock')
|
if not CacheAbstract.cache_stats_name in self.storage:
|
||||||
self.shelve_name = os.path.join(folder, 'cache.shelve')
|
self.storage[CacheAbstract.cache_stats_name] = {'hit_total': 0, 'misses': 0}
|
||||||
|
|
||||||
def clear(self, regex=None):
|
|
||||||
self.initialize()
|
|
||||||
storage = self._open_shelve_and_lock()
|
|
||||||
try:
|
|
||||||
if regex is None:
|
|
||||||
storage.clear()
|
|
||||||
else:
|
|
||||||
self._clear(storage, regex)
|
|
||||||
storage.sync()
|
|
||||||
finally:
|
|
||||||
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()
|
self.initialize()
|
||||||
|
|
||||||
dt = time_expire
|
dt = time_expire
|
||||||
storage = self._open_shelve_and_lock()
|
item = self.storage.get(key)
|
||||||
try:
|
self.storage[CacheAbstract.cache_stats_name]['hit_total'] += 1
|
||||||
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 self.storage[key]
|
||||||
del storage[key]
|
|
||||||
storage.sync()
|
if f is None:
|
||||||
now = time.time()
|
return None
|
||||||
if f is None:
|
|
||||||
value = None
|
now = time.time()
|
||||||
elif item and (dt is None or item[0] > now - dt):
|
|
||||||
value = item[1]
|
if item and ((dt is None) or (item[0] > now - dt)):
|
||||||
else:
|
value = item[1]
|
||||||
value = f()
|
else:
|
||||||
storage[key] = (now, value)
|
value = f()
|
||||||
storage[CacheAbstract.cache_stats_name]['misses'] += 1
|
self.storage[key] = (now, value)
|
||||||
storage.sync()
|
self.storage[CacheAbstract.cache_stats_name]['misses'] += 1
|
||||||
finally:
|
|
||||||
self._close_shelve_and_unlock()
|
|
||||||
|
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
def clear(self, regex=None):
|
||||||
|
self.initialize()
|
||||||
|
storage = self.storage
|
||||||
|
if regex is None:
|
||||||
|
storage.clear()
|
||||||
|
else:
|
||||||
|
self._clear(storage, regex)
|
||||||
|
|
||||||
|
if not CacheAbstract.cache_stats_name in storage:
|
||||||
|
storage[CacheAbstract.cache_stats_name] = {
|
||||||
|
'hit_total': 0, 'misses': 0}
|
||||||
|
|
||||||
|
|
||||||
def increment(self, key, value=1):
|
def increment(self, key, value=1):
|
||||||
self.initialize()
|
self.initialize()
|
||||||
storage = self._open_shelve_and_lock()
|
self.storage[key] += value
|
||||||
try:
|
|
||||||
if key in storage:
|
|
||||||
value = storage[key][1] + value
|
|
||||||
storage[key] = (time.time(), value)
|
|
||||||
storage.sync()
|
|
||||||
finally:
|
|
||||||
self._close_shelve_and_unlock()
|
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class CacheAction(object):
|
class CacheAction(object):
|
||||||
def __init__(self, func, key, time_expire, cache, cache_model):
|
def __init__(self, func, key, time_expire, cache, cache_model):
|
||||||
self.__name__ = func.__name__
|
self.__name__ = func.__name__
|
||||||
|
|||||||
+2
-1
@@ -38,6 +38,7 @@ import marshal
|
|||||||
import shutil
|
import shutil
|
||||||
import imp
|
import imp
|
||||||
import logging
|
import logging
|
||||||
|
import types
|
||||||
logger = logging.getLogger("web2py")
|
logger = logging.getLogger("web2py")
|
||||||
from gluon import rewrite
|
from gluon import rewrite
|
||||||
from custom_import import custom_import_install
|
from custom_import import custom_import_install
|
||||||
@@ -211,7 +212,7 @@ def LOAD(c=None, f='index', args=None, vars=None,
|
|||||||
request.env.path_info
|
request.env.path_info
|
||||||
other_request.cid = target
|
other_request.cid = target
|
||||||
other_request.env.http_web2py_component_element = target
|
other_request.env.http_web2py_component_element = target
|
||||||
other_request.restful = request.restful # Needed when you call LOAD() on a controller who has some actions decorates with @request.restful()
|
other_request.restful = types.MethodType(request.restful.im_func, other_request) # A bit nasty but needed to use LOAD on action decorates with @request.restful()
|
||||||
other_response.view = '%s/%s.%s' % (c, f, other_request.extension)
|
other_response.view = '%s/%s.%s' % (c, f, other_request.extension)
|
||||||
|
|
||||||
other_environment = copy.copy(current.globalenv) # NASTY
|
other_environment = copy.copy(current.globalenv) # NASTY
|
||||||
|
|||||||
+20
-14
@@ -198,7 +198,6 @@ if PYTHON_VERSION[:2] < (2, 7):
|
|||||||
else:
|
else:
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
|
|
||||||
CALLABLETYPES = (types.LambdaType, types.FunctionType,
|
CALLABLETYPES = (types.LambdaType, types.FunctionType,
|
||||||
types.BuiltinFunctionType,
|
types.BuiltinFunctionType,
|
||||||
types.MethodType, types.BuiltinMethodType)
|
types.MethodType, types.BuiltinMethodType)
|
||||||
@@ -4587,24 +4586,28 @@ class CubridAdapter(MySQLAdapter):
|
|||||||
######## GAE MySQL ##########
|
######## GAE MySQL ##########
|
||||||
class DatabaseStoredFile:
|
class DatabaseStoredFile:
|
||||||
|
|
||||||
web2py_filesystem = False
|
web2py_filesystems = set()
|
||||||
|
|
||||||
def escape(self, obj):
|
def escape(self, obj):
|
||||||
return self.db._adapter.escape(obj)
|
return self.db._adapter.escape(obj)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def try_create_web2py_filesystem(db):
|
||||||
|
if not db._uri in DatabaseStoredFile.web2py_filesystems:
|
||||||
|
if db._adapter.dbengine == 'mysql':
|
||||||
|
sql = "CREATE TABLE IF NOT EXISTS web2py_filesystem (path VARCHAR(255), content LONGTEXT, PRIMARY KEY(path) ) ENGINE=InnoDB;"
|
||||||
|
elif db._adapter.dbengine in ('postgres', 'sqlite'):
|
||||||
|
sql = "CREATE TABLE IF NOT EXISTS web2py_filesystem (path VARCHAR(255), content TEXT, PRIMARY KEY(path));"
|
||||||
|
db.executesql(sql)
|
||||||
|
DatabaseStoredFile.web2py_filesystems.add(db._uri)
|
||||||
|
|
||||||
def __init__(self, db, filename, mode):
|
def __init__(self, db, filename, mode):
|
||||||
if not db._adapter.dbengine in ('mysql', 'postgres', 'sqlite'):
|
if not db._adapter.dbengine in ('mysql', 'postgres', 'sqlite'):
|
||||||
raise RuntimeError("only MySQL/Postgres/SQLite can store metadata .table files in database for now")
|
raise RuntimeError("only MySQL/Postgres/SQLite can store metadata .table files in database for now")
|
||||||
self.db = db
|
self.db = db
|
||||||
self.filename = filename
|
self.filename = filename
|
||||||
self.mode = mode
|
self.mode = mode
|
||||||
if not self.web2py_filesystem:
|
DatabaseStoredFile.try_create_web2py_filesystem(db)
|
||||||
if db._adapter.dbengine == 'mysql':
|
|
||||||
sql = "CREATE TABLE IF NOT EXISTS web2py_filesystem (path VARCHAR(255), content LONGTEXT, PRIMARY KEY(path) ) ENGINE=InnoDB;"
|
|
||||||
elif db._adapter.dbengine in ('postgres', 'sqlite'):
|
|
||||||
sql = "CREATE TABLE IF NOT EXISTS web2py_filesystem (path VARCHAR(255), content TEXT, PRIMARY KEY(path));"
|
|
||||||
self.db.executesql(sql)
|
|
||||||
DatabaseStoredFile.web2py_filesystem = True
|
|
||||||
self.p = 0
|
self.p = 0
|
||||||
self.data = ''
|
self.data = ''
|
||||||
if mode in ('r', 'rw', 'a'):
|
if mode in ('r', 'rw', 'a'):
|
||||||
@@ -4655,6 +4658,9 @@ class DatabaseStoredFile:
|
|||||||
def exists(db, filename):
|
def exists(db, filename):
|
||||||
if exists(filename):
|
if exists(filename):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
DatabaseStoredFile.try_create_web2py_filesystem(db)
|
||||||
|
|
||||||
query = "SELECT path FROM web2py_filesystem WHERE path='%s'" % filename
|
query = "SELECT path FROM web2py_filesystem WHERE path='%s'" % filename
|
||||||
try:
|
try:
|
||||||
if db.executesql(query):
|
if db.executesql(query):
|
||||||
@@ -5137,35 +5143,35 @@ class GoogleDatastoreAdapter(NoSQLAdapter):
|
|||||||
return [GAEF(first.name, '!=', self.represent(second, first.type), lambda a, b:a!=b)]
|
return [GAEF(first.name, '!=', self.represent(second, first.type), lambda a, b:a!=b)]
|
||||||
else:
|
else:
|
||||||
if not second is None:
|
if not second is None:
|
||||||
second = Key.from_path(first._tablename, long(second))
|
second = self.keyfunc(first._tablename, long(second))
|
||||||
return [GAEF(first.name, '!=', second, lambda a, b:a!=b)]
|
return [GAEF(first.name, '!=', second, lambda a, b:a!=b)]
|
||||||
|
|
||||||
def LT(self, first, second=None):
|
def LT(self, first, second=None):
|
||||||
if first.type != 'id':
|
if first.type != 'id':
|
||||||
return [GAEF(first.name, '<', self.represent(second, first.type), lambda a, b:a<b)]
|
return [GAEF(first.name, '<', self.represent(second, first.type), lambda a, b:a<b)]
|
||||||
else:
|
else:
|
||||||
second = Key.from_path(first._tablename, long(second))
|
second = self.keyfunc(first._tablename, long(second))
|
||||||
return [GAEF(first.name, '<', second, lambda a, b:a<b)]
|
return [GAEF(first.name, '<', second, lambda a, b:a<b)]
|
||||||
|
|
||||||
def LE(self, first, second=None):
|
def LE(self, first, second=None):
|
||||||
if first.type != 'id':
|
if first.type != 'id':
|
||||||
return [GAEF(first.name, '<=', self.represent(second, first.type), lambda a, b:a<=b)]
|
return [GAEF(first.name, '<=', self.represent(second, first.type), lambda a, b:a<=b)]
|
||||||
else:
|
else:
|
||||||
second = Key.from_path(first._tablename, long(second))
|
second = self.keyfunc(first._tablename, long(second))
|
||||||
return [GAEF(first.name, '<=', second, lambda a, b:a<=b)]
|
return [GAEF(first.name, '<=', second, lambda a, b:a<=b)]
|
||||||
|
|
||||||
def GT(self, first, second=None):
|
def GT(self, first, second=None):
|
||||||
if first.type != 'id' or second==0 or second == '0':
|
if first.type != 'id' or second==0 or second == '0':
|
||||||
return [GAEF(first.name, '>', self.represent(second, first.type), lambda a, b:a>b)]
|
return [GAEF(first.name, '>', self.represent(second, first.type), lambda a, b:a>b)]
|
||||||
else:
|
else:
|
||||||
second = Key.from_path(first._tablename, long(second))
|
second = self.keyfunc(first._tablename, long(second))
|
||||||
return [GAEF(first.name, '>', second, lambda a, b:a>b)]
|
return [GAEF(first.name, '>', second, lambda a, b:a>b)]
|
||||||
|
|
||||||
def GE(self, first, second=None):
|
def GE(self, first, second=None):
|
||||||
if first.type != 'id':
|
if first.type != 'id':
|
||||||
return [GAEF(first.name, '>=', self.represent(second, first.type), lambda a, b:a>=b)]
|
return [GAEF(first.name, '>=', self.represent(second, first.type), lambda a, b:a>=b)]
|
||||||
else:
|
else:
|
||||||
second = Key.from_path(first._tablename, long(second))
|
second = self.keyfunc(first._tablename, long(second))
|
||||||
return [GAEF(first.name, '>=', second, lambda a, b:a>=b)]
|
return [GAEF(first.name, '>=', second, lambda a, b:a>=b)]
|
||||||
|
|
||||||
def INVERT(self, first):
|
def INVERT(self, first):
|
||||||
|
|||||||
+3
-1
@@ -20,7 +20,7 @@ import datetime
|
|||||||
import logging
|
import logging
|
||||||
from http import HTTP
|
from http import HTTP
|
||||||
from gzip import open as gzopen
|
from gzip import open as gzopen
|
||||||
|
from recfile import generate
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'parse_version',
|
'parse_version',
|
||||||
@@ -400,6 +400,8 @@ def get_session(request, other_application='admin'):
|
|||||||
session_id = request.cookies['session_id_' + other_application].value
|
session_id = request.cookies['session_id_' + other_application].value
|
||||||
session_filename = os.path.join(
|
session_filename = os.path.join(
|
||||||
up(request.folder), other_application, 'sessions', session_id)
|
up(request.folder), other_application, 'sessions', session_id)
|
||||||
|
if not os.path.exists(session_filename):
|
||||||
|
session_filename = generate(session_filename)
|
||||||
osession = storage.load_storage(session_filename)
|
osession = storage.load_storage(session_filename)
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
osession = storage.Storage()
|
osession = storage.Storage()
|
||||||
|
|||||||
+3
-3
@@ -25,6 +25,7 @@ from gluon.serializers import json, custom_json
|
|||||||
import gluon.settings as settings
|
import gluon.settings as settings
|
||||||
from gluon.utils import web2py_uuid, secure_dumps, secure_loads
|
from gluon.utils import web2py_uuid, secure_dumps, secure_loads
|
||||||
from gluon.settings import global_settings
|
from gluon.settings import global_settings
|
||||||
|
from gluon import recfile
|
||||||
import hashlib
|
import hashlib
|
||||||
import portalocker
|
import portalocker
|
||||||
import cPickle
|
import cPickle
|
||||||
@@ -165,7 +166,6 @@ class Request(Storage):
|
|||||||
- is_local
|
- is_local
|
||||||
- is_https
|
- is_https
|
||||||
- restful()
|
- restful()
|
||||||
- settings
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, env):
|
def __init__(self, env):
|
||||||
@@ -825,7 +825,7 @@ class Session(Storage):
|
|||||||
'sessions', response.session_id)
|
'sessions', response.session_id)
|
||||||
try:
|
try:
|
||||||
response.session_file = \
|
response.session_file = \
|
||||||
open(response.session_filename, 'rb+')
|
recfile.open(response.session_filename, 'rb+')
|
||||||
portalocker.lock(response.session_file,
|
portalocker.lock(response.session_file,
|
||||||
portalocker.LOCK_EX)
|
portalocker.LOCK_EX)
|
||||||
response.session_locked = True
|
response.session_locked = True
|
||||||
@@ -1147,7 +1147,7 @@ class Session(Storage):
|
|||||||
session_folder = os.path.dirname(response.session_filename)
|
session_folder = os.path.dirname(response.session_filename)
|
||||||
if not os.path.exists(session_folder):
|
if not os.path.exists(session_folder):
|
||||||
os.mkdir(session_folder)
|
os.mkdir(session_folder)
|
||||||
response.session_file = open(response.session_filename, 'wb')
|
response.session_file = recfile.open(response.session_filename, 'wb')
|
||||||
portalocker.lock(response.session_file, portalocker.LOCK_EX)
|
portalocker.lock(response.session_file, portalocker.LOCK_EX)
|
||||||
response.session_locked = True
|
response.session_locked = True
|
||||||
if response.session_file:
|
if response.session_file:
|
||||||
|
|||||||
Executable
+63
@@ -0,0 +1,63 @@
|
|||||||
|
import os, uuid
|
||||||
|
|
||||||
|
def generate(filename, depth=2, base=512):
|
||||||
|
if os.path.sep in filename:
|
||||||
|
path, filename = os.path.split(filename)
|
||||||
|
else:
|
||||||
|
path = None
|
||||||
|
dummyhash = sum(ord(c)*256**(i % 4) for i,c in enumerate(filename)) % base**depth
|
||||||
|
folders = []
|
||||||
|
for level in range(depth-1,-1,-1):
|
||||||
|
code, dummyhash = divmod(dummyhash, base**level)
|
||||||
|
folders.append("%03x" % code)
|
||||||
|
folders.append(filename)
|
||||||
|
if path:
|
||||||
|
folders.insert(0,path)
|
||||||
|
return os.path.join(*folders)
|
||||||
|
|
||||||
|
def exists(filename, path=None):
|
||||||
|
if os.path.exists(filename):
|
||||||
|
return True
|
||||||
|
if path is None:
|
||||||
|
path, filename = os.path.split(filename)
|
||||||
|
fullfilename = os.path.join(path, generate(filename))
|
||||||
|
if os.path.exists(fullfilename):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def remove(filename, path=None):
|
||||||
|
if os.path.exists(filename):
|
||||||
|
return os.unlink(filename)
|
||||||
|
if path is None:
|
||||||
|
path, filename = os.path.split(filename)
|
||||||
|
fullfilename = os.path.join(path, generate(filename))
|
||||||
|
if os.path.exists(fullfilename):
|
||||||
|
return os.unlink(fullfilename)
|
||||||
|
raise IOError
|
||||||
|
|
||||||
|
def open(filename, mode="r", path=None):
|
||||||
|
if not path:
|
||||||
|
path, filename = os.path.split(filename)
|
||||||
|
fullfilename = None
|
||||||
|
if not mode.startswith('w'):
|
||||||
|
fullfilename = os.path.join(path, filename)
|
||||||
|
if not os.path.exists(fullfilename):
|
||||||
|
fullfilename = None
|
||||||
|
if not fullfilename:
|
||||||
|
fullfilename = os.path.join(path, generate(filename))
|
||||||
|
if mode.startswith('w') and not os.path.exists(os.path.dirname(fullfilename)):
|
||||||
|
os.makedirs(os.path.dirname(fullfilename))
|
||||||
|
return file(fullfilename, mode)
|
||||||
|
|
||||||
|
def test():
|
||||||
|
if not os.path.exists('tests'):
|
||||||
|
os.mkdir('tests')
|
||||||
|
for k in range(20):
|
||||||
|
filename = os.path.join('tests',str(uuid.uuid4())+'.test')
|
||||||
|
open(filename, "w").write('test')
|
||||||
|
assert open(filename, "r").read()=='test'
|
||||||
|
if exists(filename):
|
||||||
|
remove(filename)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
test()
|
||||||
+7
-4
@@ -96,7 +96,7 @@ IDENTIFIER = "%s#%s" % (socket.gethostname(),os.getpid())
|
|||||||
logger = logging.getLogger('web2py.scheduler.%s' % IDENTIFIER)
|
logger = logging.getLogger('web2py.scheduler.%s' % IDENTIFIER)
|
||||||
|
|
||||||
from gluon import DAL, Field, IS_NOT_EMPTY, IS_IN_SET, IS_NOT_IN_DB
|
from gluon import DAL, Field, IS_NOT_EMPTY, IS_IN_SET, IS_NOT_IN_DB
|
||||||
from gluon import IS_INT_IN_RANGE, IS_DATETIME
|
from gluon import IS_INT_IN_RANGE, IS_DATETIME, IS_IN_DB
|
||||||
from gluon.utils import web2py_uuid
|
from gluon.utils import web2py_uuid
|
||||||
from gluon.storage import Storage
|
from gluon.storage import Storage
|
||||||
|
|
||||||
@@ -671,7 +671,10 @@ class Scheduler(MetaScheduler):
|
|||||||
db.define_table(
|
db.define_table(
|
||||||
'scheduler_task_deps',
|
'scheduler_task_deps',
|
||||||
Field('job_name', default='job_0'),
|
Field('job_name', default='job_0'),
|
||||||
Field('task_parent', 'reference scheduler_task'),
|
Field('task_parent', 'integer',
|
||||||
|
requires=IS_IN_DB(db, 'scheduler_task.id',
|
||||||
|
'%(task_name)s')
|
||||||
|
),
|
||||||
Field('task_child', 'reference scheduler_task'),
|
Field('task_child', 'reference scheduler_task'),
|
||||||
Field('can_visit', 'boolean', default=False),
|
Field('can_visit', 'boolean', default=False),
|
||||||
migrate=self.__get_migrate('scheduler_task_deps', migrate)
|
migrate=self.__get_migrate('scheduler_task_deps', migrate)
|
||||||
@@ -1311,7 +1314,7 @@ class Scheduler(MetaScheduler):
|
|||||||
"""
|
"""
|
||||||
from gluon.dal import Query
|
from gluon.dal import Query
|
||||||
sr, st = self.db.scheduler_run, self.db.scheduler_task
|
sr, st = self.db.scheduler_run, self.db.scheduler_task
|
||||||
if isinstance(ref, int):
|
if isinstance(ref, (int, long)):
|
||||||
q = st.id == ref
|
q = st.id == ref
|
||||||
elif isinstance(ref, str):
|
elif isinstance(ref, str):
|
||||||
q = st.uuid == ref
|
q = st.uuid == ref
|
||||||
@@ -1362,7 +1365,7 @@ class Scheduler(MetaScheduler):
|
|||||||
Experimental
|
Experimental
|
||||||
"""
|
"""
|
||||||
st, sw = self.db.scheduler_task, self.db.scheduler_worker
|
st, sw = self.db.scheduler_task, self.db.scheduler_worker
|
||||||
if isinstance(ref, int):
|
if isinstance(ref, (int, long)):
|
||||||
q = st.id == ref
|
q = st.id == ref
|
||||||
elif isinstance(ref, str):
|
elif isinstance(ref, str):
|
||||||
q = st.uuid == ref
|
q = st.uuid == ref
|
||||||
|
|||||||
+3
-1
@@ -1289,7 +1289,7 @@ class SQLFORM(FORM):
|
|||||||
xfields.append(
|
xfields.append(
|
||||||
(self.FIELDKEY_DELETE_RECORD + SQLFORM.ID_ROW_SUFFIX,
|
(self.FIELDKEY_DELETE_RECORD + SQLFORM.ID_ROW_SUFFIX,
|
||||||
LABEL(
|
LABEL(
|
||||||
T(delete_label), separator,
|
T(delete_label), sep,
|
||||||
_for=self.FIELDKEY_DELETE_RECORD,
|
_for=self.FIELDKEY_DELETE_RECORD,
|
||||||
_id=self.FIELDKEY_DELETE_RECORD + \
|
_id=self.FIELDKEY_DELETE_RECORD + \
|
||||||
SQLFORM.ID_LABEL_SUFFIX),
|
SQLFORM.ID_LABEL_SUFFIX),
|
||||||
@@ -2111,6 +2111,8 @@ class SQLFORM(FORM):
|
|||||||
field_id = groupby #take the field passed as groupby
|
field_id = groupby #take the field passed as groupby
|
||||||
elif groupby and isinstance(groupby, Expression):
|
elif groupby and isinstance(groupby, Expression):
|
||||||
field_id = groupby.first #take the first groupby field
|
field_id = groupby.first #take the first groupby field
|
||||||
|
while not(isinstance(field_id, Field)): # Navigate to the first Field of the expression
|
||||||
|
field_id = field_id.first
|
||||||
table = field_id.table
|
table = field_id.table
|
||||||
tablename = table._tablename
|
tablename = table._tablename
|
||||||
if not any(str(f) == str(field_id) for f in fields):
|
if not any(str(f) == str(field_id) for f in fields):
|
||||||
|
|||||||
+10
-6
@@ -279,15 +279,19 @@ class TemplateParser(object):
|
|||||||
self.context = context
|
self.context = context
|
||||||
|
|
||||||
# allow optional alternative delimiters
|
# allow optional alternative delimiters
|
||||||
if delimiters is None:
|
|
||||||
delimiters = context.get('response', {})\
|
|
||||||
.get('app_settings',{}).get('template_delimiters')
|
|
||||||
if delimiters != self.default_delimiters:
|
if delimiters != self.default_delimiters:
|
||||||
escaped_delimiters = (escape(elimiters[0]),
|
escaped_delimiters = (escape(delimiters[0]),
|
||||||
escape(delimiters[1]))
|
escape(delimiters[1]))
|
||||||
self.r_tag = compile(r'(%s.*?%s)' % escaped_delimiters, DOTALL)
|
self.r_tag = compile(r'(%s.*?%s)' % escaped_delimiters, DOTALL)
|
||||||
else:
|
elif hasattr(context.get('response', None), 'delimiters'):
|
||||||
delimiters = self.default_delimiters
|
if context['response'].delimiters != self.default_delimiters:
|
||||||
|
delimiters = context['response'].delimiters
|
||||||
|
escaped_delimiters = (
|
||||||
|
escape(delimiters[0]),
|
||||||
|
escape(delimiters[1]))
|
||||||
|
self.r_tag = compile(r'(%s.*?%s)' % escaped_delimiters,
|
||||||
|
DOTALL)
|
||||||
self.delimiters = delimiters
|
self.delimiters = delimiters
|
||||||
|
|
||||||
# Create a root level Content that everything will go into.
|
# Create a root level Content that everything will go into.
|
||||||
|
|||||||
+1
-1
@@ -3007,7 +3007,7 @@ class Auth(object):
|
|||||||
|
|
||||||
if self.settings.prevent_password_reset_attacks:
|
if self.settings.prevent_password_reset_attacks:
|
||||||
key = request.vars.key
|
key = request.vars.key
|
||||||
if not key and len(request.args)>1:
|
if not key and len(request.args)>0:
|
||||||
key = request.args[-1]
|
key = request.args[-1]
|
||||||
if key:
|
if key:
|
||||||
session._reset_password_key = key
|
session._reset_password_key = key
|
||||||
|
|||||||
@@ -29,6 +29,12 @@ Typical usage:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import with_statement
|
from __future__ import with_statement
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
print os.path.join(*__file__.split(os.sep)[:-2] or ['.'])
|
||||||
|
sys.path.append(os.path.join(*__file__.split(os.sep)[:-2] or ['.']))
|
||||||
|
|
||||||
from gluon import current
|
from gluon import current
|
||||||
from gluon.storage import Storage
|
from gluon.storage import Storage
|
||||||
from optparse import OptionParser
|
from optparse import OptionParser
|
||||||
@@ -37,6 +43,7 @@ import datetime
|
|||||||
import os
|
import os
|
||||||
import stat
|
import stat
|
||||||
import time
|
import time
|
||||||
|
import glob
|
||||||
|
|
||||||
EXPIRATION_MINUTES = 60
|
EXPIRATION_MINUTES = 60
|
||||||
SLEEP_MINUTES = 5
|
SLEEP_MINUTES = 5
|
||||||
@@ -157,6 +164,9 @@ class SessionFile(object):
|
|||||||
def delete(self):
|
def delete(self):
|
||||||
try:
|
try:
|
||||||
os.unlink(self.filename)
|
os.unlink(self.filename)
|
||||||
|
path = os.path.dirname(filename)
|
||||||
|
if not path.endswith('sessions') and len(os.listdir(path))==0:
|
||||||
|
os.rmdir(path)
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -191,10 +201,11 @@ def single_loop(expiration=None, force=False, verbose=False):
|
|||||||
except:
|
except:
|
||||||
expiration = EXPIRATION_MINUTES * 60
|
expiration = EXPIRATION_MINUTES * 60
|
||||||
|
|
||||||
set_db = SessionSetDb(expiration, force, verbose)
|
|
||||||
set_files = SessionSetFiles(expiration, force, verbose)
|
set_files = SessionSetFiles(expiration, force, verbose)
|
||||||
set_db.trash()
|
|
||||||
set_files.trash()
|
set_files.trash()
|
||||||
|
set_db = SessionSetDb(expiration, force, verbose)
|
||||||
|
set_db.trash()
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
"""Main processing."""
|
"""Main processing."""
|
||||||
|
|||||||
Reference in New Issue
Block a user