mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-06-23 15:30:45 -04:00
New MOBI writer: Change values of dictype and cdetype fields to be the same as for the old writer. Fixes #847766 (8.18 doesn't overwrite previous days newsfeeds for same publications)
This commit is contained in:
parent
1ecfb81a07
commit
2bf6e7bed0
@ -13,6 +13,7 @@ class USAToday(BasicNewsRecipe):
|
|||||||
title = 'USA Today'
|
title = 'USA Today'
|
||||||
__author__ = 'Kovid Goyal'
|
__author__ = 'Kovid Goyal'
|
||||||
oldest_article = 1
|
oldest_article = 1
|
||||||
|
publication_type = 'newspaper'
|
||||||
timefmt = ''
|
timefmt = ''
|
||||||
max_articles_per_feed = 20
|
max_articles_per_feed = 20
|
||||||
language = 'en'
|
language = 'en'
|
||||||
|
@ -61,6 +61,13 @@ class MobiWriter(object):
|
|||||||
|
|
||||||
def __call__(self, oeb, path_or_stream):
|
def __call__(self, oeb, path_or_stream):
|
||||||
self.log = oeb.log
|
self.log = oeb.log
|
||||||
|
pt = None
|
||||||
|
if oeb.metadata.publication_type:
|
||||||
|
x = unicode(oeb.metadata.publication_type[0]).split(':')
|
||||||
|
if len(x) > 1:
|
||||||
|
pt = x[1].lower()
|
||||||
|
self.publication_type = pt
|
||||||
|
|
||||||
if hasattr(path_or_stream, 'write'):
|
if hasattr(path_or_stream, 'write'):
|
||||||
return self.dump_stream(oeb, path_or_stream)
|
return self.dump_stream(oeb, path_or_stream)
|
||||||
with open(path_or_stream, 'w+b') as stream:
|
with open(path_or_stream, 'w+b') as stream:
|
||||||
@ -351,7 +358,7 @@ class MobiWriter(object):
|
|||||||
elif self.indexer.is_periodical:
|
elif self.indexer.is_periodical:
|
||||||
# If you change this, remember to change the cdetype in the EXTH
|
# If you change this, remember to change the cdetype in the EXTH
|
||||||
# header as well
|
# header as well
|
||||||
bt = 0x103
|
bt = {'newspaper':0x101}.get(self.publication_type, 0x103)
|
||||||
|
|
||||||
record0.write(pack(b'>IIIII',
|
record0.write(pack(b'>IIIII',
|
||||||
0xe8, bt, 65001, uid, 6))
|
0xe8, bt, 65001, uid, 6))
|
||||||
@ -525,15 +532,16 @@ class MobiWriter(object):
|
|||||||
nrecs += 1
|
nrecs += 1
|
||||||
|
|
||||||
# Write cdetype
|
# Write cdetype
|
||||||
if self.is_periodical:
|
if not self.is_periodical:
|
||||||
# If you set the book type header field to 0x101 use NWPR here if
|
exth.write(pack(b'>II', 501, 12))
|
||||||
# you use 0x103 use MAGZ
|
exth.write(b'EBOK')
|
||||||
data = b'MAGZ'
|
nrecs += 1
|
||||||
else:
|
else:
|
||||||
data = b'EBOK'
|
# Should be b'NWPR' for doc type of 0x101 and b'MAGZ' for doctype
|
||||||
exth.write(pack(b'>II', 501, len(data)+8))
|
# of 0x103 but the old writer didn't write them, and I dont know
|
||||||
exth.write(data)
|
# what it should be for type 0x102 (b'BLOG'?) so write nothing
|
||||||
nrecs += 1
|
# instead
|
||||||
|
pass
|
||||||
|
|
||||||
# Add a publication date entry
|
# Add a publication date entry
|
||||||
if oeb.metadata['date']:
|
if oeb.metadata['date']:
|
||||||
|
@ -146,6 +146,11 @@ class LibraryServer(ContentServer, MobileServer, XMLServer, OPDSServer, Cache,
|
|||||||
self.config = {}
|
self.config = {}
|
||||||
self.is_running = False
|
self.is_running = False
|
||||||
self.exception = None
|
self.exception = None
|
||||||
|
self.config['/'] = {
|
||||||
|
'tools.sessions.on' : True,
|
||||||
|
'tools.sessions.timeout': 60, # Session times out after 60 minutes
|
||||||
|
}
|
||||||
|
|
||||||
if not wsgi:
|
if not wsgi:
|
||||||
self.setup_loggers()
|
self.setup_loggers()
|
||||||
cherrypy.engine.bonjour.subscribe()
|
cherrypy.engine.bonjour.subscribe()
|
||||||
@ -154,6 +159,7 @@ class LibraryServer(ContentServer, MobileServer, XMLServer, OPDSServer, Cache,
|
|||||||
'tools.gzip.mime_types': ['text/html', 'text/plain',
|
'tools.gzip.mime_types': ['text/html', 'text/plain',
|
||||||
'text/xml', 'text/javascript', 'text/css'],
|
'text/xml', 'text/javascript', 'text/css'],
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.password:
|
if opts.password:
|
||||||
self.config['/'] = {
|
self.config['/'] = {
|
||||||
'tools.digest_auth.on' : True,
|
'tools.digest_auth.on' : True,
|
||||||
|
@ -28,6 +28,10 @@ class Browser(B):
|
|||||||
B.set_cookiejar(self, *args, **kwargs)
|
B.set_cookiejar(self, *args, **kwargs)
|
||||||
self._clone_actions['set_cookiejar'] = ('set_cookiejar', args, kwargs)
|
self._clone_actions['set_cookiejar'] = ('set_cookiejar', args, kwargs)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def cookiejar(self):
|
||||||
|
return self._clone_actions['set_cookiejar'][1][0]
|
||||||
|
|
||||||
def set_handle_redirect(self, *args, **kwargs):
|
def set_handle_redirect(self, *args, **kwargs):
|
||||||
B.set_handle_redirect(self, *args, **kwargs)
|
B.set_handle_redirect(self, *args, **kwargs)
|
||||||
self._clone_actions['set_handle_redirect'] = ('set_handle_redirect',
|
self._clone_actions['set_handle_redirect'] = ('set_handle_redirect',
|
||||||
|
@ -33,13 +33,13 @@ missing = object()
|
|||||||
|
|
||||||
class Session(object):
|
class Session(object):
|
||||||
"""A CherryPy dict-like Session object (one per request)."""
|
"""A CherryPy dict-like Session object (one per request)."""
|
||||||
|
|
||||||
__metaclass__ = cherrypy._AttributeDocstrings
|
__metaclass__ = cherrypy._AttributeDocstrings
|
||||||
|
|
||||||
_id = None
|
_id = None
|
||||||
id_observers = None
|
id_observers = None
|
||||||
id_observers__doc = "A list of callbacks to which to pass new id's."
|
id_observers__doc = "A list of callbacks to which to pass new id's."
|
||||||
|
|
||||||
id__doc = "The current session ID."
|
id__doc = "The current session ID."
|
||||||
def _get_id(self):
|
def _get_id(self):
|
||||||
return self._id
|
return self._id
|
||||||
@ -48,33 +48,33 @@ class Session(object):
|
|||||||
for o in self.id_observers:
|
for o in self.id_observers:
|
||||||
o(value)
|
o(value)
|
||||||
id = property(_get_id, _set_id, doc=id__doc)
|
id = property(_get_id, _set_id, doc=id__doc)
|
||||||
|
|
||||||
timeout = 60
|
timeout = 60
|
||||||
timeout__doc = "Number of minutes after which to delete session data."
|
timeout__doc = "Number of minutes after which to delete session data."
|
||||||
|
|
||||||
locked = False
|
locked = False
|
||||||
locked__doc = """
|
locked__doc = """
|
||||||
If True, this session instance has exclusive read/write access
|
If True, this session instance has exclusive read/write access
|
||||||
to session data."""
|
to session data."""
|
||||||
|
|
||||||
loaded = False
|
loaded = False
|
||||||
loaded__doc = """
|
loaded__doc = """
|
||||||
If True, data has been retrieved from storage. This should happen
|
If True, data has been retrieved from storage. This should happen
|
||||||
automatically on the first attempt to access session data."""
|
automatically on the first attempt to access session data."""
|
||||||
|
|
||||||
clean_thread = None
|
clean_thread = None
|
||||||
clean_thread__doc = "Class-level Monitor which calls self.clean_up."
|
clean_thread__doc = "Class-level Monitor which calls self.clean_up."
|
||||||
|
|
||||||
clean_freq = 5
|
clean_freq = 5
|
||||||
clean_freq__doc = "The poll rate for expired session cleanup in minutes."
|
clean_freq__doc = "The poll rate for expired session cleanup in minutes."
|
||||||
|
|
||||||
def __init__(self, id=None, **kwargs):
|
def __init__(self, id=None, **kwargs):
|
||||||
self.id_observers = []
|
self.id_observers = []
|
||||||
self._data = {}
|
self._data = {}
|
||||||
|
|
||||||
for k, v in kwargs.iteritems():
|
for k, v in kwargs.iteritems():
|
||||||
setattr(self, k, v)
|
setattr(self, k, v)
|
||||||
|
|
||||||
if id is None:
|
if id is None:
|
||||||
self.regenerate()
|
self.regenerate()
|
||||||
else:
|
else:
|
||||||
@ -84,30 +84,30 @@ class Session(object):
|
|||||||
# See http://www.cherrypy.org/ticket/709.
|
# See http://www.cherrypy.org/ticket/709.
|
||||||
self.id = None
|
self.id = None
|
||||||
self.regenerate()
|
self.regenerate()
|
||||||
|
|
||||||
def regenerate(self):
|
def regenerate(self):
|
||||||
"""Replace the current session (with a new id)."""
|
"""Replace the current session (with a new id)."""
|
||||||
if self.id is not None:
|
if self.id is not None:
|
||||||
self.delete()
|
self.delete()
|
||||||
|
|
||||||
old_session_was_locked = self.locked
|
old_session_was_locked = self.locked
|
||||||
if old_session_was_locked:
|
if old_session_was_locked:
|
||||||
self.release_lock()
|
self.release_lock()
|
||||||
|
|
||||||
self.id = None
|
self.id = None
|
||||||
while self.id is None:
|
while self.id is None:
|
||||||
self.id = self.generate_id()
|
self.id = self.generate_id()
|
||||||
# Assert that the generated id is not already stored.
|
# Assert that the generated id is not already stored.
|
||||||
if self._exists():
|
if self._exists():
|
||||||
self.id = None
|
self.id = None
|
||||||
|
|
||||||
if old_session_was_locked:
|
if old_session_was_locked:
|
||||||
self.acquire_lock()
|
self.acquire_lock()
|
||||||
|
|
||||||
def clean_up(self):
|
def clean_up(self):
|
||||||
"""Clean up expired sessions."""
|
"""Clean up expired sessions."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
try:
|
try:
|
||||||
os.urandom(20)
|
os.urandom(20)
|
||||||
except (AttributeError, NotImplementedError):
|
except (AttributeError, NotImplementedError):
|
||||||
@ -119,7 +119,7 @@ class Session(object):
|
|||||||
def generate_id(self):
|
def generate_id(self):
|
||||||
"""Return a new session id."""
|
"""Return a new session id."""
|
||||||
return os.urandom(20).encode('hex')
|
return os.urandom(20).encode('hex')
|
||||||
|
|
||||||
def save(self):
|
def save(self):
|
||||||
"""Save session data."""
|
"""Save session data."""
|
||||||
try:
|
try:
|
||||||
@ -129,12 +129,12 @@ class Session(object):
|
|||||||
t = datetime.timedelta(seconds = self.timeout * 60)
|
t = datetime.timedelta(seconds = self.timeout * 60)
|
||||||
expiration_time = datetime.datetime.now() + t
|
expiration_time = datetime.datetime.now() + t
|
||||||
self._save(expiration_time)
|
self._save(expiration_time)
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
if self.locked:
|
if self.locked:
|
||||||
# Always release the lock if the user didn't release it
|
# Always release the lock if the user didn't release it
|
||||||
self.release_lock()
|
self.release_lock()
|
||||||
|
|
||||||
def load(self):
|
def load(self):
|
||||||
"""Copy stored session data into this session instance."""
|
"""Copy stored session data into this session instance."""
|
||||||
data = self._load()
|
data = self._load()
|
||||||
@ -145,7 +145,7 @@ class Session(object):
|
|||||||
else:
|
else:
|
||||||
self._data = data[0]
|
self._data = data[0]
|
||||||
self.loaded = True
|
self.loaded = True
|
||||||
|
|
||||||
# Stick the clean_thread in the class, not the instance.
|
# Stick the clean_thread in the class, not the instance.
|
||||||
# The instances are created and destroyed per-request.
|
# The instances are created and destroyed per-request.
|
||||||
cls = self.__class__
|
cls = self.__class__
|
||||||
@ -157,23 +157,23 @@ class Session(object):
|
|||||||
t.subscribe()
|
t.subscribe()
|
||||||
cls.clean_thread = t
|
cls.clean_thread = t
|
||||||
t.start()
|
t.start()
|
||||||
|
|
||||||
def delete(self):
|
def delete(self):
|
||||||
"""Delete stored session data."""
|
"""Delete stored session data."""
|
||||||
self._delete()
|
self._delete()
|
||||||
|
|
||||||
def __getitem__(self, key):
|
def __getitem__(self, key):
|
||||||
if not self.loaded: self.load()
|
if not self.loaded: self.load()
|
||||||
return self._data[key]
|
return self._data[key]
|
||||||
|
|
||||||
def __setitem__(self, key, value):
|
def __setitem__(self, key, value):
|
||||||
if not self.loaded: self.load()
|
if not self.loaded: self.load()
|
||||||
self._data[key] = value
|
self._data[key] = value
|
||||||
|
|
||||||
def __delitem__(self, key):
|
def __delitem__(self, key):
|
||||||
if not self.loaded: self.load()
|
if not self.loaded: self.load()
|
||||||
del self._data[key]
|
del self._data[key]
|
||||||
|
|
||||||
def pop(self, key, default=missing):
|
def pop(self, key, default=missing):
|
||||||
"""Remove the specified key and return the corresponding value.
|
"""Remove the specified key and return the corresponding value.
|
||||||
If key is not found, default is returned if given,
|
If key is not found, default is returned if given,
|
||||||
@ -184,46 +184,46 @@ class Session(object):
|
|||||||
return self._data.pop(key)
|
return self._data.pop(key)
|
||||||
else:
|
else:
|
||||||
return self._data.pop(key, default)
|
return self._data.pop(key, default)
|
||||||
|
|
||||||
def __contains__(self, key):
|
def __contains__(self, key):
|
||||||
if not self.loaded: self.load()
|
if not self.loaded: self.load()
|
||||||
return key in self._data
|
return key in self._data
|
||||||
|
|
||||||
def has_key(self, key):
|
def has_key(self, key):
|
||||||
"""D.has_key(k) -> True if D has a key k, else False."""
|
"""D.has_key(k) -> True if D has a key k, else False."""
|
||||||
if not self.loaded: self.load()
|
if not self.loaded: self.load()
|
||||||
return self._data.has_key(key)
|
return self._data.has_key(key)
|
||||||
|
|
||||||
def get(self, key, default=None):
|
def get(self, key, default=None):
|
||||||
"""D.get(k[,d]) -> D[k] if k in D, else d. d defaults to None."""
|
"""D.get(k[,d]) -> D[k] if k in D, else d. d defaults to None."""
|
||||||
if not self.loaded: self.load()
|
if not self.loaded: self.load()
|
||||||
return self._data.get(key, default)
|
return self._data.get(key, default)
|
||||||
|
|
||||||
def update(self, d):
|
def update(self, d):
|
||||||
"""D.update(E) -> None. Update D from E: for k in E: D[k] = E[k]."""
|
"""D.update(E) -> None. Update D from E: for k in E: D[k] = E[k]."""
|
||||||
if not self.loaded: self.load()
|
if not self.loaded: self.load()
|
||||||
self._data.update(d)
|
self._data.update(d)
|
||||||
|
|
||||||
def setdefault(self, key, default=None):
|
def setdefault(self, key, default=None):
|
||||||
"""D.setdefault(k[,d]) -> D.get(k,d), also set D[k]=d if k not in D."""
|
"""D.setdefault(k[,d]) -> D.get(k,d), also set D[k]=d if k not in D."""
|
||||||
if not self.loaded: self.load()
|
if not self.loaded: self.load()
|
||||||
return self._data.setdefault(key, default)
|
return self._data.setdefault(key, default)
|
||||||
|
|
||||||
def clear(self):
|
def clear(self):
|
||||||
"""D.clear() -> None. Remove all items from D."""
|
"""D.clear() -> None. Remove all items from D."""
|
||||||
if not self.loaded: self.load()
|
if not self.loaded: self.load()
|
||||||
self._data.clear()
|
self._data.clear()
|
||||||
|
|
||||||
def keys(self):
|
def keys(self):
|
||||||
"""D.keys() -> list of D's keys."""
|
"""D.keys() -> list of D's keys."""
|
||||||
if not self.loaded: self.load()
|
if not self.loaded: self.load()
|
||||||
return self._data.keys()
|
return self._data.keys()
|
||||||
|
|
||||||
def items(self):
|
def items(self):
|
||||||
"""D.items() -> list of D's (key, value) pairs, as 2-tuples."""
|
"""D.items() -> list of D's (key, value) pairs, as 2-tuples."""
|
||||||
if not self.loaded: self.load()
|
if not self.loaded: self.load()
|
||||||
return self._data.items()
|
return self._data.items()
|
||||||
|
|
||||||
def values(self):
|
def values(self):
|
||||||
"""D.values() -> list of D's values."""
|
"""D.values() -> list of D's values."""
|
||||||
if not self.loaded: self.load()
|
if not self.loaded: self.load()
|
||||||
@ -231,11 +231,11 @@ class Session(object):
|
|||||||
|
|
||||||
|
|
||||||
class RamSession(Session):
|
class RamSession(Session):
|
||||||
|
|
||||||
# Class-level objects. Don't rebind these!
|
# Class-level objects. Don't rebind these!
|
||||||
cache = {}
|
cache = {}
|
||||||
locks = {}
|
locks = {}
|
||||||
|
|
||||||
def clean_up(self):
|
def clean_up(self):
|
||||||
"""Clean up expired sessions."""
|
"""Clean up expired sessions."""
|
||||||
now = datetime.datetime.now()
|
now = datetime.datetime.now()
|
||||||
@ -249,29 +249,29 @@ class RamSession(Session):
|
|||||||
del self.locks[id]
|
del self.locks[id]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def _exists(self):
|
def _exists(self):
|
||||||
return self.id in self.cache
|
return self.id in self.cache
|
||||||
|
|
||||||
def _load(self):
|
def _load(self):
|
||||||
return self.cache.get(self.id)
|
return self.cache.get(self.id)
|
||||||
|
|
||||||
def _save(self, expiration_time):
|
def _save(self, expiration_time):
|
||||||
self.cache[self.id] = (self._data, expiration_time)
|
self.cache[self.id] = (self._data, expiration_time)
|
||||||
|
|
||||||
def _delete(self):
|
def _delete(self):
|
||||||
del self.cache[self.id]
|
del self.cache[self.id]
|
||||||
|
|
||||||
def acquire_lock(self):
|
def acquire_lock(self):
|
||||||
"""Acquire an exclusive lock on the currently-loaded session data."""
|
"""Acquire an exclusive lock on the currently-loaded session data."""
|
||||||
self.locked = True
|
self.locked = True
|
||||||
self.locks.setdefault(self.id, threading.RLock()).acquire()
|
self.locks.setdefault(self.id, threading.RLock()).acquire()
|
||||||
|
|
||||||
def release_lock(self):
|
def release_lock(self):
|
||||||
"""Release the lock on the currently-loaded session data."""
|
"""Release the lock on the currently-loaded session data."""
|
||||||
self.locks[self.id].release()
|
self.locks[self.id].release()
|
||||||
self.locked = False
|
self.locked = False
|
||||||
|
|
||||||
def __len__(self):
|
def __len__(self):
|
||||||
"""Return the number of active sessions."""
|
"""Return the number of active sessions."""
|
||||||
return len(self.cache)
|
return len(self.cache)
|
||||||
@ -279,32 +279,32 @@ class RamSession(Session):
|
|||||||
|
|
||||||
class FileSession(Session):
|
class FileSession(Session):
|
||||||
"""Implementation of the File backend for sessions
|
"""Implementation of the File backend for sessions
|
||||||
|
|
||||||
storage_path: the folder where session data will be saved. Each session
|
storage_path: the folder where session data will be saved. Each session
|
||||||
will be saved as pickle.dump(data, expiration_time) in its own file;
|
will be saved as pickle.dump(data, expiration_time) in its own file;
|
||||||
the filename will be self.SESSION_PREFIX + self.id.
|
the filename will be self.SESSION_PREFIX + self.id.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
SESSION_PREFIX = 'session-'
|
SESSION_PREFIX = 'session-'
|
||||||
LOCK_SUFFIX = '.lock'
|
LOCK_SUFFIX = '.lock'
|
||||||
|
|
||||||
def __init__(self, id=None, **kwargs):
|
def __init__(self, id=None, **kwargs):
|
||||||
# The 'storage_path' arg is required for file-based sessions.
|
# The 'storage_path' arg is required for file-based sessions.
|
||||||
kwargs['storage_path'] = os.path.abspath(kwargs['storage_path'])
|
kwargs['storage_path'] = os.path.abspath(kwargs['storage_path'])
|
||||||
Session.__init__(self, id=id, **kwargs)
|
Session.__init__(self, id=id, **kwargs)
|
||||||
|
|
||||||
def setup(cls, **kwargs):
|
def setup(cls, **kwargs):
|
||||||
"""Set up the storage system for file-based sessions.
|
"""Set up the storage system for file-based sessions.
|
||||||
|
|
||||||
This should only be called once per process; this will be done
|
This should only be called once per process; this will be done
|
||||||
automatically when using sessions.init (as the built-in Tool does).
|
automatically when using sessions.init (as the built-in Tool does).
|
||||||
"""
|
"""
|
||||||
# The 'storage_path' arg is required for file-based sessions.
|
# The 'storage_path' arg is required for file-based sessions.
|
||||||
kwargs['storage_path'] = os.path.abspath(kwargs['storage_path'])
|
kwargs['storage_path'] = os.path.abspath(kwargs['storage_path'])
|
||||||
|
|
||||||
for k, v in kwargs.iteritems():
|
for k, v in kwargs.iteritems():
|
||||||
setattr(cls, k, v)
|
setattr(cls, k, v)
|
||||||
|
|
||||||
# Warn if any lock files exist at startup.
|
# Warn if any lock files exist at startup.
|
||||||
lockfiles = [fname for fname in os.listdir(cls.storage_path)
|
lockfiles = [fname for fname in os.listdir(cls.storage_path)
|
||||||
if (fname.startswith(cls.SESSION_PREFIX)
|
if (fname.startswith(cls.SESSION_PREFIX)
|
||||||
@ -316,17 +316,17 @@ class FileSession(Session):
|
|||||||
"manually delete the lockfiles found at %r."
|
"manually delete the lockfiles found at %r."
|
||||||
% (len(lockfiles), plural, cls.storage_path))
|
% (len(lockfiles), plural, cls.storage_path))
|
||||||
setup = classmethod(setup)
|
setup = classmethod(setup)
|
||||||
|
|
||||||
def _get_file_path(self):
|
def _get_file_path(self):
|
||||||
f = os.path.join(self.storage_path, self.SESSION_PREFIX + self.id)
|
f = os.path.join(self.storage_path, self.SESSION_PREFIX + self.id)
|
||||||
if not os.path.abspath(f).startswith(self.storage_path):
|
if not os.path.abspath(f).startswith(self.storage_path):
|
||||||
raise cherrypy.HTTPError(400, "Invalid session id in cookie.")
|
raise cherrypy.HTTPError(400, "Invalid session id in cookie.")
|
||||||
return f
|
return f
|
||||||
|
|
||||||
def _exists(self):
|
def _exists(self):
|
||||||
path = self._get_file_path()
|
path = self._get_file_path()
|
||||||
return os.path.exists(path)
|
return os.path.exists(path)
|
||||||
|
|
||||||
def _load(self, path=None):
|
def _load(self, path=None):
|
||||||
if path is None:
|
if path is None:
|
||||||
path = self._get_file_path()
|
path = self._get_file_path()
|
||||||
@ -338,20 +338,20 @@ class FileSession(Session):
|
|||||||
f.close()
|
f.close()
|
||||||
except (IOError, EOFError):
|
except (IOError, EOFError):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _save(self, expiration_time):
|
def _save(self, expiration_time):
|
||||||
f = open(self._get_file_path(), "wb")
|
f = open(self._get_file_path(), "wb")
|
||||||
try:
|
try:
|
||||||
pickle.dump((self._data, expiration_time), f)
|
pickle.dump((self._data, expiration_time), f)
|
||||||
finally:
|
finally:
|
||||||
f.close()
|
f.close()
|
||||||
|
|
||||||
def _delete(self):
|
def _delete(self):
|
||||||
try:
|
try:
|
||||||
os.unlink(self._get_file_path())
|
os.unlink(self._get_file_path())
|
||||||
except OSError:
|
except OSError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def acquire_lock(self, path=None):
|
def acquire_lock(self, path=None):
|
||||||
"""Acquire an exclusive lock on the currently-loaded session data."""
|
"""Acquire an exclusive lock on the currently-loaded session data."""
|
||||||
if path is None:
|
if path is None:
|
||||||
@ -363,17 +363,17 @@ class FileSession(Session):
|
|||||||
except OSError:
|
except OSError:
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
else:
|
else:
|
||||||
os.close(lockfd)
|
os.close(lockfd)
|
||||||
break
|
break
|
||||||
self.locked = True
|
self.locked = True
|
||||||
|
|
||||||
def release_lock(self, path=None):
|
def release_lock(self, path=None):
|
||||||
"""Release the lock on the currently-loaded session data."""
|
"""Release the lock on the currently-loaded session data."""
|
||||||
if path is None:
|
if path is None:
|
||||||
path = self._get_file_path()
|
path = self._get_file_path()
|
||||||
os.unlink(path + self.LOCK_SUFFIX)
|
os.unlink(path + self.LOCK_SUFFIX)
|
||||||
self.locked = False
|
self.locked = False
|
||||||
|
|
||||||
def clean_up(self):
|
def clean_up(self):
|
||||||
"""Clean up expired sessions."""
|
"""Clean up expired sessions."""
|
||||||
now = datetime.datetime.now()
|
now = datetime.datetime.now()
|
||||||
@ -395,7 +395,7 @@ class FileSession(Session):
|
|||||||
os.unlink(path)
|
os.unlink(path)
|
||||||
finally:
|
finally:
|
||||||
self.release_lock(path)
|
self.release_lock(path)
|
||||||
|
|
||||||
def __len__(self):
|
def __len__(self):
|
||||||
"""Return the number of active sessions."""
|
"""Return the number of active sessions."""
|
||||||
return len([fname for fname in os.listdir(self.storage_path)
|
return len([fname for fname in os.listdir(self.storage_path)
|
||||||
@ -412,38 +412,38 @@ class PostgresqlSession(Session):
|
|||||||
data text,
|
data text,
|
||||||
expiration_time timestamp
|
expiration_time timestamp
|
||||||
)
|
)
|
||||||
|
|
||||||
You must provide your own get_db function.
|
You must provide your own get_db function.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, id=None, **kwargs):
|
def __init__(self, id=None, **kwargs):
|
||||||
Session.__init__(self, id, **kwargs)
|
Session.__init__(self, id, **kwargs)
|
||||||
self.cursor = self.db.cursor()
|
self.cursor = self.db.cursor()
|
||||||
|
|
||||||
def setup(cls, **kwargs):
|
def setup(cls, **kwargs):
|
||||||
"""Set up the storage system for Postgres-based sessions.
|
"""Set up the storage system for Postgres-based sessions.
|
||||||
|
|
||||||
This should only be called once per process; this will be done
|
This should only be called once per process; this will be done
|
||||||
automatically when using sessions.init (as the built-in Tool does).
|
automatically when using sessions.init (as the built-in Tool does).
|
||||||
"""
|
"""
|
||||||
for k, v in kwargs.iteritems():
|
for k, v in kwargs.iteritems():
|
||||||
setattr(cls, k, v)
|
setattr(cls, k, v)
|
||||||
|
|
||||||
self.db = self.get_db()
|
self.db = self.get_db()
|
||||||
setup = classmethod(setup)
|
setup = classmethod(setup)
|
||||||
|
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
if self.cursor:
|
if self.cursor:
|
||||||
self.cursor.close()
|
self.cursor.close()
|
||||||
self.db.commit()
|
self.db.commit()
|
||||||
|
|
||||||
def _exists(self):
|
def _exists(self):
|
||||||
# Select session data from table
|
# Select session data from table
|
||||||
self.cursor.execute('select data, expiration_time from session '
|
self.cursor.execute('select data, expiration_time from session '
|
||||||
'where id=%s', (self.id,))
|
'where id=%s', (self.id,))
|
||||||
rows = self.cursor.fetchall()
|
rows = self.cursor.fetchall()
|
||||||
return bool(rows)
|
return bool(rows)
|
||||||
|
|
||||||
def _load(self):
|
def _load(self):
|
||||||
# Select session data from table
|
# Select session data from table
|
||||||
self.cursor.execute('select data, expiration_time from session '
|
self.cursor.execute('select data, expiration_time from session '
|
||||||
@ -451,34 +451,34 @@ class PostgresqlSession(Session):
|
|||||||
rows = self.cursor.fetchall()
|
rows = self.cursor.fetchall()
|
||||||
if not rows:
|
if not rows:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
pickled_data, expiration_time = rows[0]
|
pickled_data, expiration_time = rows[0]
|
||||||
data = pickle.loads(pickled_data)
|
data = pickle.loads(pickled_data)
|
||||||
return data, expiration_time
|
return data, expiration_time
|
||||||
|
|
||||||
def _save(self, expiration_time):
|
def _save(self, expiration_time):
|
||||||
pickled_data = pickle.dumps(self._data)
|
pickled_data = pickle.dumps(self._data)
|
||||||
self.cursor.execute('update session set data = %s, '
|
self.cursor.execute('update session set data = %s, '
|
||||||
'expiration_time = %s where id = %s',
|
'expiration_time = %s where id = %s',
|
||||||
(pickled_data, expiration_time, self.id))
|
(pickled_data, expiration_time, self.id))
|
||||||
|
|
||||||
def _delete(self):
|
def _delete(self):
|
||||||
self.cursor.execute('delete from session where id=%s', (self.id,))
|
self.cursor.execute('delete from session where id=%s', (self.id,))
|
||||||
|
|
||||||
def acquire_lock(self):
|
def acquire_lock(self):
|
||||||
"""Acquire an exclusive lock on the currently-loaded session data."""
|
"""Acquire an exclusive lock on the currently-loaded session data."""
|
||||||
# We use the "for update" clause to lock the row
|
# We use the "for update" clause to lock the row
|
||||||
self.locked = True
|
self.locked = True
|
||||||
self.cursor.execute('select id from session where id=%s for update',
|
self.cursor.execute('select id from session where id=%s for update',
|
||||||
(self.id,))
|
(self.id,))
|
||||||
|
|
||||||
def release_lock(self):
|
def release_lock(self):
|
||||||
"""Release the lock on the currently-loaded session data."""
|
"""Release the lock on the currently-loaded session data."""
|
||||||
# We just close the cursor and that will remove the lock
|
# We just close the cursor and that will remove the lock
|
||||||
# introduced by the "for update" clause
|
# introduced by the "for update" clause
|
||||||
self.cursor.close()
|
self.cursor.close()
|
||||||
self.locked = False
|
self.locked = False
|
||||||
|
|
||||||
def clean_up(self):
|
def clean_up(self):
|
||||||
"""Clean up expired sessions."""
|
"""Clean up expired sessions."""
|
||||||
self.cursor.execute('delete from session where expiration_time < %s',
|
self.cursor.execute('delete from session where expiration_time < %s',
|
||||||
@ -486,43 +486,43 @@ class PostgresqlSession(Session):
|
|||||||
|
|
||||||
|
|
||||||
class MemcachedSession(Session):
|
class MemcachedSession(Session):
|
||||||
|
|
||||||
# The most popular memcached client for Python isn't thread-safe.
|
# The most popular memcached client for Python isn't thread-safe.
|
||||||
# Wrap all .get and .set operations in a single lock.
|
# Wrap all .get and .set operations in a single lock.
|
||||||
mc_lock = threading.RLock()
|
mc_lock = threading.RLock()
|
||||||
|
|
||||||
# This is a seperate set of locks per session id.
|
# This is a seperate set of locks per session id.
|
||||||
locks = {}
|
locks = {}
|
||||||
|
|
||||||
servers = ['127.0.0.1:11211']
|
servers = ['127.0.0.1:11211']
|
||||||
|
|
||||||
def setup(cls, **kwargs):
|
def setup(cls, **kwargs):
|
||||||
"""Set up the storage system for memcached-based sessions.
|
"""Set up the storage system for memcached-based sessions.
|
||||||
|
|
||||||
This should only be called once per process; this will be done
|
This should only be called once per process; this will be done
|
||||||
automatically when using sessions.init (as the built-in Tool does).
|
automatically when using sessions.init (as the built-in Tool does).
|
||||||
"""
|
"""
|
||||||
for k, v in kwargs.iteritems():
|
for k, v in kwargs.iteritems():
|
||||||
setattr(cls, k, v)
|
setattr(cls, k, v)
|
||||||
|
|
||||||
import memcache
|
import memcache
|
||||||
cls.cache = memcache.Client(cls.servers)
|
cls.cache = memcache.Client(cls.servers)
|
||||||
setup = classmethod(setup)
|
setup = classmethod(setup)
|
||||||
|
|
||||||
def _exists(self):
|
def _exists(self):
|
||||||
self.mc_lock.acquire()
|
self.mc_lock.acquire()
|
||||||
try:
|
try:
|
||||||
return bool(self.cache.get(self.id))
|
return bool(self.cache.get(self.id))
|
||||||
finally:
|
finally:
|
||||||
self.mc_lock.release()
|
self.mc_lock.release()
|
||||||
|
|
||||||
def _load(self):
|
def _load(self):
|
||||||
self.mc_lock.acquire()
|
self.mc_lock.acquire()
|
||||||
try:
|
try:
|
||||||
return self.cache.get(self.id)
|
return self.cache.get(self.id)
|
||||||
finally:
|
finally:
|
||||||
self.mc_lock.release()
|
self.mc_lock.release()
|
||||||
|
|
||||||
def _save(self, expiration_time):
|
def _save(self, expiration_time):
|
||||||
# Send the expiration time as "Unix time" (seconds since 1/1/1970)
|
# Send the expiration time as "Unix time" (seconds since 1/1/1970)
|
||||||
td = int(time.mktime(expiration_time.timetuple()))
|
td = int(time.mktime(expiration_time.timetuple()))
|
||||||
@ -532,20 +532,20 @@ class MemcachedSession(Session):
|
|||||||
raise AssertionError("Session data for id %r not set." % self.id)
|
raise AssertionError("Session data for id %r not set." % self.id)
|
||||||
finally:
|
finally:
|
||||||
self.mc_lock.release()
|
self.mc_lock.release()
|
||||||
|
|
||||||
def _delete(self):
|
def _delete(self):
|
||||||
self.cache.delete(self.id)
|
self.cache.delete(self.id)
|
||||||
|
|
||||||
def acquire_lock(self):
|
def acquire_lock(self):
|
||||||
"""Acquire an exclusive lock on the currently-loaded session data."""
|
"""Acquire an exclusive lock on the currently-loaded session data."""
|
||||||
self.locked = True
|
self.locked = True
|
||||||
self.locks.setdefault(self.id, threading.RLock()).acquire()
|
self.locks.setdefault(self.id, threading.RLock()).acquire()
|
||||||
|
|
||||||
def release_lock(self):
|
def release_lock(self):
|
||||||
"""Release the lock on the currently-loaded session data."""
|
"""Release the lock on the currently-loaded session data."""
|
||||||
self.locks[self.id].release()
|
self.locks[self.id].release()
|
||||||
self.locked = False
|
self.locked = False
|
||||||
|
|
||||||
def __len__(self):
|
def __len__(self):
|
||||||
"""Return the number of active sessions."""
|
"""Return the number of active sessions."""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
@ -555,15 +555,15 @@ class MemcachedSession(Session):
|
|||||||
|
|
||||||
def save():
|
def save():
|
||||||
"""Save any changed session data."""
|
"""Save any changed session data."""
|
||||||
|
|
||||||
if not hasattr(cherrypy.serving, "session"):
|
if not hasattr(cherrypy.serving, "session"):
|
||||||
return
|
return
|
||||||
|
|
||||||
# Guard against running twice
|
# Guard against running twice
|
||||||
if hasattr(cherrypy.request, "_sessionsaved"):
|
if hasattr(cherrypy.request, "_sessionsaved"):
|
||||||
return
|
return
|
||||||
cherrypy.request._sessionsaved = True
|
cherrypy.request._sessionsaved = True
|
||||||
|
|
||||||
if cherrypy.response.stream:
|
if cherrypy.response.stream:
|
||||||
# If the body is being streamed, we have to save the data
|
# If the body is being streamed, we have to save the data
|
||||||
# *after* the response has been written out
|
# *after* the response has been written out
|
||||||
@ -589,7 +589,7 @@ close.priority = 90
|
|||||||
def init(storage_type='ram', path=None, path_header=None, name='session_id',
|
def init(storage_type='ram', path=None, path_header=None, name='session_id',
|
||||||
timeout=60, domain=None, secure=False, clean_freq=5, **kwargs):
|
timeout=60, domain=None, secure=False, clean_freq=5, **kwargs):
|
||||||
"""Initialize session object (using cookies).
|
"""Initialize session object (using cookies).
|
||||||
|
|
||||||
storage_type: one of 'ram', 'file', 'postgresql'. This will be used
|
storage_type: one of 'ram', 'file', 'postgresql'. This will be used
|
||||||
to look up the corresponding class in cherrypy.lib.sessions
|
to look up the corresponding class in cherrypy.lib.sessions
|
||||||
globals. For example, 'file' will use the FileSession class.
|
globals. For example, 'file' will use the FileSession class.
|
||||||
@ -603,31 +603,31 @@ def init(storage_type='ram', path=None, path_header=None, name='session_id',
|
|||||||
secure: if False (the default) the cookie 'secure' value will not
|
secure: if False (the default) the cookie 'secure' value will not
|
||||||
be set. If True, the cookie 'secure' value will be set (to 1).
|
be set. If True, the cookie 'secure' value will be set (to 1).
|
||||||
clean_freq (minutes): the poll rate for expired session cleanup.
|
clean_freq (minutes): the poll rate for expired session cleanup.
|
||||||
|
|
||||||
Any additional kwargs will be bound to the new Session instance,
|
Any additional kwargs will be bound to the new Session instance,
|
||||||
and may be specific to the storage type. See the subclass of Session
|
and may be specific to the storage type. See the subclass of Session
|
||||||
you're using for more information.
|
you're using for more information.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
request = cherrypy.request
|
request = cherrypy.request
|
||||||
|
|
||||||
# Guard against running twice
|
# Guard against running twice
|
||||||
if hasattr(request, "_session_init_flag"):
|
if hasattr(request, "_session_init_flag"):
|
||||||
return
|
return
|
||||||
request._session_init_flag = True
|
request._session_init_flag = True
|
||||||
|
|
||||||
# Check if request came with a session ID
|
# Check if request came with a session ID
|
||||||
id = None
|
id = None
|
||||||
if name in request.cookie:
|
if name in request.cookie:
|
||||||
id = request.cookie[name].value
|
id = request.cookie[name].value
|
||||||
|
|
||||||
# Find the storage class and call setup (first time only).
|
# Find the storage class and call setup (first time only).
|
||||||
storage_class = storage_type.title() + 'Session'
|
storage_class = storage_type.title() + 'Session'
|
||||||
storage_class = globals()[storage_class]
|
storage_class = globals()[storage_class]
|
||||||
if not hasattr(cherrypy, "session"):
|
if not hasattr(cherrypy, "session"):
|
||||||
if hasattr(storage_class, "setup"):
|
if hasattr(storage_class, "setup"):
|
||||||
storage_class.setup(**kwargs)
|
storage_class.setup(**kwargs)
|
||||||
|
|
||||||
# Create and attach a new Session instance to cherrypy.serving.
|
# Create and attach a new Session instance to cherrypy.serving.
|
||||||
# It will possess a reference to (and lock, and lazily load)
|
# It will possess a reference to (and lock, and lazily load)
|
||||||
# the requested session data.
|
# the requested session data.
|
||||||
@ -638,11 +638,11 @@ def init(storage_type='ram', path=None, path_header=None, name='session_id',
|
|||||||
"""Update the cookie every time the session id changes."""
|
"""Update the cookie every time the session id changes."""
|
||||||
cherrypy.response.cookie[name] = id
|
cherrypy.response.cookie[name] = id
|
||||||
sess.id_observers.append(update_cookie)
|
sess.id_observers.append(update_cookie)
|
||||||
|
|
||||||
# Create cherrypy.session which will proxy to cherrypy.serving.session
|
# Create cherrypy.session which will proxy to cherrypy.serving.session
|
||||||
if not hasattr(cherrypy, "session"):
|
if not hasattr(cherrypy, "session"):
|
||||||
cherrypy.session = cherrypy._ThreadLocalProxy('session')
|
cherrypy.session = cherrypy._ThreadLocalProxy('session')
|
||||||
|
|
||||||
set_response_cookie(path=path, path_header=path_header, name=name,
|
set_response_cookie(path=path, path_header=path_header, name=name,
|
||||||
timeout=timeout, domain=domain, secure=secure)
|
timeout=timeout, domain=domain, secure=secure)
|
||||||
|
|
||||||
@ -650,7 +650,7 @@ def init(storage_type='ram', path=None, path_header=None, name='session_id',
|
|||||||
def set_response_cookie(path=None, path_header=None, name='session_id',
|
def set_response_cookie(path=None, path_header=None, name='session_id',
|
||||||
timeout=60, domain=None, secure=False):
|
timeout=60, domain=None, secure=False):
|
||||||
"""Set a response cookie for the client.
|
"""Set a response cookie for the client.
|
||||||
|
|
||||||
path: the 'path' value to stick in the response cookie metadata.
|
path: the 'path' value to stick in the response cookie metadata.
|
||||||
path_header: if 'path' is None (the default), then the response
|
path_header: if 'path' is None (the default), then the response
|
||||||
cookie 'path' will be pulled from request.headers[path_header].
|
cookie 'path' will be pulled from request.headers[path_header].
|
||||||
@ -665,14 +665,15 @@ def set_response_cookie(path=None, path_header=None, name='session_id',
|
|||||||
cookie[name] = cherrypy.serving.session.id
|
cookie[name] = cherrypy.serving.session.id
|
||||||
cookie[name]['path'] = (path or cherrypy.request.headers.get(path_header)
|
cookie[name]['path'] = (path or cherrypy.request.headers.get(path_header)
|
||||||
or '/')
|
or '/')
|
||||||
|
|
||||||
# We'd like to use the "max-age" param as indicated in
|
# We'd like to use the "max-age" param as indicated in
|
||||||
# http://www.faqs.org/rfcs/rfc2109.html but IE doesn't
|
# http://www.faqs.org/rfcs/rfc2109.html but IE doesn't
|
||||||
# save it to disk and the session is lost if people close
|
# save it to disk and the session is lost if people close
|
||||||
# the browser. So we have to use the old "expires" ... sigh ...
|
# the browser. So we have to use the old "expires" ... sigh ...
|
||||||
## cookie[name]['max-age'] = timeout * 60
|
## cookie[name]['max-age'] = timeout * 60
|
||||||
if timeout:
|
if False and timeout: # Changed by Kovid, we want the user to have to
|
||||||
cookie[name]['expires'] = http.HTTPDate(time.time() + (timeout * 60))
|
# re-authenticate on browser restart
|
||||||
|
cookie[name]['expires'] = http.HTTPDate(time.time() + timeout)
|
||||||
if domain is not None:
|
if domain is not None:
|
||||||
cookie[name]['domain'] = domain
|
cookie[name]['domain'] = domain
|
||||||
if secure:
|
if secure:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user