Compare commits

...
26 Commits
Author SHA1 Message Date
mdipierro 99087ab37a R-2.9.8 2014-09-06 23:09:44 -05:00
mdipierro 4bcd905f4f fixed appadmin/check_credentials problem 2014-09-06 22:57:26 -05:00
mdipierro 83bda542ad fixed opening get_session for new session files 2014-09-06 18:10:54 -05:00
mdipierro 1ea27f7f15 linked readthedocs 2014-09-05 16:47:20 -05:00
mdipierro 95b54857a3 fixed separator in trunk 2014-09-05 08:12:03 -05:00
mdipierro 327b1cbfdd R-2.9.7 2014-09-04 22:37:12 -05:00
mdipierro 3bd44d4d84 R-2.9.7 2014-09-04 22:31:42 -05:00
mdipierro 7e50bd6050 R-2.9.7 2014-09-04 22:30:19 -05:00
mdipierro c1c3621bf3 using recfile for sessions for speed 2014-09-04 22:28:51 -05:00
mdipierro 2d9f0fafdc better cache-disk, thanks Leonel 2014-09-04 22:27:52 -05:00
mdipierro 9fd827c561 added recfile.py 2014-09-04 22:16:09 -05:00
mdipierro 6ba9f450b2 Merge pull request #489 from jonathannew/master
fix custom view delimiters
2014-09-04 22:06:29 -05:00
mdipierro d1d85e9614 Merge pull request #488 from niphlod/fix/scheduler
avoid multiple cascade paths
2014-09-04 22:05:14 -05:00
mdipierro 6649721a7d Merge pull request #485 from dokime7/patch-5
Fix LOAD on action @request.restful()
2014-09-04 22:04:35 -05:00
mdipierro a51007949f Merge pull request #487 from ilvalle/grid-fix
fix grid groupby with more than 2 Fields in the expression
2014-09-04 22:04:05 -05:00
Jonathan New 8c5422d2d6 fix custom view delimiters 2014-09-04 19:16:00 +08:00
mdipierro b8a29a67aa typo in try_create_web2py_filesystem 2014-09-03 17:14:00 -05:00
mdipierro 3902cb0b27 support for multiple db filesystems, thanks Luca 2014-09-03 16:37:19 -05:00
mdipierro d744a99e13 fixed partially problem with web2py_filesystem on GAE 2014-09-03 16:29:05 -05:00
niphlod 1456c0da1e references can be long too 2014-09-03 21:23:24 +02:00
niphlod fa5100cb2a avoid multiple cascade paths 2014-09-03 21:09:09 +02:00
ilvalle 9b9a5034ad fix grid groupby with more than 2 Fields in the expression 2014-09-03 19:30:22 +02:00
mdipierro d1e4ede9b3 fixed problem with delimiters, thanks Anthony 2014-09-03 10:52:31 -05:00
mdipierro f1ab50fb91 fixed a problem with reset_password 2014-09-02 12:17:26 -05:00
Jeremie Dokime 52fac63b9e Fix LOAD on action @request.restful()
LOAD didn't work on action decorated with @request.restful() when args and/or vars are passed because the restful method is called with the main "request" object (browser url action) instead of the "other_request" object used by LOAD.
So I have injected the restful method with the good object context.
It's a bit nasty, so if someone knows how to do it better, I'm happy.
2014-09-02 18:16:51 +02:00
mdipierro d73c668f2d Key.from_path -> self.keyfunc, thanks Quint 2014-09-02 10:10:14 -05:00
15 changed files with 799 additions and 674 deletions
+4 -1
View File
@@ -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)
+10 -10
View File
@@ -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
View File
@@ -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)
[![Docs Status](https://readthedocs.org/projects/web2py/badge/?version=latest)](http://web2py.rtfd.org/)
## Tests ## Tests
[![Build Status](https://travis-ci.org/web2py/web2py.png)](https://travis-ci.org/web2py/web2py) [![Build Status](https://travis-ci.org/web2py/web2py.png)](https://travis-ci.org/web2py/web2py)
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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:
+63
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
+13 -2
View File
@@ -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."""