
    eF                     f    d dl Z d dlZd dlmZmZ d dlmZ ddlm	Z	  G d d      Z
 G d d	e	      Zy)
    N)exc_infoversion_info)Lock   )
BaseFolderc                   :    e Zd ZdZd Zd Zd Zd Zd Zd Z	d Z
y	)
DatabaseFileLockzLock at database file level.c                 0    t               | _        d| _        y )Nr   )r   _lock_counterselfs    ?/usr/share/offlineimap3/offlineimap/folder/LocalStatusSQLite.py__init__zDatabaseFileLock.__init__   s    V
    c                 8    | j                   j                          y N)r   acquirer   s    r   	__enter__zDatabaseFileLock.__enter__        

r   c                 8    | j                   j                          y r   )r   release)r   typvaluetbs       r   __exit__zDatabaseFileLock.__exit__#   r   r   c                 .    | xj                   dz  c_         y Nr   r   r   s    r   registerNewUserz DatabaseFileLock.registerNewUser&       r   c                 .    | xj                   dz  c_         y r   r   r   s    r   removeOneUserzDatabaseFileLock.removeOneUser)   r!   r   c                     | j                   S r   )r   r   s    r   getLockzDatabaseFileLock.getLock,   s    zzr   c                      | j                   dk  S r   r   r   s    r   shouldClosezDatabaseFileLock.shouldClose/   s    }}q  r   N)__name__
__module____qualname____doc__r   r   r   r    r#   r%   r'    r   r   r	   r	      s(    &!r   r	   c                        e Zd ZdZdZi Z fdZd Zd Zd Z	d Z
d Zd	 Zd
 ZddZd Zd Zd Zd Zd Zd Zd Zd dZd Zd Zd!dZd Zd Zd Zd Zd Zd Zd Z d Z! xZ"S )"LocalStatusSQLiteFolderan  LocalStatus backend implemented with an SQLite database

    As python-sqlite currently does not allow to access the same sqlite
    objects from various threads, we need to open get and close a db
    connection and cursor for all operations. This is a big disadvantage
    and we might want to investigate if we cannot hold an object open
    for a thread somehow.   c                    d| _         t        t        |   ||       |j                  | _        t
        j                  j                  | j                         | j                               | _
        d| _        	 t        j                  dk\  rdnd| _        t
        j                  j                  | j                        }t
        j                  j!                  |      st        j"                  |       t
        j                  j%                  |      st'        d|z        d | _        | j                  t        j*                  vr%t-               t        j*                  | j                  <   t        j*                  | j                     | _        d| _        y )N.F      r   z-SQLite database path '%s' is not a directory.r   )sepsuperr.   r   rootospathjoingetrootgetfolderbasenamefilename
_newfolderr   minor_threading_mode_constdirnameexistsmakedirsisdirUserWarning
connectionlocksr	   _databaseFileLock_in_transactions)r   name
repositoryr@   	__class__s       r   r   z LocalStatusSQLiteFolder.__init__G   s   %t5dJGOO	T\\^T5K5K5MN	 +7*<*<r*AQq"''//$--0ww~~g&KK ww}}W%M%& ' ' == 7 = ==;K;M#))$--8!8!>!>t}}!M !r   c                 l    | j                         s$| j                  J | xj                  dz  c_        y y r   )dofsyncrE   rH   r   s    r   r   z!LocalStatusSQLiteFolder.__enter__b   s1    ||~??...!!Q&! r   c                     | j                         sQ| j                  dkD  sJ | xj                  dz  c_        | j                  dk  r| j                  j                          y y y )Nr   r   )rM   rH   rE   commit)r   exc_typeexc_valexc_tbs       r   r   z LocalStatusSQLiteFolder.__exit__g   sX    ||~((1,,,!!Q&!$$q(&&( ) r   c           	         t         j                  | j                  k(  sJ d       | j                  j	                         5  	 t        j
                  | j                  d      | _        | j                  j                          	 | j                  j                  d      }t        |j                         d	         }|t        j                   k  r| j#                  |       	 d d d        y # t         j                  $ r-}t        d| j                  d|dt               d         d }~ww xY w# t         j$                  $ r | j'                          Y qw xY w# 1 sw Y   y xY w)
