Index:=20trac/db/util.py=0A=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=0A---=20=
trac/db/util.py=09(revision=208664)=0A+++=20trac/db/util.py=09(working=20=
copy)=0A@@=20-15,6=20+15,51=20@@=0A=20#=0A=20#=20Author:=20Christopher=20=
Lenz=20<cmlenz@gmx.de>=0A=20=0A+from=20__future__=20import=20=
with_statement=0A+from=20contextlib=20import=20contextmanager=0A+=0A=
+@contextmanager=0A+def=20transaction(env,=20db,=20raise_exceptions=20=3D=20=
False):=0A+=20=20=20=20"""=0A+=20=20=20=20Simple=20transaction=20context=20=
manager.=0A+=20=20=20=20Usage:=0A+=20=20=20=20--=20def=20api_method(p1,=20=
p2):=0A+=20=20=20=20--=20=20=20=20=20with=20transaction(env,=20db)=20as=20=
cursor:=0A+=20=20=20=20--=20=20=20=20=20=20=20=20=20#=20implementation=0A=
+=20=20=20=20--=20=20=20=20=20=20=20=20=20result=20=3D=20data=0A+=20=20=20=
=20--=0A+=20=20=20=20"""=0A+=20=20=20=20dbtmp=20=3D=20db=20or=20=
env.get_db_cnx()=0A+=20=20=20=20try:=0A+=20=20=20=20=20=20=20=20yield=20=
dbtmp.cursor=0A+=20=20=20=20except:=0A+=20=20=20=20=20=20=20=20if=20not=20=
db:=20dbtmp.rollback()=0A+=20=20=20=20=20=20=20=20if=20raise_exceptions:=20=
raise=0A+=20=20=20=20else:=0A+=20=20=20=20=20=20=20=20if=20not=20db:=20=
dbtmp.commit()=0A+=0A+@contextmanager=0A+def=20db_transaction(env,=20db,=20=
raise_exceptions=20=3D=20False):=0A+=20=20=20=20"""=0A+=20=20=20=20=
Transaction=20context=20manager=20for=20more=20complicated=20code,=0A+=20=
=20=20=20that=20needs=20access=20to=20the=20connection=20itself,=0A+=20=20=
=20=20either=20for=20helper=20methods=20or=20to=20pass=20it=20along.=0A+=20=
=20=20=20Usage:=0A+=20=20=20=20--=20def=20api_method(p1,=20p2):=0A+=20=20=
=20=20--=20=20=20=20=20with=20db_transaction(env,=20db)=20as=20db:=0A+=20=
=20=20=20--=20=20=20=20=20=20=20=20=20#=20implementation=0A+=20=20=20=20=
--=20=20=20=20=20=20=20=20=20result=20=3D=20data=0A+=20=20=20=20--=0A+=20=
=20=20=20"""=0A+=20=20=20=20dbtmp=20=3D=20db=20or=20env.get_db_cnx()=0A+=20=
=20=20=20try:=0A+=20=20=20=20=20=20=20=20yield=20dbtmp=0A+=20=20=20=20=
except:=0A+=20=20=20=20=20=20=20=20if=20not=20db:=20dbtmp.rollback()=0A+=20=
=20=20=20=20=20=20=20if=20raise_exceptions:=20raise=0A+=20=20=20=20else:=0A=
+=20=20=20=20=20=20=20=20if=20not=20db:=20dbtmp.commit()=0A+=0A=20def=20=
sql_escape_percent(sql):=0A=20=20=20=20=20import=20re=0A=20=20=20=20=20=
return=20re.sub("'((?:[^']|(?:''))*)'",=0AIndex:=20trac/wiki/model.py=0A=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=0A---=20=
trac/wiki/model.py=09(revision=208664)=0A+++=20trac/wiki/model.py=09=
(working=20copy)=0A@@=20-16,6=20+16,7=20@@=0A=20#=20Author:=20Jonas=20=
Borgstr=C3=B6m=20<jonas@edgewall.com>=0A=20#=20=20=20=20=20=20=20=20=20=
Christopher=20Lenz=20<cmlenz@gmx.de>=0A=20=0A+from=20__future__=20import=20=
with_statement=0A=20import=20time=0A=20from=20datetime=20import=20=
datetime=0A=20=0A@@=20-24,6=20+25,7=20@@=0A=20from=20trac.util.datefmt=20=
import=20utc,=20to_timestamp=0A=20from=20trac.util.translation=20import=20=
_=0A=20from=20trac.wiki.api=20import=20WikiSystem=0A+from=20trac.db.util=20=
import=20transaction,=20db_transaction=0A=20=0A=20=0A=20class=20=
WikiPage(object):=0A@@=20-51,20=20+53,21=20@@=0A=20=20=20=20=20=20=20=20=20=
self.old_readonly=20=3D=20self.readonly=0A=20=0A=20=20=20=20=20def=20=
_fetch(self,=20name,=20version=3DNone,=20db=3DNone):=0A-=20=20=20=20=20=20=
=20=20if=20not=20db:=0A-=20=20=20=20=20=20=20=20=20=20=20=20db=20=3D=20=
self.env.get_db_cnx()=0A-=20=20=20=20=20=20=20=20cursor=20=3D=20=
db.cursor()=0A+=20=20=20=20=20=20=20=20if=20not=20db:=20db=20=3D=20=
self.env.get_db_cnx()=0A+=20=20=20=20=20=20=20=20cur=20=3D=20db.cursor()=0A=
=20=20=20=20=20=20=20=20=20if=20version=20is=20not=20None:=0A-=20=20=20=20=
=20=20=20=20=20=20=20=20cursor.execute("SELECT=20=
version,time,author,text,comment,readonly=20"=0A-=20=20=20=20=20=20=20=20=
=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20"FROM=20wiki=20=
"=0A-=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=
=20=20=20=20"WHERE=20name=3D%s=20AND=20version=3D%s",=0A-=20=20=20=20=20=20=
=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20(name,=20=
int(version)))=0A+=20=20=20=20=20=20=20=20=20=20=20=20=
cur.execute("SELECT=20version,time,author,text,comment,readonly=20"=0A+=20=
=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=
"FROM=20wiki=20"=0A+=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=
=20=20=20=20=20=20"WHERE=20name=3D%s=20AND=20version=3D%s",=0A+=20=20=20=20=
=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20(name,=20=
int(version)))=0A=20=20=20=20=20=20=20=20=20else:=0A-=20=20=20=20=20=20=20=
=20=20=20=20=20cursor.execute("SELECT=20=
version,time,author,text,comment,readonly=20"=0A-=20=20=20=20=20=20=20=20=
=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20"FROM=20wiki=20=
"=0A-=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=
=20=20=20=20"WHERE=20name=3D%s=20ORDER=20BY=20version=20DESC=20LIMIT=20=
1",=0A-=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=
=20=20=20=20=20(name,))=0A-=20=20=20=20=20=20=20=20row=20=3D=20=
cursor.fetchone()=0A+=20=20=20=20=20=20=20=20=20=20=20=20=
cur.execute("SELECT=20version,time,author,text,comment,readonly=20"=0A+=20=
=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=
"FROM=20wiki=20"=0A+=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=
=20=20=20=20=20=20"WHERE=20name=3D%s=20ORDER=20BY=20version=20DESC=20=
LIMIT=201",=0A+=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=
=20=20=20=20(name,))=0A+=20=20=20=20=20=20=20=20row=20=3D=20=
cur.fetchone()=0A+=0A+=20=20=20=20=20=20=20=20#import=20pdb;=20=
pdb.set_trace()=0A=20=20=20=20=20=20=20=20=20if=20row:=0A=20=20=20=20=20=20=
=20=20=20=20=20=20=20version,time,author,text,comment,readonly=20=3D=20=
row=0A=20=20=20=20=20=20=20=20=20=20=20=20=20self.version=20=3D=20=
int(version)=0A@@=20-83,37=20+86,29=20@@=0A=20=0A=20=20=20=20=20def=20=
delete(self,=20version=3DNone,=20db=3DNone):=0A=20=20=20=20=20=20=20=20=20=
assert=20self.exists,=20'Cannot=20delete=20non-existent=20page'=0A-=20=20=
=20=20=20=20=20=20if=20not=20db:=0A-=20=20=20=20=20=20=20=20=20=20=20=20=
db=20=3D=20self.env.get_db_cnx()=0A-=20=20=20=20=20=20=20=20=20=20=20=20=
handle_ta=20=3D=20True=0A-=20=20=20=20=20=20=20=20else:=0A-=20=20=20=20=20=
=20=20=20=20=20=20=20handle_ta=20=3D=20False=0A+=20=20=20=20=20=20=20=20=
with=20db_transaction(self.env,=20db)=20as=20db:=0A+=20=20=20=20=20=20=20=
=20=20=20=20=20cur=20=3D=20db.cursor()=0A+=20=20=20=20=20=20=20=20=20=20=20=
=20if=20version=20is=20None:=0A+=20=20=20=20=20=20=20=20=20=20=20=20=20=20=
=20=20#=20Delete=20a=20wiki=20page=20completely=0A+=20=20=20=20=20=20=20=20=
=20=20=20=20=20=20=20=20cur.execute("DELETE=20FROM=20wiki=20WHERE=20=
name=3D%s",=20(self.name,))=0A+=20=20=20=20=20=20=20=20=20=20=20=20=20=20=
=20=20self.env.log.info('Deleted=20page=20%s'=20%=20self.name)=0A+=20=20=20=
=20=20=20=20=20=20=20=20=20else:=0A+=20=20=20=20=20=20=20=20=20=20=20=20=20=
=20=20=20#=20Delete=20only=20a=20specific=20page=20version=0A+=20=20=20=20=
=20=20=20=20=20=20=20=20=20=20=20=20cur.execute("DELETE=20FROM=20wiki=20=
WHERE=20name=3D%s=20and=20version=3D%s",=0A+=20=20=20=20=20=20=20=20=20=20=
=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20(self.name,=20=
version))=0A+=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=
self.env.log.info('Deleted=20version=20%d=20of=20page=20%s'=0A+=20=20=20=20=
=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=
=20=20=20=20=20%=20(version,=20self.name))=0A=20=0A-=20=20=20=20=20=20=20=
=20cursor=20=3D=20db.cursor()=0A-=20=20=20=20=20=20=20=20if=20version=20=
is=20None:=0A-=20=20=20=20=20=20=20=20=20=20=20=20#=20Delete=20a=20wiki=20=
page=20completely=0A-=20=20=20=20=20=20=20=20=20=20=20=20=
cursor.execute("DELETE=20FROM=20wiki=20WHERE=20name=3D%s",=20=
(self.name,))=0A-=20=20=20=20=20=20=20=20=20=20=20=20=
self.env.log.info('Deleted=20page=20%s'=20%=20self.name)=0A-=20=20=20=20=20=
=20=20=20else:=0A-=20=20=20=20=20=20=20=20=20=20=20=20#=20Delete=20only=20=
a=20specific=20page=20version=0A-=20=20=20=20=20=20=20=20=20=20=20=20=
cursor.execute("DELETE=20FROM=20wiki=20WHERE=20name=3D%s=20and=20=
version=3D%s",=0A-=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=
=20=20=20=20=20=20=20=20(self.name,=20version))=0A-=20=20=20=20=20=20=20=20=
=20=20=20=20self.env.log.info('Deleted=20version=20%d=20of=20page=20%s'=0A=
-=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=
=20=20=20=20=20=20%=20(version,=20self.name))=0A+=20=20=20=20=20=20=20=20=
=20=20=20=20if=20version=20is=20None=20or=20version=20=3D=3D=20=
self.version:=0A+=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=
self._fetch(self.name,=20None,=20db)=0A=20=0A-=20=20=20=20=20=20=20=20if=20=
version=20is=20None=20or=20version=20=3D=3D=20self.version:=0A-=20=20=20=20=
=20=20=20=20=20=20=20=20self._fetch(self.name,=20None,=20db)=0A+=20=20=20=
=20=20=20=20=20=20=20=20=20if=20not=20self.exists:=0A+=20=20=20=20=20=20=20=
=20=20=20=20=20=20=20=20=20#=20Invalidate=20page=20name=20cache=0A+=20=20=
=20=20=20=20=20=20=20=20=20=20=20=20=20=20=
WikiSystem(self.env).pages.invalidate(db)=0A+=20=20=20=20=20=20=20=20=20=20=
=20=20=20=20=20=20#=20Delete=20orphaned=20attachments=0A+=20=20=20=20=20=20=
=20=20=20=20=20=20=20=20=20=20from=20trac.attachment=20import=20=
Attachment=0A+=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=
Attachment.delete_all(self.env,=20'wiki',=20self.name,=20db)=0A=20=0A-=20=
=20=20=20=20=20=20=20if=20not=20self.exists:=0A-=20=20=20=20=20=20=20=20=20=
=20=20=20#=20Invalidate=20page=20name=20cache=0A-=20=20=20=20=20=20=20=20=
=20=20=20=20WikiSystem(self.env).pages.invalidate(db)=0A-=20=20=20=20=20=20=
=20=20=20=20=20=20#=20Delete=20orphaned=20attachments=0A-=20=20=20=20=20=20=
=20=20=20=20=20=20from=20trac.attachment=20import=20Attachment=0A-=20=20=20=
=20=20=20=20=20=20=20=20=20Attachment.delete_all(self.env,=20'wiki',=20=
self.name,=20db)=0A-=0A-=20=20=20=20=20=20=20=20if=20handle_ta:=0A-=20=20=
=20=20=20=20=20=20=20=20=20=20db.commit()=0A-=0A=20=20=20=20=20=20=20=20=20=
#=20Let=20change=20listeners=20know=20about=20the=20deletion=0A=20=20=20=20=
=20=20=20=20=20if=20not=20self.exists:=0A=20=20=20=20=20=20=20=20=20=20=20=
=20=20for=20listener=20in=20WikiSystem(self.env).change_listeners:=0A@@=20=
-125,38=20+120,28=20@@=0A=20=0A=20=0A=20=20=20=20=20def=20save(self,=20=
author,=20comment,=20remote_addr,=20t=3DNone,=20db=3DNone):=0A-=20=20=20=20=
=20=20=20=20if=20not=20db:=0A-=20=20=20=20=20=20=20=20=20=20=20=20db=20=3D=
=20self.env.get_db_cnx()=0A-=20=20=20=20=20=20=20=20=20=20=20=20=
handle_ta=20=3D=20True=0A-=20=20=20=20=20=20=20=20else:=0A-=20=20=20=20=20=
=20=20=20=20=20=20=20handle_ta=20=3D=20False=0A-=0A-=20=20=20=20=20=20=20=
=20if=20t=20is=20None:=0A-=20=20=20=20=20=20=20=20=20=20=20=20t=20=3D=20=
datetime.now(utc)=0A-=0A-=20=20=20=20=20=20=20=20if=20self.text=20!=3D=20=
self.old_text:=0A-=20=20=20=20=20=20=20=20=20=20=20=20cursor=20=3D=20=
db.cursor()=0A-=20=20=20=20=20=20=20=20=20=20=20=20=
cursor.execute("INSERT=20INTO=20wiki=20(name,version,time,author,ipnr,"=0A=
-=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=
=20=20=20"text,comment,readonly)=20VALUES=20(%s,%s,%s,%s,%s,%s,"=0A-=20=20=
=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=
"%s,%s)",=20(self.name,=20self.version=20+=201,=0A-=20=20=20=20=20=20=20=20=
=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=
=20=20=20=20=20to_timestamp(t),=20author,=20remote_addr,=0A-=20=20=20=20=20=
=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=
=20=20=20=20=20=20=20=20self.text,=20comment,=20self.readonly))=0A-=20=20=
=20=20=20=20=20=20=20=20=20=20self.version=20+=3D=201=0A-=20=20=20=20=20=20=
=20=20=20=20=20=20self.resource=20=3D=20=
self.resource(version=3Dself.version)=0A-=20=20=20=20=20=20=20=20elif=20=
self.readonly=20!=3D=20self.old_readonly:=0A-=20=20=20=20=20=20=20=20=20=20=
=20=20cursor=20=3D=20db.cursor()=0A-=20=20=20=20=20=20=20=20=20=20=20=20=
cursor.execute("UPDATE=20wiki=20SET=20readonly=3D%s=20WHERE=20name=3D%s",=0A=
-=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=
=20=20=20(self.readonly,=20self.name))=0A-=20=20=20=20=20=20=20=20else:=0A=
+=20=20=20=20=20=20=20=20new_text=20=3D=20self.text=20!=3D=20=
self.old_text=0A+=20=20=20=20=20=20=20=20if=20not=20new_text=20and=20=
self.readonly=20=3D=3D=20self.old_readonly:=0A=20=20=20=20=20=20=20=20=20=
=20=20=20=20raise=20TracError(_('Page=20not=20modified'))=0A+=20=20=20=20=
=20=20=20=20t=20=3D=20t=20or=20datetime.now(utc)=0A=20=0A-=20=20=20=20=20=
=20=20=20if=20self.version=20=3D=3D=201:=0A-=20=20=20=20=20=20=20=20=20=20=
=20=20#=20Invalidate=20page=20name=20cache=0A-=20=20=20=20=20=20=20=20=20=
=20=20=20WikiSystem(self.env).pages.invalidate(db)=0A-=20=20=20=20=20=20=20=
=20=0A-=20=20=20=20=20=20=20=20if=20handle_ta:=0A-=20=20=20=20=20=20=20=20=
=20=20=20=20db.commit()=0A-=0A+=20=20=20=20=20=20=20=20with=20=
db_transaction(self.env,=20db)=20as=20db:=0A+=20=20=20=20=20=20=20=20=20=20=
=20=20cur=20=3D=20db.cursor()=0A+=20=20=20=20=20=20=20=20=20=20=20=20=
if(new_text):=0A+=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=
cur.execute("INSERT=20INTO=20wiki=20(name,version,time,author,ipnr,"=0A+=20=
=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=
=20=20"text,comment,readonly)=20VALUES=20(%s,%s,%s,%s,%s,%s,"=0A+=20=20=20=
=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=
"%s,%s)",=20(self.name,=20self.version=20+=201,=0A+=20=20=20=20=20=20=20=20=
=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=
=20=20=20=20=20=20to_timestamp(t),=20author,=20remote_addr,=0A+=20=20=20=20=
=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=
=20=20=20=20=20=20=20=20=20=20self.text,=20comment,=20self.readonly))=0A=
+=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20self.version=20+=3D=201=0A=
+=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20self.resource=20=3D=20=
self.resource(version=3Dself.version)=0A+=20=20=20=20=20=20=20=20=20=20=20=
=20else:=0A+=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=
cur.execute("UPDATE=20wiki=20SET=20readonly=3D%s=20WHERE=20name=3D%s",=0A=
+=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=
=20=20=20=20(self.readonly,=20self.name))=0A+=20=20=20=20=20=20=20=20=20=20=
=20=20if=20self.version=20=3D=3D=201:=0A+=20=20=20=20=20=20=20=20=20=20=20=
=20=20=20=20=20#=20Invalidate=20page=20name=20cache=0A+=20=20=20=20=20=20=
=20=20=20=20=20=20=20=20=20=20WikiSystem(self.env).pages.invalidate(db)=0A=
+=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=0A=20=20=20=20=20=20=20=
=20=20for=20listener=20in=20WikiSystem(self.env).change_listeners:=0A=20=20=
=20=20=20=20=20=20=20=20=20=20=20if=20self.version=20=3D=3D=201:=0A=20=20=
=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=
listener.wiki_page_added(self)=0A=
