
    iA                        U d Z g dZddlZddlmZ ddlZddlZddlZddlZddl	Z	ddl
Z
ddlZddlZddlZddlZddlZddlZddlmZ ddlmZ ddlmZ  ej*                  d      Zeeej2                  edz  f   Zej8                  ed	<    G d
 dej<                        Zej@                   G d d             Z! G d dejD                        Z# G d dejH                  e#      Z%y)z}Implementation of caching solutions.

- :py:obj:`searx.cache.ExpireCache` and its :py:obj:`searx.cache.ExpireCacheCfg`

----
)ExpireCacheCfgExpireCacheStatsExpireCacheExpireCacheSQLite    N)Iterator)sqlitedb)logger)get_settingcacheCacheRowTypec                       e Zd ZU dZeed<   	 dZeed<   	 dZeed<   	 dZ	eed<   	 d	Z
eed
<   	 dZej                  d   ed<   	  ed      j                         Zeed<   	 d Zy)r   z/Configuration of a :py:obj:`ExpireCache` cache.name db_urli (  MAX_VALUE_LENi:	 MAXHOLD_TIMEi  MAINTENANCE_PERIODauto)r   offMAINTENANCE_MODEzserver.secret_keypasswordc                     | j                   sOt        j                         t        j                  z   dt
        j                  | j                         dz   | _         y y )Nsxng_cache_z.db)r   tempfile
gettempdirossepr   normalize_namer   selfs    /root/searxng/searx/cache.py__post_init__zExpireCacheCfg.__post_init__L   sI    {{"--/"&&8[IcIcdhdmdmInHoor;ssDK     N)__name__
__module____qualname____doc__str__annotations__r   r   intr   r   r   typingLiteralr
   encoder   bytesr"    r#   r!   r   r   #   s    9
IFC> #M3"-(L#(U%% 7=fnn]3<	 ""56==?He?tr#   r   c                   4    e Zd ZU dZeeee   f   ed<   	 d Z	y)r   z@Dataclass which provides information on the status of the cache.cached_itemsc                    d}d}g }| j                   j                         D ]  \  }}|dz  }|s|j                  d|dd       $|D ]s  \  }}}d}	|r.t        j                  j	                  |      j                  d      }	|dz  }|j                  d|dd|	 d	|d
dt        |      j                   d| d	       u  |j                  d|        |j                  d|        dj                  |      S )Nr      [20sz] emptyr   z%Y-%m-%d %H:%M:%Sz]  12z --> () zNumber of contexts: znumber of key/value pairs: 
)	r1   itemsappenddatetimefromtimestampstrftimetyper$   join)
r    c_ctxc_kvlinesctx_namekv_listkeyvalueexpirevalid_untils
             r!   reportzExpireCacheStats.reporte   s#   !%!2!2!8!8!: 	sHgQJEq#g67&- s"UF "*"3"3"A"A&"I"R"RSf"gK	q#bQs2h&QUV[Q\QeQePffhinhoopqrs	s 	+E73424&9:yyr#   N)
r$   r%   r&   r'   dictr(   listr   r)   rJ   r/   r#   r!   r   r   R   s$    JsD..// r#   r   c                      e Zd ZU dZeed<   dZeed<   ej                  ddede
j                  dedz  dedz  d	ef
d
       Zej                  ddede
j                  dedz  d	e
j                  fd       Zej                  ddeded	efd       Zej                  d	efd       Zeded	dfd       Zeded	efd       Zde
j                  d	efdZded	e
j                  fdZdeez  d	efdZy)r   zUAbstract base class for the implementation of a key/value cache
    with expire date.cfg