Nz'Your sqlite is not multithreading safe.Fcheck_same_threadzcannot open database file 'z': zf.
You might want to check the rights to that file and if it cleanly opens with the 'sqlite<3>' commandr/   z1SELECT value from metadata WHERE key='db_version'r   )sqlitethreadsafetyr?   rG   r%   connectr<   rE   r    OperationalErrorrD   r   executeintfetchoner.   cur_version$_LocalStatusSQLiteFolder__upgrade_dbDatabaseError#_LocalStatusSQLiteFolder__create_db)r   ecursorversions       r   	openfilesz!LocalStatusSQLiteFolder.openfileso   s3   ""d&@&@@kBkk@##++- 	/
7"(..CH#J&&668
/00GI foo/234@@@%%g.3	/ 	/ ** 7! ]]A' )1
1	7 77 '' #  "#%	/ 	/sO   EA C'D*AE'D':(D""D''E*#EEEEEc                     	 t        j                  | j                         y# t        $ r6}| j                  j                  dd| j                  d|       Y d}~yd}~ww xY w)z>Remove any pre-existing database. Do not call in dry-run mode. zcould not remove file z: N)r7   unlinkr<   OSErroruidebug)r   ra   s     r   purgezLocalStatusSQLiteFolder.purge   sL    	.IIdmm$ 	.GGMM"==!- . .	.s   " 	A!,AA!c                      yNFr,   r   s    r   storesmessagesz&LocalStatusSQLiteFolder.storesmessages   s    r   c                     | j                   S r   )r<   r   s    r   getfullnamez#LocalStatusSQLiteFolder.getfullname   s    }}r   c                     | j                   S r   )r=   r   s    r   isnewfolderz#LocalStatusSQLiteFolder.isnewfolder   s    r   c                 |   d}|s	 | j                   j                         5  |:|r| j                  j                  |       nW| j                  j	                  |       n;|r| j                  j                  ||       n| j                  j	                  ||       d}| j
                  s| j                  j                          ddd       |syy# 1 sw Y   xY w# t        j                  $ rO}|j                  d   dk(  rn2|j                  d   dk(  r| j                  j                  dd       d}n Y d}~nd}~ww xY w)	a  Execute some SQL, retrying if the db was locked.

        :param sql: the SQL string passed to execute()
        :param args: the variable values to `sql`. E.g. (1,2) or {uid:1,
            flags:'T'}. See sqlite docs for possibilities.
        :param executemany: bool indicating whether we want to
            perform conn.executemany() or conn.execute().
        :returns: None or raises an Exception.FNTr   z(cannot commit - no transaction is activezdatabase is lockedrf   z!Locked sqlite database, retrying.)rG   r%   rE   executemanyrZ   rH   rO   rV   rY   argsri   rj   )r   sqlru   rt   successra   s         r   __sql_writez#LocalStatusSQLiteFolder.__sql_write   s
    ++335 1|& OO77< OO33C8& OO77TB OO33C>"G00..01 1 1 ** 66!9 JJVVAY"66GGMM"&IJ#Gs0   C B C C CC D;,AD66D;c                 h   | j                   | j                   j                          t        j                  | j                  d      | _         |dk  ra| j
                  j                  d| j                  d|        | j                   j                  d       | j                   j                          yy)z<Upgrade the sqlite format from version 'from_ver' to currentNFrT   r   z<Upgrading LocalStatus cache from version 1 to version 2 for :a!  ALTER TABLE status ADD mtime INTEGER DEFAULT 0;
                                             ALTER TABLE status ADD labels VARCHAR(256) DEFAULT '';
                                             UPDATE metadata SET value='2' WHERE key='db_version';
                                          )
