
    i                     f   d dl Z d dlZd dlmZmZmZ d dlmZmZ d dl	m
Z
mZmZ d dlmZ d dlmZ d dlmZ dd	lmZmZmZmZ dd
lmZ ddlmZ ddlmZ ddlmZ ddlm Z! ddlm"Z# e
rddlmZ$ dd
lmZ%  e            Z&e!Z dede'de(fdZ)dZ* G d de          Z+e G d de                      Z,dS )    N)	dataclassfieldreplace)Enumauto)TYPE_CHECKINGAnyOptional)TypesFactory)Searcher)	Collation   )CalendarObjectResourceEventJournalTodo)Calendar)cdav)error)_build_search_xml_query)_collation_to_caldav)_filter_search_results)r   featurespropreturnc                     ddl m} |dk    rdn|}d| }||j        v r|                     |          S |                     d          S )u  Check if is-not-defined search is supported for a specific property.

    Checks the property-specific sub-feature (e.g. search.is-not-defined.category
    or search.is-not-defined.dtend) if one is defined, otherwise falls back to
    the parent search.is-not-defined feature.

    The ``categories`` → ``category`` mapping exists because the feature is named
    after the singular form while the iCalendar property is plural.
    r   )
FeatureSet
categoriescategoryzsearch.is-not-defined.zsearch.is-not-defined)compatibility_hintsr   FEATURESis_supported)r   r   r   feature_propsub_features        H/root/projects/butler/venv/lib/python3.11/site-packages/caldav/search.py_is_not_defined_supportedr&       sm     0/////!%!5!5::4L9<99Kj)))$$[111  !8999    )_property_filters_property_operator_property_locale_property_collationc                   v    e Zd ZdZ e            Z e            Z e            Z e            Z e            Z	dS )SearchActionzNActions yielded by the search generator to be executed by sync/async wrappers.N)
__name__
__module____qualname____doc__r   RECURSIVE_SEARCHSEARCH_WITH_COMPTYPESREQUEST_REPORTLOAD_OBJECTRETURN r'   r%   r-   r-   <   sM        XXtvv DFFTVVN$&&KTVVFFFr'   r-   c                        e Zd ZU dZdZed         ed<    ee          Z	eed<    edd          Z
ed	         ed
<   	 	 	 	 d(dedededededz  dedz  ddf fdZ	 	 d)dee         dz  dedd fdZdedededeej                 dz  dedz  dedz  dedz  fdZ	 	 	 	 	 	 	 d*dedededeej                 dz  dededee         fdZ	 	 	 	 	 	 d+dedededeej                 dz  dedededee         fd Z	 	 	 	 	 	 	 d*dd!dededeej                 dz  dededed"         fd#Z	 	 	 	 	 	 d+dd!dededeej                 dz  dedededed"         fd$Z	 	 	 d,d%ee         dedz  dededee         f
d&Zd-d'Z xZS ).CalDAVSearchera  The baseclass (which is generic, and not CalDAV-specific)
    allows building up a search query search logic.

    The base class also allows for simple client-side filtering (and
    at some point in the future, more complex client-side filtering).

    The CalDAV protocol is difficult, ambigiuous and does not offer
    all kind of searches.  Client-side filtering may be needed to
    smoothen over differences in how the different servers handle
    search queries, as well as allowing for more complex searches.

    A search may be performed by first setting up a CalDAVSearcher,
    populate it with filter options, and then initiate the search.
    The recommended approach (as of 3.0) is to create the searcher
    from a calendar:

    ``searcher = calendar.searcher(event=True, start=..., end=...)``
    ``searcher.add_property_filter("SUMMARY", "meeting")``
    ``results = searcher.search()``

    For simple searches, the direct method call still works:

    ``calendar.search(event=True, start=..., end=..., ...)``

    The ``todo``, ``event`` and ``journal`` parameters are booleans
    for filtering the component type.  It's currently recommended to
    set one and only one of them to True, as of 2025-11 there is no
    guarantees for correct behaviour if setting two of them.  Also, if
    none is given (the default), all objects should be returned -
    however, all examples in the CalDAV RFC filters things by
    component, and the different servers do different things when
    confronted with a search missing component type.  With the correct
    ``compatibility_hints`` (``davclient.features``) configured for
    the caldav server, the algorithms will ensure correct behaviour.

    Both the iCalendar standard and the (Cal)DAV standard defines
    "properties".  Make sure not to confuse those.  iCalendar
    properties used for filtering can be passed using
    ``searcher.add_property_filter``.
    Nr   