hash_tokenNrF   rG   rH   ctxreturnc                      y)aJ  Set *key* to *value*.  To set a timeout on key use argument
        ``expire`` (in sec.).  If expire is unset the default is taken from
        :py:obj:`ExpireCacheCfg.MAXHOLD_TIME`.  After the timeout has expired,
        the key will automatically be deleted.

        The ``ctx`` argument specifies the context of the ``key``.  A key is
        only unique in its context.

        The concrete implementations of this abstraction determine how the
        context is mapped in the connected database.  In SQL databases, for
        example, the context is a DB table or in a Key/Value DB it could be
        a prefix for the key.

        If the context is not specified (the default is ``None``) then a
        default context should be used, e.g. a default table for SQL databases
        or a default prefix in a Key/Value DB.
        Nr/   )r    rF   rG   rH   rP   s        r!   setzExpireCache.set       r#   defaultc                      y)z@Return *value* of *key*.  If key is unset, ``None`` is returned.Nr/   )r    rF   rU   rP   s       r!   getzExpireCache.get   rT   r#   forcetruncatec                      y)a4  Performs maintenance on the cache.

        ``force``:
          Maintenance should be carried out even if the maintenance interval has
          not yet been reached.

        ``truncate``:
          Truncate the entire cache, which is necessary, for example, if the
          password has changed.
        Nr/   )r    rX   rY   s      r!   maintenancezExpireCache.maintenance   rT   r#   c                      y)zgReturns a :py:obj:`ExpireCacheStats`, which provides information
        about the status of the cache.Nr/   r   s    r!   statezExpireCache.state   rT   r#   r   c                     t        |       S )zFactory to build a caching instance.

        .. note::

           Currently, only the SQLite adapter is available, but other database
           types could be implemented in the future, e.g. a Valkey (Redis)
           adapter.
        )r   )rN   s    r!   build_cachezExpireCache.build_cache   s     !%%r#   r   c                     dt         j                  z   t         j                  z   }dj                  | D cg c]	  }||v s| c}      S c c}w )zReturns a normalized name that can be used as a file name or as a SQL
        table name (is used, for example, to normalize the context name).z-_.r   )stringascii_lettersdigitsr@   )r   _validcs      r!   r   zExpireCache.normalize_name   s@    
 ---=ww47a1;7887s
   	A	A	c                 0    t        j                  |      }|S N)pickledumps)r    rG   dumps      r!   	serializezExpireCache.serialize   s    ll5)r#   c                 0    t        j                  |      }|S rg   )rh   loads)r    rG   objs      r!   deserializezExpireCache.deserialize   s    ll5!
r#   c                     t        |t              rt        |d      }t        j                  || j
                  j                  z   d      }|j                         S )a
  Creates a hash of the argument ``name``.  The hash value is formed
        from the ``name`` combined with the :py:obj:`password
        <ExpireCacheCfg.password>`.  Can be used, for example, to make the
        ``key`` stored in the DB unreadable for third parties.zutf-8)encodingsha256)	digestmod)
isinstancer(   r.   hmacnewrN   r   	hexdigest)r    r   ms      r!   secret_hashzExpireCache.secret_hash   sE     dC 0DHHTDHH---B{{}r#   rg   NNFF)r$   r%   r&   r'   r   r)   rO   r(   abcabstractmethodr+   Anyr*   boolrS   rW   r[   r   r]   staticmethodr_   r   r.   rk   ro   ry   r/   r#   r!   r   r   |   s    
"J"s 6:: sTz d
 ^b  & 	Os OVZZ OS4Z OSYS]S] O O 	
 
 
$ 
 
 	*' * * 	& 	&,? 	& 	& 9S 9S 9 9vzz e  6:: 	e 	 	r#   r   c                       e Zd ZU dZdZeed<   i Zee	e	f   ed<   dZ
e	ed<   def fdZd	ej                  d
ef fdZd"deded
efdZde	d
efdZed
ee	   fd       Zdee	   fdZed
efd       Zd#de	dej2                  dedz  de	dz  d
ef
dZ	 d#dee   de	dz  d
efdZ	 d#dee   de	dz  d
eeee	   f   fdZd$de	dej2                  de	dz  d
ej2                  fdZde	d
e ee	ej2                  f      fd Z!d
e"fd!Z# xZ$S )%r   a  Cache that manages key/value pairs in a SQLite DB.  The DB model in the
    SQLite DB is implemented in abstract class :py:obj:`SQLiteAppl
    <searx.sqlitedb.SQLiteAppl>`.

    The following configurations are required / supported:

    - :py:obj:`ExpireCacheCfg.db_url`
    - :py:obj:`ExpireCacheCfg.MAXHOLD_TIME`
    - :py:obj:`ExpireCacheCfg.MAINTENANCE_PERIOD`
    - :py:obj:`ExpireCacheCfg.MAINTENANCE_MODE`
    r3   	DB_SCHEMADDL_CREATE_TABLESzCACHE-TABLECACHE_TABLE_PREFIXrN   c                     || _         |j                  dk(  rt        j                  d       t        |   |j                         y)zdAn instance of the SQLite expire cache is build up from a
        :py:obj:`config <ExpireCacheCfg>`.z:memory:z/don't use SQLite DB in :memory: in production!!N)rN   r   logcriticalsuper__init__)r    rN   	__class__s     r!   r   zExpireCacheSQLite.__init__   s6     $'::#LLJK$r#   connrQ   c                    t         |   |      }|syt        j                  | j                  j
                        j                         }| j                  | j                        }||k7  re|*t        j                  d| j                  j                         | j                  dd       | j                  j                  | j                  |       y)NFz2[%s] hash token changed: truncate all cache tablesT)rX   rY   )r   inithashlibrr   rN   r   rw   