rE   closerV   rX   r<   ri   _msgrJ   executescriptrO   )r   from_vers     r   __upgrade_dbz$LocalStatusSQLiteFolder.__upgrade_db   s     ??&OO!!# ..;@B
 q=GGLL//41 2OO)) +. / OO""$ r   c                     | j                   j                  d| j                  d|        | j                  j	                  d       | j                  j                          d| _        y)zuCreate a new db file.

        self.connection must point to the opened and valid SQlite
        database connection.z!Creating new Local Status db for rz   z
        CREATE TABLE metadata (key VARCHAR(50) PRIMARY KEY, value VARCHAR(128));
        INSERT INTO metadata VALUES('db_version', '2');
        CREATE TABLE status (id INTEGER PRIMARY KEY, flags VARCHAR(50), mtime INTEGER, labels VARCHAR(256));
        TN)ri   r|   rJ   rE   r}   rO   r=   r   s    r   __create_dbz#LocalStatusSQLiteFolder.__create_db   sR    
 	oot- 	.%% ' 	
 	 r   c                 2    |t               t               dddS )Nr   )uidflagslabelstimemtime)setr   r   s     r   msglist_item_initializerz0LocalStatusSQLiteFolder.msglist_item_initializer   s    SUceQQRSSr   c           
      $   | j                          | j                  j                  d      }|D ]  }|d   }| j                  |      | j                  |<   t        |d         }	 t        |d   j                  d      D cg c].  }t        |j                               dkD  r|j                         0 c}      }|| j                  |   d<   || j                  |   d<   |d   | j                  |   d	<    y c c}w # t        $ r t               }Y Vw xY w)
Nz(SELECT id,flags,mtime,labels from statusr   r   r3   ,r   r   r/   r   )
dropmessagelistcacherE   rZ   r   messagelistr   splitlenstripAttributeError)r   rb   rowr   r   lbr   s          r   cachemessagelistz(LocalStatusSQLiteFolder.cachemessagelist   s   !!#(()ST 	4Ca&C$($A$A#$FDS!AKE!!fll3/HR36rxxz?Q3F !hhj H I .3DS!'*.4DS!(+-0VDS!'*+	4
H!  s$   #C9>3C4
1C94C99DDc                    | j                   j                         5  | j                   j                          | j                   j                         r	 | j                  j                          d d d        y #  Y xY w# 1 sw Y   y xY wr   )rG   r%   r#   r'   rE   r{   r   s    r   
closefilesz"LocalStatusSQLiteFolder.closefiles
  sq    ##++- 	""002%%113OO))+		 	
	 	s   5A;A44A86A;;Bc                      y r   r,   r   s    r   savezLocalStatusSQLiteFolder.save  s    r   c                    | j                   j                         5  g }t        | j                  j	                               D ]Y  \  }}|d   }dj                  t        |d               }dj                  t        |d               }|j                  ||||f       [ | j                  d|d       d	d	d	       y	# 1 sw Y   y	xY w)
z-Saves the entire messagelist to the database.r   rf   r   , r   zFINSERT OR REPLACE INTO status (id,flags,mtime,labels) VALUES (?,?,?,?)Trt   N)	rG   r%   listr   itemsr9   sortedappend#_LocalStatusSQLiteFolder__sql_write)r   datar   msgr   r   r   s          r   saveallzLocalStatusSQLiteFolder.saveall  s     ##++- 
	5D !1!1!7!7!9: 9SGs7| 456#h-#89S%78	9  H!t  5
	5 
	5 
	5s   BB::Cc                    |