comp_class)default_factory_explicit_operatorsF)defaultreprr   	_calendarTkeyvalueoperatorcase_sensitive	collationlocaler   c                     |T| j                             |                                           t                                          ||||||           dS t                                          |||||           dS )a
  Adds a filter for some specific iCalendar property.

        Examples of valid iCalendar properties: SUMMARY,
        LOCATION, DESCRIPTION, DTSTART, STATUS, CLASS, etc

        :param key: iCalendar property name (e.g., SUMMARY).
                   Special virtual property "category" (singular) is also supported
                   for substring matching within category names
        :param value: Filter value, should adhere to the type defined in the RFC
        :param operator: Comparison operator ("contains", "==", "undef"). If not
                        specified, the server decides the matching behavior (usually
                        substring search per RFC). If explicitly set to "contains"
                        and the server doesn't support substring search, client-side
                        filtering is used (may transfer more data from server).
        :param case_sensitive: If False, text comparisons are case-insensitive.
                              Note: CalDAV standard case-insensitivity only applies
                              to ASCII characters.
        :param collation: Advanced collation strategy for text comparison.
                         May not work on all servers.
        :param locale: Locale string (e.g., "de_DE") for locale-aware collation.
                      Only used with collation=Collation.LOCALE. May not work on
                      all servers.

        **Supported operators:**

        * **contains** - substring match (e.g., "rain" matches "Training session"
          and "Singing in the rain")
        * **==** - exact match required, enforced client-side
        * **undef** - matches if property is not defined (value parameter ignored)

        **Special handling for categories:**

        - **"categories"** (plural): Exact category name matching
          - "contains": subset check (all filter categories must be in component)
          - "==": exact set equality (same categories, order doesn't matter)
          - Commas in filter values split into multiple categories

        - **"category"** (singular): Substring matching within category names
          - "contains": substring match (e.g., "out" matches "outdoor")
          - "==": exact match to at least one category name
          - Commas in filter values treated as literal characters

        Examples:
            # Case-insensitive search
            searcher.add_property_filter("SUMMARY", "meeting", case_sensitive=False)

            # Explicit substring search (guaranteed via client-side if needed)
            searcher.add_property_filter("LOCATION", "room", operator="contains")

            # Exact match
            searcher.add_property_filter("STATUS", "CONFIRMED", operator="==")
        N)rC   rD   rE   )r<   addlowersuperadd_property_filter)selfr@   rA   rB   rC   rD   rE   	__class__s          r%   rJ   z"CalDAVSearcher.add_property_filteru   s    z $((555GG''UHniY_````` GG''-# (     r'   filters_to_removeclear_all_filtersc                    i }t           D ]S}|ri ||<   
|rGt          | |                                          ||<   |D ]}||                             |d           STt	          | fi |}|r|s| j        t          |          z
  |_        |S )a  Create a clone of this searcher with specified property filters removed.

        This is used for compatibility workarounds where we need to remove certain
        filters from the server query and apply them client-side instead.

        :param filters_to_remove: List of property filter keys to remove (e.g., ["categories", "category"])
        :param clear_all_filters: If True, remove all property filters
        :return: Cloned searcher with filters removed
        N)_PROPERTY_FILTER_ATTRSgetattrcopypopr   r<   set)rK   rM   rN   replacementsattrr@   clones          r%   _clone_without_filtersz%CalDAVSearcher._clone_without_filters   s     * 	 	D  %'T""" %,T4%8%8%=%=%?%?T", 6 6C &**355556 ---- 	Z%6 	Z(,(@3GXCYCY(YE%r'   calendarserver_expandsplit_expandedpropsxmlpost_filter_hacksc           
   #   P   K    j         t          d          j        j                            dt
                    } j        s j        s j        s j	        o	|dk    o|du}	|	r|sd}d}|g j        r j
        rW j        sPd j        v sGd	 j        v s>j        j                            d
          rj        j                            d          sd} j        s|sd} j        rB|s@j        j                            d          s!j        j                            d          rd} j        s|r" j        r j        st          j        d          j        j                            d          s j        r~|durzd  j                                        D             }