propertiesrO   r   warningr   r[   rS   )r    r   ret_valrv   oldr   s        r!   r   zExpireCacheSQLite.init   s    ',t$nnTXX../99;oodoo.#:PRVRZRZR_R_`4$7OO5r#   rX   rY   c                 ,   |s+t        t        j                               | j                  k  ry| j                  j	                  dd       |r| j                  | j                         yt        t        j                               }| j                         5 }| j                  D ]:  }|j                  d| d|f      }t        j                  d|j                  |       < 	 d d d        j                  d       |j                          y# 1 sw Y   +xY w)	NFLAST_MAINTENANCEr   TDELETE FROM z WHERE expire < ?z3deleted %s keys from table %s (expire date reached)zPRAGMA wal_checkpoint(TRUNCATE))r*   timenext_maintenance_timer   rS   truncate_tablestable_namesconnectexecuter   debugrowcountclose)r    rX   rY   rH   r   tableress          r!   r[   zExpireCacheSQLite.maintenance  s    TYY[)D,F,FF 	.3  !1!12 TYY[!\\^ 	ft)) fll\%8I#JVIV		OQTQ]Q]_def	f 	67

	f 	fs   A
D

Dr   c                    || j                   v ryt        j                  d|       dj                  d| dddd| j                  j
                   d	d
g      }d| d| d}| j                         5 }|j                  |       |j                  |       ddd       j                          | j                  j                  | j                   d| |       y# 1 sw Y   ExY w)z|Create DB ``table`` if it has not yet been created, no recreates are
        initiated if the table already exists.
        Fz;key/value table '%s' NOT exists in DB -> create DB table ..r9   zCREATE TABLE IF NOT EXISTS z (z  key        TEXT,z  value      BLOB,z6  expire     INTEGER DEFAULT (strftime('%s', 'now') + z),zPRIMARY KEY (key))z(CREATE INDEX IF NOT EXISTS index_expire_z ON z	(expire);N-T)r   r   infor@   rN   r   r   r   r   r   rS   r   )r    r   	sql_table	sql_indexr   s        r!   create_tablezExpireCacheSQLite.create_table   s     D$$$NPUVII-eWB7$$HI^I^H__ab$
	 ?ugT%PYZ	\\^ 	$tLL#LL#	$ 	

t667q@%H	$ 	$s   1#CC!c                     d| j                    d}| j                  j                  |      j                         xs g }|D cg c]  }|d   	 c}S c c}w )z3List of key/value tables already created in the DB.z.SELECT value FROM properties WHERE name LIKE 'z%%'r   )r   DBr   fetchall)r    sqlrowsrs       r!   r   zExpireCacheSQLite.table_names;  sR     ?t?V?V>WWZ[wws#,,.4""#!###s   Ar   c                     t         j                  ddj                  |             | j                         5 }|D ]  }|j	                  d|         	 d d d        j                          y# 1 sw Y   xY w)Nztruncate table: %s,r   T)r   r   r@   r   r   r   )r    r   r   r   s       r!   r   z!ExpireCacheSQLite.truncate_tablesB  sj    		&(=>\\^ 	5t$ 5|E7345	5 	

		5 	5s   A,,A5c                     | j                   j                  | j                  j                  dt	        t        j
                                     z   S )z2Returns (unix epoch) time of the next maintenance.r   )rN   r   r   m_timer*   r   r   s    r!   r   z'ExpireCacheSQLite.next_maintenance_timeJ  s;     xx**T__-C-CDVX[\`\e\e\gXh-iiir#   NrF   rG   rH   rP   c           	      8   | j                  |||fg|      \  }}|rAt        j                  d| j                  j                  ||t        |             t        |      S |D ].  }t        j                  d| j                  j                  ||       0 t        |      S )a  Set key/value in DB table given by argument ``ctx``.  If expire is
        unset the default is taken from :py:obj:`ExpireCacheCfg.MAXHOLD_TIME`.
        If ``ctx`` argument is ``None`` (the default), a table name is
        generated from the :py:obj:`ExpireCacheCfg.name`.  If DB table does not
        exists, it will be created (on demand) by :py:obj:`self.create_table
        <ExpireCacheSQLite.create_table>`.
        )rP   z2%s -- %s: key '%s' updated or inserted (%s errors)%s -- %s: %s)_setmanyr   r   rN   r   lenerrorr   )r    rF   rG   rH   rP   re   err_msg_listmsgs           r!   rS   zExpireCacheSQLite.setR  s     --#uf)=(>C-H<IIJDHHMM[^`cehiuevw Aw $ C		.$((--cBCAwr#   opt_listc                 p   t        j                          }| j                  ||      \  }}t        j                          }|D ].  }t        j                  d| j                  j
                  ||       0 t        j                  d| j                  j
                  ||t        |      ||z
  t        |             |S )zEfficient bootload of the cache from a list of options.  The list
        contains tuples with the arguments described in
        :py:obj:`ExpireCacheSQLite.set`.)r   rP   r   zI%s -- %s: %s/%s key/value pairs updated or inserted in %s sec (%s errors))r   r   r   r   rN   r   r   r   )r    r   rP   _startre   r   _endr   s           r!   setmanyzExpireCacheSQLite.setmanyb  s     --s-C<yy{ 	?CIIndhhmmS#>	? 			WHHMMM6M	
 r#   c           
      H   |}| j                          |}|s%| j                  | j                  j                        }| j	                  |       d| d}g }g }|D ]  \  }}	}