t               }|dk  r|S | j                  |      r| j                  ||       |S | j                  |      | j                  |<   |||||d| j                  |<   dj                  t        |            }dj                  t        |            }	 | j                  d||||f       |S # t        $ r3}t        t        |      dt        |      t               d         d}~ww xY w)	zWrites a new message, with the specified uid.

        See folder/Base for detail. Note that savemessage() does not
        check against dryrun settings, so you need to ensure that
        savemessage is never called in a dryrun mode.Nr   )r   r   r   r   r   rf   r   z;INSERT INTO status (id,flags,mtime,labels) VALUES (?,?,?,?)z while inserting UID r/   )r   	uidexistssavemessageflagsr   r   r9   r   r   	ExceptionrD   strr   )r   r   r   r   rtimer   r   ra   s           r   savemessagez#LocalStatusSQLiteFolder.savemessageL  s     >UF7J>>#!!#u-J $ = =c B(+eUUZfl mu&6&>*	-Z!5%8: 
	  	-"1vs3x1&jm- -	-s   !B9 9	C5.C00C5c                     | j                  |      sJ || j                  |   d<   dj                  t        |            }| j	                  d||f       y )Nr   rf   z$UPDATE status SET flags=? WHERE id=?)r   r   r9   r   r   )r   r   r   s      r   r   z(LocalStatusSQLiteFolder.savemessageflagsl  sP    ~~c""").g&u&?%Nr   c                 &    | j                   |   d   S )Nr   r   r   s     r   getmessageflagsz'LocalStatusSQLiteFolder.getmessageflagsr      $W--r   c                     || j                   |   d<   |r|| j                   |   d<   dj                  t        |            }|r| j                  d|||f       y | j                  d||f       y )Nr   r   r   z.UPDATE status SET labels=?, mtime=? WHERE id=?%UPDATE status SET labels=? WHERE id=?)r   r9   r   r   )r   r   r   r   s       r   savemessagelabelsz)LocalStatusSQLiteFolder.savemessagelabelsu  st    *0h'-2DS!'*6&>*MPVX]_bOcdDvsmTr   c           	      (   t        |j                               D cg c]!  \  }}dj                  t        |            |f# }}}| j	                  d|d       t        |j                               D ]  \  }}|| j
                  |   d<    yc c}}w )zQ
        Saves labels from a dictionary in a single database operation.

        r   r   Tr   r   N)r   r   r9   r   r   r   )r   r   r   lr   s        r   savemessageslabelsbulkz.LocalStatusSQLiteFolder.savemessageslabelsbulk  s    
 ;?v||~:NOQ6!9%s+OO@$TXY6<<>* 	0FC./DS!(+	0 Ps   &Bc                    g }|D ]B  }| j                   |   d   |z  }|j                  dj                  t        |            |f       D | j	                  d|d       |D ]'  }| j                   |   d   |z  | j                   |   d<   ) y Nr   r   r   Tr   r   r   r9   r   r   r   uidsr   r   r   	newlabelss         r   addmessageslabelsz)LocalStatusSQLiteFolder.addmessageslabels       	=C((-h7&@IKK6)#45s;<	= 	@$TXY 	WC.2.>.>s.CH.MPV.VDS!(+	Wr   c                    g }|D ]B  }| j                   |   d   |z
  }|j                  dj                  t        |            |f       D | j	                  d|d       |D ]'  }| j                   |   d   |z
  | j                   |   d<   ) y r   r   r   s         r   deletemessageslabelsz,LocalStatusSQLiteFolder.deletemessageslabels  r   r   c                 &    | j                   |   d   S )Nr   r   r   s     r   getmessagelabelsz(LocalStatusSQLiteFolder.getmessagelabels  s    $X..r   c                     t        |j                               D cg c]	  \  }}||f }}}| j                  d|d       t        |j                               D ]  \  }}|| j                  |   d<    yc c}}w )zGSaves mtimes from the mtimes dictionary in a single database operation.z$UPDATE status SET mtime=? WHERE id=?Tr   r   N)r   r   r   r   )r   mtimesr   mtr   s        r   savemessagesmtimebulkz-LocalStatusSQLiteFolder.savemessagesmtimebulk  sy     *.flln)=>gc2S	>>?SWXFLLN+ 	0GC-/DS!'*	0 ?s   A6c                 &    | j                   |   d   S )Nr   r   r   s     r   getmessagemtimez'LocalStatusSQLiteFolder.getmessagemtime  r   r   c                 b    || j                   vry | j                  d|f       | j                   |= y )NDELETE FROM status WHERE id=?)r   r   r   s     r   deletemessagez%LocalStatusSQLiteFolder.deletemessage  s4    d&&&83&Ac"r   c                     |D cg c]  }|| j                   v s| }}t        |      sy| j                  dt        t	        |            d       |D ]  }| j                   |=  yc c}w )zDelete list of UIDs from status cache

        This function uses sqlites executemany() function which is
        much faster than iterating through deletemessage() when we have
        many messages to delete.Nr   T)r   r   r   r   zip)r   uidlistr   s      r   deletemessagesz&LocalStatusSQLiteFolder.deletemessages  sn     #*E3SD4D4D-D3EE7|8$s7~:NPTU 	(C!!#&	( Fs
   A%A%rm   )r   Nr   )#r(   r)   r*   r+   r]   rF   r   r   r   rd   rk   rn   rp   rr   r   r^   r`   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   __classcell__)rK   s   @r   r.   r.   3   s     KE"6'
)/>."H%. T445h@O.	U0WW/0.$(r   r.   )r7   sqlite3rV   sysr   r   	threadingr   Baser   r	   r.   r,   r   r   <module>r      s.   $ 
  %  ! !4M(j M(r   