|
rU                     |
          }t(          j        |||||ddffV }t(          j                             ||||          fV  dS j        j                            d          smd j        v s	d	 j        v r[|durW                     dd	g          }t(          j        |||||ddffV }t(          j                             ||||          fV  dS |dur}fd j                                        D             }|rV                     |          }t(          j        |||||ddffV }t(          j                             |d||          fV  dS j        j                            d          so|durk fd j        D             }|rV                     |          }t(          j        |||||ddffV }t(          j                             |d||          fV  dS j        j                            d          sk j        s j        r] j        rV                     d          }t(          j        |||||ddffV }t(          j                             ||||          fV  dS  j        rC j
        du r9t1           d          }d|_
        d|_        |	sj        j                            d          rj        j                            d          rsj        j                            d          rj        j                            d          r5g }dD ]/}t(          j        ||d||d|ffV }|                    |           0nt(          j        ||d||d|ffV }g }t5                      }|D ]:}|j        |vr/|                    |j                   |                    |           ;n|}|r/t=          |t
                    s;|j                             d          s! !                    ||||          \  } _         j        sVj        j                            d          s7 j
        d _
        t(          j"        ||||||ffV }t(          j        |fV  dS 	 t(          j#        | j        |ffV \  }}nf# t          j        $ rT}j        j        j$        r= j        s6d|j%        vr-t(          j"        ||||||ffV }t(          j        |fV  Y d}~dS  d}~ww xY w|s6 j        s/|d k    r)t(          j"        ||||||ffV }t(          j        |fV  dS |s|d k    r j        rzd!  j                                        D             }|rU                     |          }t(          j        |||||ddffV }t(          j                             ||||          fV  dS g }|D ]N}	 t(          j&        |fV  |                    |           )# tN          $ r tQ          j        d"d#           Y Kw xY w|}d$ |D             }                     ||||          }|D ]#}	 t(          j&        |fV  # tN          $ r Y  w xY wt(          j         )                    |          fV  dS )%aN  Core search implementation as a generator yielding actions.

        This generator contains all the search logic and yields (action, data) tuples
        that the caller (sync or async) executes. Results are sent back via .send().

        Yields:
            Tuples of (SearchAction, data) where data depends on action type
        NzgNo calendar provided. Either pass a calendar to search() or create the searcher via calendar.searcher()zsearch.comp-typebrokenFno_comp_filterTr   r   zsearch.text.case-sensitivezsearch.time-range.accuratez%save-load.event.recurrences.exceptionz%search.recurrences.expanded.exceptionz!can't expand without a date rangezsearch.textc                 $    g | ]\  }}|d k    |S undefr7   .0r   ops      r%   
<listcomp>z/CalDAVSearcher._search_impl.<locals>.<listcomp>+  s(     ! ! !!rgr'   zsearch.text.categoryc                 Z    g | ]'\  }}|d k    t          j        j        |          %|(S rd   )r&   clientr   )rg   r   rh   rY   s      r%   ri   z/CalDAVSearcher._search_impl.<locals>.<listcomp>O  sC     + + +D"==)B8?C[]a)b)b=  ==r'   )r^   r[   rZ   zsearch.text.substringc                 H    g | ]}|j         v j        |         d k    |S )contains)r<   r)   )rg   r   rK   s     r%   ri   z/CalDAVSearcher._search_impl.<locals>.<listcomp>j  sE     ! ! !43338OPT8UYc8c8c 8c8c8cr'   zsearch.combined-is-logical-and)rN   )include_completedz)search.recurrences.includes-implicit.todoz1search.recurrences.includes-implicit.todo.pending)ignore_completed1ignore_completed2ignore_completed3calendar-query)r\   filtersr_   zsearch.comp-type-optional400insistc                 $    g | ]\  }}|d k    |S rd   r7   rf   s      r%   ri   z/CalDAVSearcher._search_impl.<locals>.<listcomp>  s)     % % %%T2BRYMMDMMMr'   z@Server does not want to reveal details about the calendar object)exc_infoc                 :    g | ]}|                                 |S r7   )has_component)rg   os     r%   ri   z/CalDAVSearcher._search_impl.<locals>.<listcomp>6  s'    ;;;):):;1;;;r'   )*r?   
ValueErrorrk   r   r"   strr:   todoeventjournalrn   expandr(   startendr   ReportErrorr)   itemsrX   r-   r2   r6   filterr   extendrT   urlrG   append
isinstancetagendswithbuild_search_xml_queryr3   r4   backward_compatibility_modereasonr5   	Exceptionloggingsort)rK   rY   rZ   r[   r\   r]   r^   r_   comp_type_supportrb   text_filter_propsrW   objectsundef_props_without_supportexplicit_containsmatcheshacksresult	match_setitemorig_xmlresponseerrnon_undef_filtersobj2rz   objs   ``                         r%   _search_implzCalDAVSearcher._search_impl   s     $ ~H>   %O4AABTVYZZ_G	GTZG4< )!X-)5( 	
  	 *)K Y #5 {  t555T333?+889UVV 4?+889UVV 4 K { 	#= 	#"N K	!!	! O,99:abb	! (556]^^		! !M; 	M- 	M: MTX M'(KLLL (55mDD	&	 5((! !%)%<%B%B%D%D! ! ! ! 