| j                  |	      }t        |      | j                  j                  kD  r$|j                  | d| dt        |       d       _|
s| j                  j                  }
t        t        j                               |
z   }
|||
||
f}|j                  |        |sd|fS |r2| j                  5  | j                  j                  ||       d d d        n;| j                         5 }|j                  ||       d d d        j                          t        |      |fS # 1 sw Y   xY w# 1 sw Y   2xY w)NzINSERT INTO zU (key, value, expire) VALUES (?, ?, ?)    ON CONFLICT DO UPDATE SET value=?, expire=?)rG   z.key='z,' - serialized value too big to cache (len: r8   r   )r[   r   rN   r   r   rk   r   r   r;   r   r*   r   r   executemanyr   r   )r    r   rP   r   
table_namesql_strsql_rowsr   rF   _valrH   rG   sql_argsr   s                 r!   r   zExpireCacheSQLite._setmany{  s    
,,TXX]];J*% :, '+ , 	  	 #%!) 	&Cv>>>5E5zDHH222##ugVC58dehineodppr$st..%.F UFE6:HOOH%	& l?" 7##GX67 7  44  (34JJL8}l**7 74 4s   FFFF!rU   c                 &   |}| j                          |s%| j                  | j                  j                        }|| j                  vr|S d| d}| j
                  j                  ||f      j                         }||S | j                  |d         S )a  Get value of ``key`` from table given by argument ``ctx``.  If
        ``ctx`` argument is ``None`` (the default), a table name is generated
        from the :py:obj:`ExpireCacheCfg.name`.  If ``key`` not exists (in
        table), the ``default`` value is returned.

        zSELECT value FROM z WHERE key = ?r   )	r[   r   rN   r   r   r   r   fetchonero   )r    rF   rU   rP   r   r   rows          r!   rW   zExpireCacheSQLite.get  s     ''6E(((N"5'8ggoocC6*335;NA''r#   c              #     K   |}| j                          |s%| j                  | j                  j                        }|| j                  v r?| j
                  j                  d|       D ]  }|d   | j                  |d         f  yyw)zIterate over key/value pairs from table given by argument ``ctx``.
        If ``ctx`` argument is ``None`` (the default), a table name is
        generated from the :py:obj:`ExpireCacheCfg.name`.zSELECT key, value FROM r   r3   N)r[   r   rN   r   r   r   r   ro   )r    rP   r   r   s       r!   pairszExpireCacheSQLite.pairs  s      ''6ED$$$ww)@'HI 7!fd..s1v6667 %s   BB
c           	          i }| j                   D ]Y  }g ||<   | j                  j                  d|       D ]1  }||   j                  |d   | j	                  |d         |d   f       3 [ t        |      S )NzSELECT key, value, expire FROM r   r3      )r1   )r   r   r   r;   ro   r   )r    r1   r   r   s       r!   r]   zExpireCacheSQLite.state  s    68%% 	WE"$Lww)H'PQ WU#**CFD4D4DSV4LcRSf+UVW	W  \::r#   r{   rg   rz   )%r$   r%   r&   r'   r   r*   r)   r   rK   r(   r   r   r   sqlite3
Connectionr   r   r[   r   propertyrL   r   r   r   r+   r~   rS   r   r   tupler   rW   r   r   r   r]   __classcell__)r   s   @r!   r   r      s   
 Is )+tCH~*++%N %++    $ <# $ 6 $T#Y $ $49  js j js 6:: sTz d
 ^b & |$ 4Z 
	8 9+|$9+ 4Z9+ 
sDI~		9+v(s (VZZ (S4Z (SYS]S] (.7 7%VZZ*@!A 7;' ;r#   r   )&r'   __all__r|   collections.abcr   dataclassesr<   r   ru   r   rh   r   ra   r   r   r+   msgspecsearxr   r	   r
   getChildr   r   r(   r~   r*   r   	TypeAliasr)   Structr   	dataclassr   ABCr   
SQLiteApplr   r/   r#   r!   <module>r      s    U 
 $     	          foog!&sFJJd
'B!Cf C,tW^^ ,t^ &  &  & RW#'' WtK;++[ K;r#   