334EFF 1Hm^UCQUW[\!  
 !'KKnmTT    
 (556LMM	!777:I_;_;_5((//z0JKKE--TSWX  G
 #G[.-PP    F e##+ + + + $ 7 = = ? ?+ + +'
 + 334OPP 1Hm^UCQUW[\!  
 !'KK$('5&3	         (556MNN	5((! ! ! ! 3! ! !
 ! 334EFF 1Hm^UCQUW[\!  
 !'KK$('5&3	         '445UVV 	z TX ) 
 77$7OOE$5-PSUY[_`%  G
 %+G[.-XX    F> 9 y	/588DD999E&*E# EL #O,99-HH O,99:Z[[
 !0==C   /<<K  \ + +E$5-sDRWX$  F NN6****+ !1HmUE3fU!   GI ) )89,,MM$(+++NN4((()
 H z#s33 CG<L<LM]<^<^ )-)D)D!F *E * *&do ? 8?+C+P+P+, ,  )1-1D* !6}neXvWbc    $*F3333 /sDOU;+ % %!'' $   O,H O SZ// %:$)*!$"'$  F (.7777FFFFF),  4? v7I7I 6}neXvWbc    $*F3333  v11d6L1% %)-)@)F)F)H)H% % %! %  778IJJE$5!$)*!$  	$  F %+FKWW    F  	 	A#/3333A   V!     
  <;g;;;++g{NMRR  	 	C#/55555    "DIIg$6$6777777sC    X= =Z AZZZ +%^ ^43^4"_33
` ?` c           	         |                      |||||||          }d}		 |                    |	          \  }
}n# t          $ r g cY S w xY w	 	 |
t          j        k    r'|\  }}}}}}}}|                    |||||||          }	n|
t          j        k    r&|\  }}}}}}}|                     |||||||          }	nk|
t          j        k    r |\  }}}}|	                    |||          }	n;|
t          j
        k    r|                    d           d}	n|
t          j        k    r|S |                    |	          \  }
}n# t          $ r g cY S w xY w)a  Do the search on a CalDAV calendar.

        Only CalDAV-specific parameters goes to this method.  Those
        parameters are pretty obscure - mostly for power users and
        internal usage.  Unless you have some very special needs, the
        recommendation is to not pass anything.

        :param calendar: Calendar to be searched (optional if searcher was created
                        from a calendar via ``calendar.searcher()``)
        :param server_expand: Ask the CalDAV server to expand recurrences
        :param split_expanded: Don't collect a recurrence set in one ical calendar
        :param props: CalDAV properties to send in the query
        :param xml: XML query to be sent to the server (string or elements)
        :param post_filter: Do client-side filtering after querying the server
        :param _hacks: Please don't ask!

        Make sure not to confuse he CalDAV properties with iCalendar properties.

        If ``xml`` is given, any other filtering will not be sent to the server.
        They may still be applied through client-side filtering. (TODO: work in progress)

        ``post_filter`` takes three values, ``True`` will always
        filter the results, ``False`` will never filter the results,
        and the default ``None`` will cause automagics to happen (not
        implemented yet).  Or perhaps I'll just set it to True as
        default.  TODO - make a decision here

        In the CalDAV protocol, a VCALENDAR object returned from the
        server may contain only one event/task/journal - but if the
        object is recurrent, it may contain several recurrences.
        ``split_expanded`` will split the recurrences into several
        objects.  If you don't know what you're doing, then leave this
        flag on.

        NTr\   only_if_unloaded)r   sendStopIterationr-   r2   searchr3   _search_with_comptypesr4    _request_report_build_resultlistr5   loadr6   )rK   rY   rZ   r[   r\   r]   r^   r_   genr   actiondatarW   calsrv_expspl_expprpxmpfhkcomp_clss                        r%   r   zCalDAVSearcher.searchB  s   Z m^UCf
 
 	88F++LFDD 	 	 	III		\:::DHAE3#r2r"\\#wb"bQQFF|AAA=A:C'3B!88gwPSUWY[]_``FF|:::-1*CXs AA"hVYAZZFF|777IItI444!FF|222K"xx//    			%	s(   8 AACD= $D= =EEc                 R   |r-t          |t                    s	d|j        v rt          d          g }| j        | j        | j        J t          t          t          fD ]6}	t          |           }
|	|
_        ||
                    |||||||          z  }7|                     |          S )zh
        Internal method - does three searches, one for each comp class (event, journal, todo).
        rr   =full xml given, and it has to be patched to include comp_type)r   r|   r   NotImplementedErrorr~   r}   r   r   r   r   r   r:   r   r   )rK   rY   rZ   r[   r\   r]   r_   r^   r   r:   rW   s              r%   r   z%CalDAVSearcher._search_with_comptypes  s      	JsC(( 	,<,G,G%O   z!di&7DL<P<P<P $0 	 	JDMME)Eu||-[RX  GG yy!!!r'   AsyncCalendarAsyncCalendarObjectResourcec           	        K   |                      |||||||          }d}		 |                    |	          \  }
}n# t          $ r g cY S w xY w	 	 |
t          j        k    r-|\  }}}}}}}}|                    |||||||           d{V }	n|
t          j        k    r,|\  }}}}}}}|                     |||||||           d{V }	n|
t          j        k    r&|\  }}}}|	                    |||           d{V }	nW|
t          j
        k    r5|                    d          }t          j        |          r| d{V  d}	n|
t          j        k    r|S |                    |	          \  }
}n# t          $ r g cY S w xY w4)a  Async version of search() - does the search on an AsyncCalendar.

        This method mirrors the sync search() method but uses async HTTP operations.
        All the same compatibility logic is applied.

        See the sync search() method for full documentation.
        NTr   r   )r   r   r   r-   r2   async_searchr3   _async_search_with_comptypesr4   r   r5   r   inspectisawaitabler6   )rK   rY   rZ   r[   r\   r]   r^   r_   r   r   r   r   rW   r   r   r   r   r   r   r   r   load_results                         r%   r   zCalDAVSearcher.async_search  s0     " m^UCf
 
 	88F++LFDD 	 	 	III		\:::DHAE3#r2r#(#5#5c7GSRTVXZ\#]#]]]]]]]FF|AAA=A:C'3B#'#D#DWgsBB$ $      FF |:::-1*CXs#&#G#GH\_#G#`#```````FF|777"&))T)"B"BK*;77 *))))))))!FF|222K"xx//    			-	s(   : A	A	DE- E- -E<;E<c           
        K   |r-t          |t                    s	d|j        v rt          d          g }| j        | j        | j        J t          t          t          fD ]N}	t          |           }
|	|
_        |
                    |||||||           d{V }|                    |           O|                     |          S )zW
        Internal async method - does three searches, one for each comp class.
        rr   r   N)r   r|   r   r   r~   r}   r   r   r   r   r   r:   r   r   r   )rK   rY   rZ   r[   r\   r]   r_   r^   r   r:   rW   resultss               r%   r   z+CalDAVSearcher._async_search_with_comptypes  s        	JsC(( 	,<,G,G%O   68z!di&7DL<P<P<P $0 	$ 	$JDMME)E!..-[RX       G NN7####yy!!!r'   r   c                 *    t          || |||          S )aQ  Apply client-side filtering and handle recurrence expansion/splitting.

        This method delegates to the operations layer filter_search_results().
        See that function for full documentation.

        :param objects: List of Event/Todo/Journal objects to filter
        :param post_filter: Whether to apply the searcher's filter logic
        :param split_expanded: Whether to split recurrence sets into separate objects
        :param server_expand: Whether server was asked to expand recurrences
        :return: Filtered and/or split list of CalendarObjectResource objects
        )r   searcherr^   r[   rZ   )filter_search_results)rK   r   r^   r[   rZ   s        r%   r   zCalDAVSearcher.filter  s*    $ %#)'
 
 
 	
r'   c                 F    t          | ||||          \  }}|| _        ||fS )a  Build a CalDAV calendar-query XML request.

        Delegates to the operations layer for the actual XML building.
        This method updates self.comp_class as a side effect based on
        the search parameters.

        :param server_expand: Ask server to expand recurrences
        :param props: Additional CalDAV properties to request
        :param filters: Pre-built filter elements (or None to build from self)
        :param _hacks: Compatibility hack mode
        :return: Tuple of (xml_element, comp_class)
        )r   rZ   r\   rs   r_   )r   r:   )rK   rZ   r\   rs   r_   r]   r:   s          r%   r   z%CalDAVSearcher.build_search_xml_query  s?     2'
 
 
Z %Z  r'   )NTNN)NF)NFTNNNN)FTNNNN)NTF)FNNN)r.   r/   r0   r1   r:   r
   __annotations__r   rT   r<   r?   r|   r	   boolr   rJ   listrX   r   r   CalendarDatar   r   r   r   r   r   r   r   __classcell__)rL   s   @r%   r9   r9   F   s        ' 'R 6:J12999$uS999999&+eDu&E&E&EIx
#EEE #&*!J JJ J 	J
 J t#J d
J 
J J J J J J\ /3"' 9t+   
	   <a8a8 a8 	a8
 D%&-a8 4Za8 D[a8 d
a8 a8 a8 a8J "##04J JJ J 	J
 D%&-J J J 
$	%J J J J^ $#04 " "" " 	"
 D%&-" " " " 
$	%" " " "> %)##042 2!2 2 	2
 D%&-2 2 2 
+	,2 2 2 2n $#04 " "!" " 	"
 D%&-" " " " 
+	," " " "B $(##
 
,-
 D[
 	

 
 
$	%
 
 
 
4! ! ! ! ! ! ! !r'   r9   )-r   r   dataclassesr   r   r   enumr   r   typingr   r	   r
   icalendar.propr   icalendar_searcherr   icalendar_searcher.collationr   calendarobjectresourcer   r   r   r   
collectionr   elementsr   libr   operations.search_opsr   r   collation_to_caldavr   r   r   r   _icalendar_typesr|   r   r&   rP   r-   r9   r7   r'   r%   <module>r      s$     1 1 1 1 1 1 1 1 1 1         / / / / / / / / / / ' ' ' ' ' ' ' ' ' ' ' ' 2 2 2 2 2 2 P P P P P P P P P P P P                         : : : : : : N N N N N N R R R R R R 6      655555<>>  + : :3 :4 : : : :(     4    f! f! f! f! f!X f! f! f! f! f!r'   