diff --git a/addon.xml b/addon.xml index c05bbf6b..a4dfd29d 100644 --- a/addon.xml +++ b/addon.xml @@ -1,14 +1,13 @@ <?xml version="1.0" encoding="UTF-8" standalone="yes"?> <addon id="plugin.video.emby" name="Emby" - version="3.0.34a" + version="3.0.35a" provider-name="Emby.media"> <requires> <import addon="xbmc.python" version="2.25.0"/> - <import addon="script.module.requests" version="2.9.1" /> - <import addon="plugin.video.emby.movies" version="0.11" /> - <import addon="plugin.video.emby.tvshows" version="0.11" /> - <import addon="plugin.video.emby.musicvideos" version="0.11" /> + <import addon="plugin.video.emby.movies" version="0.12" /> + <import addon="plugin.video.emby.tvshows" version="0.12" /> + <import addon="plugin.video.emby.musicvideos" version="0.12" /> </requires> <extension point="xbmc.python.pluginsource" library="default.py"> @@ -18,11 +17,11 @@ </extension> <extension point="kodi.context.item"> <menu id="kodi.core.main"> - <item library="contextmenu.py"> + <item library="context.py"> <label>30401</label> <visible>[!String.IsEmpty(ListItem.DBID) + !String.IsEqual(ListItem.DBID,-1) | !String.IsEmpty(ListItem.Property(embyid))] + !String.IsEmpty(Window(10000).Property(emby_context))</visible> </item> - <item library="contextmenu_play.py"> + <item library="context_play.py"> <label>30402</label> <visible>[[!String.IsEmpty(ListItem.DBID) + !String.IsEqual(ListItem.DBID,-1) | !String.IsEmpty(ListItem.Property(embyid))] + [String.IsEqual(ListItem.DBTYPE,movie) | String.IsEqual(ListItem.DBTYPE,episode)]] + !String.IsEmpty(Window(10000).Property(emby_context)) + !String.IsEmpty(Window(10000).Property(emby_context_transcode))</visible> </item> diff --git a/contextmenu_play.py b/context.py similarity index 60% rename from contextmenu_play.py rename to context.py index f0766f40..3bf53b07 100644 --- a/contextmenu_play.py +++ b/context.py @@ -11,27 +11,28 @@ import xbmcaddon ################################################################################################# -_ADDON = xbmcaddon.Addon(id='plugin.video.emby') -_CWD = _ADDON.getAddonInfo('path').decode('utf-8') -_BASE_LIB = xbmc.translatePath(os.path.join(_CWD, 'resources', 'lib')).decode('utf-8') -sys.path.append(_BASE_LIB) +__addon__ = xbmcaddon.Addon(id='plugin.video.emby').getAddonInfo('path').decode('utf-8') +__base__ = xbmc.translatePath(os.path.join(__addon__, 'resources', 'lib')).decode('utf-8') +sys.path.append(__base__) ################################################################################################# -import loghandler -from context_entry import ContextMenu +from entrypoint import Context ################################################################################################# -loghandler.config() -log = logging.getLogger("EMBY.contextmenu_play") +LOG = logging.getLogger("EMBY.context") ################################################################################################# + if __name__ == "__main__": + LOG.info("--->[ context ]") + try: - # Start the context menu - ContextMenu(True) + Context() except Exception as error: - log.exception(error) + LOG.exception(error) + + LOG.info("---<[ context ]") diff --git a/contextmenu.py b/context_play.py similarity index 59% rename from contextmenu.py rename to context_play.py index 31323d4a..5cfac752 100644 --- a/contextmenu.py +++ b/context_play.py @@ -1,37 +1,38 @@ -# -*- coding: utf-8 -*- - -################################################################################################# - -import logging -import os -import sys - -import xbmc -import xbmcaddon - -################################################################################################# - -_ADDON = xbmcaddon.Addon(id='plugin.video.emby') -_CWD = _ADDON.getAddonInfo('path').decode('utf-8') -_BASE_LIB = xbmc.translatePath(os.path.join(_CWD, 'resources', 'lib')).decode('utf-8') -sys.path.append(_BASE_LIB) - -################################################################################################# - -import loghandler -from context_entry import ContextMenu - -################################################################################################# - -loghandler.config() -log = logging.getLogger("EMBY.contextmenu") - -################################################################################################# - -if __name__ == "__main__": - - try: - # Start the context menu - ContextMenu() - except Exception as error: - log.exception(error) +# -*- coding: utf-8 -*- + +################################################################################################# + +import logging +import os +import sys + +import xbmc +import xbmcaddon + +################################################################################################# + +__addon__ = xbmcaddon.Addon(id='plugin.video.emby').getAddonInfo('path').decode('utf-8') +__base__ = xbmc.translatePath(os.path.join(__addon__, 'resources', 'lib')).decode('utf-8') +sys.path.append(__base__) + +################################################################################################# + +from entrypoint import Context + +################################################################################################# + +LOG = logging.getLogger("EMBY.context") + +################################################################################################# + + +if __name__ == "__main__": + + LOG.info("--->[ context ]") + + try: + Context(True) + except Exception as error: + LOG.exception(error) + + LOG.info("---<[ context ]") diff --git a/default.py b/default.py index 4f807baa..9b3ffc37 100644 --- a/default.py +++ b/default.py @@ -2,6 +2,48 @@ ################################################################################################# +import logging +import os +import sys + +import xbmc +import xbmcaddon + +################################################################################################# + +__addon__ = xbmcaddon.Addon(id='plugin.video.emby').getAddonInfo('path').decode('utf-8') +__base__ = xbmc.translatePath(os.path.join(__addon__, 'resources', 'lib')).decode('utf-8') +sys.path.append(__base__) + +################################################################################################# + +from entrypoint import Events + +################################################################################################# + +LOG = logging.getLogger("EMBY.default") + +################################################################################################# + + +if __name__ == "__main__": + + LOG.info("--->[ default ]") + + try: + Events() + except Exception as error: + LOG.exception(error) + + LOG.info("---<[ default ]") + + +""" + +# -*- coding: utf-8 -*- + +################################################################################################# + import logging import os import sys @@ -23,7 +65,7 @@ sys.path.append(_BASE_LIB) import entrypoint import loghandler from utils import window, dialog, language as lang -#from ga_client import GoogleAnalytics +from ga_client import GoogleAnalytics import database ################################################################################################# @@ -178,13 +220,13 @@ if __name__ == "__main__": try: Main() except Exception as error: - """ if not (hasattr(error, 'quiet') and error.quiet): ga = GoogleAnalytics() errStrings = ga.formatException() ga.sendEventData("Exception", errStrings[0], errStrings[1]) - """ log.exception(error) raise log.info("plugin.video.emby stopped") + +""" diff --git a/fanart.jpg b/fanart.jpg index 77b142fe..4f565065 100644 Binary files a/fanart.jpg and b/fanart.jpg differ diff --git a/kodi_icon.png b/kodi_icon.png deleted file mode 100644 index 99749229..00000000 Binary files a/kodi_icon.png and /dev/null differ diff --git a/resources/language/Czech/strings.xml b/resources/language/Czech/strings.xml deleted file mode 100644 index 695233ca..00000000 --- a/resources/language/Czech/strings.xml +++ /dev/null @@ -1,358 +0,0 @@ -<?xml version="1.0" encoding="utf-8" standalone="yes"?> -<strings> - - <!-- Add-on settings --> - <string id="29999">Emby pro Kodi</string> - <string id="30000">IP Adresa serveru</string> - <string id="30001">Název serveru</string> - <string id="30002">Přehrávat přes HTTP místo SMB</string> - <string id="30004">Úroveň záznamů</string> - <string id="30016">Název zařízení</string> - <string id="30022">Rozšířené</string> - <string id="30024">Uživ. jméno</string> - <string id="30030">Číslo portu</string> - - <string id="30035">Zobrazit počet naposledy přidaných hudebních alb:</string> - <string id="30036">Zobrazit počet naposledy přidaných filmů:</string> - <string id="30037">Zobrazit počet naposledy přidaných epizod seriálů:</string> - - <string id="30042">Obnovit</string> - <string id="30043">Smazat</string> - <string id="30044">Nesprávné uživatelské jméno nebo heslo</string> - <string id="30045">Uživatelské jméno nenalezeno</string> - <string id="30052">Mažu</string> - <string id="30053">Čekám až server provede smazání</string> - - <string id="30068">Třídit podle:</string> - <string id="30069">Žádný</string> - <string id="30070">Akční</string> - <string id="30071">Dobrodružný</string> - <string id="30072">Animovaný</string> - <string id="30073">Kriminální</string> - <string id="30074">Komedie</string> - <string id="30075">Dokumentární</string> - <string id="30076">Drama</string> - <string id="30077">Fantasy</string> - <string id="30078">Zahraniční</string> - <string id="30079">Historický</string> - <string id="30080">Horor</string> - <string id="30081">Hudební</string> - <string id="30082">Muzikál</string> - <string id="30083">Tajemný</string> - <string id="30084">Romantický</string> - <string id="30085">Vědeckofantastický</string> - <string id="30086">Krátký</string> - <string id="30087">Napínavý</string> - <string id="30088">Thriller</string> - <string id="30089">Western</string> - - <string id="30090">Filtr žánrů</string> - <string id="30091">Potvrdit smazání</string><!-- Verified --> - - <string id="30093">Označit jako shlédnuté</string> - <string id="30094">Označit jako neshlédnuté</string> - - <string id="30097">Řadit podle</string> - <string id="30098">Řadit sestupně</string> - <string id="30099">Řadit vzestupně</string> - - <!-- resume dialog --> - <string id="30105">Pokračovat</string> - <string id="30106">Pokračovat od</string> - <string id="30107">Přehrát od začáku</string> - - <string id="30114">>Nabídnout smazání po skončení přehrávání</string><!-- Verified --> - <string id="30115">Pro epizody</string><!-- Verified --> - <string id="30116">Pro filmy</string><!-- Verified --> - - <string id="30118">Přidat procenta obnovení</string> - <string id="30119">Přidat číslo epizody</string> - <string id="30120">Zobrazit průběh načítání</string> - <string id="30121">Načítám obsah</string> - <string id="30122">Přijímám data</string> - - <string id="30125">Hotovo</string> - <string id="30132">Varování</string><!-- Verified --> - <string id="30135">Chyba</string> - <string id="30138">Vyhledat</string> - - <string id="30157">Povolit rozšířené obrázky (např. CoverArt)</string><!-- Verified --> - <string id="30158">Metadata</string> - <string id="30159">Obrázky</string> - <string id="30160">Kvalita videa</string><!-- Verified --> - - <string id="30165">Přímé přehrávání</string><!-- Verified --> - <string id="30166">Překódování</string> - <string id="30167">Zjištění serveru uspělo</string> - <string id="30168">Nalezen server</string> - <string id="30169">Adresa:</string> - - <!-- Video nodes --> - <string id="30170">Naposledy přidané seriály</string><!-- Verified --> - <string id="30171">Rozkoukané seriály</string><!-- Verified --> - <string id="30172">Všechna hudba</string> - <string id="30173">Kanály</string><!-- Verified --> - <string id="30174">Naposledy přidané filmy</string><!-- Verified --> - <string id="30175">Naposledy přidané epizody</string><!-- Verified --> - <string id="30176">Naposledy přidaná alba</string> - <string id="30177">Rozkoukané filmy</string><!-- Verified --> - <string id="30178">Rozkoukané epizody</string><!-- Verified --> - <string id="30179">Další epizody</string><!-- Verified --> - <string id="30180">Oblíbené filmy</string><!-- Verified --> - <string id="30181">Oblíbené seriály</string><!-- Verified --> - <string id="30182">Oblíbené epizody</string> - <string id="30183">Často přehrávaná alba</string> - <string id="30184">Nadcházející epizody</string> - <string id="30185">Kolekce</string> - <string id="30186">Ukázky</string> - <string id="30187">Videoklipy</string> - <string id="30188">Fotky</string> - <string id="30189">Neshlédnuté filmy</string><!-- Verified --> - <string id="30190">Žánry filmů</string> - <string id="30191">Filmová studia</string> - <string id="30192">Filmoví herci</string> - <string id="30193">Neshlédnuté epizody</string> - <string id="30194">Žánry seriálů</string> - <string id="30195">Televizní stanice</string> - <string id="30196">Televizní herci</string> - <string id="30197">Playlisty</string> - - <string id="30199">Nastavit náhledy</string> - <string id="30200">Vybrat uživatele</string><!-- Verified --> - <string id="30204">Nemohu se připojit k serveru</string> - - <string id="30207">Skladby</string> - <string id="30208">Alba</string> - <string id="30209">Interpreti alba</string> - <string id="30210">Interpreti</string> - <string id="30211">Hudební žánry</string> - - <string id="30220">Poslední</string> - <string id="30221">Rozkoukané</string> - <string id="30222">Nadcházející</string> - <string id="30223">Uživatelské náhledy</string> - <string id="30224">Hlášení metriky</string> - - <string id="30227">Náhodné filmy</string> - <string id="30228">Náhodné epizody</string> - <string id="30229">Náhodné položky</string><!-- Verified --> - <string id="30230">Doporučené položky</string><!-- Verified --> - - <string id="30235">Extra</string><!-- Verified --> - <string id="30236">Synchronizovat tématickou hudbu</string> - <string id="30237">Synchronizovat Extra Fanart</string> - <string id="30238">Synchronizovat kolekce filmů</string> - - <string id="30239">Resetovat místní databázi Kodi</string><!-- Verified --> - <string id="30243">Povolit HTTPS</string><!-- Verified --> - <string id="30245">Kodeky vynuceného překódování</string> - - <string id="30249">Povolit zprávu o připojení k serveru po spuštění</string><!-- Verified --> - - <string id="30251">Naposledy přidaná domácí videa</string><!-- Verified --> - <string id="30252">Naposledy přidané fotky</string><!-- Verified --> - <string id="30253">Oblíbená domácí videa</string><!-- Verified --> - <string id="30254">Oblíbené fotky</string><!-- Verified --> - <string id="30255">Oblíbená alba</string> - - <string id="30256">Naposledy přidané videoklipy</string><!-- Verified --> - <string id="30257">Rozkoukané videoklipy</string><!-- Verified --> - <string id="30258">Neshlédnuté videoklipy</string><!-- Verified --> - - <!-- Default views --> - <string id="30300">Aktivní</string> - <string id="30301">Vyčistit nastavení</string> - <string id="30302">Filmy</string> - <string id="30303">Kolekce</string> - <string id="30304">Ukázky</string> - <string id="30305">Seriály</string> - <string id="30306">Sezóny</string> - <string id="30307">Episody</string> - <string id="30308">Hudební interpreti</string> - <string id="30309">Hudební alba</string> - <string id="30310">Videoklipy</string> - <string id="30311">Skladby</string> - <string id="30312">Kanály</string> - - <!-- contextmenu --> - <string id="30401">Nastavení Emby</string> - <string id="30405">Přidat do oblíbených Emby</string> - <string id="30406">Odstranit z oblíbených Emby</string> - <string id="30407">Nastavit vlastní hodnocení skladby</string> - <string id="30408">Nastavení doplňku Emby</string> - <string id="30409">Smazat položku ze serveru</string> - <string id="30410">Obnovit tuto položku</string> - <string id="30411">Nastavit vlastní hodnocení skladby (0-5)</string> - <string id="30412">Překódovat</string> - - <!-- add-on settings --> - <string id="30500">Ověřit hostitelův SSL certifikát</string> - <string id="30501">Klientský SSL certifikát</string> - <string id="30502">Použít alternativní adresu</string> - <string id="30503">Alternativní adresa serveru</string> - <string id="30504">Použít alternativní název zařízení</string> - <string id="30505">[COLOR yellow]Opakovat přihlášení[/COLOR]</string> - <string id="30506">Synchronizace</string> - <string id="30507">Zobrazovat průběh pokud je položek více než</string> - <string id="30508">Synchronizovat prázdné seriály</string> - <string id="30509">Povolit hudební knihovnu</string> - <string id="30510">Přímé streamování hudební knihovny</string> - <string id="30511">Režim přehrávání</string> - <string id="30512">Vynutit ukládání obrázků do mezipaměti</string> - <string id="30513">Omezit vlákna mezipaměti obrázků (doporučeno pro rpi)</string> - <string id="30514">Povolit rychlé spuštění (vyžaduje plugin na serveru)</string> - <string id="30515">Maximální počet položek vyžádaných najednou</string> - <string id="30516">Přehrávání videa</string> - <string id="30517">Přihlašovací údaje k síti</string> - <string id="30518">Povolit režim Emby cinema</string> - <string id="30519">Zeptat se na přehrání ukázek</string> - <string id="30520">Přeskočit potvrzení Emby o smazání (používejte na vlastní riziko)</string> - <string id="30521">Poskočit vzad při obnovení přehrávání (v sekundách)</string> - <string id="30522">Vynutit překódování H265</string> - <string id="30523">Nastavení hudebních metadat (není kompatibilní s přímým streamováním)</string> - <string id="30524">Načíst hodnocení skladeb přímo ze souborů</string> - <string id="30525">Převést hodnocení hudby na hodnocení Emby</string> - <string id="30526">Povolit aktualizaci hodnocení skladeb přímo v souborech</string> - <string id="30527">Ignorovat speciály v nadcházejících epizodách</string> - <string id="30528">Stálí uživatelé pro přidání do relace</string> - <string id="30529">Zpoždění startu (v sekundách)</string> - <string id="30530">Povolit zprávu o restartu serveru</string> - <string id="30531">Povolit upozornění na nový obsah</string> - <string id="30532">Doba zobrazení upozornění video knihovny (v sekundách)</string> - <string id="30533">Doba zobrazení upozornění hudební knihovny (v sekundách)</string> - <string id="30534">Zprávy serveru</string> - <string id="30535">Vygenerovat nový identifikátor zařízení</string> - <string id="30536">Synchronizovat při vypnutém šetřiči obrazovky</string> - <string id="30537">Vynutit překódování Hi10P</string> - <string id="30538">Vypnuté</string> - <string id="30539">Přihlášení</string> - <string id="30540">Ruční přihlášení</string> - <string id="30541">Emby Connect</string> - <string id="30542">Server</string> - <string id="30543">Uživ. jméno nebo mail</string> - <string id="30544">Povolit opravu uzamčené databáze (zpomalí průběh synchronizace)</string> - <string id="30545">Zobrazit zprávu o odpojení serveru</string> - <string id="30546">Povolit sběr analytických dat</string> - <string id="30547">Zobrazit zprávu (v sekundách)</string> - <string id="30548">Stahovací vlákna (doporučeno: 2-3)</string> - - <!-- dialogs --> - <string id="30600">Přihlásit pomocí Emby Connect</string> - <string id="30602">Password</string> - <string id="30603">Prosím přečtěte si podmínky použití. Použití jakéhokoliv softwaru Emby je podmíněno souhlasem s nimi.</string> - <string id="30604">Oskenuj si</string> - <string id="30605">Přihlásit</string> - <string id="30606">Zrušit</string> - <string id="30607">Vybrat hlavní server</string> - <string id="30608">Uživatelské jméno nebo heslo nemohou být prázdné</string> - <string id="30609">Nemohu se připojit ke zvolenému serveru</string> - <string id="30610">Přihlásit k</string><!-- Connect to {server} --> - <string id="30611">Ručně přidat server</string> - <string id="30612">Prosím přihlaste se</string> - <string id="30613">Uživatelské jméno nemůže být prázdné</string> - <string id="30614">Připojit k serveru</string> - <string id="30615">Hostitel</string> - <string id="30616">Připojit</string> - <string id="30617">Server nebo port nemůžou být prázdné</string> - <string id="30618">Změnit uživatele Emby Connect</string> - - <!-- service add-on --> - <string id="33000">Vítáme uživatele</string> - <string id="33001">Chyba připojení</string> - <string id="33002">Server je nedosažitelný</string> - <string id="33003">Server je online</string> - <string id="33004">položek přidáno do playlistu</string> - <string id="33005">položek zařazeno do fronty playlistu</string> - <string id="33006">Server se restartuje</string> - <string id="33007">Přístup je povolen</string> - <string id="33008">Zadejte heslo pro uživatele:</string> - <string id="33009">Neplatné uživatelské jméno nebo heslo</string> - <string id="33010">Proběhlo moc neúspěšných pokusů o ověření</string> - <string id="33011">Nemohu přehrávat přímo</string> - <string id="33012">Přímé přehrávání 3x neuspělo. Povolte přehrávání přes HTTP.</string> - <string id="33013">Zvolte audiostopu</string> - <string id="33014">Zvolte titulkovou stopu</string> - <string id="33015">Smazat soubor ze serveru Emby?</string> - <string id="33016">Přehrát ukázky?</string> - <string id="33017">Shromažďuji filmy z:</string> - <string id="33018">Shromažďuji kolekce</string> - <string id="33019">Shromažďuji videoklipy z:</string> - <string id="33020">Shromažďuji seriály z:</string> - <string id="33021">Shromažďuji:</string> - <string id="33022">Zjištěná databáze musí být pro tuto verzi Emby pro Kodi znovu vytvořena. Pokračovat?</string> - <string id="33023">Emby pro Kodi nemusí fungovat správně dokud nebude databáze resetována.</string> - <string id="33024">Ruším synchronizaci databáze. Tato verze Kodi není podporována.</string> - <string id="33025">dokončeno za:</string> - <string id="33026">Porovnávám filmy z:</string> - <string id="33027">Porovnávám kolekce</string> - <string id="33028">Porovnávám videoklipy z:</string> - <string id="33029">Porovnávám seriály z:</string> - <string id="33030">Porovnávám epizody z:</string> - <string id="33031">Porovnávám:</string> - <string id="33032">Nepodařilo se vygenerovat nový identifikátor zařízení. Pro více informací se podívejte do záznamu.</string> - <string id="33033">Byl vygenerován nový identifikátor. Kodi se nyní restartuje.</string> - - <string id="33034">Použít následující server?</string> - <string id="33035">POZOR! Pokud zvolíte "Nativní" režim, přijdete o některé funkce Emby, jako např. režim Emby cinema, nastavení přímého přehrávání a překódování a rodičovská nastavení.</string> - <string id="33036">Doplněk (výchozí)</string> - <string id="33037">Nativní (přímé cesty)</string> - <string id="33038">Přidat síťové přihlašovací údaje pro přístup Kodi k vašemu obsahu? Důležité: Pro použití údajů musí být Kodi restartováno. Údaje můžete zadat i později.</string> - <string id="33039">Zakázat hudební knihovnu Emby?</string> - <string id="33040">Přímo přehrávat hudební knihovnu? Zvolte tuto položku pokud bude k hudební knihovně přistupováno vzdáleně.</string> - <string id="33041">Smazat soubor(y) z Emby serveru? Tímto úplně smažete soubor(y) z disku!</string> - <string id="33042">Spuštění procesu ukládání do mezipaměti bude nějakou chvílit trvat. Pokračovat?</string> - <string id="33043">Synchronizace mezipaměti obrázků</string> - <string id="33044">Resetovat stávající mezipaměť obrázků?</string> - <string id="33045">Aktualizuji mezipaměť obrázků:</string> - <string id="33046">Čekám na ukončení všech vláken:</string> - <string id="33047">Kodi nemůže najít soubor:</string> - <string id="33048">Zkontrolujte přihlašovací údaje k síti v nastavení doplňku, nebo použijte nahrazování cest (Nástěnka Emby > knihovna). Zastavit synchronizaci?</string> - <string id="33049">Přidáno:</string> - <string id="33050">Pokud přihlášení neuspěje mockrát, může Emby server zablokovat váš účet. Přesto pokračovat?</string> - <string id="33051">Živé televizní vysílání (experimentální)</string> - <string id="33052">Nahrávky televizního vysílání (experimentální)</string> - <string id="33053">Nastavení</string> - <string id="33054">Přidat uživatele k relaci</string> - <string id="33055">Obnovit playlisty/video uzly Emby</string> - <string id="33056">Provést ruční synchronizaci</string> - <string id="33057">Opravit místní databázi (vynutit aktualizaci všeho obsahu)</string> - <string id="33058">Provést reset místní databáze</string> - <string id="33059">Uložit všechny obrázky do mezipaměti</string> - <string id="33060">Synchronizovat tématickou hudbu do Kodi</string> - <string id="33061">Přidat/odebrat uživatele do relace</string> - <string id="33062">Přidat uživatele</string> - <string id="33063">Odebrat uživatele</string> - <string id="33064">Odebrat uživatele z relace</string> - <string id="33065">Úspěch!</string> - <string id="33066">Odstraněno z relace:</string> - <string id="33067">Přidáno do relace:</string> - <string id="33068">Nemohu přidat/odebrat uživatele do relace.</string> - <string id="33069">Úkol dokončen</string> - <string id="33070">Úkol neuspěl</string> - <string id="33071">Přímé přehrávání</string> - <string id="33072">Metoda přehrávání pro tématickou hudbu</string> - <string id="33073">Soubor nastavení TV Tunes neexistuje. Zkontrolujte nastavení a opakujte úkol.</string> - <string id="33074">Opravdu chcete resetovat místní databázi Kodi?</string> - <string id="33075">Změnit/odstranit síťové přihlašovací údaje</string> - <string id="33076">Změnit</string> - <string id="33077">Odstranit</string> - <string id="33078">Odstraněno:</string> - <string id="33079">Zadejte síťové uživ. jméno</string> - <string id="33080">Zadejte síťové heslo</string> - <string id="33081">Přidány síťové přihlašovací údaje pro:</string> - <string id="33082">Zadejte název serveru, nebo IP adresu podle vašich cest ke knihovnám Emby. Např. název serveru: \\\\SERVER-PC\\cesta\\ je "SERVER-PC"</string> - <string id="33083">Změnit název serveru, nebo IP adresu</string> - <string id="33084">Zadejte název serveru nebo IP adresu</string> - <string id="33085">Nemohu provést reset databáze. Zkuste to znovu.</string> - <string id="33086">Odstranit všechny obrázky v mezipaměti?</string> - <string id="33087">Resetovat všechna nastavení doplňku Emby?</string> - <string id="33088">Reset databáze dokončen, pro provedení změn je potřeba restartovat Kodi.</string> - <string id="33089">Zadejte složku pro zálohy</string> - <string id="33090">Nahradit existující zálohu?</string> - <string id="33091">Vytvořit zálohu v:</string> - <string id="33092">Vytvořit zálohu</string> - <string id="33093">Složka zálohy</string> - <string id="33094">Zvolte typ obsahu k opravě</string> - <string id="33095">Nepovedlo se získat poslední položky pomocí rychlé synchronizace, bude použita plná synchronizace.</string> - -</strings> \ No newline at end of file diff --git a/resources/language/Dutch/strings.xml b/resources/language/Dutch/strings.xml deleted file mode 100644 index ff48b16f..00000000 --- a/resources/language/Dutch/strings.xml +++ /dev/null @@ -1,350 +0,0 @@ -<?xml version="1.0" encoding="utf-8" standalone="yes"?> -<strings> - <!-- Add-on settings --> - <string id="29999">Emby voor Kodi</string> - <string id="30000">Primaire server adres</string> - <string id="30002">Afspelen vanaf HTTP in plaats van SMB</string> - <string id="30004">Log niveau</string> - <string id="30016">Apparaatnaam</string> - <string id="30022">Geavanceerd</string> - <string id="30024">Gebruikersnaam</string> - <string id="30030">Poortnummer</string> - <string id="30035">Aantal te tonen recente Music Albums:</string> - <string id="30036">Aantal te tonen recente Films:</string> - <string id="30037">Aantal te tonen recente TV-series:</string> - <string id="30042">Vernieuwen</string> - <string id="30043">Wissen</string> - <string id="30044">Ongeldige Gebruikersnaam/Wachtwoord</string> - <string id="30045">Gebruikersnaam niet gevonden</string> - <string id="30052">Wissen...</string> - <string id="30053">Wacht op server voor wissen</string> - <string id="30068">Sorteer op</string> - <string id="30069">Geen</string> - <string id="30070">Actie</string> - <string id="30071">Avontuur</string> - <string id="30072">Animatie</string> - <string id="30073">Misdaad</string> - <string id="30074">Comedy</string> - <string id="30075">Documentaire</string> - <string id="30076">Drama</string> - <string id="30077">Fantasie</string> - <string id="30078">Vreemdtalig</string> - <string id="30079">Geschiedenis</string> - <string id="30080">Horror</string> - <string id="30081">Muziek</string> - <string id="30082">Musical</string> - <string id="30083">Mysterie</string> - <string id="30084">Romantiek</string> - <string id="30085">Sciencefiction</string> - <string id="30086">Kort</string> - <string id="30087">Spanning</string> - <string id="30088">Thriller</string> - <string id="30089">Western</string> - <string id="30090">Genre Filter</string> - <string id="30091">Bevestig bestandsverwijdering</string> - <!-- Verified --> - <string id="30093">Markeer als bekeken</string> - <string id="30094">Markeer als onbekeken</string> - <string id="30097">Sorteer op</string> - <string id="30098">Sorteer oplopend</string> - <string id="30099">Sorteer aflopend</string> - <!-- resume dialog --> - <string id="30105">Hervatten</string> - <string id="30106">Hervatten vanaf</string> - <string id="30107">Start vanaf begin</string> - <string id="30114">Verwijderen na afspelen aanbieden</string> - <!-- Verified --> - <string id="30115">Voor Afleveringen</string> - <!-- Verified --> - <string id="30116">Voor Films</string> - <!-- Verified --> - <string id="30118">Hervat-percentage toevoegen</string> - <string id="30119">Afleveringnummer toevoegen</string> - <string id="30120">Toon voortgang</string> - <string id="30121">Inhoud laden</string> - <string id="30122">Data ontvangen</string> - <string id="30125">Gereed</string> - <string id="30132">Waarschuwing</string> - <!-- Verified --> - <string id="30135">Fout</string> - <string id="30138">Zoeken</string> - <string id="30157">Activeer speciale afbeeldingen (bijv. CoverArt)</string> - <!-- Verified --> - <string id="30158">Metadata</string> - <string id="30159">Afbeeldingen</string> - <string id="30160">Video kwaliteit</string> - <!-- Verified --> - <string id="30165">Direct Afspelen</string> - <!-- Verified --> - <string id="30166">Transcoderen</string> - <string id="30167">Server Detectie Geslaagd</string> - <string id="30168">Gevonden server</string> - <string id="30169">Adres:</string> - <!-- Video nodes --> - <string id="30170">Onlangs toegevoegde TV-series</string> - <!-- Verified --> - <string id="30171">Niet afgekeken TV-series</string> - <!-- Verified --> - <string id="30172">Alle Muziek</string> - <string id="30173">Kanalen</string> - <!-- Verified --> - <string id="30174">Onlangs toegevoegde films</string> - <!-- Verified --> - <string id="30175">Onlangs toegevoegde afleveringen</string> - <!-- Verified --> - <string id="30176">Onlangs toegevoegde albums</string> - <string id="30177">Niet afgekeken films</string> - <!-- Verified --> - <string id="30178">Niet afgekeken afleveringen</string> - <!-- Verified --> - <string id="30179">Volgende (NextUp) afleveringen</string> - <!-- Verified --> - <string id="30180">Favoriete films</string> - <!-- Verified --> - <string id="30181">Favoriete TV-series</string> - <!-- Verified --> - <string id="30182">Favoriete afleveringen</string> - <string id="30183">Vaak afgespeelde albums</string> - <string id="30184">Binnenkort op TV</string> - <string id="30185">BoxSets</string> - <string id="30186">Trailers</string> - <string id="30187">Muziek video's</string> - <string id="30188">Foto's</string> - <string id="30189">Onbekeken Films</string> - <!-- Verified --> - <string id="30190">Film Genres</string> - <string id="30191">Film Studio's</string> - <string id="30192">Film Acteurs</string> - <string id="30193">Onbekeken Afleveringen</string> - <string id="30194">TV Genres</string> - <string id="30195">TV Studio's</string> - <string id="30196">TV Acteurs</string> - <string id="30197">Afspeellijsten</string> - <string id="30199">Weergaven instellen</string> - <string id="30200">Selecteer gebruiker</string> - <!-- Verified --> - <string id="30204">Kan niet verbinden met server</string> - <string id="30207">Titels</string> - <string id="30208">Albums</string> - <string id="30209">Album artiesten</string> - <string id="30210">Artiesten</string> - <string id="30211">Muziek Genres</string> - <string id="30220">Laatste</string> - <string id="30221">Bezig</string> - <string id="30222">Volgende (NextUp)</string> - <string id="30223">Gebruikersweergaven</string> - <string id="30224">Rapporteer Metrics</string> - <string id="30227">Willekeurige Films</string> - <string id="30228">Willekeurige Afleveringen</string> - <string id="30229">Willekeurige Items</string> - <!-- Verified --> - <string id="30230">Aanbevolen Items</string> - <!-- Verified --> - <string id="30235">Extra's</string> - <!-- Verified --> - <string id="30236">Synchroniseer Herkenningsmelodie</string> - <string id="30237">Synchroniseer Extra Fanart</string> - <string id="30238">Synchroniseer Film Boxsets</string> - <string id="30239">Lokale Kodi database opnieuw instellen</string> - <!-- Verified --> - <string id="30243">Activeer HTTPS</string> - <!-- Verified --> - <string id="30245">Forceer Transcoderen van Codecs</string> - <string id="30249">Activeer server verbindings melding bij het opstarten</string> - <!-- Verified --> - <string id="30251">Onlangs bekeken Thuis Video's</string> - <!-- Verified --> - <string id="30252">Onlangs toegevoegde Foto's</string> - <!-- Verified --> - <string id="30253">Favoriete Home Video's</string> - <!-- Verified --> - <string id="30254">Favoriete Foto's</string> - <!-- Verified --> - <string id="30255">Favoriete Albums</string> - <string id="30256">Onlangs toegevoegde Muziek Video's</string> - <!-- Verified --> - <string id="30257">Niet afgekeken Muziek Video's</string> - <!-- Verified --> - <string id="30258">Onbekeken Muziek Video's</string> - <!-- Verified --> - <!-- Default views --> - <string id="30300">Actief</string> - <string id="30301">Herstel naar standaard</string> - <string id="30302">Films</string> - <string id="30303">BoxSets</string> - <string id="30304">Trailers</string> - <string id="30305">TV-series</string> - <string id="30306">Seizoenen</string> - <string id="30307">Afleveringen</string> - <string id="30308">Muziek - artiesten</string> - <string id="30309">Muziek - albums</string> - <string id="30310">Muziek Video's</string> - <string id="30311">Muziek - nummers</string> - <string id="30312">Kanalen</string> - <!-- contextmenu --> - <string id="30401">Emby opties</string> - <string id="30405">Toevoegen aan Emby favorieten</string> - <string id="30406">Verwijderen uit Emby favorieten</string> - <string id="30407">Instellen aangepaste waardering titels</string> - <string id="30408">Emby addon instellingen</string> - <string id="30409">Verwijder item van de server</string> - <string id="30410">Dit item vernieuwen</string> - <string id="30411">Instellen aangepaste titel waardering (0-5)</string> - <!-- add-on settings --> - <string id="30500">Controleer Host SSL Certificaat</string> - <string id="30501">Client SSL Certificaat</string> - <string id="30502">Gebruik alternatief adres</string> - <string id="30503">Alternatief Serveradres</string> - <string id="30504">Gebruik alternatieve apparaatnaam</string> - <string id="30505">[COLOR yellow]Probeer opnieuw in te loggen[/COLOR]</string> - <string id="30506">Synchronisatie</string> - <string id="30507">Toon voortgang indien meer items dan</string> - <string id="30508">Synchroniseer lege TV-series</string> - <string id="30509">Activeer Muziekbibliotheek</string> - <string id="30510">Direct Stream muziekbibliotheek</string> - <string id="30511">Afspeelmodus</string> - <string id="30512">Forceer afbeelding caching</string> - <string id="30513">Limiteer afbeelding cache threads (aanbevolen voor rpi)</string> - <string id="30514">Activeer [COLOR yellow]Fast Startup[/COLOR] (vereist server plugin)</string> - <string id="30515">Maximaal aantal ineens van de server op te vragen items</string> - <string id="30516">Afspelen</string> - <string id="30517">Netwerkreferenties</string> - <string id="30518">Activeer Emby bioscoopmodus</string> - <string id="30519">Vragen om trailers af te spelen</string> - <string id="30520">Wis bevestiging in contextmenu overslaan (Eigen risico!)</string> - <string id="30521">Spring terug op hervatten (in seconden)</string> - <string id="30522">Forceer transcoderen H265</string> - <string id="30523">Muziek metadata opties (niet compatibel met direct stream)</string> - <string id="30524">Importeer muziek titel waardering direct uit bestanden</string> - <string id="30525">Converteer muziek titel waardering naar Emby waardering</string> - <string id="30526">Toestaan dat waardeing in muziekbestanden worden bijgewerkt</string> - <string id="30527">Negeer specials in de volgende afleveringen</string> - <string id="30528">Gebruikers permanent toevoegen aan de sessie</string> - <string id="30529">Opstart vertraging (in seconden)</string> - <string id="30530">Activeer server herstart bericht</string> - <string id="30531">Activeer nieuwe inhoud notificatie</string> - <string id="30532">Duur van de videobibliotheek pop-up (in seconden)</string> - <string id="30533">Duur van de muziekbibliotheek pop-up (in seconden)</string> - <string id="30534">Server berichten</string> - <string id="30535">Genereer een nieuw apparaat Id</string> - <string id="30536">Sync als screensaver is uitgeschakeld</string> - <string id="30537">Forceer Transcoderen Hi10P</string> - <string id="30538">Uitgeschakeld</string> - <string id="30539">Inloggen</string> - <string id="30540">Handmatig inloggen</string> - <string id="30541">Emby Connect</string> - <string id="30542">Server</string> - <string id="30543">Gebruikersnaam of e-mail</string> - <string id="30544">Activeer database Vergrendeld fix (synchronisatie proces zal vertragen)</string> - <string id="30545">Activeer server offline bericht</string> - <!-- dialogs --> - <string id="30600">Meld je aan met Emby Connect</string> - <string id="30602">Wachtwoord</string> - <string id="30603">Zie onze gebruiksvoorwaarden. Het gebruik van alle Emby software betekent acceptatie van deze voorwaarden.</string> - <string id="30604">Scan mij</string> - <string id="30605">Aanmelden</string> - <string id="30606">Annuleren</string> - <string id="30607">Hoofdserver selecteren</string> - <string id="30608">Gebruikersnaam of wachtwoord mag niet leeg zijn</string> - <string id="30609">Kan geen verbinding maken met de geselecteerde server</string> - <string id="30610">Verbinding maken met</string> - <!-- Connect to {server} --> - <string id="30611">Handmatig server toevoegen</string> - <string id="30612">Meld u aub aan</string> - <string id="30613">Gebruikersnaam mag niet leeg zijn</string> - <string id="30614">Verbinding maken met server</string> - <string id="30615">Host</string> - <string id="30616">Verbinden</string> - <string id="30617">Server of poort kan niet leeg zijn</string> - <string id="30618">Wissel van Emby Connect gebruiker</string> - <!-- service add-on --> - <string id="33000">Welkom</string> - <string id="33001">Fout bij maken van verbinding</string> - <string id="33002">De server is onbereikbaar</string> - <string id="33003">Server is online</string> - <string id="33004">items toegevoegd aan afspeellijst</string> - <string id="33005">items in de wachtrij voor afspeellijst</string> - <string id="33006">Server is opnieuw aan het opstarten</string> - <string id="33007">Toegang is ingeschakeld</string> - <string id="33008">Voer het wachtwoord in voor de gebruiker:</string> - <string id="33009">Foutieve gebruikersnaam of wachtwoord</string> - <string id="33010">Te vaak mislukt te authenticeren</string> - <string id="33011">Niet in staat om direct af te spelen</string> - <string id="33012">Direct afspelen is 3x mislukt. Afspelen vanaf HTTP Ingeschakeld.</string> - <string id="33013">Kies het audiokanaal</string> - <string id="33014">Kies de ondertiteling</string> - <string id="33015">Bestand van uw Emby server verwijderen?</string> - <string id="33016">Trailers afspelen?</string> - <string id="33017">Films verzamelen van:</string> - <string id="33018">Boxsets verzamelen</string> - <string id="33019">Muziek-video's verzamelen van:</string> - <string id="33020">TV-series verzamelen van:</string> - <string id="33021">Verzamelen:</string> - <string id="33022">Gedetecteerd dat de database moet worden vernieuwd voor deze versie van Emby voor Kodi. Doorgaan?</string> - <string id="33023">Emby voor Kodi kan mogelijk niet correct werken tot de database is teruggezet.</string> - <string id="33024">Database synchronisatie proces geannuleerd. De huidige Kodi versie wordt niet ondersteund.</string> - <string id="33025">voltooid in:</string> - <string id="33026">Films vergelijken met:</string> - <string id="33027">Boxsets vergelijken met</string> - <string id="33028">Muziek-video's vergelijken met:</string> - <string id="33029">TV-series vergelijken met:</string> - <string id="33030">Afleveringen vergelijken met:</string> - <string id="33031">Vergelijken:</string> - <string id="33032">Nieuw apparaat Id genereren mislukt. Zie je logs voor meer informatie.</string> - <string id="33033">Nieuw apparaat Id gegenereerd. Kodi zal nu opnieuw opstarten.</string> - <string id="33034">Verder gaan met de volgende server?</string> - <string id="33035">LET OP! Als u de Native modus kiest, zullen bepaalde Emby functies ontbreken, zoals: Emby bioscoop-modus, directe afspeel/transcodeer opties en de ouderlijk-toezicht planner.</string> - <string id="33036">Addon (Standaard)</string> - <string id="33037">Native (Directe paden)</string> - <string id="33038">Voeg netwerkreferenties toe aan Kodi om toegang tot uw inhoud toe te staan? Belangrijk: Kodi moet opnieuw worden opgestart om de referenties te zien. Zij kunnen ook later worden toegevoegd.</string> - <string id="33039">De-activeer Emby muziekbibliotheek?</string> - <string id="33040">Direct Stream de muziekbibliotheek? Selecteer deze optie als de muziekbibliotheek op afstand worden benaderd.</string> - <string id="33041">Bestand(en) van de Emby Server verwijderen? Dit zal de bestanden ook van de schijf verwijderen!</string> - <string id="33042">Het uitvoeren van de caching proces kan enige tijd duren. Toch verder gaan?</string> - <string id="33043">Afbeeldingen cache sync</string> - <string id="33044">Herstel bestaande afbeeldingen cache?</string> - <string id="33045">Bijwerken afbeeldingen cache:</string> - <string id="33046">Wachten op alle taken om af te sluiten:</string> - <string id="33047">Kodi kan bestand niet vinden:</string> - <string id="33048">Het kan nodig zijn om uw netwerkreferenties te controleren in de add-on-instellingen of gebruik de Emby padvervanging om je pad correct op te geven (Emby dashboard> Bibliotheek). Stop synchroniseren?</string> - <string id="33049">Toegevoegd:</string> - <string id="33050">Wanneer u zich te vaak foutief aanmeld, zal de Emby Server uw account blokkeren. Toch doorgaan?</string> - <string id="33051">Live TV-kanalen (experimenteel)</string> - <string id="33052">Live TV-opnames (experimenteel)</string> - <string id="33053">Instellingen</string> - <string id="33054">Gebruiker toevoegen aan sessie</string> - <string id="33055">Vernieuw Emby afspeellijsen/Video knooppunten</string> - <string id="33056">Handmatig synchroniseren</string> - <string id="33057">Reparatie lokale database (forceer-bijwerken van alle inhoud)</string> - <string id="33058">Lokale database herstellen</string> - <string id="33059">Cache alle afbeeldingen</string> - <string id="33060">Sync herkenningsmelodie/leaders van Emby naar Kodi</string> - <string id="33061">Gebruiker toevoegen/verwijderen van de sessie</string> - <string id="33062">Gebruiker toevoegen</string> - <string id="33063">Gebruiker verwijderen</string> - <string id="33064">Gebruiker verwijderen van de sessie</string> - <string id="33065">Geslaagd!</string> - <string id="33066">Verwijderd uit de bekijken sessie:</string> - <string id="33067">Toegevoegd aan de bekijken sessie:</string> - <string id="33068">Niet in staat om gebruiker toe te voegen/verwijderen uit de sessie.</string> - <string id="33069">De taak is geslaagd</string> - <string id="33070">De taak is mislukt</string> - <string id="33071">Direct Stream</string> - <string id="33072">Afspeel methode voor uw herkenningsmelodie/leaders</string> - <string id="33073">Het instellingenbestand bestaat niet in TV-Tunes. Wijzig een instelling en voer de taak opnieuw uit.</string> - <string id="33074">Weet u zeker dat u uw lokale Kodi database opnieuw in wilt stellen?</string> - <string id="33075">Wijzig/verwijder netwerkreferenties</string> - <string id="33076">Wijzig</string> - <string id="33077">Verwijder</string> - <string id="33078">Verwijderd:</string> - <string id="33079">Geef netwerk gebruikersnaam op:</string> - <string id="33080">Geeft netwerk wachtwoord op:</string> - <string id="33081">Netwerkreferenties toegevoegd voor:</string> - <string id="33082">Voer de servernaam of het IP-adres in, zoals aangegeven in uw Emby bibliotheek paden. Bijvoorbeeld, de naam van de server: \\\\SERVER-PC\\pad\\ is \"SERVER-PC\"</string> - <string id="33083">Wijzig de servernaam of IP-adres</string> - <string id="33084">Geef de servernaam of het IP-adres op</string> - <string id="33085">Kon de database niet opnieuw instellen. Probeer het nog eens.</string> - <string id="33086">Verwijder al gecachte afbeeldingen?</string> - <string id="33087">Alle Emby add-on-instellingen opnieuw instellen?</string> - <string id="33088">Database opnieuw instellen is voltooid, Kodi zal nu opnieuw opstarten om de wijzigingen toe te passen.</string> -</strings> diff --git a/resources/language/English/strings.xml b/resources/language/English/strings.xml deleted file mode 100644 index d3fef2e8..00000000 --- a/resources/language/English/strings.xml +++ /dev/null @@ -1,361 +0,0 @@ -<?xml version="1.0" encoding="utf-8" standalone="yes"?> -<strings> - - <!-- Add-on settings --> - <string id="29999">Emby for Kodi</string> - <string id="30000">Server address</string> - <string id="30001">Server name</string> - <string id="30002">Force HTTP playback</string> - <string id="30004">Log level</string> - <string id="30016">Device Name</string> - <string id="30022">Advanced</string> - <string id="30024">Username</string> - <string id="30030">Port Number</string> - - <string id="30035">Number of recent Music Albums to show:</string> - <string id="30036">Number of recent Movies to show:</string> - <string id="30037">Number of recent TV episodes to show:</string> - - <string id="30042">Refresh</string> - <string id="30043">Delete</string> - <string id="30044">Incorrect Username/Password</string> - <string id="30045">Username not found</string> - <string id="30052">Deleting</string> - <string id="30053">Waiting for server to delete</string> - - <string id="30068">Sort By</string> - <string id="30069">None</string> - <string id="30070">Action</string> - <string id="30071">Adventure</string> - <string id="30072">Animation</string> - <string id="30073">Crime</string> - <string id="30074">Comedy</string> - <string id="30075">Documentary</string> - <string id="30076">Drama</string> - <string id="30077">Fantasy</string> - <string id="30078">Foreign</string> - <string id="30079">History</string> - <string id="30080">Horror</string> - <string id="30081">Music</string> - <string id="30082">Musical</string> - <string id="30083">Mystery</string> - <string id="30084">Romance</string> - <string id="30085">Science Fiction</string> - <string id="30086">Short</string> - <string id="30087">Suspense</string> - <string id="30088">Thriller</string> - <string id="30089">Western</string> - - <string id="30090">Genre Filter</string> - <string id="30091">Confirm file deletion</string><!-- Verified --> - - <string id="30093">Mark watched</string> - <string id="30094">Mark unwatched</string> - - <string id="30097">Sort by</string> - <string id="30098">Sort Order Descending</string> - <string id="30099">Sort Order Ascending</string> - - <!-- resume dialog --> - <string id="30105">Resume</string> - <string id="30106">Resume from</string> - <string id="30107">Start from beginning</string> - - <string id="30114">Offer delete after playback</string><!-- Verified --> - <string id="30115">For Episodes</string><!-- Verified --> - <string id="30116">For Movies</string><!-- Verified --> - - <string id="30118">Add Resume Percent</string> - <string id="30119">Add Episode Number</string> - <string id="30120">Show Load Progress</string> - <string id="30121">Loading Content</string> - <string id="30122">Retrieving Data</string> - - <string id="30125">Done</string> - <string id="30132">Warning</string><!-- Verified --> - <string id="30135">Error</string> - <string id="30138">Search</string> - - <string id="30157">Enable Enhanced Images (eg CoverArt)</string><!-- Verified --> - <string id="30158">Metadata</string> - <string id="30159">Artwork</string> - <string id="30160">Video Quality</string><!-- Verified --> - - <string id="30165">Direct Play</string><!-- Verified --> - <string id="30166">Transcoding</string> - <string id="30167">Server Detection Succeeded</string> - <string id="30168">Found server</string> - <string id="30169">Address:</string> - - <!-- Video nodes --> - <string id="30170">Recently Added TV Shows</string><!-- Verified --> - <string id="30171">In Progress TV Shows</string><!-- Verified --> - <string id="30172">All Music</string> - <string id="30173">Channels</string><!-- Verified --> - <string id="30174">Recently Added Movies</string><!-- Verified --> - <string id="30175">Recently Added Episodes</string><!-- Verified --> - <string id="30176">Recently Added Albums</string> - <string id="30177">In Progress Movies</string><!-- Verified --> - <string id="30178">In Progress Episodes</string><!-- Verified --> - <string id="30179">Next Episodes</string><!-- Verified --> - <string id="30180">Favorite Movies</string><!-- Verified --> - <string id="30181">Favorite Shows</string><!-- Verified --> - <string id="30182">Favorite Episodes</string> - <string id="30183">Frequent Played Albums</string> - <string id="30184">Upcoming TV</string> - <string id="30185">BoxSets</string> - <string id="30186">Trailers</string> - <string id="30187">Music Videos</string> - <string id="30188">Photos</string> - <string id="30189">Unwatched Movies</string><!-- Verified --> - <string id="30190">Movie Genres</string> - <string id="30191">Movie Studios</string> - <string id="30192">Movie Actors</string> - <string id="30193">Unwatched Episodes</string> - <string id="30194">TV Genres</string> - <string id="30195">TV Networks</string> - <string id="30196">TV Actors</string> - <string id="30197">Playlists</string> - - <string id="30199">Set Views</string> - <string id="30200">Select User</string><!-- Verified --> - <string id="30204">Unable to connect to server</string> - - <string id="30207">Songs</string> - <string id="30208">Albums</string> - <string id="30209">Album Artists</string> - <string id="30210">Artists</string> - <string id="30211">Music Genres</string> - - <string id="30220">Latest</string> - <string id="30221">In Progress</string> - <string id="30222">NextUp</string> - <string id="30223">User Views</string> - <string id="30224">Report Metrics</string> - - <string id="30227">Random Movies</string> - <string id="30228">Random Episodes</string> - <string id="30229">Random Items</string><!-- Verified --> - <string id="30230">Recommended Items</string><!-- Verified --> - - <string id="30235">Extras</string><!-- Verified --> - <string id="30236">Sync Theme Music</string> - <string id="30237">Sync Extra Fanart</string> - <string id="30238">Sync Movie BoxSets</string> - - <string id="30239">Reset local Kodi database</string><!-- Verified --> - <string id="30243">Enable HTTPS</string><!-- Verified --> - <string id="30245">Force Transcoding Codecs</string> - - <string id="30249">Enable server connection message on startup</string><!-- Verified --> - - <string id="30251">Recently added Home Videos</string><!-- Verified --> - <string id="30252">Recently added Photos</string><!-- Verified --> - <string id="30253">Favourite Home Videos</string><!-- Verified --> - <string id="30254">Favourite Photos</string><!-- Verified --> - <string id="30255">Favourite Albums</string> - - <string id="30256">Recently added Music videos</string><!-- Verified --> - <string id="30257">In progress Music videos</string><!-- Verified --> - <string id="30258">Unwatched Music videos</string><!-- Verified --> - - <!-- Default views --> - <string id="30300">Active</string> - <string id="30301">Clear Settings</string> - <string id="30302">Movies</string> - <string id="30303">BoxSets</string> - <string id="30304">Trailers</string> - <string id="30305">Series</string> - <string id="30306">Seasons</string> - <string id="30307">Episodes</string> - <string id="30308">Music Artists</string> - <string id="30309">Music Albums</string> - <string id="30310">Music Videos</string> - <string id="30311">Music Tracks</string> - <string id="30312">Channels</string> - - <!-- contextmenu --> - <string id="30401">Emby Options</string> - <string id="30402">Emby Force transcode</string> - <string id="30405">Add to Emby favorites</string> - <string id="30406">Remove from Emby favorites</string> - <string id="30407">Set custom song rating</string> - <string id="30408">Emby addon settings</string> - <string id="30409">Delete item from the server</string> - <string id="30410">Refresh this item</string> - <string id="30411">Set custom song rating (0-5)</string> - <string id="30412">Transcode</string> - - <!-- add-on settings --> - <string id="30500">Verify Host SSL Certificate</string> - <string id="30501">Client SSL certificate</string> - <string id="30502">Use alternate address</string> - <string id="30503">Alternate Server Address</string> - <string id="30504">Use altername device name</string> - <string id="30505">[COLOR yellow]Retry login[/COLOR]</string> - <string id="30506">Sync Options</string> - <string id="30507">Show progress if item count greater than</string> - <string id="30508">Sync empty TV Shows</string> - <string id="30509">Enable Music Library</string> - <string id="30510">Direct stream music library</string> - <string id="30511">Playback Mode</string> - <string id="30512">Force artwork caching</string> - <string id="30513">Limit artwork cache threads (recommended for rpi)</string> - <string id="30514">Enable fast startup (requires server plugin)</string> - <string id="30515">Maximum items to request from the server per download thread</string> - <string id="30516">Video Playback</string> - <string id="30517">Network credentials</string> - <string id="30518">Enable Emby cinema mode</string> - <string id="30519">Ask to play trailers</string> - <string id="30520">Skip Emby delete confirmation (use at your own risk)</string> - <string id="30521">Jump back on resume (in seconds)</string> - <string id="30522">Force transcode H265</string> - <string id="30523">Music metadata options (not compatible with direct stream)</string> - <string id="30524">Import music song rating directly from files</string> - <string id="30525">Convert music song rating to Emby rating</string> - <string id="30526">Allow rating in song files to be updated</string> - <string id="30527">Ignore specials in next episodes</string> - <string id="30528">Permanent users to add to the session</string> - <string id="30529">Startup delay (in seconds)</string> - <string id="30530">Enable server restart message</string> - <string id="30531">Enable new content notification</string> - <string id="30532">Duration of the video library pop up (in seconds)</string> - <string id="30533">Duration of the music library pop up (in seconds)</string> - <string id="30534">Server messages</string> - <string id="30535">Generate a new device Id</string> - <string id="30536">Sync when screensaver is deactivated</string> - <string id="30537">Force Transcode Hi10P</string> - <string id="30538">Disabled</string> - <string id="30539">Login</string> - <string id="30540">Manual login</string> - <string id="30541">Emby Connect</string> - <string id="30542">Server</string> - <string id="30543">Username or email</string> - <string id="30544">Enable database locked fix (will slow syncing process)</string> - <string id="30545">Enable server offline message</string> - <string id="30546">Enable analytic metric logging</string> - <string id="30547">Display message (in seconds)</string> - <string id="30548">Download threads (recommended: 2-3)</string> - - <!-- dialogs --> - <string id="30600">Sign in with Emby Connect</string> - <string id="30602">Password</string> - <string id="30603">Please see our terms of use. The use of any Emby software constitutes acceptance of these terms.</string> - <string id="30604">Scan me</string> - <string id="30605">Sign in</string> - <string id="30606">Cancel</string> - <string id="30607">Select main server</string> - <string id="30608">Username or password cannot be empty</string> - <string id="30609">Unable to connect to the selected server</string> - <string id="30610">Connect to</string><!-- Connect to {server} --> - <string id="30611">Manually add server</string> - <string id="30612">Please sign in</string> - <string id="30613">Username cannot be empty</string> - <string id="30614">Connect to server</string> - <string id="30615">Host</string> - <string id="30616">Connect</string> - <string id="30617">Server or port cannot be empty</string> - <string id="30618">Change Emby Connect user</string> - - <!-- service add-on --> - <string id="33000">Welcome</string> - <string id="33001">Error connecting</string> - <string id="33002">Server is unreachable</string> - <string id="33003">Server is online</string> - <string id="33004">items added to playlist</string> - <string id="33005">items queued to playlist</string> - <string id="33006">Server is restarting</string> - <string id="33007">Access is enabled</string> - <string id="33008">Enter password for user:</string> - <string id="33009">Invalid username or password</string> - <string id="33010">Failed to authenticate too many times</string> - <string id="33011">Unable to direct play file</string> - <string id="33012">Direct play failed 3 times. Enabled play from HTTP.</string> - <string id="33013">Choose the audio stream</string> - <string id="33014">Choose the subtitles stream</string> - <string id="33015">Delete file from your Emby server?</string> - <string id="33016">Play trailers?</string> - <string id="33017">Gathering movies from:</string> - <string id="33018">Gathering boxsets</string> - <string id="33019">Gathering music videos from:</string> - <string id="33020">Gathering tv shows from:</string> - <string id="33021">Gathering:</string> - <string id="33022">Detected the database needs to be recreated for this version of Emby for Kodi. Proceed?</string> - <string id="33023">Emby for Kodi may not work correctly until the database is reset.</string> - <string id="33024">Cancelling the database syncing process. The current Kodi version is unsupported.</string> - <string id="33025">completed in:</string> - <string id="33026">Comparing movies from:</string> - <string id="33027">Comparing boxsets</string> - <string id="33028">Comparing music videos from:</string> - <string id="33029">Comparing tv shows from:</string> - <string id="33030">Comparing episodes from:</string> - <string id="33031">Comparing:</string> - <string id="33032">Failed to generate a new device Id. See your logs for more information.</string> - <string id="33033">A new device Id has been generated. Kodi will now restart.</string> - - <string id="33034">Proceed with the following server?</string> - <string id="33035">Caution! If you choose Native mode, certain Emby features will be missing, such as: Emby cinema mode, direct stream/transcode options and parental access schedule.</string> - <string id="33036">Addon (Default)</string> - <string id="33037">Native (Direct Paths)</string> - <string id="33038">Add network credentials to allow Kodi access to your content? Important: Kodi will need to be restarted to see the credentials. They can also be added at a later time.</string> - <string id="33039">Disable Emby music library?</string> - <string id="33040">Direct stream the music library? Select this option if the music library will be remotely accessed.</string> - <string id="33041">Delete file(s) from Emby Server? This will also delete the file(s) from disk!</string> - <string id="33042">Running the caching process may take some time. Continue anyway?</string> - <string id="33043">Artwork cache sync</string> - <string id="33044">Reset existing artwork cache?</string> - <string id="33045">Updating artwork cache:</string> - <string id="33046">Waiting for all threads to exit:</string> - <string id="33047">Kodi can't locate file:</string> - <string id="33048">You may need to verify your network credentials in the add-on settings or use the Emby path substitution to format your path correctly (Emby dashboard > library). Stop syncing?</string> - <string id="33049">Added:</string> - <string id="33050">If you fail to log in too many times, the Emby server might lock your account. Proceed anyway?</string> - <string id="33051">Live TV Channels (experimental)</string> - <string id="33052">Live TV Recordings (experimental)</string> - <string id="33053">Settings</string> - <string id="33054">Add user to session</string> - <string id="33055">Refresh Emby playlists/Video nodes</string> - <string id="33056">Perform manual sync</string> - <string id="33057">Repair local database (force update all content)</string> - <string id="33058">Perform local database reset</string> - <string id="33059">Cache all artwork</string> - <string id="33060">Sync Emby Theme Media to Kodi</string> - <string id="33061">Add/Remove user from the session</string> - <string id="33062">Add user</string> - <string id="33063">Remove user</string> - <string id="33064">Remove user from the session</string> - <string id="33065">Success!</string> - <string id="33066">Removed from viewing session:</string> - <string id="33067">Added to viewing session:</string> - <string id="33068">Unable to add/remove user from the session.</string> - <string id="33069">The task succeeded</string> - <string id="33070">The task failed</string> - <string id="33071">Direct Stream</string> - <string id="33072">Playback method for your themes</string> - <string id="33073">The settings file does not exist in TV Tunes. Change a setting and run the task again.</string> - <string id="33074">Are you sure you want to reset your local Kodi database?</string> - <string id="33075">Modify/Remove network credentials</string> - <string id="33076">Modify</string> - <string id="33077">Remove</string> - <string id="33078">Removed:</string> - <string id="33079">Enter the network username</string> - <string id="33080">Enter the network password</string> - <string id="33081">Added network credentials for:</string> - <string id="33082">Input the server name or IP address as indicated in your emby library paths. For example, the server name: \\\\SERVER-PC\\path\\ is "SERVER-PC"</string> - <string id="33083">Modify the server name or IP address</string> - <string id="33084">Enter the server name or IP address</string> - <string id="33085">Could not reset the database. Try again.</string> - <string id="33086">Remove all cached artwork?</string> - <string id="33087">Reset all Emby add-on settings?</string> - <string id="33088">Database reset has completed, Kodi will now restart to apply the changes.</string> - <string id="33089">Enter folder name for backup</string> - <string id="33090">Replace existing backup?</string> - <string id="33091">Create backup at:</string> - <string id="33092">Create a backup</string> - <string id="33093">Backup folder</string> - <string id="33094">Select content type to repair</string> - <string id="33095">Failed to retrieve latest updates using fast sync, using full sync.</string> - <string id="33096">Limit video resolution to screen resolution</string> - <string id="33097">Important, cleanonupdate was removed in your advanced settings to prevent conflict with Emby for Kodi. Kodi will restart now.</string> - <string id="33098">Refresh boxsets</string> -</strings> \ No newline at end of file diff --git a/resources/language/French/strings.xml b/resources/language/French/strings.xml deleted file mode 100644 index cd314aaa..00000000 --- a/resources/language/French/strings.xml +++ /dev/null @@ -1,350 +0,0 @@ -<?xml version="1.0" encoding="utf-8" standalone="yes"?> -<strings> - <!-- Add-on settings --> - <string id="29999">Emby pour Kodi</string> - <string id="30000">Adresse principale du serveur</string> - <string id="30002">Lire avec HTTP à la place de SMB</string> - <string id="30004">Niveau de journalisation</string> - <string id="30016">Nom de l'appareil</string> - <string id="30022">Avancé</string> - <string id="30024">Nom d'utilisateur</string> - <string id="30030">Numéro de port</string> - <string id="30035">Nombre d'album de musique récents à afficher:</string> - <string id="30036">Nombre de films récents à afficher:</string> - <string id="30037">Nombre d'épisodes télévisés récents à afficher:</string> - <string id="30042">Actualiser</string> - <string id="30043">Supprimer</string> - <string id="30044">Nom d'utilisateur/Mot de passe incorrect</string> - <string id="30045">Nom d'utilisateur introuvable</string> - <string id="30052">Suppression</string> - <string id="30053">En attente du serveur pour la suppression</string> - <string id="30068">Trier par</string> - <string id="30069">Aucun</string> - <string id="30070">Action</string> - <string id="30071">Aventure</string> - <string id="30072">Animation</string> - <string id="30073">Crime</string> - <string id="30074">Comédie</string> - <string id="30075">Documentaire</string> - <string id="30076">Drame</string> - <string id="30077">Fantaisie</string> - <string id="30078">Étranger</string> - <string id="30079">Historique</string> - <string id="30080">Horreur</string> - <string id="30081">Musique</string> - <string id="30082">Musical</string> - <string id="30083">Mystère</string> - <string id="30084">Romance</string> - <string id="30085">Science Fiction</string> - <string id="30086">Court</string> - <string id="30087">Suspense</string> - <string id="30088">Thriller</string> - <string id="30089">Western</string> - <string id="30090">Filtre de Genre</string> - <string id="30091">Confirmer la suppression du fichier</string> - <!-- Verified --> - <string id="30093">Marquer comme lu</string> - <string id="30094">Marquer comme non vu</string> - <string id="30097">Trier par</string> - <string id="30098">Ordre de Trie décroissant</string> - <string id="30099">Ordre de Trie croissant</string> - <!-- resume dialog --> - <string id="30105">Reprendre</string> - <string id="30106">Reprendre à partir de</string> - <string id="30107">Lire depuis le début</string> - <string id="30114">Offrir la possibilité de supprimer après la lecture</string> - <!-- Verified --> - <string id="30115">Pour Épisodes</string> - <!-- Verified --> - <string id="30116">Pour Films</string> - <!-- Verified --> - <string id="30118">Ajouter un pourcentage de reprise</string> - <string id="30119">Ajouter Numéro Épisode</string> - <string id="30120">Afficher la progression du chargement</string> - <string id="30121">Chargement du contenu</string> - <string id="30122">Récupération des données</string> - <string id="30125">Fait</string> - <string id="30132">Avertissement</string> - <!-- Verified --> - <string id="30135">Erreur</string> - <string id="30138">Rechercher</string> - <string id="30157">Activer les images améliorées (eg Coverart)</string> - <!-- Verified --> - <string id="30158">Métadonnées</string> - <string id="30159">Artwork</string> - <string id="30160">Qualité vidéo</string> - <!-- Verified --> - <string id="30165">Lecture directe</string> - <!-- Verified --> - <string id="30166">Transcodage</string> - <string id="30167">Détection du serveur Réussi</string> - <string id="30168">Serveur trouvé</string> - <string id="30169">Addresse:</string> - <!-- Video nodes --> - <string id="30170">Séries TV Récemment Ajouté</string> - <!-- Verified --> - <string id="30171">Séries TV En cours</string> - <!-- Verified --> - <string id="30172">Toute la musique</string> - <string id="30173">Chaînes</string> - <!-- Verified --> - <string id="30174">Films récemment ajoutés</string> - <!-- Verified --> - <string id="30175">Épisodes récemment ajoutés</string> - <!-- Verified --> - <string id="30176">Albums récemment ajoutés</string> - <string id="30177">Films en Cours</string> - <!-- Verified --> - <string id="30178">Épisodes en Cours</string> - <!-- Verified --> - <string id="30179">Prochain Épisodes</string> - <!-- Verified --> - <string id="30180">Films Favoris</string> - <!-- Verified --> - <string id="30181">Séries Favorites</string> - <!-- Verified --> - <string id="30182">Épisodes Favoris</string> - <string id="30183">Albums fréquemment joués</string> - <string id="30184">TV à venir</string> - <string id="30185">Collections</string> - <string id="30186">Bandes-annonces</string> - <string id="30187">Vidéo Clips</string> - <string id="30188">Photos</string> - <string id="30189">Films Non vu</string> - <!-- Verified --> - <string id="30190">Film Genres</string> - <string id="30191">Film Studios</string> - <string id="30192">Film Acteurs</string> - <string id="30193">Épisodes Non vu</string> - <string id="30194">TV Genres</string> - <string id="30195">TV Réseaux</string> - <string id="30196">TV Acteurs</string> - <string id="30197">Listes de lecture</string> - <string id="30199">Définir Vues</string> - <string id="30200">Sélectionner l'utilisateur</string> - <!-- Verified --> - <string id="30204">Impossible de se connecter au serveur</string> - <string id="30207">Chansons</string> - <string id="30208">Albums</string> - <string id="30209">Artiste de l'album</string> - <string id="30210">Artistes</string> - <string id="30211">Music Genres</string> - <string id="30220">Derniers</string> - <string id="30221">En cours</string> - <string id="30222">Prochain</string> - <string id="30223">Vues de l'utilisateur</string> - <string id="30224">Rapport Metrics</string> - <string id="30227">Films aléatoire</string> - <string id="30228">Épisodes aléatoire</string> - <string id="30229">Objets aléatoire</string> - <!-- Verified --> - <string id="30230">Élements recommandés</string> - <!-- Verified --> - <string id="30235">Extras</string> - <!-- Verified --> - <string id="30236">Sync Thème Musique</string> - <string id="30237">Sync Extra Fanart</string> - <string id="30238">Sync Saga Films</string> - <string id="30239">Réinitialiser la base de données locale de Kodi</string> - <!-- Verified --> - <string id="30243">Activer HTTPS</string> - <!-- Verified --> - <string id="30245">Forcer le transcodage Codecs</string> - <string id="30249">Activer le message de connexion au serveur pendant le démarrage</string> - <!-- Verified --> - <string id="30251">Vidéos personnel récemment ajoutés</string> - <!-- Verified --> - <string id="30252">Photos récemment ajoutés</string> - <!-- Verified --> - <string id="30253">Vidéos personnelles favorites</string> - <!-- Verified --> - <string id="30254">Photos favorites</string> - <!-- Verified --> - <string id="30255">Albums favoris</string> - <string id="30256">Vidéo Clips récemment ajoutés</string> - <!-- Verified --> - <string id="30257">Vidéo Clips en cours</string> - <!-- Verified --> - <string id="30258">Vidéo Clips non vu</string> - <!-- Verified --> - <!-- Default views --> - <string id="30300">Actif</string> - <string id="30301">Effacer les Paramètres</string> - <string id="30302">Films</string> - <string id="30303">Collections</string> - <string id="30304">Bandes-annonces</string> - <string id="30305">Séries</string> - <string id="30306">Saisons</string> - <string id="30307">Épisodes</string> - <string id="30308">Artistes musicaux</string> - <string id="30309">Albums de musique</string> - <string id="30310">Vidéo Clips</string> - <string id="30311">Pistes Musicales</string> - <string id="30312">Chaînes</string> - <!-- contextmenu --> - <string id="30401">Emby options</string> - <string id="30405">Ajouter aux favoris Emby</string> - <string id="30406">Supprimer des favoris Emby</string> - <string id="30407">Définir une note personnalisée de la chanson</string> - <string id="30408">Paramètres addon Emby</string> - <string id="30409">Supprimer un élément du serveur</string> - <string id="30410">Actualiser cet article</string> - <string id="30411">Définir une note personnalisée de la chanson (0-5)</string> - <!-- add-on settings --> - <string id="30500">Vérifier certificat SSL</string> - <string id="30501">Certificat SSL client</string> - <string id="30502">Utiliser une adresse alternative</string> - <string id="30503">Adresse du serveur alternatif</string> - <string id="30504">Utiliser un nom alternatif de périphérique</string> - <string id="30505">[COLOR yellow]Relancez la connexion[/COLOR]</string> - <string id="30506">Options de synchronisation</string> - <string id="30507">Afficher l'avancement si le total d'objets est supérieur à</string> - <string id="30508">Sync Séries TV vides</string> - <string id="30509">Activer la bibliothèque musicale</string> - <string id="30510">Direct stream bibliothèque musicale</string> - <string id="30511">Mode de lecture</string> - <string id="30512">Force mise en cache d'artwork</string> - <string id="30513">Limiter artwork en cache (recommandé pour rpi)</string> - <string id="30514">Activer le démarrage rapide (nécessite le plugin du serveur)</string> - <string id="30515">Nombre maximum d'éléments à demander à partir du serveur en une seule fois</string> - <string id="30516">Lecture</string> - <string id="30517">Identifiants réseau</string> - <string id="30518">Activer le mode cinéma</string> - <string id="30519">Demander à jouer des bandes annonces</string> - <string id="30520">Ne pas demander de confirmation de suppression pour le menu contextuel (utiliser à vos risques et périls)</string> - <string id="30521">Aller en arrière à la reprise (en secondes)</string> - <string id="30522">Force transcode H265</string> - <string id="30523">Options métadonnées musique (non compatibles avec direct stream)</string> - <string id="30524">Importation de la note de la chanson directement à partir des fichiers</string> - <string id="30525">Convertir la note du morceau pour Emby note</string> - <string id="30526">Autoriser les notes dans les fichiers des chansons à être mis à jour</string> - <string id="30527">Ignorer les spéciaux dans les épisodes suivants</string> - <string id="30528">Utilisateurs permanents à ajouter à la session</string> - <string id="30529">Délai de démarrage (en secondes)</string> - <string id="30530">Activer le message redémarrage du serveur</string> - <string id="30531">Activer une notification nouveau contenu</string> - <string id="30532">Durée de la fenêtre de la bibliothèque vidéo (en secondes)</string> - <string id="30533">Durée de la fenêtre de la bibliothèque musical (en secondes)</string> - <string id="30534">Messages du serveur</string> - <string id="30535">Générer un nouveau Id d'appareil</string> - <string id="30536">Sync si l'écran est désactivé</string> - <string id="30537">Force Transcode Hi10P</string> - <string id="30538">Désactivé</string> - <string id="30539">Connexion</string> - <string id="30540">Connexion manuelle</string> - <string id="30541">Emby Connect</string> - <string id="30542">Serveur</string> - <string id="30543">Nom d'utilisateur ou adresse mail</string> - <string id="30544">Activer la protection anti-verrouillage de la base de données (cela ralentira la synchronisation)</string> - <string id="30545">Activer le message serveur hors-ligne</string> - <!-- dialogs --> - <string id="30600">Se connecter avec Emby Connect</string> - <string id="30602">Mot de passe</string> - <string id="30603">Merci de vous référer à nos conditions d'utilisations. L'utilisation de tout logiciel Emby nécessite l'adhésion à ces conditions.</string> - <string id="30604">Scannez-moi</string> - <string id="30605">Se connecter</string> - <string id="30606">Annuler</string> - <string id="30607">Sélectionner le serveur principal</string> - <string id="30608">Les champs Nom d'utilisateur ou Mot de passe ne peuvent pas être vide</string> - <string id="30609">Impossible de se connecter au serveur sélectionné</string> - <string id="30610">Se connecter à</string> - <!-- Connect to {server} --> - <string id="30611">Ajouter un serveur manuellement</string> - <string id="30612">Merci de vous identifier</string> - <string id="30613">Le nom d'utilisateur ne peut pas être vide</string> - <string id="30614">Se connecter au serveur</string> - <string id="30615">Hôte</string> - <string id="30616">Connexion</string> - <string id="30617">Le serveur ou le port ne peuvent pas être vide</string> - <string id="30618">Changer d'utilisateur Emby Connect</string> - <!-- service add-on --> - <string id="33000">Bienvenue</string> - <string id="33001">Erreur de connexion</string> - <string id="33002">Le Serveur est inaccessible</string> - <string id="33003">Le Serveur est en ligne</string> - <string id="33004">Éléments ajoutés à la liste de lecture</string> - <string id="33005">Éléments en file d'attente à la liste de lecture</string> - <string id="33006">Le serveur redémarre</string> - <string id="33007">L'accès est activé</string> - <string id="33008">Entrer le mot de passe pour l'utilisateur:</string> - <string id="33009">Utilisateur ou mot de passe invalide</string> - <string id="33010">Échec de l'authentification de trop nombreuses fois</string> - <string id="33011">Lecture directe du fichier impossible</string> - <string id="33012">Lecture directe a échoué 3 fois. Activer la lecture a partir de HTTP.</string> - <string id="33013">Choisissez le flux audio</string> - <string id="33014">Choisissez le flux de sous-titres</string> - <string id="33015">Supprimer le fichier de votre serveur Emby?</string> - <string id="33016">Lire bande-annonces ?</string> - <string id="33017">Récupération des films à partir de:</string> - <string id="33018">Récupération collections</string> - <string id="33019">Récupération des vidéo clips à partir de:</string> - <string id="33020">Récupération des séries Tv à partir de:</string> - <string id="33021">Récupération:</string> - <string id="33022">La base de données doit être créé pour cette version de Emby pour Kodi. Procéder ?</string> - <string id="33023">Emby pour Kodi peut ne pas fonctionner correctement jusqu'à ce que la base de données soit remise à zéro.</string> - <string id="33024">Annulation du processus de synchronisation de la base de données. La version actuelle de Kodi n’est pas prise en charge.</string> - <string id="33025">complété en:</string> - <string id="33026">Comparaison des films à partir de:</string> - <string id="33027">Comparaison des collections</string> - <string id="33028">Comparaison des vidéo clips à partir de:</string> - <string id="33029">Comparaison des séries tv à partir de:</string> - <string id="33030">Comparaison des épisodes à partir de:</string> - <string id="33031">Comparaison:</string> - <string id="33032">Impossible de générer un nouvel ID de périphérique. Consultez les fichiers journaux pour plus d'informations.</string> - <string id="33033">Un nouvel Id de périphérique a été généré. Kodi va redémarrer maintenant.</string> - <string id="33034">Procédez avec le serveur suivant ?</string> - <string id="33035">Avertissement ! Si vous choisissez le mode natif, certaines fonctionnalités de Emby seront manquantes, tels que: Emby mode cinéma, direct stream/transcode et planification d'accès parental.</string> - <string id="33036">Addon (Par défaut)</string> - <string id="33037">Natif (Chemins directs)</string> - <string id="33038">Ajouter les informations d'identification du réseau pour permettre l'accès à votre contenu Kodi ? Important: Kodi devra être redémarré pour voir les informations d'identification. Elles peuvent également être ajoutés à un moment ultérieur.</string> - <string id="33039">Désactiver bibliothèque musicale Emby?</string> - <string id="33040">Direct stream la bibliothèque musicale ? Sélectionnez cette option si la bibliothèque musicale sera accessible à distance.</string> - <string id="33041">Supprimer les fichiers de Emby Server ? Cela permettra également de supprimer les fichiers du disque !</string> - <string id="33042">L'exécution du processus de mise en cache peut prendre un certain temps.</string> - <string id="33043">Artwork cache sync</string> - <string id="33044">Réinitialiser le cache des artwork existant ?</string> - <string id="33045">Mise à jour du cache des artwork :</string> - <string id="33046">Attendre que tous les sujets aient quitter :</string> - <string id="33047">Kodi ne peut pas localiser le fichier:</string> - <string id="33048">Vous devrez peut-être vérifier vos informations d'identification de réseau dans les paramètres add-on ou utiliser la substitution de chemin Emby pour formater votre chemin correctement (Emby tableau de bord> bibliothèque). Arrêter la synchronisation ?</string> - <string id="33049">Ajoutée:</string> - <string id="33050">Si vous ne parvenez pas à vous connecter de trop nombreuses fois, le serveur Emby peut verrouiller votre compte. Continuer quand même ?</string> - <string id="33051">Chaînes TV en direct (expérimental)</string> - <string id="33052">Enregistrements TV en direct (expérimental)</string> - <string id="33053">Paramètres</string> - <string id="33054">Ajouter l'utilisateur à la session</string> - <string id="33055">Actualiser listes de lecture/nœuds vidéo d'Emby</string> - <string id="33056">Effectuer une sync manuelle</string> - <string id="33057">Réparer la base de données locale (force la mise à jour de tout le contenu)</string> - <string id="33058">Effectuer une réinitialisation de la base de données locale</string> - <string id="33059">Mettre en cache tout les artwork</string> - <string id="33060">Sync Emby Thème Media pour Kodi</string> - <string id="33061">Ajouter/Supprimer l'utilisateur de la session</string> - <string id="33062">Ajouter un utilisateur</string> - <string id="33063">Supprimer un utilisateur</string> - <string id="33064">Supprimer l'utilisateur de la session</string> - <string id="33065">Réussi !</string> - <string id="33066">Suppression de la session de visualisation:</string> - <string id="33067">Ajouté à la session de visualisation:</string> - <string id="33068">Impossible d'ajouter/supprimer l'utilisateur de la session.</string> - <string id="33069">La tâche a réussi</string> - <string id="33070">La tâche a échoué</string> - <string id="33071">Direct Stream</string> - <string id="33072">Méthode de lecture pour votre thèmes</string> - <string id="33073">Le fichier de paramètres n'existe pas dans Tunes TV. Modifier un paramètre et exécutez à nouveau la tâche.</string> - <string id="33074">Êtes-vous sûr de vouloir réinitialiser votre base de données locale Kodi ?</string> - <string id="33075">Modifier/Supprimer les informations d'identification du réseau</string> - <string id="33076">Modifier</string> - <string id="33077">Supprimer</string> - <string id="33078">Supprimé:</string> - <string id="33079">Entrer le nom d'utilisateur réseau</string> - <string id="33080">Entrer le mot de passe réseau</string> - <string id="33081">Ajouter des informations d'identification de réseau pour:</string> - <string id="33082">Entrez le nom du serveur ou l'adresse IP comme indiqué dans vos chemins de bibliothèque de Emby. Par exemple, le nom du serveur: \\\\SERVEUR-PC\\chemin\\ est \"SERVEUR-PC\"</string> - <string id="33083">Modifier le nom du serveur ou l'adresse IP</string> - <string id="33084">Entrer le nom du serveur ou l'adresse IP</string> - <string id="33085">Impossible de réinitialiser la base de données. Réessayer.</string> - <string id="33086">Supprimer toutes les artwork en cache?</string> - <string id="33087">Réinitialiser tous les réglages de l'addon Emby?</string> - <string id="33088">La réinitialisation de la base de données est terminée, Kodi va maintenant redémarrer pour appliquer les modifications.</string> -</strings> diff --git a/resources/language/German/strings.xml b/resources/language/German/strings.xml deleted file mode 100644 index 7a27863b..00000000 --- a/resources/language/German/strings.xml +++ /dev/null @@ -1,352 +0,0 @@ -<?xml version="1.0" encoding="utf-8" standalone="yes"?> -<strings> - <!-- Add-on settings --> - <string id="29999">Emby für Kodi</string> - <string id="30000">Primäre Serveradresse</string> - <string id="30002">Via HTTP abspielen anstatt SMB</string> - <string id="30004">Log Level</string> - <string id="30016">Gerätename</string> - <string id="30022">Erweitert</string> - <string id="30024">Benutzername</string> - <string id="30030">Portnummer</string> - <string id="30035">Anzahl der zuletzt hinzugefügten Musikalben:</string> - <string id="30036">Anzahl der zuletzt hinzugefügten Filme, die gezeigt werden:</string> - <string id="30037">Anzahl der zuletzt hinzugefügten Episoden, die gezeigt werden:</string> - <string id="30042">Aktualisieren</string> - <string id="30043">Löschen</string> - <string id="30044">Benutzername/Passwort falsch</string> - <string id="30045">Benutzername nicht gefunden</string> - <string id="30052">Löschen</string> - <string id="30053">Lösche von Server</string> - <string id="30068">Sortiere nach</string> - <string id="30069">Kein Filter</string> - <string id="30070">Action</string> - <string id="30071">Abenteuer</string> - <string id="30072">Animation</string> - <string id="30073">Krimi</string> - <string id="30074">Komödie</string> - <string id="30075">Dokumentation</string> - <string id="30076">Drama</string> - <string id="30077">Fantasy</string> - <string id="30078">Ausländisch</string> - <string id="30079">Geschichte</string> - <string id="30080">Horror</string> - <string id="30081">Musik</string> - <string id="30082">Musical</string> - <string id="30083">Mystery</string> - <string id="30084">Romanze</string> - <string id="30085">Science Fiction</string> - <string id="30086">Kurzfilm</string> - <string id="30087">Spannung</string> - <string id="30088">Thriller</string> - <string id="30089">Western</string> - <string id="30090">Genre-Filter</string> - <string id="30091">Löschen von Dateien bestätigen</string> - <!-- Verified --> - <string id="30093">Als 'gesehen' markieren</string> - <string id="30094">Als 'ungesehen' markieren</string> - <string id="30097">Sortieren nach</string> - <string id="30098">Sortierreihenfolge absteigend</string> - <string id="30099">Sortierreihenfolge aufsteigend</string> - <!-- resume dialog --> - <string id="30105">Fortsetzen</string> - <string id="30106">Fortsetzen bei</string> - <string id="30107">Am Anfang starten</string> - <string id="30114">Löschen von Medien nach dem Abspielen anbieten</string> - <!-- Verified --> - <string id="30115">Für Episoden</string> - <!-- Verified --> - <string id="30116">Für Filme</string> - <!-- Verified --> - <string id="30118">Prozentanzeige für Fortsetzen</string> - <string id="30119">Episodennummer hinzufügen</string> - <string id="30120">Ladefortschritt anzeigen</string> - <string id="30121">Lade Inhalt</string> - <string id="30122">Lade Daten</string> - <string id="30125">Fertig</string> - <string id="30132">Warnung</string> - <!-- Verified --> - <string id="30135">Fehler</string> - <string id="30138">Suche</string> - <string id="30157">Aktiviere erweiterte Bilder (z.B. CoverArt)</string> - <!-- Verified --> - <string id="30158">Metadaten</string> - <string id="30159">Artwork</string> - <string id="30160">Videoqualität</string> - <!-- Verified --> - <string id="30165">Direkte Wiedergabe</string> - <!-- Verified --> - <string id="30166">Transkodierung</string> - <string id="30167">Serversuche erfolgreich</string> - <string id="30168">Server gefunden</string> - <string id="30169">Addresse:</string> - <!-- Video nodes --> - <string id="30170">Zuletzt hinzugefügte Serien</string> - <!-- Verified --> - <string id="30171">Begonnene Serien</string> - <!-- Verified --> - <string id="30172">Alles an Musik</string> - <string id="30173">Kanäle</string> - <!-- Verified --> - <string id="30174">Zuletzt hinzugefügte Filme</string> - <!-- Verified --> - <string id="30175">Zuletzt hinzugefügte Episoden</string> - <!-- Verified --> - <string id="30176">Zuletzt hinzugefügte Alben</string> - <string id="30177">Begonnene Filme</string> - <!-- Verified --> - <string id="30178">Begonnene Episoden</string> - <!-- Verified --> - <string id="30179">Nächste Episoden</string> - <!-- Verified --> - <string id="30180">Favorisierte Filme</string> - <!-- Verified --> - <string id="30181">Favorisierte Serien</string> - <!-- Verified --> - <string id="30182">Favorisierte Episoden</string> - <string id="30183">Häufig gespielte Alben</string> - <string id="30184">Anstehende Serien</string> - <string id="30185">Sammlungen</string> - <string id="30186">Trailer</string> - <string id="30187">Musikvideos</string> - <string id="30188">Fotos</string> - <string id="30189">Ungesehene Filme</string> - <!-- Verified --> - <string id="30190">Filmgenres</string> - <string id="30191">Studios</string> - <string id="30192">Filmdarsteller</string> - <string id="30193">Ungesehene Episoden</string> - <string id="30194">Seriengenres</string> - <string id="30195">Fernsehsender</string> - <string id="30196">Seriendarsteller</string> - <string id="30197">Wiedergabelisten</string> - <string id="30199">Ansichten festlegen</string> - <string id="30200">Wähle Benutzer</string> - <!-- Verified --> - <string id="30204">Verbindung zum Server fehlgeschlagen</string> - <string id="30207">Songs</string> - <string id="30208">Alben</string> - <string id="30209">Album-Interpreten</string> - <string id="30210">Interpreten</string> - <string id="30211">Musik-Genres</string> - <string id="30220">Zuletzt hinzugefügte</string> - <string id="30221">Begonnene</string> - <string id="30222">Anstehende</string> - <string id="30223">Benutzerdefinierte Ansichten</string> - <string id="30224">Statistiken senden</string> - <string id="30227">Zufällige Filme</string> - <string id="30228">Zufällige Episoden</string> - <string id="30229">Zufallseintrag</string> - <!-- Verified --> - <string id="30230">Empfohlene Medien</string> - <!-- Verified --> - <string id="30235">Extras</string> - <!-- Verified --> - <string id="30236">Synchronisiere Themen-Musik</string> - <string id="30237">Synchronisiere Extra-Fanart</string> - <string id="30238">Synchronisiere Film-BoxSets</string> - <string id="30239">Lokale Kodi-Datenbank zurücksetzen</string> - <!-- Verified --> - <string id="30243">Aktiviere HTTPS</string> - <!-- Verified --> - <string id="30245">Erzwinge Codec-Transkodierung</string> - <string id="30249">Aktiviere Server-Verbindungsmeldungen beim Starten</string> - <!-- Verified --> - <string id="30251">Kürzliche hinzugefügte Heimvideos</string> - <!-- Verified --> - <string id="30252">Kürzlich hinzugefügte Fotos</string> - <!-- Verified --> - <string id="30253">Favorisierte Heim Videos</string> - <!-- Verified --> - <string id="30254">Favorisierte Fotos</string> - <!-- Verified --> - <string id="30255">Favorisierte Alben</string> - <string id="30256">Kürzlich hinzugefügte Musikvideos</string> - <!-- Verified --> - <string id="30257">Begonnene Musikvideos</string> - <!-- Verified --> - <string id="30258">Ungesehene Musikvideos</string> - <!-- Verified --> - <!-- Default views --> - <string id="30300">Aktiviert</string> - <string id="30301">Zurücksetzen</string> - <string id="30302">Filme</string> - <string id="30303">Sammlungen</string> - <string id="30304">Trailer</string> - <string id="30305">Serien</string> - <string id="30306">Staffeln</string> - <string id="30307">Episoden</string> - <string id="30308">Interpreten</string> - <string id="30309">Musikalben</string> - <string id="30310">Musikvideos</string> - <string id="30311">Musikstücke</string> - <string id="30312">Kanäle</string> - <!-- contextmenu --> - <string id="30401">Emby Einstellungen</string> - <string id="30405">Zu Emby Favoriten hinzufügen</string> - <string id="30406">Entferne von Emby Favoriten</string> - <string id="30407">Setze eigenes Song-Rating</string> - <string id="30408">Emby Addon Einstellungen</string> - <string id="30409">Lösche Element vom Server</string> - <string id="30410">Dieses Element aktualisieren</string> - <string id="30411">Setze eigenes Song-Rating (0-5)</string> - <!-- add-on settings --> - <string id="30500">Überprüfe Host-SSL-Zertifikat</string> - <string id="30501">Client-SSL-Zertifikat</string> - <string id="30502">Benutze alternative Adresse</string> - <string id="30503">Alternative Serveradresse</string> - <string id="30504">Benutze alternativen Gerätenamen</string> - <string id="30505">[COLOR yellow]Erneut versuchen[/COLOR]</string> - <string id="30506">Synchronisations Einstellungen</string> - <string id="30507">Zeige Fortschritt, wenn die Datensatzanzahl größer ist als</string> - <string id="30508">Synchronisiere leere Serien</string> - <string id="30509">Aktiviere Musik Bibliothek</string> - <string id="30510">Direktes streamen der Musikbibliothek</string> - <string id="30511">Wiedergabemodus</string> - <string id="30512">Erzwinge Artworkcaching</string> - <string id="30513">Limitiere Artworkcache-Threads (empfohlen für rpi)</string> - <string id="30514">Aktiviere Schnellstart (benötigt Server plugin)</string> - <string id="30515">Maximale zeitgleiche Abfrageanzahl der Elemente vom Server</string> - <string id="30516">Wiedergabe</string> - <string id="30517">Netzwerkanmeldung</string> - <string id="30518">Aktiviere Emby Kino-Modus</string> - <string id="30519">Frage nach Trailerwiedergabe</string> - <string id="30520">Überspringe Emby Löschanfrage für das Kontextmenü (Nutzung auf eigene Gefahr)</string> - <string id="30521">Rücksprung beim Fortsetzen (in Sekunden)</string> - <string id="30522">Erzwinge H.265-Transkodierung</string> - <string id="30523">Musik-Metadateneinstellungen (nicht kompatibel mit direktem Streamen)</string> - <string id="30524">Importiere Songrating direkt aus Datei</string> - <string id="30525">Konvertiere Songrating zu Emby-Rating</string> - <string id="30526">Erlaube Aktualisierung des Ratings in Musikdateien</string> - <string id="30527">Ignoriere Bonusmaterial bei nächsten Episoden</string> - <string id="30528">Permanentes Hinzufügen von Benutzern zu dieser Sitzung</string> - <string id="30529">Startverzögerung (in Sekunden)</string> - <string id="30530">Aktiviere Serverneustart-Meldung</string> - <string id="30531">Aktiviere Benachrichtigung bei neuen Inhalten</string> - <string id="30532">Dauer der Anzeige für Videobibliotheks-PopUp (in Sekunden)</string> - <string id="30533">Dauer der Anzeige für Musikbibliotheks-PopUp (in Sekunden)</string> - <string id="30534">Server-Nachrichten</string> - <string id="30535">Erstelle neue Geräte-ID</string> - <string id="30536">Synchronisieren bei deakiviertem Bildschirmschoner</string> - <string id="30537">Erzwinge Hi10P-Transkodierung</string> - <string id="30538">Deaktiviert</string> - <string id="30539">Login</string> - <string id="30540">Manueller Login</string> - <string id="30541">Emby Connect</string> - <string id="30542">Server</string> - <string id="30543">Nutzername oder E-Mail</string> - <string id="30544">Aktiviere Datenbank Lock Fix (verlangsamt Synchronisationsprozess)</string> - <string id="30545">Aktiviere Server Offline-Nachricht</string> - <!-- dialogs --> - <string id="30600">Mit Emby Connect anmelden</string> - <string id="30602">Passwort</string> - <string id="30603">Bitte sieh in unsere Nutzungsbedingungen. Die Nutzung jeglicher Emby Software stellt die Akzeptanz dieser Bedingungen dar.</string> - <string id="30604">Scanne mich</string> - <string id="30605">Anmelden</string> - <string id="30606">Abbrechen</string> - <string id="30607">Wähle Hauptserver</string> - <string id="30608">Nutzername oder Passwort können nicht leer sein</string> - <string id="30609">Verbindung zum ausgewählten Server fehlgeschlagen</string> - <string id="30610">Verbinden zu</string> - <!-- Connect to {server} --> - <string id="30611">Server manuell hinzufügen</string> - <string id="30612">Bitte anmelden</string> - <string id="30613">Nutzername kann nicht leer sein</string> - <string id="30614">Mit Server verbinden</string> - <string id="30615">Hostrechner</string> - <string id="30616">Verbinden</string> - <string id="30617">Server oder Port können nicht leer sein</string> - <string id="30618">Emby Connect-Nutzer wechseln</string> - <!-- service add-on --> - <string id="33000">Willkommen</string> - <string id="33001">Fehler bei Verbindung</string> - <string id="33002">Server kann nicht erreicht werden</string> - <string id="33003">Server ist online</string> - <string id="33004">Elemente zur Playlist hinzugefügt</string> - <string id="33005">Elemente in Playlist eingereiht</string> - <string id="33006">Server startet neu</string> - <string id="33007">Zugang erlaubt</string> - <string id="33008">Nutzerpasswort eingeben:</string> - <string id="33009">Falscher Benutzername oder Passwort</string> - <string id="33010">Authentifizierung zu oft fehlgeschlagen</string> - <string id="33011">Direkte Wiedergabe der Datei nicht möglich</string> - <string id="33012">Direkte Wiedergabe 3 mal fehlgeschlagen. Aktiviere Wiedergabe über HTTP.</string> - <string id="33013">Wähle Audiostream</string> - <string id="33014">Wähle Untertitelstream</string> - <string id="33015">Datei von deinem Emby Server löschen?</string> - <string id="33016">Trailer abspielen?</string> - <string id="33017">Erfasse Filme von:</string> - <string id="33018">Erfasse Sammlungen</string> - <string id="33019">Erfasse Musikvideos von:</string> - <string id="33020">Erfasse Serien von:</string> - <string id="33021">Erfassung:</string> - <string id="33022">Für diese Version von 'Emby für Kodi' muss die Datenbank neu erstellt werden. Fortfahren?</string> - <string id="33023">'Emby für Kodi' funktioniert womöglich nicht richtig, bis die Datenbank zurückgesetzt wurde.</string> - <string id="33024">Beende Datenbank-Synchronisationsprozess. Die aktuelle Kodi-Version wird nicht unterstützt.</string> - <string id="33025">abgeschlossen in:</string> - <string id="33026">Vergleiche Filme von:</string> - <string id="33027">Vergleiche Boxsets</string> - <string id="33028">Vergleiche Musikvideos von:</string> - <string id="33029">Vergleiche Serien von:</string> - <string id="33030">Vergleiche Episoden von:</string> - <string id="33031">Vergleiche:</string> - <string id="33032">Erstellung einer neuen Geräte-ID fehlgeschlagen. Schau in die Logs für weitere Informationen.</string> - <string id="33033">Einer neue Geräte-ID wurde erstellt. Kodi startet nun neu.</string> - <string id="33034">Mit dem folgendem Server fortfahren?</string> - <string id="33035">Achtung! Wenn du den 'Nativen Modus' auswählst, fehlen einige Emby Funktionalitäten, wie: Emby Kinomodus, Direct Stream/Transkodiereigenschaften und elterliche Zugangsplanung.</string> - <string id="33036">Addon (Standard)</string> - <string id="33037">Nativ (Direkte Pfade)</string> - <string id="33038">Netzwerkanmeldeinformationen hinzufügen, um Kodi Zugriff auf die Inhalte zu geben? Wichtig: Kodi muss neugestartet werden, um die Netzwerk -Anmeldeinformationen zu sehen. Die Daten können auch später hinzugefügt werden.</string> - <string id="33039">Deakiviere Emby Musikbibliothek?</string> - <string id="33040">Direct Stream für die Musikbibliothek aktivieren? Wählen Sie diese Option wenn auf die Bibliothek später außerhalb des eigenen Netzwerkes zugegriffen wird.</string> - <string id="33041">Datei(en) vom Emby-Server löschen? Die Datei(en) werden auch von der Festplatte gelöscht!</string> - <string id="33042">Der Caching-Prozess dauert etwas. Weitermachen?</string> - <string id="33043">Artworkcache Synchronisation</string> - <string id="33044">Vorhandenen Artworkcache zurücksetzen?</string> - <string id="33045">Aktualisiere Artworkcache:</string> - <string id="33046">Warte auf Beendigung aller Threads:</string> - <string id="33047">Kodi kann Datei nicht finden:</string> - <string id="33048">Sie müssen Ihre Netzwerk -Anmeldeinformationen in den Addon-Einstellungen bestätigen oder Ihre Pfadersetzungen innerhalb des Emby-Servers korrekt setzen (Emby Dashboard -> Bibliothek). Synchronisation beenden?</string> - <string id="33049">Hinzugefügt:</string> - <string id="33050">Wenn Sie sich zu oft falsch anmelden, blockiert der Emby Server möglicherwiese den Account. Trotzdem weiter?</string> - <string id="33051">Live-TV Kanäle (experimentell)</string> - <string id="33052">Live-TV Aufnahmen (experimentell)</string> - <string id="33053">Einstellungen</string> - <string id="33054">Füge Benutzer zur Sitzung hinzu</string> - <string id="33055">Aktualisiere Emby Abspiellisten/Videoknoten</string> - <string id="33056">Manuelle Synchronisation ausführen</string> - <string id="33057">Repariere lokale Datenbank (erzwinge Aktualisierung des gesamten Inhalts)</string> - <string id="33058">Lokale Datenbank zurücksetzen</string> - <string id="33059">Gesamtes Artwork cachen</string> - <string id="33060">Emby Theme-Medien (Video/Musik-Themen) mit Kodi synchronisieren</string> - <string id="33061">Hinzufügen/Entfernen von Benutzer zur Sitzung</string> - <string id="33062">Benutzer hinzufügen</string> - <string id="33063">Benutzer entfernen</string> - <string id="33064">Benutzer von Sitzung entfernen</string> - <string id="33065">Erfolg!</string> - <string id="33066">Von der Videositzung entfernt:</string> - <string id="33067">Zur Videositzung hinzugefügt:</string> - <string id="33068">Benutzer von Session entfernen nicht möglich.</string> - <string id="33069">Task erfolgreich ausgeführt</string> - <string id="33070">Task fehlgeschlagen</string> - <string id="33071">Direktes Streamen</string> - <string id="33072">Playbackmethode für Ihre Themes</string> - <string id="33073">Die Einstellungsdatei existiert nicht in TV Tunes. Ändern Sie eine Einstellung und starten den Task erneut.</string> - <string id="33074">Sind Sie sicher, dass Sie die lokale Kodi Datenbank zurücksetzen möchten?</string> - <string id="33075">Bearbeiten/Entfernen der Netzwerkanmeldeinformationen</string> - <string id="33076">Ändern</string> - <string id="33077">Entfernen</string> - <string id="33078">Entfernt:</string> - <string id="33079">Netzwerk-Nutzernamen eingeben</string> - <string id="33080">Netzwerk-Passwort eingeben</string> - <string id="33081">Netzwerkanmeldeinformationen hinzugefügt für:</string> - <string id="33082">Servername oder IP-Adresse, wie in der Emby Server Bibliothek angezeigt, eingeben. (Bsp Servername: \"\\\\SERVER-PC\\Pfad\\\" ist \"SERVER-PC\")</string> - <string id="33083">Servername oder IP-Adresse ändern</string> - <string id="33084">Servername oder IP-Adresse eingeben</string> - <string id="33085">Konnte die Datenbank nicht zurücksetzen. Nochmal versuchen.</string> - <string id="33086">Alle zwischengespeicherten Bilder entfernen?</string> - <string id="33087">Alle Emby Addon-Einstellungen zurücksetzen?</string> - <string id="33088">Zurücksetzen der Datenbank abgeschlossen, Kodi wird nun neustarten um die Änderungen anzuwenden.</string> - <string id="33096">Video Auflösung auf Bildschirm Auflösung limitieren</string> - -</strings> diff --git a/resources/language/Italian/strings.xml b/resources/language/Italian/strings.xml deleted file mode 100644 index 8994d20d..00000000 --- a/resources/language/Italian/strings.xml +++ /dev/null @@ -1,350 +0,0 @@ -<?xml version="1.0" encoding="utf-8" standalone="yes"?> -<strings> - <!-- Add-on settings --> - <string id="29999">Emby per Kodi</string> - <string id="30000">Indirizzo Server Principale</string> - <string id="30002">Riproduci da HTTP anzichè SMB</string> - <string id="30004">Livello log</string> - <string id="30016">Nome Dispositivo</string> - <string id="30022">Avanzate</string> - <string id="30024">Nome Utente</string> - <string id="30030">Numero Porta</string> - <string id="30035">Numero di Album Musicali recenti da mostrare:</string> - <string id="30036">Numero di Film recenti da mostrare:</string> - <string id="30037">Numero di Episodi recenti da mostrare:</string> - <string id="30042">Aggiorna</string> - <string id="30043">Elimina</string> - <string id="30044">Nome Utente o Password errati</string> - <string id="30045">Utente sconosciuto</string> - <string id="30052">Eliminando</string> - <string id="30053">Attendo che il server elimini</string> - <string id="30068">Ordina Per</string> - <string id="30069">Nessuno</string> - <string id="30070">Azione</string> - <string id="30071">Avventura</string> - <string id="30072">Animazione</string> - <string id="30073">Giallo</string> - <string id="30074">Commedia</string> - <string id="30075">Documentario</string> - <string id="30076">Drammatico</string> - <string id="30077">Fantasy</string> - <string id="30078">Straniero</string> - <string id="30079">Storico</string> - <string id="30080">Horror</string> - <string id="30081">Musica</string> - <string id="30082">Musical</string> - <string id="30083">Mistero</string> - <string id="30084">Romantico</string> - <string id="30085">Fantascienza</string> - <string id="30086">Corto</string> - <string id="30087">Suspense</string> - <string id="30088">Thriller</string> - <string id="30089">Western</string> - <string id="30090">Filtro Genere</string> - <string id="30091">Conferma eliminazione file</string> - <!-- Verified --> - <string id="30093">Segna come visto</string> - <string id="30094">Segna come non visto</string> - <string id="30097">Ordina per</string> - <string id="30098">Ordinamento Decrescente</string> - <string id="30099">Ordinamento Crescente</string> - <!-- resume dialog --> - <string id="30105">Riprendi</string> - <string id="30106">Riprendi da</string> - <string id="30107">Riproduci dall'inizio</string> - <string id="30114">Offri di eliminare dopo la riproduzione</string> - <!-- Verified --> - <string id="30115">Per Episodi</string> - <!-- Verified --> - <string id="30116">Per Film</string> - <!-- Verified --> - <string id="30118">Aggiungi Percentuale di Completamento</string> - <string id="30119">Aggiungi Numero Episodio</string> - <string id="30120">Mostra Progresso nel Caricamento</string> - <string id="30121">Caricamento Contenuto</string> - <string id="30122">Recupero Dati</string> - <string id="30125">Fatto</string> - <string id="30132">Attenzione</string> - <!-- Verified --> - <string id="30135">Errore</string> - <string id="30138">Cerca</string> - <string id="30157">Abilita Immagini Migliorate (es. Copertine)</string> - <!-- Verified --> - <string id="30158">Metadati</string> - <string id="30159">Artwork</string> - <string id="30160">Qualità Video</string> - <!-- Verified --> - <string id="30165">Riproduzione Diretta</string> - <!-- Verified --> - <string id="30166">Trascodifica</string> - <string id="30167">Rilevazione Server Completa</string> - <string id="30168">Server trovato</string> - <string id="30169">Indirizzo:</string> - <!-- Video nodes --> - <string id="30170">Serie TV Aggiunte di Recente</string> - <!-- Verified --> - <string id="30171">Serie TV in Corso</string> - <!-- Verified --> - <string id="30172">Tutta la Musica</string> - <string id="30173">Canali</string> - <!-- Verified --> - <string id="30174">Film Aggiunti di Recente</string> - <!-- Verified --> - <string id="30175">Episodi Aggiunti di Recente</string> - <!-- Verified --> - <string id="30176">Album Aggiunti di Recente</string> - <string id="30177">Film in Corso</string> - <!-- Verified --> - <string id="30178">Episodi in Corso</string> - <!-- Verified --> - <string id="30179">Prossimi Episodi</string> - <!-- Verified --> - <string id="30180">Film Preferiti</string> - <!-- Verified --> - <string id="30181">Spettacoli Preferiti</string> - <!-- Verified --> - <string id="30182">Episodi Preferiti</string> - <string id="30183">Album Riprodotti di Frequente</string> - <string id="30184">In onda a breve</string> - <string id="30185">Cofanetti</string> - <string id="30186">Trailer</string> - <string id="30187">Video Musicali</string> - <string id="30188">Foto</string> - <string id="30189">Film non Visti</string> - <!-- Verified --> - <string id="30190">Generi Film</string> - <string id="30191">Studios Cinema</string> - <string id="30192">Attori Cinema</string> - <string id="30193">Episodi non Visti</string> - <string id="30194">Generi TV</string> - <string id="30195">Reti TV</string> - <string id="30196">Attori TV</string> - <string id="30197">Playlist</string> - <string id="30199">Imposta Viste</string> - <string id="30200">Seleziona Utente</string> - <!-- Verified --> - <string id="30204">Impossibile connettersi al server</string> - <string id="30207">Brani</string> - <string id="30208">Album</string> - <string id="30209">Artisti Album</string> - <string id="30210">Artisti</string> - <string id="30211">Generi Musicali</string> - <string id="30220">Più recenti</string> - <string id="30221">In Corso</string> - <string id="30222">A seguire</string> - <string id="30223">Viste Utente</string> - <string id="30224">Riporta Statistiche</string> - <string id="30227">Film Casuali</string> - <string id="30228">Episodi Casuali</string> - <string id="30229">Elementi Casuali</string> - <!-- Verified --> - <string id="30230">Elementi Consigliati</string> - <!-- Verified --> - <string id="30235">Extra</string> - <!-- Verified --> - <string id="30236">Sincronizza Sigla</string> - <string id="30237">Sincronizza Fanart</string> - <string id="30238">Sinc. Cofanetti Film</string> - <string id="30239">Resetta il database locale di Kodi</string> - <!-- Verified --> - <string id="30243">Abilita HTTPS</string> - <!-- Verified --> - <string id="30245">Forza Codec Trascodifica</string> - <string id="30249">Abilita messaggio di connessione server all'avvio</string> - <!-- Verified --> - <string id="30251">Video personali aggiunti di recente</string> - <!-- Verified --> - <string id="30252">Foto aggiunte di recente</string> - <!-- Verified --> - <string id="30253">Video Personali Preferiti</string> - <!-- Verified --> - <string id="30254">Foto Preferite</string> - <!-- Verified --> - <string id="30255">Album Preferiti</string> - <string id="30256">Video musicali aggiunti di recente</string> - <!-- Verified --> - <string id="30257">Video musicali in corso</string> - <!-- Verified --> - <string id="30258">Video musicali non visti</string> - <!-- Verified --> - <!-- Default views --> - <string id="30300">Attivo</string> - <string id="30301">Ripristina Impostazioni</string> - <string id="30302">Film</string> - <string id="30303">Cofanetti</string> - <string id="30304">Trailer</string> - <string id="30305">Serie</string> - <string id="30306">Stagioni</string> - <string id="30307">Episodi</string> - <string id="30308">Artisti Musicali</string> - <string id="30309">Album Musicali</string> - <string id="30310">Video Musicali</string> - <string id="30311">Tracce Musicali</string> - <string id="30312">Canali</string> - <!-- contextmenu --> - <string id="30401">Opzioni Emby</string> - <string id="30405">Aggiungi ai preferiti di Emby</string> - <string id="30406">Rimuovi dai preferiti di Emby</string> - <string id="30407">Imposta voto brano personalizzato</string> - <string id="30408">Impostazioni add-on Emby</string> - <string id="30409">Elimina elemento dal server</string> - <string id="30410">Aggiorna questo elemento</string> - <string id="30411">Imposta voto personale (0-5)</string> - <!-- add-on settings --> - <string id="30500">Verifica Certificato Host SSL</string> - <string id="30501">Certificato Client SSL</string> - <string id="30502">Usa indirizzo alternativo</string> - <string id="30503">Indirizzo Server Alternativo</string> - <string id="30504">Usa Nome dispositivo alternativo</string> - <string id="30505">[COLOR yellow]Ritenta l'accesso[/COLOR]</string> - <string id="30506">Opzioni Sinc.</string> - <string id="30507">Mostra avanzamento se il numero di elementi è maggiore di</string> - <string id="30508">Sinc. Spettacoli TV vuoti</string> - <string id="30509">Abilita Libreria Musica</string> - <string id="30510">Libreria di musica streaming</string> - <string id="30511">Modalità Riproduzione</string> - <string id="30512">Forza caching artwork</string> - <string id="30513">Limita thread della cache artwork (raccomandato per rpi)</string> - <string id="30514">Abilita avvio veloce (richiede plug-in lato server)</string> - <string id="30515">Massimo di elementi richiesti in contemporanea</string> - <string id="30516">Riproduzione</string> - <string id="30517">Credenziali Rete</string> - <string id="30518">Abilita modalità cinema Emby</string> - <string id="30519">Chiedi di riprodurre trailer</string> - <string id="30520">Salta la conferma di eliminazione dal menu contestuale (usa a tuo rischio)</string> - <string id="30521">Anticipo alla ripresa (in secondi)</string> - <string id="30522">Forza trascodifica H.265</string> - <string id="30523">Impostazioni metadati musica (non compatibile con streaming)</string> - <string id="30524">Importa i voti dei brani direttamente dai file</string> - <string id="30525">Converti i voti dei brani ai voti Emby</string> - <string id="30526">Consenti di aggiornare i voti nei file dei brani</string> - <string id="30527">Ignora speciali tra i prossimi episodi</string> - <string id="30528">Utenti da aggiungere permanentemente alla sessione</string> - <string id="30529">Ritarda avvio (in secondi)</string> - <string id="30530">Abilita messaggio di riavvio server</string> - <string id="30531">Abilita notifica nuovi contenuti</string> - <string id="30532">Persistenza del pannello della libreria video (in secondi)</string> - <string id="30533">Persistenza del pannello della libreria musicale ( in secondi)</string> - <string id="30534">Messaggi del server</string> - <string id="30535">Genera un nuovo id dispositivo</string> - <string id="30536">Sincronizza quando lo screensaver è inattivo</string> - <string id="30537">Forza trascodifica Hi10P</string> - <string id="30538">Disabilitato</string> - <string id="30539">Accedi</string> - <string id="30540">Accesso Manuale</string> - <string id="30541">Emby Connect</string> - <string id="30542">Server</string> - <string id="30543">Nome utente o e-mail</string> - <string id="30544">Abilita fix database occupato (rallenta il processo di sync.)</string> - <string id="30545">Abilita il messaggio di server offline</string> - <!-- dialogs --> - <string id="30600">Accedi con Emby Connect</string> - <string id="30602">Password</string> - <string id="30603">Per favore leggi i nostri termini d'uso. L'uso di un software Emby sottointende l'acettazione di tali termini.</string> - <string id="30604">Scansionami</string> - <string id="30605">Accedi</string> - <string id="30606">Annulla</string> - <string id="30607">Seleziona server principale</string> - <string id="30608">Il nome utente e la password non possono essere vuoti</string> - <string id="30609">Impossibile connettersi al server selezionato</string> - <string id="30610">Connetti a</string> - <!-- Connect to {server} --> - <string id="30611">Aggiungi server manualmente</string> - <string id="30612">Per favore accedi</string> - <string id="30613">Il nome utente non può essere vuoto</string> - <string id="30614">Connetti al server</string> - <string id="30615">Host</string> - <string id="30616">Connetti</string> - <string id="30617">Il server e la porta non possono essere vuoti</string> - <string id="30618">Cambia utente Emby Connect</string> - <!-- service add-on --> - <string id="33000">Benvenuto</string> - <string id="33001">Errore di connessione</string> - <string id="33002">Il server non è raggiungibile</string> - <string id="33003">Il server è online</string> - <string id="33004">elementi aggiunti alla playlist</string> - <string id="33005">elementi accodati alla playlist</string> - <string id="33006">Il server si sta riavviando</string> - <string id="33007">l'Accesso è abilitato</string> - <string id="33008">Inserisci la password per l'utente:</string> - <string id="33009">Nome utente o password non validi</string> - <string id="33010">Autenticazione fallita troppe volte</string> - <string id="33011">Impossibile riprodurre direttamente il file</string> - <string id="33012">la riproduzione diretta è fallita 3 volte. Riproduzione da HTTP attiva.</string> - <string id="33013">Scegli la traccia audio</string> - <string id="33014">Scegli la traccia sottotitoli</string> - <string id="33015">Elimino il file dal tuo Server Emby?</string> - <string id="33016">Riproduco trailer?</string> - <string id="33017">Recuperando i film da:</string> - <string id="33018">Recuperando le collezioni</string> - <string id="33019">Recuperando i video musicali da:</string> - <string id="33020">Recuperando gli spettacoli TV da:</string> - <string id="33021">Recuperando:</string> - <string id="33022">Il database deve essere ricostruito per questa versione di Emby per Kodi. Procedo?</string> - <string id="33023">Emby per Kodi non funzionerà correttamente finchè il database non viene resettato.</string> - <string id="33024">Processo di sincronizzazione annullato. La version corrente di Kodi non è supportata.</string> - <string id="33025">completato in:</string> - <string id="33026">Confrontando film da:</string> - <string id="33027">Confrontando collezioni</string> - <string id="33028">Confrontando video musicali da:</string> - <string id="33029">Confrontando spettacoli TV da:</string> - <string id="33030">Confrontando episodi da:</string> - <string id="33031">Confrontando:</string> - <string id="33032">Impossibile generare un nuovo id dispositivo. Controlla il log per maggiori informazioni.</string> - <string id="33033">Un nuovo id dispositivo è stato generato. Ora Kodi si riavvierà.</string> - <string id="33034">Procedo con il server seguente?</string> - <string id="33035">Attenzionie! Se scegli la modalità Nativa, alcune funzionalità di Emby saranno disattivate, come: Modalità Cinema, opzioni di streaming/trascodifica e pianificazione accesso parentale.</string> - <string id="33036">Add-on (Predefinito)</string> - <string id="33037">Nativa (Perrcorsi Diretti)</string> - <string id="33038">Vuoi aggiungere le credenziali di rete per permettere a Kodi di accedere ai tuoi contenuti? Importante: Kodi dovrà essere riavviato. Possono anche essere aggiunte più avanti.</string> - <string id="33039">Disabilito la libreria Musicale Emby?</string> - <string id="33040">Vuoi usare lo streaming della libreria musicale? Scegli questa opzione se la libreria musicale sarà usata in remoto.</string> - <string id="33041">Elimina file dal Server Emby? Questo eliminerà anche i file dal disco!</string> - <string id="33042">Il processo di caching potrebbe impiegare alcuni minuti. Continuo?</string> - <string id="33043">Sinc. cache artwork</string> - <string id="33044">Resetta la cache artwork?</string> - <string id="33045">Aggiornando la cache artwork:</string> - <string id="33046">In attesa dell'uscita di tutti i thread:</string> - <string id="33047">Kodi non trova il file:</string> - <string id="33048">Potresti dover verificare le credenziali di reta nella impostazioni dell'add-on o usare la sostituzione percorso Emby per ottenere un percorso funzionante (Pannello di controllo Emby > Libreria). Interrompo sinc.?</string> - <string id="33049">Aggiunto:</string> - <string id="33050">Se fallisci l'accesso troppe volte, il server Emby potrebbe bloccare il tuo account. Vuoi continuare?</string> - <string id="33051">Canali TV in diretta (Sperimentale)</string> - <string id="33052">Registrazioni TV in diretta (sperimentale)</string> - <string id="33053">Impostazioni</string> - <string id="33054">Aggiungi utente alla sessione</string> - <string id="33055">Aggiorna nodi playlist/Video Emby</string> - <string id="33056">Esegui sinc. manuale</string> - <string id="33057">Ripara il database locale (forza l'aggiornamento di tutti i contenuti)</string> - <string id="33058">Resetta il database locale</string> - <string id="33059">Aggiungi tutte le artwork alla cache</string> - <string id="33060">Sinc. Sigle Emby a Kodi</string> - <string id="33061">Aggiungi/Rimuovi utente dalla sessione</string> - <string id="33062">Aggiungi utente</string> - <string id="33063">Rimuovi utente</string> - <string id="33064">Rimuovi utente dalla sessione</string> - <string id="33065">Successo!</string> - <string id="33066">Rimosso dalla sessione:</string> - <string id="33067">Aggiunto alla sessione:</string> - <string id="33068">Impossibile aggiungere/rimuovere l'utente dalla sessione.</string> - <string id="33069">Attività completa</string> - <string id="33070">Attività fallita</string> - <string id="33071">Streaming Diretto</string> - <string id="33072">Modalità di riproduzioni delle sigle</string> - <string id="33073">Il file impostazioni di TV Tunes non esiste. Cambia una delle sue opzioni e riavvia l'attività.</string> - <string id="33074">Sei sicuro di voler resettare il tuo database Kodi locale?</string> - <string id="33075">Modifica o Rimuovi credenziali di rete</string> - <string id="33076">Modifica</string> - <string id="33077">Rimuovi</string> - <string id="33078">Rimosso:</string> - <string id="33079">Inserisci il nome utente di rete</string> - <string id="33080">Inserisci la password di rete</string> - <string id="33081">Aggiunte credenziali di rete per:</string> - <string id="33082">Inserisci il nome del server o l'indirizzo IP come indicato nei percorsi della libreria Emby. Per esempio, il nome del server di \\\\SERVER-PC\\path è \"SERVER-PC\"</string> - <string id="33083">Modificai l nome del server o l'indirizzo IP</string> - <string id="33084">Inserisci il nome del server o l'indirizzo IP</string> - <string id="33085">Impossibile resettare il database. Riprova più tardi.</string> - <string id="33086">Rimuovi tutte le artwork dalla cache</string> - <string id="33087">Resetta tutte le impostazioni dell'add-on Emby?</string> - <string id="33088">Il database è stato resettato, Kodi verrà ora riavviato per applicare le modifiche.</string> -</strings> diff --git a/resources/language/Polish/strings.xml b/resources/language/Polish/strings.xml deleted file mode 100644 index efd503aa..00000000 --- a/resources/language/Polish/strings.xml +++ /dev/null @@ -1,360 +0,0 @@ -<?xml version="1.0" encoding="utf-8" standalone="yes"?> -<strings> - - <!-- Add-on settings --> - <string id="29999">Emby dla Kodi</string> - <string id="30000">Adres serwera</string> - <string id="30001">Nazwa serwera</string> - <string id="30002">Odtwarzaj za pomocą HTTP zamiast SMB</string> - <string id="30004">Poziom szczegółowości diagnostyki</string> - <string id="30016">Nazwa urządzenia</string> - <string id="30022">Zaawansowane</string> - <string id="30024">Użytkownik</string> - <string id="30030">Numer portu</string> - - <string id="30035">Liczba wyświetlanych Albumów ostatnio dodanych:</string> - <string id="30036">Liczba wyświetlanych Filmów ostatnio dodanych:</string> - <string id="30037">Liczba wyświetlanych Seriali ostatnio dodanych:</string> - - <string id="30042">Odśwież</string> - <string id="30043">Usuń</string> - <string id="30044">Niepoprawna nazwa/hasło użytkownika</string> - <string id="30045">Użytkownik o podanej nazwie nie istnieje</string> - <string id="30052">Trwa usuwanie</string> - <string id="30053">Oczekiwanie na zakończenie usuwania przez serwer</string> - - <string id="30068">Sortowanie</string> - <string id="30069">Brak</string> - <string id="30070">Akcja</string> - <string id="30071">Przygoda</string> - <string id="30072">Animacja</string> - <string id="30073">Kryminał</string> - <string id="30074">Komedia</string> - <string id="30075">Dokument</string> - <string id="30076">Dramat</string> - <string id="30077">Fantazja</string> - <string id="30078">Zagraniczny</string> - <string id="30079">Historyczny</string> - <string id="30080">Horror</string> - <string id="30081">Muzyczny</string> - <string id="30082">Muzykal</string> - <string id="30083">Tajemnica</string> - <string id="30084">Romans</string> - <string id="30085">Fantastyka</string> - <string id="30086">Krótkometrażowy</string> - <string id="30087">Suspense</string> - <string id="30088">Dreszczowiec</string> - <string id="30089">Western</string> - - <string id="30090">Filtr gatunku</string> - <string id="30091">Potwierdzaj usuwanie pliku</string><!-- Verified --> - - <string id="30093">Oznacz jako obejrzane</string> - <string id="30094">Oznacz jako nieobejrzane</string> - - <string id="30097">Sortowanie</string> - <string id="30098">Sortowanie malejące</string> - <string id="30099">Sortowanie rosnące</string> - - <!-- resume dialog --> - <string id="30105">Wznów odtwarzanie</string> - <string id="30106">Wznów odtwarzanie od</string> - <string id="30107">Odtwarzaj od początku</string> - - <string id="30114">Oferuj usuwanie po zakończeniu odtwarzania</string><!-- Verified --> - <string id="30115">Dla odcinków</string><!-- Verified --> - <string id="30116">Dla filmów</string><!-- Verified --> - - <string id="30118">Dodawaj postęp odtwarzania</string> - <string id="30119">Dodawaj numer odcinka</string> - <string id="30120">Pokazuj postęp wczytywania</string> - <string id="30121">Trwa wczytywanie zawartości</string> - <string id="30122">Trwa pobieranie danych</string> - - <string id="30125">Zakończone</string> - <string id="30132">Ostrzeżenie</string><!-- Verified --> - <string id="30135">Błąd</string> - <string id="30138">Szukaj</string> - ` - <string id="30157">Aktywuj obsługę dodatkowych grafik (np. okładek)</string><!-- Verified --> - <string id="30158">Metadane</string> - <string id="30159">Grafika</string> - <string id="30160">Jakość wideo</string><!-- Verified --> - - <string id="30165">Odtwarzanie bezpośrednie</string><!-- Verified --> - <string id="30166">Transkodowanie</string> - <string id="30167">Detekcja serwera zakończona powodzeniem</string> - <string id="30168">Odnaleziono serwer</string> - <string id="30169">Adres:</string> - - <!-- Video nodes --> - <string id="30170">Seriale ostatnio dodane</string><!-- Verified --> - <string id="30171">Seriale w trakcie oglądania</string><!-- Verified --> - <string id="30172">Biblioteka muzyki</string> - <string id="30173">Kanały</string><!-- Verified --> - <string id="30174">Filmy ostatnio dodane</string><!-- Verified --> - <string id="30175">Odcinki ostatnio dodane</string><!-- Verified --> - <string id="30176">Albumy ostatnio dodane</string> - <string id="30177">Filmy w trakcie oglądania</string><!-- Verified --> - <string id="30178">Odcinki w trakcie oglądania</string><!-- Verified --> - <string id="30179">Kolejne odcinki</string><!-- Verified --> - <string id="30180">Filmy ulubione</string><!-- Verified --> - <string id="30181">Seriale ulubione</string><!-- Verified --> - <string id="30182">Odcinki ulubione</string> - <string id="30183">Albumy często odtwarzane</string> - <string id="30184">Wkrótce na antenie</string> - <string id="30185">Serie filmowe</string> - <string id="30186">Zwiastuny</string> - <string id="30187">Teledyski</string> - <string id="30188">Obrazy</string> - <string id="30189">Filmy nieobejrzane</string><!-- Verified --> - <string id="30190">Gatunki filmowe</string> - <string id="30191">Wytwórnie filmowe</string> - <string id="30192">Aktorzy filmowi</string> - <string id="30193">Odcinki nieobejrzane</string> - <string id="30194">Gatunki telewizyjne</string> - <string id="30195">Sieci telewizyjne</string> - <string id="30196">Aktorzy telewizyjni</string> - <string id="30197">Listy odtwarzania</string> - - <string id="30199">Ustaw widoki</string> - <string id="30200">Wybierz użytkownika</string><!-- Verified --> - <string id="30204">Nieudane połączenie z serwerem</string> - - <string id="30207">Utwory</string> - <string id="30208">Albumy</string> - <string id="30209">Wykonawcy albumów</string> - <string id="30210">Wykonawcy</string> - <string id="30211">Gatunki muzyki</string> - - <string id="30220">Ostatnio dodane</string> - <string id="30221">W trakcie oglądania</string> - <string id="30222">Kolejne odcinki</string> - <string id="30223">Widoki użytkownika</string> - <string id="30224">Metryki raportowe</string> - - <string id="30227">Filmy losowe</string> - <string id="30228">Odcinki losowe</string> - <string id="30229">Pozycje losowe</string><!-- Verified --> - <string id="30230">Pozycje polecane</string><!-- Verified --> - - <string id="30235">Dodatkowe</string><!-- Verified --> - <string id="30236">Synchronizuj motywy muzyczne</string> - <string id="30237">Synchronizuj dodatkowe fototapety</string> - <string id="30238">Synchronizuj serie filmowe</string> - - <string id="30239">Wyczyść lokalną bazę</string><!-- Verified --> - <string id="30243">Używaj połączenia HTTPS</string><!-- Verified --> - <string id="30245">Wymuszaj kodeki transkodowania</string> - - <string id="30249">Pokazuj powiadomienie o połączeniu z serwerem</string><!-- Verified --> - - <string id="30251">Nagrania prywatne ostatnio dodane</string><!-- Verified --> - <string id="30252">Obrazy ostatnio dodane</string><!-- Verified --> - <string id="30253">Nagrania prywatne ulubione</string><!-- Verified --> - <string id="30254">Obrazy ulubione</string><!-- Verified --> - <string id="30255">Albumy ulubione</string> - - <string id="30256">Teledyski ostatnio dodane</string><!-- Verified --> - <string id="30257">Teledyski w trakcie oglądania</string><!-- Verified --> - <string id="30258">Teledyski nieodtwarzane</string><!-- Verified --> - - <!-- Default views --> - <string id="30300">Aktywne</string> - <string id="30301">Wyczyść ustawienia</string> - <string id="30302">Filmy</string> - <string id="30303">Serie filmowe</string> - <string id="30304">Zwiastuny</string> - <string id="30305">Seriale</string> - <string id="30306">Sezony</string> - <string id="30307">Odcinki</string> - <string id="30308">Wykonawcy</string> - <string id="30309">Albumy</string> - <string id="30310">Teledyski</string> - <string id="30311">Utwory</string> - <string id="30312">Kanały</string> - - <!-- contextmenu --> - <string id="30401">Opcje Emby</string> - <string id="30405">Dodaj do ulubionych</string> - <string id="30406">Usuń z ulubionych</string> - <string id="30407">Ustaw ocenę utworu</string> - <string id="30408">Ustawienia dodatku</string> - <string id="30409">Usuń pozycję z serwera</string> - <string id="30410">Odśwież pozycję</string> - <string id="30411">Ustaw ocenę utworu (0-5)</string> - <string id="30412">Transkoduj</string> - - <!-- add-on settings --> - <string id="30500">Weryfikuj certyfikat SSL serwera</string> - <string id="30501">Certyfikat SSL klienta</string> - <string id="30502">Używaj alternatywnego adresu</string> - <string id="30503">Alternatywny adres serwera</string> - <string id="30504">Używaj alternatywnej nazwy urządzenia</string> - <string id="30505">[COLOR yellow]Ponów próbę logowania[/COLOR]</string> - <string id="30506">Synchronizacja</string> - <string id="30507">Pokazuj postęp dla liczby pozycji powyżej</string> - <string id="30508">Synchronizuj seriale bez odcinków</string> - <string id="30509">Aktywuj bibliotekę muzyki</string> - <string id="30510">Bezpośrednia transmisja biblioteki muzyki</string> - <string id="30511">Tryb odtwarzania</string> - <string id="30512">Wymuszaj buforowanie grafik</string> - <string id="30513">Limit wątków bufora grafik (rekomendowane dla RPi)</string> - <string id="30514">Aktywuj szybkie uruchamianie (wymagana wtyczka serwera)</string> - <string id="30515">Limit pozycji pobieranych z serwera per wątek</string> - <string id="30516">Odtwarzanie</string> - <string id="30517">Poświadczenia sieciowe</string> - <string id="30518">Aktywuj Tryb kinowy Emby</string> - <string id="30519">Potwierdzaj odtwarzanie zwiastunów</string> - <string id="30520">Pomijaj potwierdzenie usuwania dla menu kontekstowego (używasz na własne ryzyko)</string> - <string id="30521">Cofaj przed wznowieniem odtwarzania (w sekundach)</string> - <string id="30522">Wymuszaj transkodowanie H265</string> - <string id="30523">Opcje metadanych muzyki (niekompatybilne z transmisją bezpośrednią)</string> - <string id="30524">Importuj ocenę utworów z plików</string> - <string id="30525">Konwertuj ocenę utworu do oceny Emby</string> - <string id="30526">Zezwalaj na aktualizację oceny w plikach utworów</string> - <string id="30527">Pomijaj odcinki specjalne na liście kolejnych odcinków</string> - <string id="30528">Stali użytkownicy dodawani do sesji</string> - <string id="30529">Opóźniaj uruchomienie (w sekundach)</string> - <string id="30530">Pokazuj powiadomienie o ponownym uruchomieniu serwera</string> - <string id="30531">Pokazuj powiadomienie o nowej zawartości</string> - <string id="30532">Czas widoczności powiadomień biblioteki wideo (w sekundach)</string> - <string id="30533">Czas widoczności powiadomień biblioteki muzyki (w sekundach)</string> - <string id="30534">Powiadomienia serwera</string> - <string id="30535">Generuj nowy identyfikator urządzenia</string> - <string id="30536">Synchronizuj podczas nieaktywności wygaszacza ekranu</string> - <string id="30537">Wymuszaj transkodowanie Hi10P</string> - <string id="30538">Nieaktywne</string> - <string id="30539">Logowanie</string> - <string id="30540">Logowanie manualne</string> - <string id="30541">Emby Connect</string> - <string id="30542">Serwer</string> - <string id="30543">Nazwa użytkownika lub adres pocztowy</string> - <string id="30544">Aktywuj poprawkę blokowania bazy (zwalnia proces synchronizacji)</string> - <string id="30545">Pokazuj powiadomienie o niedostępności serwera</string> - <string id="30546">Aktywuj rejestrowanie metryk analitycznych</string> - <string id="30547">Wyświetlaj powiadomienia (w sekundach)</string> - <string id="30548">Liczba wątków pobierania (rekomendowane: 2-3)</string> - - <!-- dialogs --> - <string id="30600">Logowanie Emby Connect</string> - <string id="30602">Hasło</string> - <string id="30603">Prosimy o zapoznanie się z warunkami użytkowania. Korzystanie z oprogramowania Emby oznacza akceptację tych warunków.</string> - <string id="30604">Skanuj</string> - <string id="30605">Zaloguj</string> - <string id="30606">Anuluj</string> - <string id="30607">Wybierz serwer podstawowy</string> - <string id="30608">Nazwa użytkownika i hasło nie mogą być puste</string> - <string id="30609">Nieudane połączenie z wybranym serwerem</string> - <string id="30610">Połącz z</string><!-- Connect to {server} --> - <string id="30611">Dodaj serwer manualnie</string> - <string id="30612">Prosimy o zalogowanie</string> - <string id="30613">Nazwa użytkownika nie może być pusta</string> - <string id="30614">Połącz z serwerem</string> - <string id="30615">Serwer</string> - <string id="30616">Połącz</string> - <string id="30617">Serwer i port nie mogę być puste</string> - <string id="30618">Zmień użytkownika Emby Connect</string> - - <!-- service add-on --> - <string id="33000">Witaj</string> - <string id="33001">Połączenie nieudane</string> - <string id="33002">Serwer jest niedostępny</string> - <string id="33003">Serwer jest dostępny</string> - <string id="33004">pozycji dodanych do listy odtwarzania</string> - <string id="33005">pozycji dodanych do kolejki do listy odtwarzania</string> - <string id="33006">Trwa ponowne uruchomienie serwera</string> - <string id="33007">Dostęp aktywowany</string> - <string id="33008">Wprowadź hasło użytkownika:</string> - <string id="33009">Niepoprawna nazwa lub hasło użytkownika</string> - <string id="33010">Przekroczono limit nieudanych prób uwierzytelnienia</string> - <string id="33011">Nieudana bezpośrednia transmisja pliku</string> - <string id="33012">Nieudana 3 próba bezpośredniej transmisji. Aktywowano transmisję za pomocą protokołu HTTP.</string> - <string id="33013">Wybierz ścieżkę dźwiękową</string> - <string id="33013">Wybierz ścieżkę napisów</string> - <string id="33015">Czy usunąć plik z serwera Emby?</string> - <string id="33016">Czy odtwarzać zwiastuny?</string> - <string id="33017">Pobieranie filmów z:</string> - <string id="33018">Pobieranie serii filmowych</string> - <string id="33019">Pobieranie teledysków z:</string> - <string id="33020">Pobieranie seriali z:</string> - <string id="33021">Pobieranie:</string> - <string id="33022">Wykryta baza wymaga przebudowy dla aktualnej wersji dodatku Emby. Kontynuować?</string> - <string id="33023">Dodatek Emby może działać nieprawidłowo do czasu wyczyszczenia bazy.</string> - <string id="33024">Trwa anulowanie procesu synchronizacji bazy. Aktualna wersja Kodi nie jest obsługiwana.</string> - <string id="33025">zakończono w ciągu:</string> - <string id="33026">Trwa porównywanie listy filmów z:</string> - <string id="33027">Trwa porównywanie listy serii filmowych</string> - <string id="33028">Trwa porównywanie listy teledysków z:</string> - <string id="33029">Trwa porównywanie listy seriali z:</string> - <string id="33030">Trwa porównywanie listy odcinków z:</string> - <string id="33031">Trwa porównywanie:</string> - <string id="33032">Generowanie nowego identyfikatora urządzenia zakończone niepowodzeniem. Sprawdź szczegóły w pliku dziennika diagnostycznego.</string> - <string id="33033">Generowanie nowego identyfikatora urządzenia zakończone powodzeniem. Kodi zostanie teraz uruchomione ponownie.</string> - - <string id="33034">Kontynuować z następnym serwerem?</string> - <string id="33035">Uwaga! W przypadku wyboru trybu natywnego, niektóre funkcje Emby będą niedostępne, w tym: Tryb kinowy, transmisje bezpośrednie i transkodowanie, harmonogram dostępu kontroli rodzicielskiej.</string> - <string id="33036">Dodatek (Domyślne)</string> - <string id="33037">Natywne (Udziały sieciowe)</string> - <string id="33038">Czy dodać poświadczenia sieciowe, aby umożliwić Kodi bezpośredni dostęp do zawartości? Uwaga: Kodi będzie wymagać ponownego uruchomienia, aby móc korzystać z poświadczeń. Poświadczenia mogą zostać dodane później.</string> - <string id="33039">Czy dezaktywować bibliotekę muzyki Emby?</string> - <string id="33040">Czy transmitować bibliotekę muzyki bezpośrednio? Wybierz tę opcję w przypadku, gdy biblioteka muzyki jest dostępna zdalnie.</string> - <string id="33041">Czy usunąć plik(i) z serwera Emby? Spowoduje to także usunięcie pliku(ów) z dysku!</string> - <string id="33042">Proces buforowania może trwać bardzo długo. Czy mimo to kontynuować?</string> - <string id="33043">Synchronizacja bufora grafik</string> - <string id="33044">Czy wyczyścić istniejący bufor grafik?</string> - <string id="33045">Trwa aktualizacja bufora grafik:</string> - <string id="33046">Trwa oczekiwanie na zakończenie wszystkich procesów:</string> - <string id="33047">Kodi nie może zlokalizować pliku:</string> - <string id="33048">Poświadczenia sieciowe wymagają weryfikacji w ustawieniach dodatku lub skorzystaj z mapowania ścieżek, w celu poprawnego formatowania ścieżek (Kokpit Emby > Biblioteka). Zatrzymać synchronizację?</string> - <string id="33049">Dodano:</string> - <string id="33050">W przypadku przekroczonego limitu prób logowania, Twoje konto może zostać zablokowane na serwerze Emby. Czy mimo to kontynuować?</string> - <string id="33051">Kanały telewizyjne (eksperymentalne)</string> - <string id="33052">Nagrania telewizyjne (eksperymentalne)</string> - <string id="33053">Ustawienia</string> - <string id="33054">Dodaj użytkownika do sesji</string> - <string id="33055">Odśwież listy odtwarzania i bibliotekę wideo</string> - <string id="33056">Uruchom synchronizację</string> - <string id="33057">Napraw lokalną bazę (wymusza aktualizację całej zawartości)</string> - <string id="33058">Uruchom czyszczenie lokalnej bazy</string> - <string id="33059">Buforuj wszystkie grafiki</string> - <string id="33060">Synchronizuj motywy muzyczne</string> - <string id="33061">Dodaj/Usuń użytkownika</string> - <string id="33062">Dodaj użytkownika</string> - <string id="33063">Usuń użytkownika</string> - <string id="33064">Usuń użytkownika z sesji</string> - <string id="33065">Sukces!</string> - <string id="33066">Usunięto z sesji przeglądania:</string> - <string id="33067">Dodano do sesji przeglądania:</string> - <string id="33068">Nieudane dodawanie/usuwanie użytkownika.</string> - <string id="33069">Zadanie zakończone powodzeniem</string> - <string id="33070">Zadanie zakończone niepowodzeniem</string> - <string id="33071">Transmisja bezpośrednia</string> - <string id="33072">Metoda odtwarzania motywów muzycznych</string> - <string id="33073">Plik ustawień motywów muzycznych nie istnieje. Zmień ustawienie i uruchom zadanie ponownie.</string> - <string id="33074">Czy jesteś pewien, że chcesz wyczyścić lokalną bazę Kodi?</string> - <string id="33075">Modyfikuj/Usuń poświadczenia sieciowe</string> - <string id="33076">Modyfikuj</string> - <string id="33077">Usuń</string> - <string id="33078">Usunięto:</string> - <string id="33079">Wprowadź nazwę użytkownika sieciowego</string> - <string id="33080">Wprowadź hasło użytkownika sieciowego</string> - <string id="33081">Dodano poświadczenia sieciowe dla:</string> - <string id="33082">Wprowadź nazwę lub adres IP serwera - podanych w ścieżkach biblioteki serwera Emby. Na przykład, nazwą serwera \\\\Mój-Server\\ścieżka\\ jest "Mój-Server"</string> - <string id="33083">Modyfikuj nazwę serwera lub adres IP</string> - <string id="33084">Wprowadź nazwę serwera lub adres IP</string> - <string id="33085">Wyczyszczenie bazy było niemożliwe. Spróbuj ponownie.</string> - <string id="33086">Czy usunąć z bufora wszystkie grafiki?</string> - <string id="33087">Czy wyczyścić wszystkie ustawienia dodatku?</string> - <string id="33088">Czyszczenie bazy mediów zakończone powodzeniem, teraz nastąpi ponowne uruchomienie Kodi, w celu zastosowania zmian.</string> - <string id="33089">Wprowadź nazwę folderu kopii zapasowej</string> - <string id="33090">Czy zastąpić istniejącą kopię zapasową?</string> - <string id="33091">Utwórz kopię zapasową o:</string> - <string id="33092">Utwórz kopię zapasową</string> - <string id="33093">Folder kopii zapasowej</string> - <string id="33094">Wybierz typ zawartości do naprawy</string> - <string id="33095">Nieudane pobieranie najnowszych aktualizacji przy pomocy szybkiej sychronizacji, rozpoczęto pełną sychronizację.</string> - <string id="33096">Ograniczaj rozdzielczość wideo do rozdzielczości wyświetlacza</string> - <string id="33097">Uwaga! Opcja cleanonupdate została usunięta z pliku ustawień zaawansowanych, aby zapobiec konfliktom z dodatkiem Emby. Kodi zostanie teraz uruchomione ponownie.</string> - -</strings> \ No newline at end of file diff --git a/resources/language/Portuguese/strings.xml b/resources/language/Portuguese/strings.xml deleted file mode 100644 index fe9800f1..00000000 --- a/resources/language/Portuguese/strings.xml +++ /dev/null @@ -1,350 +0,0 @@ -<?xml version="1.0" encoding="utf-8" standalone="yes"?> -<strings> - <!-- Add-on settings --> - <string id="29999">Emby for Kodi</string> - <string id="30000">Endereço do Servidor Primário</string> - <string id="30002">Reproduzir por HTTP ao invés de SMB</string> - <string id="30004">Nível do log</string> - <string id="30016">Nome do Dispositivo</string> - <string id="30022">Avançado</string> - <string id="30024">Nome do usuário</string> - <string id="30030">Número da Porta</string> - <string id="30035">Número de Álbuns de Música recentes a exibir:</string> - <string id="30036">Número de Filmes recentes a exibir:</string> - <string id="30037">Número de episódios de TV recentes a exibir:</string> - <string id="30042">Atualizar</string> - <string id="30043">Excluir</string> - <string id="30044">Nome de usuário/Senha incorretos</string> - <string id="30045">Nome de usuário não encontrado</string> - <string id="30052">Excluindo</string> - <string id="30053">Aguardando pelo servidor para excluir</string> - <string id="30068">Classificar por</string> - <string id="30069">Nenhum</string> - <string id="30070">Ação</string> - <string id="30071">Aventura</string> - <string id="30072">Animação</string> - <string id="30073">Crime</string> - <string id="30074">Comédia</string> - <string id="30075">Documentário</string> - <string id="30076">Drama</string> - <string id="30077">Fantasia</string> - <string id="30078">Estrangeiro</string> - <string id="30079">História</string> - <string id="30080">Terror</string> - <string id="30081">Música</string> - <string id="30082">Musical</string> - <string id="30083">Mistério</string> - <string id="30084">Romance</string> - <string id="30085">Ficção Científica</string> - <string id="30086">Curta Metragem</string> - <string id="30087">Suspense</string> - <string id="30088">Suspense</string> - <string id="30089">Western</string> - <string id="30090">Filtro do Gênero</string> - <string id="30091">Confirmar exclusão do arquivo</string> - <!-- Verified --> - <string id="30093">Marcar como assistido</string> - <string id="30094">Marcar como não-assistido</string> - <string id="30097">Classificar por</string> - <string id="30098">Classificar em Ordem Descendente</string> - <string id="30099">Classificar em Ordem Ascendente</string> - <!-- resume dialog --> - <string id="30105">Retomar</string> - <string id="30106">Retomar a partir de</string> - <string id="30107">Iniciar do começo</string> - <string id="30114">Disponibilizar exclusão depois da reprodução</string> - <!-- Verified --> - <string id="30115">Para Episódios</string> - <!-- Verified --> - <string id="30116">Para Filmes</string> - <!-- Verified --> - <string id="30118">Adicionar a Porcentagem para Retomar</string> - <string id="30119">Adicionar o Número do Episódio</string> - <string id="30120">Exibir Progresso do Carregamento</string> - <string id="30121">Carregando Conteúdo</string> - <string id="30122">Recuperando Dados</string> - <string id="30125">Feito</string> - <string id="30132">Aviso</string> - <!-- Verified --> - <string id="30135">Erro</string> - <string id="30138">Busca</string> - <string id="30157">Ativar Imagens Melhoradas (ex. Capa)</string> - <!-- Verified --> - <string id="30158">Metadados</string> - <string id="30159">Artwork</string> - <string id="30160">Qualidade do Vídeo</string> - <!-- Verified --> - <string id="30165">Reprodução Direta</string> - <!-- Verified --> - <string id="30166">Transcodificação</string> - <string id="30167">Sucesso na Detecção do Servidor</string> - <string id="30168">Servidor Encontrado</string> - <string id="30169">Endereço:</string> - <!-- Video nodes --> - <string id="30170">Séries Recentemente Adicionadas</string> - <!-- Verified --> - <string id="30171">Séries em Reprodução</string> - <!-- Verified --> - <string id="30172">Todas as Músicas</string> - <string id="30173">Canais</string> - <!-- Verified --> - <string id="30174">Filmes Recentemente Adicionados</string> - <!-- Verified --> - <string id="30175">Episódios Recentemente Adicionados</string> - <!-- Verified --> - <string id="30176">Álbuns Recentemente Adicionados</string> - <string id="30177">Filmes em Reprodução</string> - <!-- Verified --> - <string id="30178">Episódios em Reprodução</string> - <!-- Verified --> - <string id="30179">Próximos Episódios</string> - <!-- Verified --> - <string id="30180">Filmes Favoritos</string> - <!-- Verified --> - <string id="30181">Séries Favoritas</string> - <!-- Verified --> - <string id="30182">Episódios Favoritos</string> - <string id="30183">Álbuns Mais Reproduzidos</string> - <string id="30184">Séries a Estrear</string> - <string id="30185">BoxSets</string> - <string id="30186">Trailers</string> - <string id="30187">Vídeos de Música</string> - <string id="30188">Fotos</string> - <string id="30189">Filmes Não-Assistidos</string> - <!-- Verified --> - <string id="30190">Gêneros do Filme</string> - <string id="30191">Estúdios do Filme</string> - <string id="30192">Atores do Filme</string> - <string id="30193">Episódios Não-Assistidos</string> - <string id="30194">Gêneros da Série</string> - <string id="30195">Redes de TV</string> - <string id="30196">Atores da Série</string> - <string id="30197">Listas de Reprodução</string> - <string id="30199">Definir Visualizações</string> - <string id="30200">Selecionar Usuário</string> - <!-- Verified --> - <string id="30204">Não foi possível conectar ao servidor</string> - <string id="30207">Músicas</string> - <string id="30208">Álbuns</string> - <string id="30209">Artistas do Álbum</string> - <string id="30210">Artistas</string> - <string id="30211">Gêneros da Música</string> - <string id="30220">Mais Recentes</string> - <string id="30221">Em Reprodução</string> - <string id="30222">Próxima</string> - <string id="30223">Visualizações do Usuário</string> - <string id="30224">Métricas do Relatório</string> - <string id="30227">Filmes Aleatórios</string> - <string id="30228">Episódios Aleatórios</string> - <string id="30229">Itens Aleatórios</string> - <!-- Verified --> - <string id="30230">Itens Recomendados</string> - <!-- Verified --> - <string id="30235">Extras</string> - <!-- Verified --> - <string id="30236">Sincronizar Música-Tema</string> - <string id="30237">Sincronizar Extra Fanart</string> - <string id="30238">Sincronizar Imagens de Coletâneas</string> - <string id="30239">Repor base de dados local do Kodi</string> - <!-- Verified --> - <string id="30243">Ativar HTTPS</string> - <!-- Verified --> - <string id="30245">Forçar Codecs de Transcodificação</string> - <string id="30249">Ativar mensagem de conexão do servidor ao iniciar</string> - <!-- Verified --> - <string id="30251">Vídeos Caseiros adicionados recentemente</string> - <!-- Verified --> - <string id="30252">Fotos adicionadas recentemente</string> - <!-- Verified --> - <string id="30253">Vídeos Caseiros Favoritos</string> - <!-- Verified --> - <string id="30254">Fotos Favoritas</string> - <!-- Verified --> - <string id="30255">Álbuns Favoritos</string> - <string id="30256">Vídeos de Música adicionados recentemente</string> - <!-- Verified --> - <string id="30257">Vídeos de Música em Reprodução</string> - <!-- Verified --> - <string id="30258">Vídeos de Música não-assistidos</string> - <!-- Verified --> - <!-- Default views --> - <string id="30300">Ativo</string> - <string id="30301">Limpar Definições</string> - <string id="30302">Filmes</string> - <string id="30303">BoxSets</string> - <string id="30304">Trailers</string> - <string id="30305">Séries</string> - <string id="30306">Temporadas</string> - <string id="30307">Episódios</string> - <string id="30308">Artistas da Música</string> - <string id="30309">Álbuns de Música</string> - <string id="30310">Vídeos de Música</string> - <string id="30311">Trilhas de Música</string> - <string id="30312">Canais</string> - <!-- contextmenu --> - <string id="30401">Opções do Emby</string> - <string id="30405">Adicionar aos favoritos do Emby</string> - <string id="30406">Remover dos Favoritos do Emby</string> - <string id="30407">Definir a avaliação personalizada da música</string> - <string id="30408">Ajustes do addon do Emby</string> - <string id="30409">Excluir o item do servidor</string> - <string id="30410">Atualizar este item</string> - <string id="30411">Definir a avaliação personalizada da música (0-5)</string> - <!-- add-on settings --> - <string id="30500">Verificar o Certificado SSL do Servidor</string> - <string id="30501">Certificado SSL do cliente</string> - <string id="30502">Usar endereço alternativo</string> - <string id="30503">Endereço Alternativo do Servidor</string> - <string id="30504">Usar Nome alternativo do dispositivo</string> - <string id="30505">[COLOR yellow]Tentar Login Novamente[/COLOR]</string> - <string id="30506">Opções de Sincronização</string> - <string id="30507">Exibir progresso se a contagem de itens for maior que</string> - <string id="30508">Sincronizar Séries de TV vazias</string> - <string id="30509">Ativar Biblioteca de Música</string> - <string id="30510">Stream direto da biblioteca de música</string> - <string id="30511">Modo de Reprodução</string> - <string id="30512">Forçar o caching de artwork</string> - <string id="30513">Limitar as threads de cache de artwork (recomendado para rpi)</string> - <string id="30514">Ativar inicialização rápida (é necessário o plugin do servidor)</string> - <string id="30515">Máximo de itens para solicitar ao servidor de uma única vez</string> - <string id="30516">Reprodução</string> - <string id="30517">Credenciais de rede</string> - <string id="30518">Ativar cinema mode do Emby</string> - <string id="30519">Confirmar a reprodução de trailers</string> - <string id="30520">Ignorar a confirmação de exclusão no Emby para o menú de contexto (use sob seu próprio risco)</string> - <string id="30521">intervalo para voltar na função retomar (em segundos)</string> - <string id="30522">Foçar transcodificação H265</string> - <string id="30523">Opções de metadados de música (não compatível com stream direto)</string> - <string id="30524">Importar avaliação da música diretamente dos arquivos</string> - <string id="30525">Converter a avaliação da música para a avaliação Emby.</string> - <string id="30526">Permitir que as avaliações nos arquivos de música sejam atualizadas</string> - <string id="30527">Ignorar especiais nos próximos episódios</string> - <string id="30528">Usuários permanentes para adicionar à sessão</string> - <string id="30529">Atraso na inicialização (em segundos)</string> - <string id="30530">Ativar mensagem de reinicialização do servidor</string> - <string id="30531">Ativar notificação de novo conteúdo</string> - <string id="30532">Duração do janela da biblioteca de vídeo (em segundos)</string> - <string id="30533">Duração da janela da biblioteca de música (em segundos)</string> - <string id="30534">Mensagens do servidor</string> - <string id="30535">Gerar um novo id do dispositivo</string> - <string id="30536">Sincronizar quando o protetor de tela está desativado</string> - <string id="30537">Forçar Transcodificação Hi10P</string> - <string id="30538">Desativado</string> - <string id="30539">Login</string> - <string id="30540">Login Manual</string> - <string id="30541">Emby Connect</string> - <string id="30542">Servidor</string> - <string id="30543">Nome do usuário ou email</string> - <string id="30544">Ativar a correção para base de dados bloqueada (deixará o processo de sincronização mais lento)</string> - <string id="30545">Ativar mensagens offline do servidor</string> - <!-- dialogs --> - <string id="30600">Entrar com o Emby Connect</string> - <string id="30602">Senha</string> - <string id="30603">Por favor leia os termos de uso. O uso de qualquer software Emby constitui a aceitação desses termos.</string> - <string id="30604">Rastrear-me</string> - <string id="30605">Entrar</string> - <string id="30606">Cancelar</string> - <string id="30607">Selecione o servidor principal</string> - <string id="30608">O nome do usuário ou senha não podem estar em branco</string> - <string id="30609">Não foi possível conectar ao servidor selecionado</string> - <string id="30610">Conectar a</string> - <!-- Connect to {server} --> - <string id="30611">Adicionar servidor manualmente</string> - <string id="30612">Por favor, inicie a sessão</string> - <string id="30613">O nome do usuário não pode estar em branco</string> - <string id="30614">Conectar ao servidor</string> - <string id="30615">Servidor</string> - <string id="30616">Conectar</string> - <string id="30617">O servidor ou porta não podem estar em branco</string> - <string id="30618">Alterar o usuário do Emby Connect</string> - <!-- service add-on --> - <string id="33000">Bem vindo</string> - <string id="33001">Erro na conexão</string> - <string id="33002">Servidor não pode ser encontrado</string> - <string id="33003">Servidor está ativo</string> - <string id="33004">Itens adicionados à lista de reprodução</string> - <string id="33005">Itens enfileirados na lista de reprodução</string> - <string id="33006">Servidor está reiniciando</string> - <string id="33007">O acesso está ativo</string> - <string id="33008">Digite a senha do usuário:</string> - <string id="33009">Nome de usuário ou senha inválidos</string> - <string id="33010">Falha ao autenticar inúmeras vezes</string> - <string id="33011">Não é possível a reprodução direta</string> - <string id="33012">A reprodução direta falhou 3 vezes. Ative a reprodução por HTTP.</string> - <string id="33013">Escolha o stream de áudio</string> - <string id="33014">Escolha o stream de legendas</string> - <string id="33015">Excluir arquivo de seu Servidor Emby?</string> - <string id="33016">Reproduzir trailers?</string> - <string id="33017">Coletando filmes de:</string> - <string id="33018">Coletando boxsets</string> - <string id="33019">Coletando vídeos de música de:</string> - <string id="33020">Coletando séries de:</string> - <string id="33021">Coletando:</string> - <string id="33022">Foi detectado que a base de dados necessita ser recriada para esta versão do Emby for Kodi. Prosseguir?</string> - <string id="33023">O Emby for Kodi pode não funcionar corretamente até que a base de dados seja atualizada.</string> - <string id="33024">Cancelando o processo de sincronização da base de dados. A versão atual do Kodi não é suportada.</string> - <string id="33025">completa em:</string> - <string id="33026">Comparando filmes de:</string> - <string id="33027">Comparando coletâneas</string> - <string id="33028">Comparando vídeos clipes de:</string> - <string id="33029">Comparando séries de tv de:</string> - <string id="33030">Comparando episódios de:</string> - <string id="33031">Comparando:</string> - <string id="33032">Falha ao gerar um novo Id de dispositivo. Veja seus logs para mais informações.</string> - <string id="33033">Um novo Id de dispositivo foi gerado. Kodi irá reiniciar.</string> - <string id="33034">Prosseguir com o seguinte servidor?</string> - <string id="33035">Cuidado! Se escolher modo Nativo, certas funções do Emby serão perdidas, como: Emby cinema mode, opções de stream/transcodificação direta e agendamento de acesso pelos pais.</string> - <string id="33036">Addon (Padrão)</string> - <string id="33037">Nativo (Locais Diretos)</string> - <string id="33038">Adicionar credenciais de rede para permitir que o Kodi acesse seu conteúdo? Importante: Kodi precisará ser reiniciado para ver as credenciais. Elas também podem ser adicionadas mais tarde.</string> - <string id="33039">Desativar biblioteca de músicas do Emby?</string> - <string id="33040">Fazer stream direto da biblioteca de músicas? Selecione esta opção se a biblioteca de músicas for acessada remotamente.</string> - <string id="33041">Excluir arquivo(s) do Servidor Emby? Esta opção também excluirá o(s) arquivo(s) do disco!</string> - <string id="33042">Executar o processo de caching pode levar bastante tempo. Continuar assim mesmo?</string> - <string id="33043">Sincronização do cache de artwork</string> - <string id="33044">Limpar cache de artwork atual?</string> - <string id="33045">Atualizando o cache de artwork:</string> - <string id="33046">Aguardando que todos os processos terminem:</string> - <string id="33047">Kodi não pode localizar o arquivo:</string> - <string id="33048">Você precisa verificar suas credenciais de rede nos ajustes de add-on ou usar substituição de locais no Emby para formatar seu local corretamente (Painel Emby > biblioteca). Parar de sincronizar?</string> - <string id="33049">Adicionado:</string> - <string id="33050">Se falhar para entrar diversas vezes, o servidor Emby pode bloquear sua conta, Deseja prosseguir?</string> - <string id="33051">Canais de TV ao Vivo (experimental)</string> - <string id="33052">Gravações de TV ao Vivo (experimental)</string> - <string id="33053">Ajustes</string> - <string id="33054">Adicionar usuário à sessão</string> - <string id="33055">Atualizar nós de listas de reprodução/Vídeo</string> - <string id="33056">Executar sincronização manual</string> - <string id="33057">Reparar base de dados local (forçar atualização para todo o conteúdo)</string> - <string id="33058">Executar reset da base de dados local</string> - <string id="33059">Colocar toda artwork no cache</string> - <string id="33060">Sincronizar Mídia de Tema do Emby para o Kodi</string> - <string id="33061">Adicionar/Remover usuário da sessão</string> - <string id="33062">Adicionar usuário</string> - <string id="33063">Remover usuário</string> - <string id="33064">Remover usuário da sessão</string> - <string id="33065">Sucesso!</string> - <string id="33066">Removido da seguinte sessão:</string> - <string id="33067">Adicionado à seguinte sessão:</string> - <string id="33068">Não foi possível adicionar/remover usuário da sessão.</string> - <string id="33069">Sucesso na tarefa</string> - <string id="33070">Falha na tarefa</string> - <string id="33071">Stream Direto</string> - <string id="33072">Método de reprodução para seus temas</string> - <string id="33073">O arquivo de ajustes não existe na TV Tunes. Altere o ajuste e execute a tarefa novamente.</string> - <string id="33074">Deseja realmente resetar a base de dados local do Kodi?</string> - <string id="33075">Modificar/Remover as credenciais de rede</string> - <string id="33076">Modificar</string> - <string id="33077">Remover</string> - <string id="33078">Removido:</string> - <string id="33079">Digite o nome de usuário da rede</string> - <string id="33080">Digite a senha da rede</string> - <string id="33081">Credenciais de rede adicionadas para:</string> - <string id="33082">Digite o nome do servidor ou endereço IP como indicado nos locais de sua biblioteca do emby. Por exemplo, o nome do servidor \\\\SERVIDOR-PC\\local\\ é \"SERVIDOR-PC\"</string> - <string id="33083">Modificar o nome do servidor ou endereço IP</string> - <string id="33084">Digite o nome do servidor ou endereço IP</string> - <string id="33085">Não é possível resetar a base de dados. Tente novamente.</string> - <string id="33086">Remover toda artwork do cache?</string> - <string id="33087">Resetar todos os ajustes do add-on Emby?</string> - <string id="33088">O reset da base de dados foi concluída, Kodi irá reiniciar para aplicar as alterações.</string> -</strings> diff --git a/resources/language/Russian/strings.xml b/resources/language/Russian/strings.xml deleted file mode 100644 index 92ac4d55..00000000 --- a/resources/language/Russian/strings.xml +++ /dev/null @@ -1,350 +0,0 @@ -<?xml version="1.0" encoding="utf-8" standalone="yes"?> -<strings> - <!-- Add-on settings --> - <string id="29999">Emby для Kodi</string> - <string id="30000">Основной адрес сервера</string> - <string id="30002">Воспроизводить по HTTP вместо SMB</string> - <string id="30004">Уровень журналирования</string> - <string id="30016">Название устройства</string> - <string id="30022">Расширенное</string> - <string id="30024">Имя пользователя</string> - <string id="30030">Номер порта</string> - <string id="30035">Число последних музыкальных альбомов для просмотра:</string> - <string id="30036">Число последних фильмов для просмотра:</string> - <string id="30037">Число последних ТВ-эпизодов для просмотра:</string> - <string id="30042">Подновить</string> - <string id="30043">Удалить</string> - <string id="30044">Неверное Имя пользователя/Пароль</string> - <string id="30045">Имя пользователя не найдено</string> - <string id="30052">Удаляется</string> - <string id="30053">В ожидании удаления с сервера</string> - <string id="30068">Сортировать по</string> - <string id="30069">Никакой</string> - <string id="30070">Действие</string> - <string id="30071">Приключения</string> - <string id="30072">Анимация</string> - <string id="30073">Криминал</string> - <string id="30074">Комедия</string> - <string id="30075">Документалистика</string> - <string id="30076">Драма</string> - <string id="30077">Фэнтези</string> - <string id="30078">Иностранное</string> - <string id="30079">История</string> - <string id="30080">Ужасы</string> - <string id="30081">Музыка</string> - <string id="30082">Мюзикл</string> - <string id="30083">Детектив</string> - <string id="30084">Мелодрама</string> - <string id="30085">Научная фантастика</string> - <string id="30086">Короткометражное</string> - <string id="30087">Саспенс</string> - <string id="30088">Триллер</string> - <string id="30089">Вестерн</string> - <string id="30090">Фильтровать по жанрам</string> - <string id="30091">Подтверить удаление файла</string> - <!-- Verified --> - <string id="30093">Отметить как просмотренное</string> - <string id="30094">Отметить как непросмотренное</string> - <string id="30097">Сортировать по</string> - <string id="30098">Порядок сортировки по убыванию</string> - <string id="30099">Порядок сортировки по возрастанию</string> - <!-- resume dialog --> - <string id="30105">Возобновить</string> - <string id="30106">Возобн. с</string> - <string id="30107">Начать. с начала</string> - <string id="30114">Предлагать удаление после воспроизведения</string> - <!-- Verified --> - <string id="30115">Для эпизодов</string> - <!-- Verified --> - <string id="30116">Для фильмов</string> - <!-- Verified --> - <string id="30118">Добавить соотношение для возобновления</string> - <string id="30119">Добавить номер эпизода</string> - <string id="30120">Показывать индикатор загрузки</string> - <string id="30121">Загружается содержание</string> - <string id="30122">Получение данных</string> - <string id="30125">Готово</string> - <string id="30132">Предупреждение</string> - <!-- Verified --> - <string id="30135">Ошибка</string> - <string id="30138">Поиск</string> - <string id="30157">Включить улучшенные рисунки (нп., CoverArt)</string> - <!-- Verified --> - <string id="30158">Метаданные</string> - <string id="30159">Иллюстрация</string> - <string id="30160">Качество видео</string> - <!-- Verified --> - <string id="30165">Прямое воспроизведение</string> - <!-- Verified --> - <string id="30166">Перекодировка</string> - <string id="30167">Успешно обнаружен сервер</string> - <string id="30168">Найден сервер</string> - <string id="30169">Адрес:</string> - <!-- Video nodes --> - <string id="30170">Недавно добавленные ТВ-программы</string> - <!-- Verified --> - <string id="30171">ТВ-программы в процессе</string> - <!-- Verified --> - <string id="30172">Вся музыка</string> - <string id="30173">Каналы</string> - <!-- Verified --> - <string id="30174">Недавно добавленные фильмы</string> - <!-- Verified --> - <string id="30175">Недавно добавленные эпизоды</string> - <!-- Verified --> - <string id="30176">Недавно добавленные альбомы</string> - <string id="30177">Фильмы в процессе</string> - <!-- Verified --> - <string id="30178">Эпизоды в процессе</string> - <!-- Verified --> - <string id="30179">Следующие эпизоды</string> - <!-- Verified --> - <string id="30180">Избранные фильмы</string> - <!-- Verified --> - <string id="30181">Избранные ТВ-программы</string> - <!-- Verified --> - <string id="30182">Избранные эпизоды</string> - <string id="30183">Часто воспроизводимые альбомы</string> - <string id="30184">Ожидаемое</string> - <string id="30185">Коллекции</string> - <string id="30186">Трейлеры</string> - <string id="30187">Муз-ые видео</string> - <string id="30188">Фотографии</string> - <string id="30189">Непросмотренные фильмы</string> - <!-- Verified --> - <string id="30190">Киножанры</string> - <string id="30191">Киностудии</string> - <string id="30192">Киноактёры</string> - <string id="30193">Непросмотренные эпизоды</string> - <string id="30194">Тележанры</string> - <string id="30195">Телесети</string> - <string id="30196">Телеактёры</string> - <string id="30197">Плей-листы</string> - <string id="30199">Назначить виды</string> - <string id="30200">Выбрать пользователя</string> - <!-- Verified --> - <string id="30204">Не удалось подсоединиться к серверу</string> - <string id="30207">Композиции</string> - <string id="30208">Альбомы</string> - <string id="30209">Исполнители альбома</string> - <string id="30210">Исполнители</string> - <string id="30211">Музыкальные жанры</string> - <string id="30220">Последнее</string> - <string id="30221">Выполняется</string> - <string id="30222">Следующее</string> - <string id="30223">Пользовательские виды</string> - <string id="30224">Отчёт метрики</string> - <string id="30227">Случайные фильмы</string> - <string id="30228">Случайные эпизоды</string> - <string id="30229">Случайные элементы</string> - <!-- Verified --> - <string id="30230">Предлагаемые элементы</string> - <!-- Verified --> - <string id="30235">Допматериалы</string> - <!-- Verified --> - <string id="30236">Синхронизировать тематическую музыку</string> - <string id="30237">Синхронизировать дополнительные иллюстрации</string> - <string id="30238">Синхронизировать коллекции фильмов</string> - <string id="30239">Сбросить локальную базу данных Kodi</string> - <!-- Verified --> - <string id="30243">Включить HTTPS</string> - <!-- Verified --> - <string id="30245">Принудительно включить кодеки для перекодировки</string> - <string id="30249">Включать сообщение о подсоединении сервера при запуске</string> - <!-- Verified --> - <string id="30251">Недавно добавленные домашние видео</string> - <!-- Verified --> - <string id="30252">Недавно добавленные фотографии</string> - <!-- Verified --> - <string id="30253">Избранные домашние видео</string> - <!-- Verified --> - <string id="30254">Избранные фотографии</string> - <!-- Verified --> - <string id="30255">Избранные альбомы</string> - <string id="30256">Недавно добавленные музыкальные видео</string> - <!-- Verified --> - <string id="30257">Музыкальные видео в процессе</string> - <!-- Verified --> - <string id="30258">Непросмотренные домашние видео</string> - <!-- Verified --> - <!-- Default views --> - <string id="30300">Активно</string> - <string id="30301">Очистить параметры</string> - <string id="30302">Фильмы</string> - <string id="30303">Коллекции</string> - <string id="30304">Трейлеры</string> - <string id="30305">Сериалы</string> - <string id="30306">Сезоны</string> - <string id="30307">Эпизоды</string> - <string id="30308">Исп-ли музыки</string> - <string id="30309">Муз-ые альбомы</string> - <string id="30310">Муз-ые видео</string> - <string id="30311">Музыкальные дорожки</string> - <string id="30312">Каналы</string> - <!-- contextmenu --> - <string id="30401">Параметры Emby</string> - <string id="30405">Добавить в Избранное Emby</string> - <string id="30406">Изъять из Избранного Emby</string> - <string id="30407">Назначить произвольную возрастную категорию композиции</string> - <string id="30408">Параметры дополнения для Emby</string> - <string id="30409">Удалить элемент с сервера</string> - <string id="30410">Подновить данный элемент</string> - <string id="30411">Назначить произвольную возрастную категорию композиции (0-5)</string> - <!-- add-on settings --> - <string id="30500">Удостоверить SSL-сертификат хоста</string> - <string id="30501">SSL-сертификат клиента</string> - <string id="30502">Использовать альтернативный адрес</string> - <string id="30503">Альтернативный адрес сервера</string> - <string id="30504">Использовать альтернативное имя устройства</string> - <string id="30505">[COLOR yellow]Войти повторно[/COLOR]</string> - <string id="30506">Параметры синхронизации</string> - <string id="30507">Показать прогресс если число элементов более чем</string> - <string id="30508">Синхронизировать пустые ТВ-программы</string> - <string id="30509">Включить медиатеку музыки</string> - <string id="30510">Прямая трансляция медиатеки музыки</string> - <string id="30511">Режим воспроизведения</string> - <string id="30512">Принудительно кэшировать иллюстрации</string> - <string id="30513">Предел ветвей кэша иллюстраций (рекомендуется для rpi)</string> - <string id="30514">Включить быстрый запуск (требуется плагин сервера)</string> - <string id="30515">Максимальное число элементов для запроса с сервера за раз</string> - <string id="30516">Воспроизведение</string> - <string id="30517">Сетевые учётные данные</string> - <string id="30518">Включить режим кинозала Emby</string> - <string id="30519">Запрашивать для воспроизведения трейлеров</string> - <string id="30520">Пропускать подтверждение удаления в Emby для контекстного меню (используйте на свой страх и риск)</string> - <string id="30521">Переход назад при возобновлении, с</string> - <string id="30522">Принудительно перекодировать H265</string> - <string id="30523">Параметры метаданных музыки (несовместимо с прямой трансляцией)</string> - <string id="30524">Импортировать оценку музыкальной композиции из файла</string> - <string id="30525">Преобразовать оценку музыкальной композиции в оценку Emby</string> - <string id="30526">Разрешить обновление оценки в файлах композиций</string> - <string id="30527">Игнорировать спецэпизоды среди следующих эпизодов</string> - <string id="30528">Постоянные пользователи для добавления в сессию</string> - <string id="30529">Задержка запуска, с</string> - <string id="30530">Включить сообщение о перезапуске сервера</string> - <string id="30531">Включить уведомление о новом содержании</string> - <string id="30532">Длительность показа всплывающего окна медиатеки видео, с</string> - <string id="30533">Длительность показа всплывающего окна медиатеки музыки, с</string> - <string id="30534">Сообщения сервера</string> - <string id="30535">Генерировать Id нового устройства</string> - <string id="30536">Синхронизировать, когда отключен хранитель экрана</string> - <string id="30537">Принудительно перекодировать Hi10P</string> - <string id="30538">Выключено</string> - <string id="30539">Вход</string> - <string id="30540">Войти вручную</string> - <string id="30541">Emby Connect</string> - <string id="30542">Сервер</string> - <string id="30543">Имя пользователя или Э-почта</string> - <string id="30544">Включить исправление блокировки базы данных (будет тормозиться процесс синхронизации)</string> - <string id="30545">Включить сообщение об отключении сервера</string> - <!-- dialogs --> - <string id="30600">Вход через Emby Connect</string> - <string id="30602">Пароль</string> - <string id="30603">Ознакомьтесь с нашими Условиями пользования. Использование любого ПО Emby означает принятие этих условий.</string> - <string id="30604">Сканировать меня</string> - <string id="30605">Войти</string> - <string id="30606">Отменить</string> - <string id="30607">Выбор головного сервера</string> - <string id="30608">Имя пользователя или пароль не могут быть пустыми</string> - <string id="30609">Не удалось подсоединиться к выбранному серверу</string> - <string id="30610">Соединение с</string> - <!-- Connect to {server} --> - <string id="30611">Добавить сервер вручную</string> - <string id="30612">Выполните вход</string> - <string id="30613">Имя пользователя не может быть пустым</string> - <string id="30614">Соединение с сервером</string> - <string id="30615">Хост</string> - <string id="30616">Подсоединиться</string> - <string id="30617">Сервр или порт не могут быть пустыми</string> - <string id="30618">Сменить пользователя Emby Connect</string> - <!-- service add-on --> - <string id="33000">Начало работы</string> - <string id="33001">Ошибка соединения</string> - <string id="33002">Сервер недостижим</string> - <string id="33003">Сервер в сети</string> - <string id="33004">Элементы, добавленные в плей-лист</string> - <string id="33005">Элементы в очереди в плей-листе</string> - <string id="33006">Сервер перезапускается</string> - <string id="33007">Доступ включён</string> - <string id="33008">Ввести пароль для пользователя:</string> - <string id="33009">Недопустимое имя пользователя или пароль.</string> - <string id="33010">Не удалось проверить подлинность слишком много раз</string> - <string id="33011">Прямое воспроизведение файла невозможно</string> - <string id="33012">Прямое воспроизведение не удалось 3 раза. Включено воспроизведение с HTTP.</string> - <string id="33013">Выбрать поток аудио</string> - <string id="33014">Выбрать поток субтитров</string> - <string id="33015">Удалить файл с вашего Emby Server?</string> - <string id="33016">Воспроизвести трейлеры?</string> - <string id="33017">Сбор фильмов с:</string> - <string id="33018">Сбор коллекций</string> - <string id="33019">Сбор музыкальных видео с:</string> - <string id="33020">Сбор ТВ-передач с:</string> - <string id="33021">Сбор:</string> - <string id="33022">Обнаружено, что базу данных необходимо пересоздать для данной версии Emby для Kodi. Приступить?</string> - <string id="33023">Emby для Kodi возможно не будет корректно работать до тех пор, пока базу данных не сбросят.</string> - <string id="33024">Процесс синхронизации базы данных отменён. Текущая версия Kodi не поддерживается.</string> - <string id="33025">выполнено в:</string> - <string id="33026">Сравниваются фильмы с:</string> - <string id="33027">Сравниваются коллекции</string> - <string id="33028">Сравниваются музыкальные видео с:</string> - <string id="33029">Сравниваются ТВ-передачи с:</string> - <string id="33030">Сравниваются ТВ-эпизоды с:</string> - <string id="33031">Сравниваются:</string> - <string id="33032">Генерирование Id нового устройства не удалось. Просмотрите ваши журналы для более подробной информации.</string> - <string id="33033">Было сгенерирован Id нового устройства. Kodi теперь перезапустится.</string> - <string id="33034">Приступить к следующему серверу?</string> - <string id="33035">Осторожно! Если вы выбрали режим Собственный, некоторые функции Emby будут отсутствовать, например, режим кинозала Emby, прямой трансляция / варианты перекодировки и расписание доступа.</string> - <string id="33036">Надстройка (по умолчанию)</string> - <string id="33037">Собственный (непосредственные пути)</string> - <string id="33038">Добавить сетевые учётные данные, чтобы разрешить доступ для Kodi к вашему содержанию? Важно: Чтобы увидеть учётные данные, необходимо перезапустить Kodi. Также они могут быть добавлены позднее.</string> - <string id="33039">Отключить музыкальную медиатеку Emby?</string> - <string id="33040">Транслировать напрямую музыкальную медиатеку? Выберите данный вариант, если будет удалённый доступ к музыкальной медиатеке.</string> - <string id="33041">Удалить файл(ы) с Emby Server? Файл(ы) будут удалены также с диска!</string> - <string id="33042">Работающий процесс кэширования может занять некоторое время. Продолжить по-любому?</string> - <string id="33043">Синхронизировать кэш иллюстраций</string> - <string id="33044">Сбросить кэш существующих иллюстраций?</string> - <string id="33045">Обновить кэш иллюстраций:</string> - <string id="33046">В ожидании для всех потоков, чтобы выйти:</string> - <string id="33047">Kodi не может обнаружить файл:</string> - <string id="33048">Вам может понадобиться проверить сетевые учётные данныев настройках надстройки или использовать подстановку путей в Emby, чтобы правильно форматировать свой путь (Инфопанель Emby > Медиатека). Остановить синхронизацию?</string> - <string id="33049">Добавлено:</string> - <string id="33050">Если вам не удалось войти слишком много раз, Emby server может заблокировать вашу учётную запись. Приступить по-любому?</string> - <string id="33051">Эфирные каналы (экспериментально)</string> - <string id="33052">Эфирные записи (экспериментально)</string> - <string id="33053">Параметры</string> - <string id="33054">Добавить пользователя к сеансу</string> - <string id="33055">Подновить узлы плей-листов/видео Emby</string> - <string id="33056">Выполнить ручную синхронизацию</string> - <string id="33057">Исправить локальную базу данных (принудительно обновить всё содержание)</string> - <string id="33058">Выполнить сброс локальной базы данных</string> - <string id="33059">Кэшировать все иллюстрации</string> - <string id="33060">Синхронизировать медиаданные темы Emby с Kodi</string> - <string id="33061">Добавить/Изъять пользователя из сеанса</string> - <string id="33062">Добавить пользователя</string> - <string id="33063">Изъять пользователя</string> - <string id="33064">Изъять пользователя из сеанса</string> - <string id="33065">Успешно!</string> - <string id="33066">Изъято из просматриваемого сеанса:</string> - <string id="33067">Добавлено в просматриваемый сеанс:</string> - <string id="33068">Невозможно добавить/изъять пользователя из сеанса.</string> - <string id="33069">Задача успешно выполнена</string> - <string id="33070">Задачу выполнить не удалось</string> - <string id="33071">Прямая трансляция</string> - <string id="33072">Метод воспроизведения для вашим тем</string> - <string id="33073">Файл параметров отсутствует в TV Tunes. Измените параметр и запустите задачу снова.</string> - <string id="33074">Вы действительно хотите выполнить сброс вашей локальной базы данных Kodi?</string> - <string id="33075">Изменить/Изъять сетевые учётные данные</string> - <string id="33076">Изменить</string> - <string id="33077">Изъять</string> - <string id="33078">Изъято:</string> - <string id="33079">Ввести сетевое имя пользователя</string> - <string id="33080">Ввести сетевой пароль</string> - <string id="33081">Добавлены сетевые учётные данные для:</string> - <string id="33082">Введите имя сервера или IP-адрес, как обозначено в путях в вашей медиатеке Emby. К примеру, имя сервера: \\\\SERVER-PC\\путь\\ является \"SERVER-PC\"</string> - <string id="33083">Изменить имя сервера или IP-адрес</string> - <string id="33084">Ввести имя сервера или IP-адрес</string> - <string id="33085">Не удалось сбросить базу данных. Повторите попытку.</string> - <string id="33086">Удалить все кэшированные иллюстрации?</string> - <string id="33087">Сбросить все параметры надстройки Emby?</string> - <string id="33088">Сброс базы данных завершён, Kodi теперь перезапустится, чтобы применить изменения.</string> -</strings> diff --git a/resources/language/Spanish/strings.xml b/resources/language/Spanish/strings.xml deleted file mode 100644 index c543b734..00000000 --- a/resources/language/Spanish/strings.xml +++ /dev/null @@ -1,329 +0,0 @@ -<?xml version="1.0" encoding="utf-8" standalone="yes"?> -<strings> - <!-- Add-on settings --> - <string id="29999">Emby para Kodi</string> - <string id="30000">Dirección Primaria del Servidor</string> - <!-- Verified --> - <string id="30002">Reproducir desde HTTP en vez de desde SMB</string> - <!-- Verified --> - <string id="30004">Nivel de bitácora</string> - <!-- Verified --> - <string id="30016">Nombre de Dispositivo</string> - <!-- Verified --> - <string id="30022">Avanzado</string> - <string id="30024">Usuario</string> - <!-- Verified --> - <string id="30030">Puerto</string> - <!-- Verified --> - <string id="30035">Cantidad de Álbumes recientes a mostrar:</string> - <string id="30036">Cantidad de Películas recientes a mostrar:</string> - <string id="30037">Cantidad de Episodios recientes a mostrar:</string> - <string id="30042">Refrescar</string> - <string id="30043">Eliminar</string> - <string id="30044">Usuario/Contraseña incorrectos</string> - <string id="30045">Usuario no encontrado</string> - <string id="30052">Eliminando</string> - <string id="30053">Esperando a eliminación en servidor</string> - <string id="30068">Clasificar por</string> - <string id="30069">Ninguno</string> - <string id="30070">Acción</string> - <string id="30071">Aventuras</string> - <string id="30072">Animación</string> - <string id="30073">Crimen</string> - <string id="30074">Comedia</string> - <string id="30075">Documental</string> - <string id="30076">Drama</string> - <string id="30077">Fantasía</string> - <string id="30078">Extranjera</string> - <string id="30079">Historia</string> - <string id="30080">Horror</string> - <string id="30081">Música</string> - <string id="30082">Musical</string> - <string id="30083">Misterio</string> - <string id="30084">Romance</string> - <string id="30085">Ciencia Ficción</string> - <string id="30086">Corto</string> - <string id="30087">Suspenso</string> - <string id="30088">Thriller</string> - <string id="30089">Oeste</string> - <string id="30090">Filtro por Género</string> - <string id="30091">Confirmar eliminación de archivo</string> - <!-- Verified --> - <string id="30093">Marcar como visto</string> - <string id="30094">Marcar como no visto</string> - <string id="30097">Clasificar por</string> - <string id="30098">Orden de clasificación Descendente</string> - <string id="30099">Orden de clasificación Ascendente</string> - <!-- resume dialog --> - <string id="30105">Reanudar</string> - <string id="30106">Reanudar desde</string> - <string id="30107">Iniciar desde el principio</string> - <string id="30114">Ofrecer eliminar luego de reproducción</string> - <!-- Verified --> - <string id="30115">Para Episodios</string> - <!-- Verified --> - <string id="30116">Para Películas</string> - <!-- Verified --> - <string id="30118">Añadir porcentaje de reanudar</string> - <string id="30119">Añadir Número de Episodio</string> - <string id="30120">Mostrar Progreso de Carga</string> - <string id="30121">Cargando Contenido</string> - <string id="30122">Obteniendo Datos</string> - <string id="30125">Completado</string> - <string id="30132">Advertencia</string> - <!-- Verified --> - <string id="30135">Error</string> - <string id="30138">Buscar</string> - <string id="30157">Activar Imágenes Avanzadas (como CoverArt)</string> - <!-- Verified --> - <string id="30158">Metadatos</string> - <string id="30159">Arte</string> - <string id="30160">Calidad de Vídeo</string> - <!-- Verified --> - <string id="30165">Reproducción Directa</string> - <!-- Verified --> - <string id="30166">Transcodificando</string> - <string id="30167">Detección de Servidor Exitosa</string> - <string id="30168">Servidor Encontrado</string> - <string id="30169">Dirección:</string> - <!-- Video nodes --> - <string id="30170">Series de TV Añadidos Recientemente</string> - <!-- Verified --> - <string id="30171">Series de TV En Progreso</string> - <!-- Verified --> - <string id="30172">Toda la Música</string> - <string id="30173">Canales</string> - <!-- Verified --> - <string id="30174">Películas Añadidas Recientemente</string> - <!-- Verified --> - <string id="30175">Episodios Añadidos Recientemente</string> - <!-- Verified --> - <string id="30176">Álbumes Añadidos Recientemente</string> - <string id="30177">Películas En Progreso</string> - <!-- Verified --> - <string id="30178">Episodios En Progreso</string> - <!-- Verified --> - <string id="30179">Próximos Episodios</string> - <!-- Verified --> - <string id="30180">Películas Favoritas</string> - <!-- Verified --> - <string id="30181">Series de TV Favoritas</string> - <!-- Verified --> - <string id="30182">Episodios Favoritos</string> - <string id="30183">Álbumes Reproducidos Frecuentemente</string> - <string id="30184">Series a Estrenar Próximamente</string> - <string id="30185">Sagas</string> - <string id="30186">Tráilers</string> - <string id="30187">Videoclips</string> - <string id="30188">Fotos</string> - <string id="30189">Películas No Vistas</string> - <!-- Verified --> - <string id="30190">Géneros de Películas</string> - <string id="30191">Estudios de Películas</string> - <string id="30192">Actores de Películas</string> - <string id="30193">Episodios No Vistos</string> - <string id="30194">Géneros de Series de TV</string> - <string id="30195">Cadenas de TV</string> - <string id="30196">Actores de Series</string> - <string id="30197">Listas de Reproducción</string> - <string id="30199">Establecer Vistas</string> - <string id="30200">Seleccionar Usuario</string> - <!-- Verified --> - <string id="30204">No se puede conectar con servidor</string> - <string id="30207">Canciones</string> - <string id="30208">Álbumes</string> - <string id="30209">Artistas de Álbumes</string> - <string id="30210">Artistas</string> - <string id="30211">Géneros Musicales</string> - <string id="30220">Últimos</string> - <string id="30221">En Progreso</string> - <string id="30222">NextUp</string> - <string id="30223">Vistas de Usuario</string> - <string id="30224">Reportar Métricas</string> - <string id="30227">Películas Aleatorias</string> - <string id="30228">Episodios Aleatorios</string> - <string id="30229">Ítems Aleatorios</string> - <!-- Verified --> - <string id="30230">Ítems Recomendados</string> - <!-- Verified --> - <string id="30235">Extras</string> - <!-- Verified --> - <string id="30236">Sincronizar Música de Tema</string> - <string id="30237">Sincronizar Extra Fanart</string> - <string id="30238">Sincronizar Sagas</string> - <string id="30239">Restablecer Base de datos local de Kodi</string> - <!-- Verified --> - <string id="30243">Activar HTTPS</string> - <!-- Verified --> - <string id="30245">Forzar Transcodificación de Códecs</string> - <string id="30249">Activar mensaje de conexión con servidor al inicio</string> - <!-- Verified --> - <string id="30251">Vídeos Caseros Añadidos Recientemente</string> - <!-- Verified --> - <string id="30252">Fotos Añadidas Recientemente</string> - <!-- Verified --> - <string id="30253">Vídeos Caseros Favoritos</string> - <!-- Verified --> - <string id="30254">Fotos Favoritas</string> - <!-- Verified --> - <string id="30255">Álbumes Favoritos</string> - <string id="30256">Videoclips Añadidos Recientemente</string> - <!-- Verified --> - <string id="30257">Videoclips En Progreso</string> - <!-- Verified --> - <string id="30258">Videoclips No Vistos</string> - <!-- Verified --> - <!-- Default views --> - <string id="30300">Activo</string> - <string id="30301">Limpiar Configuración</string> - <string id="30302">Películas</string> - <string id="30303">Sagas</string> - <string id="30304">Tráilers</string> - <string id="30305">Series de TV</string> - <string id="30306">Temporadas</string> - <string id="30307">Episodios</string> - <string id="30308">Artistas de Música</string> - <string id="30309">Álbumes</string> - <string id="30310">Videoclips</string> - <string id="30311">Canciones</string> - <string id="30312">Canales</string> - <!-- contextmenu --> - <string id="30401">Opciones Emby</string> - <string id="30405">Añadir a favoritos de Emby</string> - <string id="30406">Eliminar de favoritos de Emby</string> - <string id="30407">Establecer valoración personalizada para canción</string> - <string id="30408">Ajustes de complemento Emby</string> - <string id="30409">Eliminar ítem del servidor</string> - <string id="30410">Refrescar este ítem</string> - <string id="30411">Establecer valoración personalizada de canción (0-5)</string> - <!-- add-on settings --> - <string id="30500">Verifica Certificado SSL del Host</string> - <string id="30501">Certificado SSL del Cliente</string> - <string id="30502">Usar dirección alterna</string> - <string id="30503">Dirección Alterna de Servidor</string> - <string id="30504">Usar Nombre alterno de dispositivo</string> - <string id="30505">[COLOR yellow]Reintentar acceso[/COLOR]</string> - <string id="30506">Opciones de Sincronización</string> - <string id="30507">Mostrar Progreso de sincronización</string> - <string id="30508">Sincronizar Series de TV vacías</string> - <string id="30509">Activar Discoteca</string> - <string id="30510">Transmitir directo la discoteca</string> - <string id="30511">Modo de Reproducción</string> - <string id="30512">Forzar Guardado local (caché) de Arte</string> - <string id="30513">Limitar hilos de guardado local de arte (recomendado para rpi)</string> - <string id="30514">Activar inicio rápido (requiere plugin en el servidor)</string> - <string id="30515">Cantidad máxima de ítems a solicitar al servidor al mismo tiempo</string> - <string id="30516">Reproducción</string> - <string id="30517">Credenciales de Red</string> - <string id="30518">Activar modo Emby cinema</string> - <string id="30519">Preguntar si reproducir tráilers</string> - <string id="30520">Obviar confirmación de eliminación de Emby en menú de contexto (usar a su propio riesgo)</string> - <string id="30521">Intervalo de Salto atrás al reanudar (en segundos)</string> - <string id="30522">Forzar transcodificar H.265</string> - <string id="30523">Opciones de metadatos de Música (no compatible con transmisión directa)</string> - <string id="30524">Importar valoración de canciones directamente desde archivos</string> - <string id="30525">Convertir valoración de canciones a valoración Emby</string> - <string id="30526">Permitir actualización de valoración de canciones en los archivos</string> - <string id="30527">Ignorar especiales en próximos episodios</string> - <string id="30528">Usuarios permanentes a incluir en la sesión</string> - <string id="30529">Retraso en Inicio (en segundos)</string> - <string id="30530">Activar mensaje de reinicio del servidor</string> - <string id="30531">Activar notificación de nuevo contenido</string> - <string id="30532">Duración del popup para la videoteca (en segundos)</string> - <string id="30533">Duración del popup para la discoteca (en segundos)</string> - <string id="30534">Mensajes del Servidor</string> - <string id="30535">Generar un nuevo Id de dispositivo</string> - <string id="30536">Sincronizar cuando salvapantallas esté desactivado</string> - <string id="30537">Forzar Transcodificación de Hi10P</string> - <string id="30538">Desactivado</string> - <!-- service add-on --> - <string id="33000">Bienvenido(a)</string> - <string id="33001">Error de conexión</string> - <string id="33002">Servidor no disponible</string> - <string id="33003">Servidor está en línea</string> - <string id="33004">Ítems añadidos a lista de reproducción</string> - <string id="33005">Ítems encolados a lista de reproducción</string> - <string id="33006">El servidor está reiniciando</string> - <string id="33007">Acceso está activo</string> - <string id="33008">Introduzca contraseña para usuario:</string> - <string id="33009">Usuario o contraseña inválidos</string> - <string id="33010">Demasiados fallos de autenticación</string> - <string id="33011">No es posible reproducción directa</string> - <string id="33012">Reproducción directa falló 3 veces. Activando reproducción desde HTTP.</string> - <string id="33013">Elegir pista de audio</string> - <string id="33014">Elegir pista de subtítulos</string> - <string id="33015">¿Elimiar archivo de sus servidor de Emby?</string> - <string id="33016">¿Reproducir tráilers?</string> - <string id="33017">Obteniendo películas desde:</string> - <string id="33018">Obteniendo sagas</string> - <string id="33019">Obteniendo videoclips desde:</string> - <string id="33020">Obteniendo series de tv desde:</string> - <string id="33021">Obteniendo:</string> - <string id="33022">Se detectó que la base de datos debe ser re-creada para esta versión de Emby para Kodi. ¿Proceder?</string> - <string id="33023">Emby para Kodi podría funcionar incorrectamente hasta que la base de datos sea restablecida.</string> - <string id="33024">Cancelando el proceso de sincronización de la base de datos. La versión actual de Kodi no está soportada.</string> - <string id="33025">completado en:</string> - <string id="33026">Comparando películas desde:</string> - <string id="33027">Comparando sagas</string> - <string id="33028">Comparando videoclips desde:</string> - <string id="33029">Comparando series de tv desde:</string> - <string id="33030">Comparando episodios desde:</string> - <string id="33031">Comparando:</string> - <string id="33032">Falló la generación de un nuevo Id de dispositivo. Ver sus bitácoras para más información.</string> - <string id="33033">Se ha generado un nuevo Id de dispositivo. Kodi reinicará ahora.</string> - <string id="33034">¿Proceder con el servidor a continuación?</string> - <string id="33035">¡Cuidado! Si selecciona modo Nativo, algunas funciones de Emby no estarán presentes, tales como: modo Emby cinema, opciones de reproducción directa/transcodificación y calendario de acceso parental.</string> - <string id="33036">Complemento (Predeterminado)</string> - <string id="33037">Nativo (Rutas Directas)</string> - <string id="33038">¿Añadir credenciales de red para permitir a Kodi acceder a su contenido? Importante: Kodi necesitará ser reiniciado para ver las credenciales. Las mismas también pueden añadirse en otro momento.</string> - <string id="33039">¿Desactivar la discoteca Emby?</string> - <string id="33040">¿Transmisión Directa de la discoteca? Seleccione esta opción si habrá acceso remoto a la discoteca.</string> - <string id="33041">¿Eliminar archivo(s) del servidor Emby? ¡Esto también eliminará el(los) archivo(s) de su disco!</string> - <string id="33042">La ejecución del proceso de guardado local en caché pueder tomar un tiempo. ¿Continuar?</string> - <string id="33043">Sincronización de caché de Arte</string> - <string id="33044">¿Restablecer caché de Arte existente?</string> - <string id="33045">Actualizando caché de arte:</string> - <string id="33046">Esperando a que todos los hilos terminen:</string> - <string id="33047">Kodi no puede localizar el archivo:</string> - <string id="33048">Puede que necesite verificar sus credenciales de red en los ajustes del complemento, o utilizar la sustitución de rutas Emby para formatear correctamente su ruta (Cuadro de Mando Emby > Biblioteca). ¿Detener la sincronización?</string> - <string id="33049">Añadido:</string> - <string id="33050">Si falla demasiadas veces en iniciar la sesión, el servidor Emby puede bloquear su cuenta. ¿Proceder de todos modos?</string> - <string id="33051">Canales de TV En Vivo (experimental)</string> - <string id="33052">Grabaciones de TV En Vivo (experimental)</string> - <string id="33053">Ajustes</string> - <string id="33054">Incluir usuario en la sesión</string> - <string id="33055">Refrescar listas de reproducción/nodos de Vídeo Emby</string> - <string id="33056">Realizar sincronización manual</string> - <string id="33057">Reparar la abse de datos local (forzar la actualización de todo el contenido)</string> - <string id="33058">Realizar restablecimiento de base de datos local</string> - <string id="33059">Guardar localmente en caché todo el arte</string> - <string id="33060">Sincronizar Media de temas Emby con Kodi</string> - <string id="33061">Incluir/Eliminar usuarios de la sesión</string> - <string id="33062">Incluir usuario</string> - <string id="33063">Eliminar usuario</string> - <string id="33064">Eliminar usuario de la sesión</string> - <string id="33065">¡Éxito!</string> - <string id="33066">Eliminado de la sesión:</string> - <string id="33067">Incluido en la sesión:</string> - <string id="33068">No fue posible incluir/eliminar usuario de la sesión</string> - <string id="33069">La tarea completó exitosamente</string> - <string id="33070">La tarea falló</string> - <string id="33071">Transmisión Directa</string> - <string id="33072">Método de reproducción para sus temas</string> - <string id="33073">El archivo de configuración no existe en TV Tunes. Cambie un ajuste y ejecute la tarea de nuevo.</string> - <string id="33074">¿Está seguro(a) que quiere restablecer su base de datos local de Kodi?</string> - <string id="33075">Modificar/Eliminar credenciales de red</string> - <string id="33076">Modificar</string> - <string id="33077">Eliminar</string> - <string id="33078">Eliminadas:</string> - <string id="33079">Introduzca el usuario de red</string> - <string id="33080">Introduzca la contraseña de red</string> - <string id="33081">Se añadieron credenciales de red para:</string> - <string id="33082">Introduzca el nombre del servidor o la dirección IP según se indica en sus rutas de biblioteca Emby. Por ejemplo, el nombre de servidor en la ruta \\\\SERVER-PC\\path\\ es \"SERVER-PC\"</string> - <string id="33083">Modificar el nombre de servidor o la dirección IP</string> - <string id="33084">Introduzca el nombre del servidor o la dirección IP</string> - <string id="33085">No pudo restablecerse la base de datos. Intente nuevamente.</string> - <string id="33086">¿Remover todo el arte del caché?</string> - <string id="33087">¿Restablecer todos los ajustes del complemento Emby?</string> - <string id="33088">Restablecimiento de la base de datos completado. Kodi reiniciará ahora para aplicar los cambios.</string> -</strings> diff --git a/resources/language/Swedish/strings.xml b/resources/language/Swedish/strings.xml deleted file mode 100644 index 8c6c5f88..00000000 --- a/resources/language/Swedish/strings.xml +++ /dev/null @@ -1,330 +0,0 @@ -<?xml version="1.0" encoding="utf-8" standalone="yes"?> -<strings> - <!-- Add-on settings --> - <string id="29999">Emby för Kodi</string> - <string id="30000">Primär serveradress</string> - <!-- Verified --> - <string id="30002">Spela upp ifrån HTTP istället för SMB</string> - <!-- Verified --> - <string id="30004">Lognivå</string> - <!-- Verified --> - <string id="30016">Enhetsnamn</string> - <!-- Verified --> - <string id="30022">Avancerat</string> - <string id="30024">Användarnamn</string> - <!-- Verified --> - <string id="30030">Portnummer</string> - <!-- Verified --> - <string id="30035">Antal nya album som ska visas:</string> - <string id="30036">Antal nya filmer som ska visas:</string> - <string id="30037">Antal nya TV-avsnitt som ska visas:</string> - <string id="30042">Uppdatera</string> - <string id="30043">Ta bort</string> - <string id="30044">Ogiltigt Användarnamn/Lösenord</string> - <string id="30045">Användarnamn kunde inte hittas</string> - <string id="30052">Tar bort</string> - <string id="30053">Väntar på server för borttagning</string> - <string id="30068">Sortera efter</string> - <string id="30069">Ingen</string> - <string id="30070">Action</string> - <string id="30071">Äventyr</string> - <string id="30072">Animaterat</string> - <string id="30073">Brott</string> - <string id="30074">Komedi</string> - <string id="30075">Dokumentär</string> - <string id="30076">Drama</string> - <string id="30077">Fantasi</string> - <string id="30078">Internationell</string> - <string id="30079">Historia</string> - <string id="30080">Skräck</string> - <string id="30081">Musik</string> - <string id="30082">Musikal</string> - <string id="30083">Mystisk</string> - <string id="30084">Romantik</string> - <string id="30085">Science Fiction</string> - <string id="30086">Kort</string> - <string id="30087">Spänning</string> - <string id="30088">Thriller</string> - <string id="30089">Västern</string> - <string id="30090">Genrefilter</string> - <string id="30091">Bekräfta borttagning utav fil?</string> - <!-- Verified --> - <string id="30093">Markera som visad</string> - <string id="30094">Markera som ej visad</string> - <string id="30097">Sortera efter</string> - <string id="30098">Fallande sortering</string> - <string id="30099">Stigande sortering</string> - <!-- resume dialog --> - <string id="30105">Fortsätt</string> - <string id="30106">Fortsätt från</string> - <string id="30107">Starta från början</string> - <string id="30114">Erbjud borttagning efter uppspelning</string> - <!-- Verified --> - <string id="30115">För avsnitt</string> - <!-- Verified --> - <string id="30116">För filmer</string> - <!-- Verified --> - <string id="30118">Lägg till fortsättningsprocent</string> - <string id="30119">Lägg till avsnittsnummer</string> - <string id="30120">Visa förloppsmätare</string> - <string id="30121">Laddar innehåll</string> - <string id="30122">Hämtar data</string> - <string id="30125">Klart</string> - <string id="30132">Varning</string> - <!-- Verified --> - <string id="30135">Fel</string> - <string id="30138">Sök</string> - <string id="30157">Aktivera förbättrade bilder (t.ex CoverArt)</string> - <!-- Verified --> - <string id="30158">Metadata</string> - <string id="30159">Bilder</string> - <string id="30160">Videokvalitet</string> - <!-- Verified --> - <string id="30165">Direktuppspelning</string> - <!-- Verified --> - <string id="30166">Omkodning</string> - <string id="30167">Serversökning lyckades</string> - <string id="30168">Hittade server</string> - <string id="30169">Adress:</string> - <!-- Video nodes --> - <string id="30170">Nyligen tillagda serier</string> - <!-- Verified --> - <string id="30171">Pågående serier</string> - <!-- Verified --> - <string id="30172">All musik</string> - <string id="30173">Kanaler</string> - <!-- Verified --> - <string id="30174">Nyligen tillagda filmer</string> - <!-- Verified --> - <string id="30175">Nyligen tillagda avsnitt</string> - <!-- Verified --> - <string id="30176">Nyligen tillagda album</string> - <string id="30177">Pågående filmer</string> - <!-- Verified --> - <string id="30178">Pågående avsnitt</string> - <!-- Verified --> - <string id="30179">Nästa avsnitt</string> - <!-- Verified --> - <string id="30180">Favoritfilmer</string> - <!-- Verified --> - <string id="30181">Favoritserier</string> - <!-- Verified --> - <string id="30182">Favoritavsnitt</string> - <string id="30183">Ofta spelade album</string> - <string id="30184">Kommande TV</string> - <string id="30185">Samlingar</string> - <string id="30186">Trailers</string> - <string id="30187">Musikvideor</string> - <string id="30188">Foton</string> - <string id="30189">Osedda filmer</string> - <!-- Verified --> - <string id="30190">Filmgenrer</string> - <string id="30191">Filmstudior</string> - <string id="30192">Filmskådespelare</string> - <string id="30193">Osedda avsnitt</string> - <string id="30194">TV-genrer</string> - <string id="30195">TV-bolag</string> - <string id="30196">TV-skådespelare</string> - <string id="30197">Spellistor</string> - <string id="30199">Ställ in Vyer</string> - <string id="30200">Välj användare</string> - <!-- Verified --> - <string id="30204">Kan inte koppla till servern</string> - <string id="30207">Låtar</string> - <string id="30208">Album</string> - <string id="30209">Albumartister</string> - <string id="30210">Artister</string> - <string id="30211">Musikgenrer</string> - <string id="30220">Senaste</string> - <string id="30221">Pågående</string> - <string id="30222">Nästa</string> - <string id="30223">Användarvyer</string> - <string id="30224">Rapportera Statistik</string> - <string id="30227">Slumpade filmer</string> - <string id="30228">Slumpade avsnitt</string> - <string id="30229">Slumpade objekt</string> - <!-- Verified --> - <string id="30230">Rekommenderade objekt</string> - <!-- Verified --> - <string id="30235">Extramaterial</string> - <!-- Verified --> - <string id="30236">Synkronisera musiktema</string> - <string id="30237">Synkronisera extra fanart</string> - <string id="30238">Synkronisera filmsamlingar</string> - <string id="30239">Återställ lokal Kodi-databas</string> - <!-- Verified --> - <string id="30243">Aktivera HTTPS</string> - <!-- Verified --> - <string id="30245">Tvinga Omkodingskodecs</string> - <string id="30249">Emby Server uppkopplingsmeddelande vid uppstart</string> - <!-- Verified --> - <string id="30251">Nyligen tillagda hemvideor</string> - <!-- Verified --> - <string id="30252">Nyligen tillagda foton</string> - <!-- Verified --> - <string id="30253">Favorithemvideor</string> - <!-- Verified --> - <string id="30254">Favoritfoton</string> - <!-- Verified --> - <string id="30255">Favoritalbum</string> - <string id="30256">Nyligen tillagda musikvideor</string> - <!-- Verified --> - <string id="30257">Pågående musikvideor</string> - <!-- Verified --> - <string id="30258">Osedda musikvideor</string> - <!-- Verified --> - <!-- Default views --> - <string id="30300">Aktiv</string> - <string id="30301">Återställ inställningar</string> - <string id="30302">Filmer</string> - <string id="30303">Samlingar</string> - <string id="30304">Trailers</string> - <string id="30305">Serier</string> - <string id="30306">Säsonger</string> - <string id="30307">Avsnitt</string> - <string id="30308">Musikartister</string> - <string id="30309">Musikalbum</string> - <string id="30310">Musikvideor</string> - <string id="30311">Låtar</string> - <string id="30312">Kanaler</string> - <!-- contextmenu --> - <string id="30401">Emby-inställningar</string> - <string id="30405">Lägg till Emby-favoriter</string> - <string id="30406">Ta bort från Emby-favoriter</string> - <string id="30407">Sätt anpassat betyg för låt</string> - <string id="30408">Emby tilläggsinställningar</string> - <string id="30409">Ta bort objekt från servern</string> - <string id="30410">Uppdatera detta objekt</string> - <string id="30411">Sätt betyg på låt (0-5)</string> - <!-- add-on settings --> - <string id="30500">Kontrollera värdens ssl-certifikat</string> - <string id="30501">Klientens ssl-certifikat</string> - <string id="30502">Använd alternativ adress</string> - <string id="30503">Alternativ serveraddress</string> - <string id="30504">Använd alternativt enhetsnamn</string> - <string id="30505">[COLOR yellow]Försök logga in igen[/COLOR]</string> - <string id="30506">Synkroniseringsalternativ</string> - <string id="30507">Visa förloppsmätare vid synkronisering</string> - <string id="30508">Synkronisera tomma serier</string> - <string id="30509">Aktivera musikbibliotek</string> - <string id="30510">Direktströmma musikbibliotek</string> - <string id="30511">Uppspelningsläge</string> - <string id="30512">Tvinga cachning av bilder</string> - <string id="30513">Begränsa bild cache trådar (rekommenderas för rpi)</string> - <string id="30514">Aktivera snabb upstart (kräver servertillägg)</string> - <string id="30515">Masximalt antal objekt att begära från servern samtidigt</string> - <string id="30516">Uppspelning</string> - <string id="30517">Nätverks inlogg</string> - <string id="30518">Aktivera Emby biografläge</string> - <string id="30519">Fråga för att spela upp trailers</string> - <string id="30520">Dölj Embys borttagningsbekräftelse i kontextmenyn(använd på egen risk)</string> - <string id="30521">Gå tillbaka vid återupptagen uppspelning (sekunder)</string> - <string id="30522">Tvinga omkodning av H265</string> - <string id="30523">Musik metadata alternativ (ej kompatibel med direktströmning)</string> - <string id="30524">Importera låtbetyg direkt från filer</string> - <string id="30525">Konvertera låtbetyg till Emby-betyg</string> - <string id="30526">Tillåt uppdatering av låtbetyg i filer</string> - <string id="30527">Ignorera specialavsnitt i nästa avsnitt</string> - <string id="30528">Permanenta användare att lägga till sessionen</string> - <string id="30529">Uppstartsfördröjning (sekunder)</string> - <string id="30530">Aktivera meddelande vid omstart av servern</string> - <string id="30531">Aktivera meddelande vid nytt innehåll</string> - <string id="30532">Fördröjning av pop-up för videobiblioteket (i sekunder)</string> - <string id="30533">Fördröjning av pop-up för musikbiblioteket (i sekunder)</string> - <string id="30534">Servermeddelanden</string> - <string id="30535">Generera nytt enhetsID</string> - <string id="30536">"Synka när skärmsläckare är inaktiverad -"</string> - <string id="30537">Tvinga omkodning av Hi10P</string> - <string id="30538">Inaktiverad</string> - <!-- service add-on --> - <string id="33000">Välkommen</string> - <string id="33001">Fel vid uppkoppling</string> - <string id="33002">Kan inte nå servern</string> - <string id="33003">Servern är uppkopplad</string> - <string id="33004">objekt tillagda till spellista</string> - <string id="33005">objekt köade till spellista</string> - <string id="33006">Serverns startar om</string> - <string id="33007">Åtkomst är aktiverad</string> - <string id="33008">Skriv in lösenord för användare:</string> - <string id="33009">Ogiltigt användarnamn eller lösenord</string> - <string id="33010">Misslyckades att autentisera för många gånger</string> - <string id="33011">Kan inte direktspela</string> - <string id="33012">Direktspelning misslyckades tre gånger. Spelar upp från HTTP.</string> - <string id="33013">Välj ljudspår</string> - <string id="33014">Välj ström för undertext</string> - <string id="33015">Ta bort filen från din Emby server?</string> - <string id="33016">Spela trailers?</string> - <string id="33017">Hämtar filmer från:</string> - <string id="33018">Hämtar samlingar</string> - <string id="33019">Hämtar musikvideos från:</string> - <string id="33020">Hämtar TV-serier från:</string> - <string id="33021">Hämtar:</string> - <string id="33022">Databasen behöver återskapas för den här versionen av Emby för Kodi. Fortsätt?</string> - <string id="33023">Emby för Kodi kan tappa funktion tills databasen har återställts.</string> - <string id="33024">Avbryter synkroniseringen av databasen. Nuvarande versionen av Kodi stöds inte.</string> - <string id="33025">färdig på:</string> - <string id="33026">Jämför filmer från:</string> - <string id="33027">Jämför samlingar</string> - <string id="33028">Jämför musikvideor från:</string> - <string id="33029">Jämför TV-serier från:</string> - <string id="33030">Jämför avsnitt från:</string> - <string id="33031">Jämför:</string> - <string id="33032">Kunde inte generera ett nytt enhetsID. Se i loggarna för mer information.</string> - <string id="33033">Ett nytt enhetsID har genererats. Kodi kommer nu starta om.</string> - <string id="33034">Fortsätt med följande server?</string> - <string id="33035">OBS! Om du väljer 'Native'-läget så tappar du vissa funktioner i Emby, som; Emby bioläge, direktströmning/omkodning och schema för föräldralås.</string> - <string id="33036">Tillägg (Standard)</string> - <string id="33037">Native (Direkta Sökvägar)</string> - <string id="33038">Lägg till nätverksuppgifter för att ge Kodi åtkomst till ditt innehåll? Viktigt: Kodi kommer behöva startas om för att se uppgifterna. Dom kan också läggas till vid ett senare tillfälle.</string> - <string id="33039">Inaktivera Emby musikbibliotek?</string> - <string id="33040">Direktströmma musikbiblioteket? Välj det här alternativet om musikbiblioteket inte finns tillgängligt lokalt.</string> - <string id="33041">Ta bort fil(er) från Emby Server? Det här tar också bort ifrån disk!</string> - <string id="33042">Caching processen kan ta lite tid. Fortsätt ändå?</string> - <string id="33043">Cachesynk för bilder</string> - <string id="33044">Återställ nuvarande bildcache</string> - <string id="33045">Uppdaterar bildcache:</string> - <string id="33046">Väntar på alla trådar att avslutas:</string> - <string id="33047">Kodi kan inte hitta filen:</string> - <string id="33048">Du kan behöva verifiera dina nätverksuppgifter i addon-inställningarna eller använda Emby sökvägsersättning för att formatera din sökväg korrekt(Emby dashboard > bibliotek). Stoppa synkronisering?</string> - <string id="33049">Tillagd:</string> - <string id="33050">Om du misslyckas att logga in för många gånger, kan Emby server låsa ner ditt konto. Fortsätt ändå?</string> - <string id="33051">Live TV Kanaler (experimentellt)</string> - <string id="33052">Live TV Inspelningar (experimentellt)</string> - <string id="33053">Inställningar</string> - <string id="33054">Lägg till användare för sessionen</string> - <string id="33055">Uppdatera Emby spellistor/Videonoder</string> - <string id="33056">Kör manuell synk</string> - <string id="33057">Reparera lokala databasen (tvinga uppdatering av allt innehåll)</string> - <string id="33058">Återställ lokala databasen</string> - <string id="33059">Förlagra alla bilder</string> - <string id="33060">Synka Emby Tema till Kodi</string> - <string id="33061">Lägg till/Ta bort användare från sessionen</string> - <string id="33062">Lägg till användare</string> - <string id="33063">Ta bort användare</string> - <string id="33064">Ta bort användare från sessionen</string> - <string id="33065">Lyckades!</string> - <string id="33066">Borttagen från sessionen:</string> - <string id="33067">Tillagd till sessionen:</string> - <string id="33068">Kan inte lägga till/ta bort användaren från sessionen</string> - <string id="33069">Uppgiften lyckades</string> - <string id="33070">Uppgiften misslyckades</string> - <string id="33071">Direktströmma</string> - <string id="33072">Uppspelningsmetod för dina teman</string> - <string id="33073">Inställningsfilen finns inte i TVTunes. Ändra en inställning och kör sen uppgiften igen.</string> - <string id="33074">Är du säker på att du vill återställa din lokala Kodi databas?</string> - <string id="33075">Ändra/Ta bort nätverksuppgifter</string> - <string id="33076">Ändra</string> - <string id="33077">Ta bort</string> - <string id="33078">Borttagen:</string> - <string id="33079">Ange användarnamn för nätverk</string> - <string id="33080">Ange nätverkslösenordet</string> - <string id="33081">Lade till nätverksinlogg för:</string> - <string id="33082">Ange servernamn eller IP-adress efter din emby bibliotekssökvägar. T.ex. servernamnet: \\\\SERVER-PC\\sökväg blir \"SERVER-PC\"</string> - <string id="33083">Ändra servernamnet eller IP-adressen</string> - <string id="33084">Ange servernamnet eller IP-adressen</string> - <string id="33085">Kunde inte återställa databasen. Försök igen.</string> - <string id="33086">Ta bort alla förlagrade bilder?</string> - <string id="33087">Återställ alla Emby tilläggsinställningar?</string> - <string id="33088">Databasen har återställts, Kodi kommer nu att starta om för att verkställa ändringarna.</string> -</strings> diff --git a/resources/language/resource.language.en_gb/strings.po b/resources/language/resource.language.en_gb/strings.po new file mode 100644 index 00000000..89516663 --- /dev/null +++ b/resources/language/resource.language.en_gb/strings.po @@ -0,0 +1,743 @@ +# Emby for Kodi language file +# Addon Name: Emby for Kodi +# Addon id: plugin.video.emby +# Addon Provider: angelblue05 +msgid "" +msgstr "" +"Project-Id-Version: Emby for Kodi\n" +"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: en\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgctxt "#29999" +msgid "Emby for Kodi" +msgstr "" + +msgctxt "#30000" +msgid "Server address" +msgstr "" + +msgctxt "#30001" +msgid "Server name" +msgstr "" + +msgctxt "#30002" +msgid "Force HTTP playback" +msgstr "" + +msgctxt "#30003" +msgid "Login method" +msgstr "" + +msgctxt "#30004" +msgid "Log level" +msgstr "" + +msgctxt "#30016" +msgid "Device name" +msgstr "" + +msgctxt "#30022" +msgid "Advanced" +msgstr "" + +msgctxt "#30024" +msgid "Username" +msgstr "" + +msgctxt "#30030" +msgid "Port number" +msgstr "" + +msgctxt "#30091" +msgid "Confirm file deletion" +msgstr "" + +msgctxt "#30114" +msgid "Offer delete after playback" +msgstr "" + +msgctxt "#30115" +msgid "For Episodes" +msgstr "" + +msgctxt "#30116" +msgid "For Movies" +msgstr "" + +msgctxt "#30157" +msgid "Enable enhanced artwork (i.e. cover art)" +msgstr "" + +msgctxt "#30160" +msgid "Video quality" +msgstr "" + +msgctxt "#30170" +msgid "Recently Added TV Shows" +msgstr "" + +msgctxt "#30171" +msgid "In Progress TV Shows" +msgstr "" + +msgctxt "#30174" +msgid "Recently Added Movies" +msgstr "" + +msgctxt "#30175" +msgid "Recently Added Episodes" +msgstr "" + +msgctxt "#30177" +msgid "In Progress Movies" +msgstr "" + +msgctxt "#30178" +msgid "In Progress Episodes" +msgstr "" + +msgctxt "#30179" +msgid "Next Episodes" +msgstr "" + +msgctxt "#30180" +msgid "Favorite Movies" +msgstr "" + +msgctxt "#30181" +msgid "Favorite Shows" +msgstr "" + +msgctxt "#30182" +msgid "Favorite Episodes" +msgstr "" + +msgctxt "#30185" +msgid "Boxsets" +msgstr "" + +msgctxt "#30189" +msgid "Unwatched Movies" +msgstr "" + +msgctxt "#30229" +msgid "Random Items" +msgstr "" + +msgctxt "#30230" +msgid "Recommended Items" +msgstr "" + +msgctxt "#30235" +msgid "Interface" +msgstr "" + +msgctxt "#30239" +msgid "Reset local Kodi database" +msgstr "" + +msgctxt "#30249" +msgid "Enable welcome message" +msgstr "" + +msgctxt "#30251" +msgid "Recently added Home Videos" +msgstr "" + +msgctxt "#30252" +msgid "Recently added Photos" +msgstr "" + +msgctxt "#30253" +msgid "Favourite Home Videos" +msgstr "" + +msgctxt "#30254" +msgid "Favourite Photos" +msgstr "" + +msgctxt "#30255" +msgid "Favourite Albums" +msgstr "" + +msgctxt "#30256" +msgid "Recently added Music videos" +msgstr "" + +msgctxt "#30257" +msgid "In progress Music videos" +msgstr "" + +msgctxt "#30258" +msgid "Unwatched Music videos" +msgstr "" + +msgctxt "#30302" +msgid "Movies" +msgstr "" + +msgctxt "#30305" +msgid "TV Shows" +msgstr "" + +msgctxt "#30401" +msgid "Emby options" +msgstr "" + +msgctxt "#30402" +msgid "Emby transcode" +msgstr "" + +msgctxt "#30405" +msgid "Add to favorites" +msgstr "" + +msgctxt "#30406" +msgid "Remove from favorites" +msgstr "" + +msgctxt "#30408" +msgid "Settings" +msgstr "" + +msgctxt "#30409" +msgid "Delete from Emby" +msgstr "" + +msgctxt "#30410" +msgid "Refresh this item" +msgstr "" + +msgctxt "#30412" +msgid "Transcode" +msgstr "" + +msgctxt "#30500" +msgid "Verify connection" +msgstr "" + +msgctxt "#30504" +msgid "Use altername device name" +msgstr "" + +msgctxt "#30506" +msgid "Sync" +msgstr "" + +msgctxt "#30508" +msgid "Sync empty shows" +msgstr "" + +msgctxt "#30509" +msgid "Enable music library" +msgstr "" + +msgctxt "#30511" +msgid "Playback mode" +msgstr "" + +msgctxt "#30512" +msgid "Enable artwork caching" +msgstr "" + +msgctxt "#30515" +msgid "Paging - max items requested (default: 15)" +msgstr "" + +msgctxt "#30516" +msgid "Playback" +msgstr "" + +msgctxt "#30517" +msgid "Network credentials" +msgstr "" + +msgctxt "#30518" +msgid "Enable cinema mode" +msgstr "" + +msgctxt "#30519" +msgid "Ask to play trailers" +msgstr "" + +msgctxt "#30520" +msgid "Skip the delete confirmation (use at your own risk)" +msgstr "" + +msgctxt "#30521" +msgid "Jump back on resume (in seconds)" +msgstr "" + +msgctxt "#30522" +msgid "Transcode H265/HEVC" +msgstr "" + +msgctxt "#30527" +msgid "Ignore specials in next episodes" +msgstr "" + +msgctxt "#30528" +msgid "Permanent users" +msgstr "" + +msgctxt "#30529" +msgid "Startup delay (in seconds)" +msgstr "" + +msgctxt "#30530" +msgid "Enable server restart message" +msgstr "" + +msgctxt "#30531" +msgid "Enable new content" +msgstr "" + +msgctxt "#30532" +msgid "Duration of the video library pop up" +msgstr "" + +msgctxt "#30533" +msgid "Duration of the music library pop up" +msgstr "" + +msgctxt "#30534" +msgid "Notifications (in seconds)" +msgstr "" + +msgctxt "#30535" +msgid "Generate a new device Id" +msgstr "" + +msgctxt "#30536" +msgid "Allow the screensaver during syncs" +msgstr "" + +msgctxt "#30537" +msgid "Transcode Hi10P" +msgstr "" + +msgctxt "#30539" +msgid "Login" +msgstr "" + +msgctxt "#30540" +msgid "Manual login" +msgstr "" + +msgctxt "#30543" +msgid "Username or email" +msgstr "" + +msgctxt "#30545" +msgid "Enable server offline" +msgstr "" + +msgctxt "#30547" +msgid "Display message" +msgstr "" + +msgctxt "#30600" +msgid "Sign in with Emby Connect" +msgstr "" + +msgctxt "#30602" +msgid "Password" +msgstr "" + +msgctxt "#30605" +msgid "Sign in" +msgstr "" + +msgctxt "#30606" +msgid "Cancel" +msgstr "" + +msgctxt "#30607" +msgid "Select main server" +msgstr "" + +msgctxt "#30608" +msgid "Username or password cannot be empty" +msgstr "" + +msgctxt "#30609" +msgid "Unable to connect to the selected server" +msgstr "" + +msgctxt "#30610" +msgid "Connect to" +msgstr "" + +msgctxt "#30611" +msgid "Manually add server" +msgstr "" + +msgctxt "#30612" +msgid "Please sign in" +msgstr "" + +msgctxt "#30613" +msgid "Change Emby Connect user" +msgstr "" + +msgctxt "#30614" +msgid "Connect to server" +msgstr "" + +msgctxt "#30615" +msgid "Host" +msgstr "" + +msgctxt "#30616" +msgid "Connect" +msgstr "" + +msgctxt "#30617" +msgid "Server or port cannot be empty" +msgstr "" + +msgctxt "#30618" +msgid "Change Emby Connect user" +msgstr "" + +msgctxt "#33000" +msgid "Welcome" +msgstr "" + +msgctxt "#33006" +msgid "Server is restarting" +msgstr "" + +msgctxt "#33009" +msgid "Invalid username or password" +msgstr "" + +msgctxt "#33015" +msgid "Delete file from Emby?" +msgstr "" + +msgctxt "#33016" +msgid "Play trailers?" +msgstr "" + +msgctxt "#33018" +msgid "Gathering boxsets" +msgstr "" + +msgctxt "#33021" +msgid "Gathering:" +msgstr "" + +msgctxt "#33025" +msgid "Completed in:" +msgstr "" + +msgctxt "#33033" +msgid "A new device Id has been generated. Kodi will now restart." +msgstr "" + +msgctxt "#33035" +msgid "Caution! If you choose Native mode, certain Emby features will be missing, such as: Emby cinema mode, direct stream/transcode options and parental access schedule." +msgstr "" + +msgctxt "#33036" +msgid "Add-on (default)" +msgstr "" + +msgctxt "#33037" +msgid "Native (direct paths)" +msgstr "" + +msgctxt "#33039" +msgid "Enable music library?" +msgstr "" + +msgctxt "#33047" +msgid "Kodi can't locate file:" +msgstr "" + +msgctxt "#33048" +msgid "You may need to verify your network credentials in the add-on settings or use the Emby path substitution to format your path correctly (Emby dashboard > library). Stop syncing?" +msgstr "" + +msgctxt "#33054" +msgid "Add user to session" +msgstr "" + +msgctxt "#33058" +msgid "Perform local database reset" +msgstr "" + +msgctxt "#33060" +msgid "Sync theme media" +msgstr "" + +msgctxt "#33061" +msgid "Add/Remove user from the session" +msgstr "" + +msgctxt "#33062" +msgid "Add user" +msgstr "" + +msgctxt "#33063" +msgid "Remove user" +msgstr "" + +msgctxt "#33064" +msgid "Remove user from the session" +msgstr "" + +msgctxt "#33074" +msgid "Are you sure you want to reset your local Kodi database?" +msgstr "" + +msgctxt "#33086" +msgid "Remove all cached artwork?" +msgstr "" + +msgctxt "#33087" +msgid "Reset all Emby add-on settings?" +msgstr "" + +msgctxt "#33088" +msgid "Database reset has completed, Kodi will now restart to apply the changes." +msgstr "" + +msgctxt "#33092" +msgid "Create a backup" +msgstr "" + +msgctxt "#33093" +msgid "Backup folder" +msgstr "" + +msgctxt "#33098" +msgid "Refresh boxsets" +msgstr "" + +msgctxt "#33099" +msgid "Install the server plugin Kodi companion to automatically apply emby library updates at startup." +msgstr "" + +msgctxt "#33100" +msgid "Would you like to sync empty shows?" +msgstr "" + +msgctxt "#33101" +msgid "Since you are using native playback mode with music enabled, do you want to import music rating from files?" +msgstr "" + +msgctxt "#33102" +msgid "Resume the previous sync?" +msgstr "" + +msgctxt "#33103" +msgid "Enable the webserver service in the Kodi settings to allow artwork caching." +msgstr "" + +msgctxt "#33104" +msgid "Find more info in the github wiki/Create-and-restore-from-backup." +msgstr "" + +msgctxt "#33105" +msgid "Enable the context menu" +msgstr "" + +msgctxt "#33106" +msgid "Enable the option to transcode" +msgstr "" + +msgctxt "#33107" +msgid "Users added to the session (no space between users). (eg username,username2)" +msgstr "" + +msgctxt "#33108" +msgid "Notifications are delayed during video playback (except live tv)." +msgstr "" + +msgctxt "#33109" +msgid "Plugin" +msgstr "" + +msgctxt "#33110" +msgid "Restart Kodi to take effect." +msgstr "" + +msgctxt "#33111" +msgid "Reset the local database to apply the playback mode change." +msgstr "" + +msgctxt "#33112" +msgid "Applies to Native and Add-on playback mode" +msgstr "" + +msgctxt "#33113" +msgid "Applies to Add-on playback mode only" +msgstr "" + +msgctxt "#33114" +msgid "Enable external subtitles" +msgstr "" + +msgctxt "#33115" +msgid "Adjust for remote connection" +msgstr "" + +msgctxt "#33116" +msgid "Compress artwork (reduces quality)" +msgstr "" + +msgctxt "#33117" +msgid "Enable artwork caching? If not, Kodi will still cache your artwork at a slower pace." +msgstr "" + +msgctxt "#33118" +msgid "You've change the playback mode. Kodi needs to be reset to apply the change, would you like to do this now?" +msgstr "" + +msgctxt "#33119" +msgid "Something went wrong during the sync. You'll be able to restore progress when restarting Kodi. If the problem persists, please report on the Emby for Kodi forums, with your Kodi log." +msgstr "" + +msgctxt "#33120" +msgid "Select the libraries to add" +msgstr "" + +msgctxt "#33121" +msgid "All" +msgstr "" + +msgctxt "#33122" +msgid "Restart Kodi to resume where you left off." +msgstr "" + +msgctxt "#33123" +msgid "Sync library to Kodi" +msgstr "" + +msgctxt "#33124" +msgid "Include people (slow)" +msgstr "" + +msgctxt "#33125" +msgid "Choose the Emby views to sync to Kodi. You can optionally sync libraries at a later time." +msgstr "" + +msgctxt "#33126" +msgid "Sync later" +msgstr "" + +msgctxt "#33127" +msgid "Proceed" +msgstr "" + +msgctxt "#33128" +msgid "Failed to retrieve latest content updates. No content updates will be applied until Kodi is restarted. If this issue persists, please report on the Emby for Kodi forums, with your Kodi log." +msgstr "" + +msgctxt "#33129" +msgid "You can sync libraries by launching the Emby add-on, opening the context menu on the library of your choice (movies, tvshows, musicvideo or music) and selecting Sync to Kodi." +msgstr "" + +msgctxt "#33130" +msgid "Select the source" +msgstr "" + +msgctxt "#33131" +msgid "Refreshing boxsets" +msgstr "" + +msgctxt "#33132" +msgid "Repair library" +msgstr "" + +msgctxt "#33133" +msgid "Remove library from Kodi" +msgstr "" + +msgctxt "#33134" +msgid "Add server" +msgstr "" + +msgctxt "#33135" +msgid "Kodi will now restart to apply a small patch for your database version." +msgstr "" + +msgctxt "#33136" +msgid "Update library" +msgstr "" + +msgctxt "#33137" +msgid "Enable Kodi companion" +msgstr "" + +msgctxt "#33138" +msgid "You can update your library manually rather than rely on the server plugin Kodi companion. Launch the add-on and update libraries (or per library). To remove content, you'll need to repair the library." +msgstr "" + +msgctxt "#33139" +msgid "Update libraries" +msgstr "" + +msgctxt "#33140" +msgid "Repair libraries" +msgstr "" + +msgctxt "#33141" +msgid "Remove server" +msgstr "" + +msgctxt "#33142" +msgid "Something went wrong. Try again later." +msgstr "" + +msgctxt "#33143" +msgid "Enable the option to delete" +msgstr "" + +msgctxt "#33144" +msgid "Removing library" +msgstr "" + +msgctxt "#33145" +msgid "If Kodi can't locate your files, navigate to your content via install from zip using smb. The path shown should match what's in Emby." +msgstr "" + +msgctxt "#33146" +msgid "Unable to connect to Emby." +msgstr "" + +msgctxt "#33147" +msgid "Your access to Emby is restricted." +msgstr "" + +msgctxt "#33148" +msgid "Your access to this server is restricted." +msgstr "" + +msgctxt "#33149" +msgid "Unable to connect to this server." +msgstr "" + +msgctxt "#33150" +msgid "Update server information" +msgstr "" + +msgctxt "#33151" +msgid "Reconnect to the same server that was previously loaded. If you want to use a different server, reset your local database, including your user information." +msgstr "" + +msgctxt "#33152" +msgid "Unable to locate TV Tunes in Kodi." +msgstr "" + +msgctxt "#33153" +msgid "Your Emby theme media has been synced to Kodi" +msgstr "" diff --git a/resources/lib/__init__.py b/resources/lib/__init__.py index b93054b3..e69de29b 100644 --- a/resources/lib/__init__.py +++ b/resources/lib/__init__.py @@ -1 +0,0 @@ -# Dummy file to make this directory a package. diff --git a/resources/lib/api.py b/resources/lib/api.py deleted file mode 100644 index ed53ee66..00000000 --- a/resources/lib/api.py +++ /dev/null @@ -1,413 +0,0 @@ -# -*- coding: utf-8 -*- - -# Read an api response and convert more complex cases - -################################################################################################## - -import logging -from utils import settings - -import artwork - -################################################################################################## - -log = logging.getLogger("EMBY."+__name__) - -################################################################################################## - - -class API(object): - - def __init__(self, item): - # item is the api response - self.item = item - self.artwork = artwork.Artwork() - - def get_userdata(self): - # Default - favorite = False - likes = None - playcount = None - played = False - last_played = None - resume = 0 - - try: - userdata = self.item['UserData'] - except KeyError: # No userdata found. - pass - else: - favorite = userdata['IsFavorite'] - likes = userdata.get('Likes') - - last_played = userdata.get('LastPlayedDate') - if last_played: - last_played = last_played.split('.')[0].replace('T', " ") - - if userdata['Played']: - # Playcount is tied to the watch status - played = True - playcount = userdata['PlayCount'] - if playcount == 0: - playcount = 1 - - if last_played is None: - last_played = self.get_date_created() - - playback_position = userdata.get('PlaybackPositionTicks') - if playback_position: - resume = playback_position / 10000000.0 - - return { - - 'Favorite': favorite, - 'Likes': likes, - 'PlayCount': playcount, - 'Played': played, - 'LastPlayedDate': last_played, - 'Resume': resume - } - - def get_people(self): - # Process People - director = [] - writer = [] - cast = [] - - if 'People' in self.item: - for person in self.item['People']: - - type_ = person['Type'] - name = person['Name'] - - if type_ == 'Director': - director.append(name) - elif type_ == 'Actor': - cast.append(name) - elif type_ in ('Writing', 'Writer'): - writer.append(name) - - return { - - 'Director': director, - 'Writer': writer, - 'Cast': cast - } - - def get_actors(self): - - cast = [] - - if 'People' in self.item: - - self.artwork.get_people_artwork(self.item['People']) - - for person in self.item['People']: - - if person['Type'] == "Actor": - cast.append({ - 'name': person['Name'], - 'role': person.get('Role', "Unknown"), - 'order': len(cast) + 1, - 'thumbnail': person.get('imageurl') - }) - - return cast - - def get_media_streams(self): - - video_tracks = [] - audio_tracks = [] - subtitle_languages = [] - - try: - media_streams = self.item['MediaSources'][0]['MediaStreams'] - - except KeyError: - if not self.item.get("MediaStreams"): - return None - media_streams = self.item['MediaStreams'] - - for media_stream in media_streams: - # Sort through Video, Audio, Subtitle - stream_type = media_stream['Type'] - - if stream_type == "Video": - self._video_stream(video_tracks, media_stream) - - elif stream_type == "Audio": - self._audio_stream(audio_tracks, media_stream) - - elif stream_type == "Subtitle": - subtitle_languages.append(media_stream.get('Language', "Unknown")) - - return { - - 'video': video_tracks, - 'audio': audio_tracks, - 'subtitle': subtitle_languages - } - - def _video_stream(self, video_tracks, stream): - - codec = stream.get('Codec', "").lower() - profile = stream.get('Profile', "").lower() - - # Height, Width, Codec, AspectRatio, AspectFloat, 3D - track = { - - 'codec': codec, - 'height': stream.get('Height'), - 'width': stream.get('Width'), - 'video3DFormat': self.item.get('Video3DFormat'), - 'aspect': 1.85 - } - - try: - container = self.item['MediaSources'][0]['Container'].lower() - except Exception: - container = "" - - # Sort codec vs container/profile - if "msmpeg4" in codec: - track['codec'] = "divx" - elif "mpeg4" in codec: - if "simple profile" in profile or not profile: - track['codec'] = "xvid" - elif "h264" in codec: - if container in ("mp4", "mov", "m4v"): - track['codec'] = "avc1" - - # Aspect ratio - if 'AspectRatio' in self.item: - # Metadata AR - aspect = self.item['AspectRatio'] - else: # File AR - aspect = stream.get('AspectRatio', "0") - - try: - aspect_width, aspect_height = aspect.split(':') - track['aspect'] = round(float(aspect_width) / float(aspect_height), 6) - - except (ValueError, ZeroDivisionError): - - width = track.get('width') - height = track.get('height') - - if width and height: - track['aspect'] = round(float(width / height), 6) - else: - track['aspect'] = 1.85 - - if 'RunTimeTicks' in self.item: - track['duration'] = self.get_runtime() - - video_tracks.append(track) - - def _audio_stream(self, audio_tracks, stream): - - codec = stream.get('Codec', "").lower() - profile = stream.get('Profile', "").lower() - # Codec, Channels, language - track = { - - 'codec': codec, - 'channels': stream.get('Channels'), - 'language': stream.get('Language') - } - - if "dts-hd ma" in profile: - track['codec'] = "dtshd_ma" - - if "dts-hd hra" in profile: - track['codec'] = "dtshd_hra" - - audio_tracks.append(track) - - def get_runtime(self): - - try: - runtime = self.item['RunTimeTicks'] / 10000000.0 - - except KeyError: - runtime = self.item.get('CumulativeRunTimeTicks', 0) / 10000000.0 - - return runtime - - @classmethod - def adjust_resume(cls, resume_seconds): - - resume = 0 - if resume_seconds: - resume = round(float(resume_seconds), 6) - jumpback = int(settings('resumeJumpBack')) - if resume > jumpback: - # To avoid negative bookmark - resume = resume - jumpback - - return resume - - def get_studios(self): - # Process Studios - studios = [] - try: - studio = self.item['SeriesStudio'] - studios.append(self.verify_studio(studio)) - - except KeyError: - for studio in self.item['Studios']: - - name = studio['Name'] - studios.append(self.verify_studio(name)) - - return studios - - @classmethod - def verify_studio(cls, studio_name): - # Convert studio for Kodi to properly detect them - studios = { - - 'abc (us)': "ABC", - 'fox (us)': "FOX", - 'mtv (us)': "MTV", - 'showcase (ca)': "Showcase", - 'wgn america': "WGN", - 'bravo (us)': "Bravo", - 'tnt (us)': "TNT", - 'comedy central': "Comedy Central (US)" - } - return studios.get(studio_name.lower(), studio_name) - - def get_checksum(self): - # Use the etags checksum and userdata - userdata = self.item['UserData'] - - checksum = "%s%s%s%s%s%s%s" % ( - - self.item['Etag'], - userdata['Played'], - userdata['IsFavorite'], - userdata.get('Likes', ""), - userdata['PlaybackPositionTicks'], - userdata.get('UnplayedItemCount', ""), - userdata.get('LastPlayedDate', "") - ) - - return checksum - - def get_genres(self): - all_genres = "" - genres = self.item.get('Genres', self.item.get('SeriesGenres')) - - if genres: - all_genres = " / ".join(genres) - - return all_genres - - def get_date_created(self): - - try: - date_added = self.item['DateCreated'] - date_added = date_added.split('.')[0].replace('T', " ") - except KeyError: - date_added = None - - return date_added - - def get_premiere_date(self): - - try: - premiere = self.item['PremiereDate'] - premiere = premiere.split('.')[0].replace('T', " ") - except KeyError: - premiere = None - - return premiere - - def get_overview(self): - - try: - overview = self.item['Overview'] - overview = overview.replace("\"", "\'") - overview = overview.replace("\n", "[CR]") - overview = overview.replace("\r", " ") - overview = overview.replace("<br>", "[CR]") - except KeyError: - overview = "" - - return overview - - def get_tagline(self): - - try: - tagline = self.item['Taglines'][0] - except IndexError: - tagline = None - - return tagline - - def get_provider(self, name): - - try: - provider = self.item['ProviderIds'][name] - except KeyError: - provider = None - - return provider - - def get_mpaa(self): - # Convert more complex cases - mpaa = self.item.get('OfficialRating', "") - - if mpaa in ("NR", "UR"): - # Kodi seems to not like NR, but will accept Not Rated - mpaa = "Not Rated" - - if "FSK-" in mpaa: - mpaa = mpaa.replace("-", " ") - - return mpaa - - def get_country(self): - - try: - country = self.item['ProductionLocations'][0] - except (IndexError, KeyError): - country = None - - return country - - def get_file_path(self): - - try: - filepath = self.item['Path'] - - except KeyError: - filepath = "" - - else: - if filepath.startswith('\\\\'): - # append smb protocol - filepath = filepath.replace("\\\\", "smb://", 1) - filepath = filepath.replace("\\\\", "\\") - filepath = filepath.replace("\\", "/") - - if 'VideoType' in self.item: - if self.item['VideoType'] == "Dvd": - filepath = "%s/VIDEO_TS/VIDEO_TS.IFO" % filepath - elif self.item['VideoType'] == "BluRay": - filepath = "%s/BDMV/index.bdmv" % filepath - - # In case user made a mistake with the network share - filepath = filepath.replace("\\\\", "\\") - - if "\\" in filepath: - # Local path scenario, with special videotype - filepath = filepath.replace("/", "\\") - - if "://" in filepath: - # Protocol needs to be lowercase, otherwise weird things happen. - protocol = filepath.split('://')[0] - filepath = filepath.replace(protocol, protocol.lower()) - - return filepath diff --git a/resources/lib/artwork.py b/resources/lib/artwork.py deleted file mode 100644 index 9ac02a84..00000000 --- a/resources/lib/artwork.py +++ /dev/null @@ -1,572 +0,0 @@ -# -*- coding: utf-8 -*- - -################################################################################################# - -import logging -import os -import urllib -from sqlite3 import OperationalError - -import xbmc -import xbmcgui -import xbmcvfs -import requests - -import image_cache_thread -from utils import window, settings, dialog, language as lang, JSONRPC -from database import DatabaseConn - -################################################################################################## - -log = logging.getLogger("EMBY."+__name__) - -################################################################################################## - - -class Artwork(object): - - xbmc_host = 'localhost' - xbmc_port = None - xbmc_username = None - xbmc_password = None - - image_cache_threads = [] - image_cache_limit = 0 - - - def __init__(self): - - self.enable_texture_cache = settings('enableTextureCache') == "true" - self.image_cache_limit = int(settings('imageCacheLimit')) * 5 - log.debug("image cache thread count: %s", self.image_cache_limit) - - if not self.xbmc_port and self.enable_texture_cache: - self._set_webserver_details() - - self.user_id = window('emby_currUser') - self.server = window('emby_server%s' % self.user_id) - - - def _double_urlencode(self, text): - - text = self.single_urlencode(text) - text = self.single_urlencode(text) - - return text - - @classmethod - def single_urlencode(cls, text): - # urlencode needs a utf- string - text = urllib.urlencode({'blahblahblah': text.encode('utf-8')}) - text = text[13:] - - return text.decode('utf-8') #return the result again as unicode - - def _set_webserver_details(self): - # Get the Kodi webserver details - used to set the texture cache - get_setting_value = JSONRPC('Settings.GetSettingValue') - - web_query = { - - "setting": "services.webserver" - } - result = get_setting_value.execute(web_query) - try: - xbmc_webserver_enabled = result['result']['value'] - except (KeyError, TypeError): - xbmc_webserver_enabled = False - - if not xbmc_webserver_enabled: - # Enable the webserver, it is disabled - set_setting_value = JSONRPC('Settings.SetSettingValue') - - web_port = { - - "setting": "services.webserverport", - "value": 8080 - } - set_setting_value.execute(web_port) - self.xbmc_port = 8080 - - web_user = { - - "setting": "services.webserver", - "value": True - } - set_setting_value.execute(web_user) - self.xbmc_username = "kodi" - - # Webserver already enabled - web_port = { - - "setting": "services.webserverport" - } - result = get_setting_value.execute(web_port) - try: - self.xbmc_port = result['result']['value'] - except (TypeError, KeyError): - pass - - web_user = { - - "setting": "services.webserverusername" - } - result = get_setting_value.execute(web_user) - try: - self.xbmc_username = result['result']['value'] - except (TypeError, KeyError): - pass - - web_pass = { - - "setting": "services.webserverpassword" - } - result = get_setting_value.execute(web_pass) - try: - self.xbmc_password = result['result']['value'] - except (TypeError, KeyError): - pass - - def texture_cache_sync(self): - # This method will sync all Kodi artwork to textures13.db - # and cache them locally. This takes diskspace! - if not dialog(type_="yesno", - heading="{emby}", - line1=lang(33042)): - return - - log.info("Doing Image Cache Sync") - - pdialog = xbmcgui.DialogProgress() - pdialog.create(lang(29999), lang(33043)) - - # ask to rest all existing or not - if dialog(type_="yesno", heading="{emby}", line1=lang(33044)): - log.info("Resetting all cache data first") - self.delete_cache() - - # Cache all entries in video DB - self._cache_all_video_entries(pdialog) - # Cache all entries in music DB - self._cache_all_music_entries(pdialog) - - pdialog.update(100, "%s %s" % (lang(33046), len(self.image_cache_threads))) - log.info("Waiting for all threads to exit") - - while len(self.image_cache_threads): - for thread in self.image_cache_threads: - if thread.is_finished: - self.image_cache_threads.remove(thread) - pdialog.update(100, "%s %s" % (lang(33046), len(self.image_cache_threads))) - log.info("Waiting for all threads to exit: %s", len(self.image_cache_threads)) - xbmc.sleep(500) - - pdialog.close() - - def _cache_all_video_entries(self, pdialog): - - with DatabaseConn('video') as cursor_video: - - cursor_video.execute("SELECT url FROM art WHERE media_type != 'actor'") # dont include actors - result = cursor_video.fetchall() - total = len(result) - log.info("Image cache sync about to process %s images", total) - cursor_video.close() - - count = 0 - for url in result: - - if pdialog.iscanceled(): - break - - percentage = int((float(count) / float(total))*100) - message = "%s of %s (%s)" % (count, total, len(self.image_cache_threads)) - pdialog.update(percentage, "%s %s" % (lang(33045), message)) - self.cache_texture(url[0]) - count += 1 - - def _cache_all_music_entries(self, pdialog): - - with DatabaseConn('music') as cursor_music: - - cursor_music.execute("SELECT url FROM art") - result = cursor_music.fetchall() - total = len(result) - - log.info("Image cache sync about to process %s images", total) - - count = 0 - for url in result: - - if pdialog.iscanceled(): - break - - percentage = int((float(count) / float(total))*100) - message = "%s of %s" % (count, total) - pdialog.update(percentage, "%s %s" % (lang(33045), message)) - self.cache_texture(url[0]) - count += 1 - - @classmethod - def delete_cache(cls): - # Remove all existing textures first - path = xbmc.translatePath('special://thumbnails/').decode('utf-8') - if xbmcvfs.exists(path): - dirs, ignore_files = xbmcvfs.listdir(path) - for directory in dirs: - ignore_dirs, files = xbmcvfs.listdir(path + directory) - for file_ in files: - - if os.path.supports_unicode_filenames: - filename = os.path.join(path + directory.decode('utf-8'), - file_.decode('utf-8')) - else: - filename = os.path.join(path.encode('utf-8') + directory, file_) - - xbmcvfs.delete(filename) - log.debug("deleted: %s", filename) - - # remove all existing data from texture DB - with DatabaseConn('texture') as cursor_texture: - cursor_texture.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"') - rows = cursor_texture.fetchall() - for row in rows: - table_name = row[0] - if table_name != "version": - cursor_texture.execute("DELETE FROM " + table_name) - - def _add_worker_image_thread(self, url): - - while True: - # removed finished - for thread in self.image_cache_threads: - if thread.is_finished: - self.image_cache_threads.remove(thread) - - # add a new thread or wait and retry if we hit our limit - if len(self.image_cache_threads) < self.image_cache_limit: - - new_thread = image_cache_thread.ImageCacheThread() - new_thread.set_url(self._double_urlencode(url)) - new_thread.set_host(self.xbmc_host, self.xbmc_port) - new_thread.set_auth(self.xbmc_username, self.xbmc_password) - - counter = 0 - worked = False - while counter < 10: - try: - new_thread.start() - worked = True - break - except: - counter = counter + 1 - xbmc.sleep(1000) - - if(worked): - self.image_cache_threads.append(new_thread) - return True - else: - return False - else: - log.info("Waiting for empty queue spot: %s", len(self.image_cache_threads)) - xbmc.sleep(100) - - def cache_texture(self, url): - # Cache a single image url to the texture cache - if url and self.enable_texture_cache: - log.debug("Processing: %s", url) - - if not self.image_cache_limit: - - url = self._double_urlencode(url) - try: # Add image to texture cache by simply calling it at the http endpoint - requests.head(url=("http://%s:%s/image/image://%s" - % (self.xbmc_host, self.xbmc_port, url)), - auth=(self.xbmc_username, self.xbmc_password), - timeout=(0.01, 0.01)) - except Exception: # We don't need the result - pass - else: - self._add_worker_image_thread(url) - - def add_artwork(self, artwork, kodi_id, media_type, cursor): - # Kodi conversion table - kodi_artwork = { - - 'Primary': ["thumb", "poster"], - 'Banner': "banner", - 'Logo': "clearlogo", - 'Art': "clearart", - 'Thumb': "landscape", - 'Disc': "discart", - 'Backdrop': "fanart", - 'BoxRear': "poster" - } - # Artwork is a dictionary - for artwork_type in artwork: - - if artwork_type == 'Backdrop': - # Backdrop entry is a list - # Process extra fanart for artwork downloader (fanart, fanart1, fanart2...) - backdrops = artwork[artwork_type] - backdrops_number = len(backdrops) - - query = ' '.join(( - - "SELECT url", - "FROM art", - "WHERE media_id = ?", - "AND media_type = ?", - "AND type LIKE ?" - )) - cursor.execute(query, (kodi_id, media_type, "fanart%",)) - rows = cursor.fetchall() - - if len(rows) > backdrops_number: - # More backdrops in database. Delete extra fanart. - query = ' '.join(( - - "DELETE FROM art", - "WHERE media_id = ?", - "AND media_type = ?", - "AND type LIKE ?" - )) - cursor.execute(query, (kodi_id, media_type, "fanart_",)) - - # Process backdrops and extra fanart - for index, backdrop in enumerate(backdrops): - - self.add_update_art(image_url=backdrop, - kodi_id=kodi_id, - media_type=media_type, - image_type=("fanart" if not index else "%s%s" - % ("fanart", index)), - cursor=cursor) - - elif artwork_type == 'Primary': - # Primary art is processed as thumb and poster for Kodi. - for art_type in kodi_artwork[artwork_type]: - self.add_update_art(image_url=artwork[artwork_type], - kodi_id=kodi_id, - media_type=media_type, - image_type=art_type, - cursor=cursor) - - elif artwork_type in kodi_artwork: - # Process the rest artwork type that Kodi can use - self.add_update_art(image_url=artwork[artwork_type], - kodi_id=kodi_id, - media_type=media_type, - image_type=kodi_artwork[artwork_type], - cursor=cursor) - - def add_update_art(self, image_url, kodi_id, media_type, image_type, cursor): - - ''' Add or update the artwork (if it changed) to the Kodi database. Update the cache if - backdrop or poster image_type. - - Possible to an empty imageurl since the process is automated. - ''' - if image_type == 'poster' and media_type in ('song', 'artist', 'album'): - - log.info("skipping poster for songs, artists, albums.") - return - - if not image_url: - - log.debug("empty url for: %s/%s/%s", kodi_id, media_type, image_type) - return - - cache_image = False - - query = ' '.join(( - - "SELECT url", - "FROM art", - "WHERE media_id = ?", - "AND media_type = ?", - "AND type = ?" - )) - cursor.execute(query, (kodi_id, media_type, image_type,)) - try: - url = cursor.fetchone()[0] - - except TypeError: - cache_image = True - log.debug("Adding Art Link for kodiId: %s (%s)", kodi_id, image_url) - - query = ( - ''' - INSERT INTO art(media_id, media_type, type, url) - - VALUES (?, ?, ?, ?) - ''' - ) - cursor.execute(query, (kodi_id, media_type, image_type, image_url)) - - else: - if url != image_url: - - cache_image = True - - if (window('emby_initialScan') != "true" and - image_type in ("fanart", "poster")): - # Delete current entry before updating with the new one - self.delete_cached_artwork(url) - - log.info("Updating Art url for %s kodiId: %s (%s) -> (%s)", - image_type, kodi_id, url, image_url) - - query = ' '.join(( - - "UPDATE art", - "SET url = ?", - "WHERE media_id = ?", - "AND media_type = ?", - "AND type = ?" - )) - cursor.execute(query, (image_url, kodi_id, media_type, image_type)) - - if cache_image and image_type in ("fanart", "poster"): - self.cache_texture(image_url) - - def delete_artwork(self, kodi_id, media_type, cursor): - - query = ' '.join(( - - "SELECT url, type", - "FROM art", - "WHERE media_id = ?", - "AND media_type = ?" - )) - cursor.execute(query, (kodi_id, media_type,)) - rows = cursor.fetchall() - for row in rows: - - url = row[0] - image_type = row[1] - if image_type in ("poster", "fanart"): - self.delete_cached_artwork(url) - - @classmethod - def delete_cached_artwork(cls, url): - # Only necessary to remove and apply a new backdrop or poster - with DatabaseConn('texture') as cursor_texture: - try: - cursor_texture.execute("SELECT cachedurl FROM texture WHERE url = ?", (url,)) - cached_url = cursor_texture.fetchone()[0] - - except TypeError: - log.info("Could not find cached url") - - except OperationalError: - log.info("Database is locked. Skip deletion process.") - - else: # Delete thumbnail as well as the entry - thumbnails = xbmc.translatePath("special://thumbnails/%s" % cached_url).decode('utf-8') - log.info("Deleting cached thumbnail: %s", thumbnails) - xbmcvfs.delete(thumbnails) - - try: - cursor_texture.execute("DELETE FROM texture WHERE url = ?", (url,)) - except OperationalError: - log.debug("Issue deleting url from cache. Skipping.") - - def get_people_artwork(self, people): - # append imageurl if existing - for person in people: - - image = "" - person_id = person['Id'] - - if 'PrimaryImageTag' in person: - image = ( - "%s/emby/Items/%s/Images/Primary?" - "MaxWidth=400&MaxHeight=400&Index=0&Tag=%s" - % (self.server, person_id, person['PrimaryImageTag'])) - - person['imageurl'] = image - - return people - - def get_user_artwork(self, item_id, item_type): - # Load user information set by UserClient - return "%s/emby/Users/%s/Images/%s?Format=original" % (self.server, item_id, item_type) - - def get_all_artwork(self, item, parent_info=False): - - item_id = item['Id'] - artworks = item['ImageTags'] - backdrops = item.get('BackdropImageTags', []) - - custom_query = "" - - if settings('compressArt') == "true": - custom_query = "&Quality=90" - - if settings('enableCoverArt') == "false": - custom_query += "&EnableImageEnhancers=false" - - all_artwork = { - - 'Primary': "", - 'Art': "", - 'Banner': "", - 'Logo': "", - 'Thumb': "", - 'Disc': "", - 'Backdrop': [] - } - - def get_backdrops(item_id, backdrops): - - for index, tag in enumerate(backdrops): - artwork = ("%s/emby/Items/%s/Images/Backdrop/%s?Format=original&Tag=%s%s" - % (self.server, item_id, index, tag, custom_query)) - all_artwork['Backdrop'].append(artwork) - - def get_artwork(item_id, type_, tag, override_name=None): - - if not tag: return - - artwork = ("%s/emby/Items/%s/Images/%s/0?Format=original&Tag=%s%s" - % (self.server, item_id, type_, tag, custom_query)) - all_artwork[override_name or type_] = artwork - - # Process backdrops - get_backdrops(item_id, backdrops) - - # Process the rest of the artwork - for artwork in artworks: - # Filter backcover - if artwork != "BoxRear": - get_artwork(item_id, artwork, artworks[artwork]) - - # Process parent items if the main item is missing artwork - if parent_info: - # Process parent backdrops - if not all_artwork['Backdrop']: - - if 'ParentBackdropItemId' in item: - # If there is a parent_id, go through the parent backdrop list - get_backdrops(item['ParentBackdropItemId'], item['ParentBackdropImageTags']) - - # Process the rest of the artwork - for parent_artwork in ('Logo', 'Art', 'Thumb'): - - if not all_artwork[parent_artwork]: - - if 'Parent%sItemId' % parent_artwork in item: - get_artwork(item['Parent%sItemId' % parent_artwork], parent_artwork, - item['Parent%sImageTag' % parent_artwork]) - - if 'SeriesPrimaryImageTag' in item: - get_artwork(item['SeriesId'], "Primary", item['SeriesPrimaryImageTag'], "Series.Primary" if all_artwork['Primary'] else None) - - # Parent album works a bit differently - if not all_artwork['Primary']: - - if 'AlbumId' in item and 'AlbumPrimaryImageTag' in item: - get_artwork(item['AlbumId'], 'Primary', item['AlbumPrimaryImageTag']) - - return all_artwork diff --git a/resources/lib/client.py b/resources/lib/client.py new file mode 100644 index 00000000..a69058a2 --- /dev/null +++ b/resources/lib/client.py @@ -0,0 +1,109 @@ +# -*- coding: utf-8 -*- + +################################################################################################# + +import json +import logging +import os + +import xbmc +import xbmcaddon +import xbmcvfs + +from helper import _, window, settings, addon_id, dialog +from helper.utils import create_id + +################################################################################################## + +LOG = logging.getLogger("EMBY."+__name__) + +################################################################################################## + +def get_addon_name(): + + ''' Used for logging. + ''' + return xbmcaddon.Addon(addon_id()).getAddonInfo('name').upper() + +def get_version(): + return xbmcaddon.Addon(addon_id()).getAddonInfo('version') + +def get_platform(): + + if xbmc.getCondVisibility('system.platform.osx'): + return "OSX" + elif xbmc.getCondVisibility('system.platform.atv2'): + return "ATV2" + elif xbmc.getCondVisibility('system.platform.ios'): + return "iOS" + elif xbmc.getCondVisibility('system.platform.windows'): + return "Windows" + elif xbmc.getCondVisibility('system.platform.android'): + return "Linux/Android" + elif xbmc.getCondVisibility('system.platform.linux.raspberrypi'): + return "Linux/RPi" + elif xbmc.getCondVisibility('system.platform.linux'): + return "Linux" + else: + return "Unknown" + +def get_device_name(): + + ''' Detect the device name. If deviceNameOpt, then + use the device name in the add-on settings. + Otherwise fallback to the Kodi device name. + ''' + if not settings('deviceNameOpt.bool'): + device_name = xbmc.getInfoLabel('System.FriendlyName').decode('utf-8') + else: + device_name = settings('deviceName') + device_name = device_name.replace("\"", "_") + device_name = device_name.replace("/", "_") + + return device_name + +def get_device_id(reset=False): + + ''' Return the device_id if already loaded. + It will load from emby_guid file. If it's a fresh + setup, it will generate a new GUID to uniquely + identify the setup for all users. + + window prop: emby_deviceId + ''' + client_id = window('emby_deviceId') + + if client_id: + return client_id + + emby_guid = xbmc.translatePath('special://temp/emby_guid').decode('utf-8') + file_guid = xbmcvfs.File(emby_guid) + client_id = file_guid.read() + + if not client_id or reset: + LOG.info("Generating a new GUID.") + + client_id = str("%012X" % create_id()) + file_guid = xbmcvfs.File(emby_guid, 'w') + file_guid.write(client_id) + + file_guid.close() + + LOG.info("DeviceId loaded: %s", client_id) + window('emby_deviceId', value=client_id) + + return client_id + +def reset_device_id(): + + window('emby_deviceId', clear=True) + get_device_id(True) + dialog("ok", heading="{emby}", line1=_(33033)) + xbmc.executebuiltin('RestartApp') + +def get_info(): + return { + 'DeviceName': get_device_name(), + 'Version': get_version(), + 'DeviceId': get_device_id() + } diff --git a/resources/lib/clientinfo.py b/resources/lib/clientinfo.py deleted file mode 100644 index b68695c9..00000000 --- a/resources/lib/clientinfo.py +++ /dev/null @@ -1,97 +0,0 @@ -# -*- coding: utf-8 -*- - -################################################################################################# - -import logging -import os - -import xbmc -import xbmcaddon -import xbmcvfs - -from utils import window, settings, create_id - -################################################################################################## - -log = logging.getLogger("EMBY."+__name__) - -################################################################################################## - - -class ClientInfo(object): - - - def __init__(self): - - self.addon = xbmcaddon.Addon(self.get_addon_id()) - - @staticmethod - def get_addon_id(): - return "plugin.video.emby" - - def get_addon_name(self): - # Used for logging - return self.addon.getAddonInfo('name').upper() - - def get_version(self): - return self.addon.getAddonInfo('version') - - @classmethod - def get_device_name(cls): - - if settings('deviceNameOpt') == "false": - # Use Kodi's deviceName - device_name = xbmc.getInfoLabel('System.FriendlyName').decode('utf-8') - else: - device_name = settings('deviceName') - device_name = device_name.replace("\"", "_") - device_name = device_name.replace("/", "_") - - return device_name - - @classmethod - def get_platform(cls): - - if xbmc.getCondVisibility('system.platform.osx'): - return "OSX" - elif xbmc.getCondVisibility('system.platform.atv2'): - return "ATV2" - elif xbmc.getCondVisibility('system.platform.ios'): - return "iOS" - elif xbmc.getCondVisibility('system.platform.windows'): - return "Windows" - elif xbmc.getCondVisibility('system.platform.android'): - return "Linux/Android" - elif xbmc.getCondVisibility('system.platform.linux.raspberrypi'): - return "Linux/RPi" - elif xbmc.getCondVisibility('system.platform.linux'): - return "Linux" - else: - return "Unknown" - - @classmethod - def get_device_id(cls, reset=False): - - client_id = window('emby_deviceId') - if client_id: - return client_id - - emby_guid = xbmc.translatePath("special://temp/emby_guid").decode('utf-8') - - if reset and xbmcvfs.exists(emby_guid): - xbmcvfs.delete(emby_guid) - - guid = xbmcvfs.File(emby_guid) - client_id = guid.read() - if not client_id: - log.info("Generating a new guid...") - client_id = str("%012X" % create_id()) - guid = xbmcvfs.File(emby_guid, 'w') - guid.write(client_id) - - guid.close() - - log.info("DeviceId loaded: %s", client_id) - window('emby_deviceId', value=client_id) - - return client_id diff --git a/resources/lib/connect.py b/resources/lib/connect.py new file mode 100644 index 00000000..02e2e1c3 --- /dev/null +++ b/resources/lib/connect.py @@ -0,0 +1,341 @@ +# -*- coding: utf-8 -*- + +################################################################################################## + +import json +import logging +import os + +import xbmc +import xbmcaddon +import xbmcvfs + +import client +from database import get_credentials, save_credentials +from dialogs import ServerConnect, UsersConnect, LoginConnect, LoginManual, ServerManual +from helper import _, settings, addon_id, event, api, dialog, window +from emby import Emby +from emby.core.connection_manager import get_server_address, CONNECTION_STATE +from emby.core.exceptions import HTTPException + +################################################################################################## + +LOG = logging.getLogger("EMBY."+__name__) +XML_PATH = (xbmcaddon.Addon(addon_id()).getAddonInfo('path'), "default", "1080i") + +################################################################################################## + + +class Connect(object): + + def __init__(self): + self.info = client.get_info() + + def register(self, server_id=None, options={}): + + ''' Login into server. If server is None, then it will show the proper prompts to login, etc. + If a server id is specified then only a login dialog will be shown for that server. + ''' + LOG.info("--[ server/%s ]", server_id or 'default') + credentials = dict(get_credentials()) + servers = credentials['Servers'] + + if server_id is None and credentials['Servers']: + credentials['Servers'] = [credentials['Servers'][0]] + + elif credentials['Servers']: + + for server in credentials['Servers']: + + if server['Id'] == server_id: + credentials['Servers'] = [server] + + server_select = True if server_id is None and not settings('SyncInstallRunDone.bool') else False + new_credentials = self.register_client(credentials, options, server_id, server_select) + + for server in servers: + if server['Id'] == new_credentials['Servers'][0]['Id']: + server = new_credentials['Servers'][0] + + break + else: + servers = new_credentials['Servers'] + + credentials['Servers'] = servers + save_credentials(credentials) + + try: + Emby(server_id).start(True) + except ValueError as error: + LOG.error(error) + + def get_ssl(self): + + ''' Returns boolean value. + True: verify connection. + ''' + return settings('sslverify.bool') + + def get_client(self, server_id=None): + + ''' Get Emby client. + ''' + client = Emby(server_id) + client['config/app']("Kodi", self.info['Version'], self.info['DeviceName'], self.info['DeviceId']) + client['config']['http.user_agent'] = "Emby-Kodi/%s" % self.info['Version'] + client['config']['auth.ssl'] = self.get_ssl() + + return client + + def register_client(self, credentials=None, options=None, server_id=None, server_selection=False): + + client = self.get_client(server_id) + self.client = client + self.connect_manager = client.auth + + if server_id is None: + client['config']['app.default'] = True + + try: + state = client.authenticate(credentials or {}, options or {}) + + if state['State'] == CONNECTION_STATE['SignedIn']: + client.callback_ws = event + + if server_id is None: # Only assign for default server + + client.callback = event + self.get_user(client) + + settings('serverName', client['config/auth.server-name']) + settings('server', client['config/auth.server']) + + if settings('connectMsg.bool'): + + users = [user for user in settings('additionalUsers').split(',') if user] + users.insert(0, self.user['Name']) + dialog("notification", heading="{emby}", message="%s %s" % (_(33000), ", ".join(users)), + icon="{emby}", time=1500, sound=False) + + event('ServerOnline', {'ServerId': server_id}) + event('LoadServer', {'ServerId': server_id}) + + return state['Credentials'] + + elif (server_selection or state['State'] in (CONNECTION_STATE['ConnectSignIn'], CONNECTION_STATE['ServerSelection']) or + state['State'] == CONNECTION_STATE['Unavailable'] and not settings('SyncInstallRunDone.bool')): + + self.select_servers(state) + + elif state['State'] == CONNECTION_STATE['ServerSignIn']: + if 'ExchangeToken' not in state['Servers'][0]: + self.login() + + elif state['State'] == CONNECTION_STATE['Unavailable']: + raise HTTPException('ServerUnreachable', {}) + + return self.register_client(state['Credentials'], options, server_id, False) + + except RuntimeError as error: + + LOG.exception(error) + xbmc.executebuiltin('Addon.OpenSettings(%s)' % addon_id()) + + raise Exception('User sign in interrupted') + + except HTTPException as error: + + if error.status == 'ServerUnreachable': + event('ServerUnreachable', {'ServerId': server_id}) + + return client.get_credentials() + + + def get_user(self, client): + + ''' Save user info. + ''' + self.user = client['api'].get_user() + settings('username', self.user['Name']) + + if 'PrimaryImageTag' in self.user: + window('EmbyUserImage', api.API(self.user, client['auth/server-address']).get_user_artwork(self.user['Id'])) + + self.server = client['api'].get_system_info() + settings('markPlayed', value=str(self.server['MaxResumePct'])) + + def select_servers(self, state=None): + + state = state or self.connect_manager.connect({'enableAutoLogin': False}) + user = state.get('ConnectUser') or {} + + dialog = ServerConnect("script-emby-connect-server.xml", *XML_PATH) + dialog.set_args(**{ + 'connect_manager': self.connect_manager, + 'username': user.get('DisplayName', ""), + 'user_image': user.get('ImageUrl'), + 'servers': state.get('Servers', []), + 'emby_connect': False if user else True + }) + dialog.doModal() + + if dialog.is_server_selected(): + LOG.debug("Server selected: %s", dialog.get_server()) + return + + elif dialog.is_connect_login(): + LOG.debug("Login with emby connect") + try: + self.login_connect() + except RuntimeError: pass + + elif dialog.is_manual_server(): + LOG.debug("Adding manual server") + try: + self.manual_server() + except RuntimeError: pass + else: + raise RuntimeError("No server selected") + + LOG.info(self.client.get_credentials()) + return self.select_servers() + + def setup_manual_server(self): + + ''' Setup manual servers + ''' + client = self.get_client() + client.set_credentials(get_credentials()) + manager = client.auth + + try: + self.manual_server(manager) + except RuntimeError: + return + + credentials = client.get_credentials() + save_credentials(credentials) + + def manual_server(self, manager=None): + + ''' Return server or raise error. + ''' + dialog = ServerManual("script-emby-connect-server-manual.xml", *XML_PATH) + dialog.set_args(**{'connect_manager': manager or self.connect_manager}) + dialog.doModal() + + if dialog.is_connected(): + return dialog.get_server() + else: + raise RuntimeError("Server is not connected") + + def setup_login_connect(self): + + ''' Setup emby connect by itself. + ''' + client = self.get_client() + client.set_credentials(get_credentials()) + manager = client.auth + + try: + self.login_connect(manager) + except RuntimeError: + return + + credentials = client.get_credentials() + save_credentials(credentials) + + def login_connect(self, manager=None): + + ''' Return connect user or raise error. + ''' + dialog = LoginConnect("script-emby-connect-login.xml", *XML_PATH) + dialog.set_args(**{'connect_manager': manager or self.connect_manager}) + dialog.doModal() + + if dialog.is_logged_in(): + return dialog.get_user() + else: + raise RuntimeError("Connect user is not logged in") + + def login(self): + + users = self.connect_manager['public-users'] + server = self.connect_manager['server-address'] + + if not users: + try: + return self.login_manual() + except RuntimeError: + raise RuntimeError("No user selected") + + dialog = UsersConnect("script-emby-connect-users.xml", *XML_PATH) + dialog.set_args(**{'server': server, 'users': users}) + dialog.doModal() + + if dialog.is_user_selected(): + user = dialog.get_user() + username = user['Name'] + + if user['HasPassword']: + LOG.debug("User has password, present manual login") + try: + return self.login_manual(username) + except RuntimeError: pass + else: + return self.connect_manager['login'](server, username) + + elif dialog.is_manual_login(): + try: + return self.login_manual() + except RuntimeError: pass + else: + raise RuntimeError("No user selected") + + return self.login() + + def setup_login_manual(self): + + ''' Setup manual login by itself for default server. + ''' + client = self.get_client() + client.set_credentials(get_credentials()) + manager = client.auth + + try: + self.login_manual(manager=manager) + except RuntimeError: + return + + credentials = client.get_credentials() + save_credentials(credentials) + + def login_manual(self, user=None, manager=None): + + ''' Return manual login user authenticated or raise error. + ''' + dialog = LoginManual("script-emby-connect-login-manual.xml", *XML_PATH) + dialog.set_args(**{'connect_manager': manager or self.connect_manager, 'username': user or {}}) + dialog.doModal() + + if dialog.is_logged_in(): + return dialog.get_user() + else: + raise RuntimeError("User is not authenticated") + + def remove_server(self, server_id): + + ''' Stop client and remove server. + ''' + Emby(server_id).close() + credentials = get_credentials() + + for server in credentials['Servers']: + if server['Id'] == server_id: + credentials['Servers'].remove(server) + + break + + save_credentials(credentials) + LOG.info("[ remove server ] %s", server_id) + diff --git a/resources/lib/connect/__init__.py b/resources/lib/connect/__init__.py deleted file mode 100644 index b93054b3..00000000 --- a/resources/lib/connect/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# Dummy file to make this directory a package. diff --git a/resources/lib/connect/connectionmanager.py b/resources/lib/connect/connectionmanager.py deleted file mode 100644 index be88f20b..00000000 --- a/resources/lib/connect/connectionmanager.py +++ /dev/null @@ -1,821 +0,0 @@ -# -*- coding: utf-8 -*- - -################################################################################################# - -import hashlib -import json -import logging -import requests -import socket -import time -from datetime import datetime - -import credentials as cred - -################################################################################################# - -# Disable requests logging -from requests.packages.urllib3.exceptions import InsecureRequestWarning, InsecurePlatformWarning, SNIMissingWarning -requests.packages.urllib3.disable_warnings(InsecureRequestWarning) -requests.packages.urllib3.disable_warnings(InsecurePlatformWarning) -requests.packages.urllib3.disable_warnings(SNIMissingWarning) - -log = logging.getLogger("EMBY."+__name__.split('.')[-1]) - -################################################################################################# - -ConnectionState = { - 'Unavailable': 0, - 'ServerSelection': 1, - 'ServerSignIn': 2, - 'SignedIn': 3, - 'ConnectSignIn': 4, - 'ServerUpdateNeeded': 5 -} - -ConnectionMode = { - 'Local': 0, - 'Remote': 1, - 'Manual': 2 -} - -################################################################################################# - -def getServerAddress(server, mode): - - modes = { - ConnectionMode['Local']: server.get('LocalAddress'), - ConnectionMode['Remote']: server.get('RemoteAddress'), - ConnectionMode['Manual']: server.get('ManualAddress') - } - return (modes.get(mode) or - server.get('ManualAddress',server.get('LocalAddress',server.get('RemoteAddress')))) - - -class ConnectionManager(object): - - default_timeout = 10 - apiClients = [] - minServerVersion = "3.0.5930" - connectUser = None - - - def __init__(self, appName, appVersion, deviceName, deviceId, capabilities=None, devicePixelRatio=None): - - log.info("Begin ConnectionManager constructor") - - self.credentialProvider = cred.Credentials() - self.appName = appName - self.appVersion = appVersion - self.deviceName = deviceName - self.deviceId = deviceId - self.capabilities = capabilities - self.devicePixelRatio = devicePixelRatio - - - def setFilePath(self, path): - # Set where to save persistant data - self.credentialProvider.setPath(path) - - def _getAppVersion(self): - return self.appVersion - - def _getCapabilities(self): - return self.capabilities - - def _getDeviceId(self): - return self.deviceId - - def _connectUserId(self): - return self.credentialProvider.getCredentials().get('ConnectUserId') - - def _connectToken(self): - return self.credentialProvider.getCredentials().get('ConnectAccessToken') - - def getServerInfo(self, id_): - - servers = self.credentialProvider.getCredentials()['Servers'] - - for s in servers: - if s['Id'] == id_: - return s - - def _getLastUsedServer(self): - - servers = self.credentialProvider.getCredentials()['Servers'] - - if not len(servers): - return - - try: - servers.sort(key=lambda x: datetime.strptime(x['DateLastAccessed'], "%Y-%m-%dT%H:%M:%SZ"), reverse=True) - except TypeError: - servers.sort(key=lambda x: datetime(*(time.strptime(x['DateLastAccessed'], "%Y-%m-%dT%H:%M:%SZ")[0:6])), reverse=True) - - return servers[0] - - def _mergeServers(self, list1, list2): - - for i in range(0, len(list2), 1): - try: - self.credentialProvider.addOrUpdateServer(list1, list2[i]) - except KeyError: - continue - - return list1 - - def _connectUser(self): - - return self.connectUser - - def _resolveFailure(self): - - return { - 'State': ConnectionState['Unavailable'], - 'ConnectUser': self._connectUser() - } - - def _getMinServerVersion(self, val=None): - - if val is not None: - self.minServerVersion = val - - return self.minServerVersion - - def _updateServerInfo(self, server, systemInfo): - - if server is None or systemInfo is None: - return - - server['Name'] = systemInfo['ServerName'] - server['Id'] = systemInfo['Id'] - - if systemInfo.get('LocalAddress'): - server['LocalAddress'] = systemInfo['LocalAddress'] - if systemInfo.get('WanAddress'): - server['RemoteAddress'] = systemInfo['WanAddress'] - if systemInfo.get('MacAddress'): - server['WakeOnLanInfos'] = [{'MacAddress': systemInfo['MacAddress']}] - - def _getHeaders(self, request): - - headers = request.setdefault('headers', {}) - - if request.get('dataType') == "json": - headers['Accept'] = "application/json" - request.pop('dataType') - - headers['X-Application'] = self._addAppInfoToConnectRequest() - headers['Content-type'] = request.get('contentType', - 'application/x-www-form-urlencoded; charset=UTF-8') - - def requestUrl(self, request): - - if not request: - raise AttributeError("Request cannot be null") - - self._getHeaders(request) - request['timeout'] = request.get('timeout') or self.default_timeout - request['verify'] = request.get('ssl') or False - - action = request['type'] - request.pop('type', None) - request.pop('ssl', None) - - log.debug("ConnectionManager requesting %s" % request) - - try: - r = self._requests(action, **request) - log.info("ConnectionManager response status: %s" % r.status_code) - r.raise_for_status() - - except Exception as e: # Elaborate on exceptions? - log.error(e) - raise - - else: - try: - return r.json() - except ValueError: - r.content # Read response to release connection - return - - def _requests(self, action, **kwargs): - - if action == "GET": - r = requests.get(**kwargs) - elif action == "POST": - r = requests.post(**kwargs) - - return r - - def getEmbyServerUrl(self, baseUrl, handler): - return "%s/emby/%s" % (baseUrl, handler) - - def getConnectUrl(self, handler): - return "https://connect.emby.media/service/%s" % handler - - def _findServers(self, foundServers): - - servers = [] - - for foundServer in foundServers: - - server = self._convertEndpointAddressToManualAddress(foundServer) - - info = { - 'Id': foundServer['Id'], - 'LocalAddress': server or foundServer['Address'], - 'Name': foundServer['Name'] - } - info['LastConnectionMode'] = ConnectionMode['Manual'] if info.get('ManualAddress') else ConnectionMode['Local'] - - servers.append(info) - else: - return servers - - def _convertEndpointAddressToManualAddress(self, info): - - if info.get('Address') and info.get('EndpointAddress'): - address = info['EndpointAddress'].split(':')[0] - - # Determine the port, if any - parts = info['Address'].split(':') - if len(parts) > 1: - portString = parts[len(parts)-1] - - try: - address += ":%s" % int(portString) - return self._normalizeAddress(address) - except ValueError: - pass - - return None - - def _serverDiscovery(self): - - MULTI_GROUP = ("<broadcast>", 7359) - MESSAGE = "who is EmbyServer?" - - sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - sock.settimeout(1.0) # This controls the socket.timeout exception - - sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 20) - sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) - sock.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_LOOP, 1) - sock.setsockopt(socket.IPPROTO_IP, socket.SO_REUSEADDR, 1) - - log.debug("MultiGroup : %s", str(MULTI_GROUP)) - log.debug("Sending UDP Data: %s", MESSAGE) - - servers = [] - - try: - sock.sendto(MESSAGE, MULTI_GROUP) - except Exception as error: - log.error(error) - return servers - - while True: - try: - data, addr = sock.recvfrom(1024) # buffer size - servers.append(json.loads(data)) - - except socket.timeout: - log.info("Found Servers: %s", servers) - return servers - - except Exception as e: - log.error("Error trying to find servers: %s", e) - return servers - - def _normalizeAddress(self, address): - # Attempt to correct bad input - address = address.strip() - address = address.lower() - - if 'http' not in address: - address = "http://%s" % address - - return address - - def connectToAddress(self, address, options={}): - - if not address: - return False - - address = self._normalizeAddress(address) - - def _onFail(): - log.error("connectToAddress %s failed" % address) - return self._resolveFailure() - - try: - publicInfo = self._tryConnect(address, options=options) - except Exception: - return _onFail() - else: - log.info("connectToAddress %s succeeded" % address) - server = { - 'ManualAddress': address, - 'LastConnectionMode': ConnectionMode['Manual'] - } - self._updateServerInfo(server, publicInfo) - server = self.connectToServer(server, options) - if server is False: - return _onFail() - else: - return server - - def onAuthenticated(self, result, options={}): - - credentials = self.credentialProvider.getCredentials() - for s in credentials['Servers']: - if s['Id'] == result['ServerId']: - server = s - break - else: # Server not found? - return - - if options.get('updateDateLastAccessed') is not False: - server['DateLastAccessed'] = datetime.now().strftime('%Y-%m-%dT%H:%M:%SZ') - - server['UserId'] = result['User']['Id'] - server['AccessToken'] = result['AccessToken'] - - self.credentialProvider.addOrUpdateServer(credentials['Servers'], server) - self._saveUserInfoIntoCredentials(server, result['User']) - self.credentialProvider.getCredentials(credentials) - - def _tryConnect(self, url, timeout=None, options={}): - - url = self.getEmbyServerUrl(url, "system/info/public") - log.info("tryConnect url: %s" % url) - - return self.requestUrl({ - - 'type': "GET", - 'url': url, - 'dataType': "json", - 'timeout': timeout, - 'ssl': options.get('ssl') - }) - - def _addAppInfoToConnectRequest(self): - return "%s/%s" % (self.appName, self.appVersion) - - def _getConnectServers(self, credentials): - - log.info("Begin getConnectServers") - - servers = [] - - if not credentials.get('ConnectAccessToken') or not credentials.get('ConnectUserId'): - return servers - - url = self.getConnectUrl("servers?userId=%s" % credentials['ConnectUserId']) - request = { - - 'type': "GET", - 'url': url, - 'dataType': "json", - 'headers': { - 'X-Connect-UserToken': credentials['ConnectAccessToken'] - } - } - for server in self.requestUrl(request): - - servers.append({ - - 'ExchangeToken': server['AccessKey'], - 'ConnectServerId': server['Id'], - 'Id': server['SystemId'], - 'Name': server['Name'], - 'RemoteAddress': server['Url'], - 'LocalAddress': server['LocalAddress'], - 'UserLinkType': "Guest" if server['UserType'].lower() == "guest" else "LinkedUser", - }) - - return servers - - def getAvailableServers(self): - - log.info("Begin getAvailableServers") - - # Clone the array - credentials = self.credentialProvider.getCredentials() - - connectServers = self._getConnectServers(credentials) - foundServers = self._findServers(self._serverDiscovery()) - if not connectServers and not foundServers: # back out right away, no point in continuing - log.info("Found no servers") - return [] - - servers = list(credentials['Servers']) - self._mergeServers(servers, foundServers) - self._mergeServers(servers, connectServers) - - servers = self._filterServers(servers, connectServers) - - try: - servers.sort(key=lambda x: datetime.strptime(x['DateLastAccessed'], "%Y-%m-%dT%H:%M:%SZ"), reverse=True) - except TypeError: - servers.sort(key=lambda x: datetime(*(time.strptime(x['DateLastAccessed'], "%Y-%m-%dT%H:%M:%SZ")[0:6])), reverse=True) - - credentials['Servers'] = servers - self.credentialProvider.getCredentials(credentials) - - return servers - - def _filterServers(self, servers, connectServers): - - filtered = [] - - for server in servers: - # It's not a connect server, so assume it's still valid - if server.get('ExchangeToken') is None: - filtered.append(server) - continue - - for connectServer in connectServers: - if server['Id'] == connectServer['Id']: - filtered.append(server) - break - else: - return filtered - - def _getConnectPasswordHash(self, password): - - password = self._cleanConnectPassword(password) - - return hashlib.md5(password).hexdigest() - - def _saveUserInfoIntoCredentials(self, server, user): - - info = { - 'Id': user['Id'], - 'IsSignedInOffline': True - } - - self.credentialProvider.addOrUpdateUser(server, info) - - def _compareVersions(self, a, b): - """ - -1 a is smaller - 1 a is larger - 0 equal - """ - a = a.split('.') - b = b.split('.') - - for i in range(0, max(len(a), len(b)), 1): - try: - aVal = a[i] - except IndexError: - aVal = 0 - - try: - bVal = b[i] - except IndexError: - bVal = 0 - - if aVal < bVal: - return -1 - - if aVal > bVal: - return 1 - - return 0 - - def connectToServer(self, server, options={}): - - log.info("begin connectToServer") - - tests = [] - - if server.get('LastConnectionMode') is not None: - #tests.append(server['LastConnectionMode']) - pass - if ConnectionMode['Manual'] not in tests: - tests.append(ConnectionMode['Manual']) - if ConnectionMode['Local'] not in tests: - tests.append(ConnectionMode['Local']) - if ConnectionMode['Remote'] not in tests: - tests.append(ConnectionMode['Remote']) - - # TODO: begin to wake server - - log.info("beginning connection tests") - return self._testNextConnectionMode(tests, 0, server, options) - - def _stringEqualsIgnoreCase(self, str1, str2): - - return (str1 or "").lower() == (str2 or "").lower() - - def _testNextConnectionMode(self, tests, index, server, options): - - if index >= len(tests): - log.info("Tested all connection modes. Failing server connection.") - return self._resolveFailure() - - mode = tests[index] - address = getServerAddress(server, mode) - enableRetry = False - skipTest = False - timeout = self.default_timeout - - if mode == ConnectionMode['Local']: - enableRetry = True - timeout = 8 - - if self._stringEqualsIgnoreCase(address, server.get('ManualAddress')): - log.info("skipping LocalAddress test because it is the same as ManualAddress") - skipTest = True - - elif mode == ConnectionMode['Manual']: - - if self._stringEqualsIgnoreCase(address, server.get('LocalAddress')): - enableRetry = True - timeout = 8 - - if skipTest or not address: - log.info("skipping test at index: %s" % index) - return self._testNextConnectionMode(tests, index+1, server, options) - - log.info("testing connection mode %s with server %s" % (mode, server.get('Name'))) - try: - result = self._tryConnect(address, timeout, options) - - except Exception: - log.error("test failed for connection mode %s with server %s" % (mode, server.get('Name'))) - - if enableRetry: - # TODO: wake on lan and retry - return self._testNextConnectionMode(tests, index+1, server, options) - else: - return self._testNextConnectionMode(tests, index+1, server, options) - else: - - if self._compareVersions(self._getMinServerVersion(), result['Version']) == 1: - log.warn("minServerVersion requirement not met. Server version: %s" % result['Version']) - return { - 'State': ConnectionState['ServerUpdateNeeded'], - 'Servers': [server] - } - else: - log.info("calling onSuccessfulConnection with connection mode %s with server %s" - % (mode, server.get('Name'))) - return self._onSuccessfulConnection(server, result, mode, options) - - def _onSuccessfulConnection(self, server, systemInfo, connectionMode, options): - - credentials = self.credentialProvider.getCredentials() - - if credentials.get('ConnectAccessToken') and options.get('enableAutoLogin') is not False: - - if self._ensureConnectUser(credentials) is not False: - - if server.get('ExchangeToken'): - - self._addAuthenticationInfoFromConnect(server, connectionMode, credentials, options) - - return self._afterConnectValidated(server, credentials, systemInfo, connectionMode, True, options) - - def _afterConnectValidated(self, server, credentials, systemInfo, connectionMode, verifyLocalAuthentication, options): - - if options.get('enableAutoLogin') is False: - server['UserId'] = None - server['AccessToken'] = None - - elif (verifyLocalAuthentication and server.get('AccessToken') and - options.get('enableAutoLogin') is not False): - - if self._validateAuthentication(server, connectionMode, options) is not False: - return self._afterConnectValidated(server, credentials, systemInfo, connectionMode, False, options) - - return - - self._updateServerInfo(server, systemInfo) - server['LastConnectionMode'] = connectionMode - - if options.get('updateDateLastAccessed') is not False: - server['DateLastAccessed'] = datetime.now().strftime('%Y-%m-%dT%H:%M:%SZ') - - self.credentialProvider.addOrUpdateServer(credentials['Servers'], server) - self.credentialProvider.getCredentials(credentials) - - result = { - 'Servers': [], - 'ConnectUser': self._connectUser() - } - result['State'] = ConnectionState['SignedIn'] if (server.get('AccessToken') and options.get('enableAutoLogin') is not False) else ConnectionState['ServerSignIn'] - result['Servers'].append(server) - - # Connected - return result - - def _validateAuthentication(self, server, connectionMode, options={}): - - url = getServerAddress(server, connectionMode) - request = { - - 'type': "GET", - 'url': self.getEmbyServerUrl(url, "System/Info"), - 'ssl': options.get('ssl'), - 'dataType': "json", - 'headers': { - 'X-MediaBrowser-Token': server['AccessToken'] - } - } - try: - systemInfo = self.requestUrl(request) - self._updateServerInfo(server, systemInfo) - - if server.get('UserId'): - user = self.requestUrl({ - - 'type': "GET", - 'url': self.getEmbyServerUrl(url, "users/%s" % server['UserId']), - 'ssl': options.get('ssl'), - 'dataType': "json", - 'headers': { - 'X-MediaBrowser-Token': server['AccessToken'] - } - }) - - except Exception: - server['UserId'] = None - server['AccessToken'] = None - return False - - def loginToConnect(self, username, password): - - if not username: - raise AttributeError("username cannot be empty") - - if not password: - raise AttributeError("password cannot be empty") - - md5 = self._getConnectPasswordHash(password) - request = { - 'type': "POST", - 'url': self.getConnectUrl("user/authenticate"), - 'data': { - 'nameOrEmail': username, - 'password': md5 - }, - 'dataType': "json" - } - try: - result = self.requestUrl(request) - except Exception as e: # Failed to login - log.error(e) - return False - else: - credentials = self.credentialProvider.getCredentials() - credentials['ConnectAccessToken'] = result['AccessToken'] - credentials['ConnectUserId'] = result['User']['Id'] - credentials['ConnectUser'] = result['User']['DisplayName'] - self.credentialProvider.getCredentials(credentials) - # Signed in - self._onConnectUserSignIn(result['User']) - - return result - - def _onConnectUserSignIn(self, user): - - self.connectUser = user - log.info("connectusersignedin %s" % user) - - def _getConnectUser(self, userId, accessToken): - - if not userId: - raise AttributeError("null userId") - - if not accessToken: - raise AttributeError("null accessToken") - - url = self.getConnectUrl('user?id=%s' % userId) - - return self.requestUrl({ - - 'type': "GET", - 'url': url, - 'dataType': "json", - 'headers': { - 'X-Connect-UserToken': accessToken - } - }) - - def _addAuthenticationInfoFromConnect(self, server, connectionMode, credentials, options={}): - - if not server.get('ExchangeToken'): - raise KeyError("server['ExchangeToken'] cannot be null") - - if not credentials.get('ConnectUserId'): - raise KeyError("credentials['ConnectUserId'] cannot be null") - - url = getServerAddress(server, connectionMode) - url = self.getEmbyServerUrl(url, "Connect/Exchange?format=json") - auth = ('MediaBrowser Client="%s", Device="%s", DeviceId="%s", Version="%s"' - % (self.appName, self.deviceName, self.deviceId, self.appVersion)) - try: - auth = self.requestUrl({ - - 'url': url, - 'type': "GET", - 'dataType': "json", - 'ssl': options.get('ssl'), - 'params': { - 'ConnectUserId': credentials['ConnectUserId'] - }, - 'headers': { - 'X-MediaBrowser-Token': server['ExchangeToken'], - 'X-Emby-Authorization': auth - } - }) - except Exception: - server['UserId'] = None - server['AccessToken'] = None - return False - else: - server['UserId'] = auth['LocalUserId'] - server['AccessToken'] = auth['AccessToken'] - return auth - - def _ensureConnectUser(self, credentials): - - if self.connectUser and self.connectUser['Id'] == credentials['ConnectUserId']: - return - - elif credentials.get('ConnectUserId') and credentials.get('ConnectAccessToken'): - - self.connectUser = None - - try: - result = self._getConnectUser(credentials['ConnectUserId'], credentials['ConnectAccessToken']) - self._onConnectUserSignIn(result) - except Exception: - return False - - def connect(self, options={}): - - log.info("Begin connect") - - servers = self.getAvailableServers() - return self._connectToServers(servers, options) - - def _connectToServers(self, servers, options): - - log.info("Begin connectToServers, with %s servers" % len(servers)) - - if len(servers) == 1: - result = self.connectToServer(servers[0], options) - if result and result.get('State') == ConnectionState['Unavailable']: - result['State'] = ConnectionState['ConnectSignIn'] if result['ConnectUser'] == None else ConnectionState['ServerSelection'] - - log.info("resolving connectToServers with result['State']: %s" % result) - return result - - firstServer = self._getLastUsedServer() - # See if we have any saved credentials and can auto sign in - if firstServer and firstServer['DateLastAccessed'] != "2001-01-01T00:00:00Z": - result = self.connectToServer(firstServer, options) - if result and result.get('State') == ConnectionState['SignedIn']: - return result - - # Return loaded credentials if exists - credentials = self.credentialProvider.getCredentials() - self._ensureConnectUser(credentials) - - return { - 'Servers': servers, - 'State': ConnectionState['ConnectSignIn'] if (not len(servers) and not self._connectUser()) else ConnectionState['ServerSelection'], - 'ConnectUser': self._connectUser() - } - - def _cleanConnectPassword(self, password): - - password = password or "" - - password = password.replace("&", '&') - password = password.replace("/", '\') - password = password.replace("!", '!') - password = password.replace("$", '$') - password = password.replace("\"", '"') - password = password.replace("<", '<') - password = password.replace(">", '>') - password = password.replace("'", ''') - - return password - - def clearData(self): - - log.info("connection manager clearing data") - - self.connectUser = None - credentials = self.credentialProvider.getCredentials() - credentials['ConnectAccessToken'] = None - credentials['ConnectUserId'] = None - credentials['Servers'] = [] - self.credentialProvider.getCredentials(credentials) \ No newline at end of file diff --git a/resources/lib/connectmanager.py b/resources/lib/connectmanager.py deleted file mode 100644 index ce4ba220..00000000 --- a/resources/lib/connectmanager.py +++ /dev/null @@ -1,243 +0,0 @@ -# -*- coding: utf-8 -*- - -################################################################################################## - -import logging - -import xbmc -import xbmcaddon -import xbmcvfs - -import clientinfo -import read_embyserver as embyserver -import connect.connectionmanager as connectionmanager -from dialogs import ServerConnect, UsersConnect, LoginConnect, LoginManual, ServerManual -#from ga_client import GoogleAnalytics -from utils import window - -################################################################################################## - -log = logging.getLogger("EMBY."+__name__) -addon = xbmcaddon.Addon(id='plugin.video.emby') - -STATE = connectionmanager.ConnectionState -XML_PATH = (addon.getAddonInfo('path'), "default", "1080i") - -################################################################################################## - -class ConnectManager(object): - - _shared_state = {} # Borg - state = {} - - - def __init__(self): - - self.__dict__ = self._shared_state - - client_info = clientinfo.ClientInfo() - self.emby = embyserver.Read_EmbyServer() - - version = client_info.get_version() - device_name = client_info.get_device_name() - device_id = client_info.get_device_id() - self._connect = connectionmanager.ConnectionManager(appName="Kodi", - appVersion=version, - deviceName=device_name, - deviceId=device_id) - path = xbmc.translatePath( - "special://profile/addon_data/plugin.video.emby/").decode('utf-8') - - if not xbmcvfs.exists(path): - xbmcvfs.mkdirs(path) - - self._connect.setFilePath(path) - - if window('emby_state.json'): - self.state = window('emby_state.json') - - elif not self.state: - self.state = self._connect.connect() - log.info("Started with: %s", self.state) - window('emby_state.json', value=self.state) - - - def update_state(self): - self.state = self._connect.connect({'updateDateLastAccessed': False}) - return self.get_state() - - def get_state(self): - window('emby_state.json', value=self.state) - return self.state - - def get_server(self, server, options={}): - self.state = self._connect.connectToAddress(server, options) - return self.get_state() - - @classmethod - def get_address(cls, server): - return connectionmanager.getServerAddress(server, server['LastConnectionMode']) - - def clear_data(self): - self._connect.clearData() - - def select_servers(self): - # Will return selected server or raise error - state = self._connect.connect({'enableAutoLogin': False}) - user = state.get('ConnectUser') or {} - - dialog = ServerConnect("script-emby-connect-server.xml", *XML_PATH) - kwargs = { - 'connect_manager': self._connect, - 'username': user.get('DisplayName', ""), - 'user_image': user.get('ImageUrl'), - 'servers': state.get('Servers') or [], - 'emby_connect': False if user else True - } - dialog.set_args(**kwargs) - dialog.doModal() - - if dialog.is_server_selected(): - log.debug("Server selected") - return dialog.get_server() - - elif dialog.is_connect_login(): - log.debug("Login with Emby Connect") - try: # Login to emby connect - self.login_connect() - except RuntimeError: - pass - return self.select_servers() - - elif dialog.is_manual_server(): - log.debug("Add manual server") - try: # Add manual server address - return self.manual_server() - except RuntimeError: - return self.select_servers() - else: - raise RuntimeError("No server selected") - - def manual_server(self): - # Return server or raise error - dialog = ServerManual("script-emby-connect-server-manual.xml", *XML_PATH) - dialog.set_connect_manager(self._connect) - dialog.doModal() - - if dialog.is_connected(): - return dialog.get_server() - else: - raise RuntimeError("Server is not connected") - - def login_connect(self): - # Return connect user or raise error - dialog = LoginConnect("script-emby-connect-login.xml", *XML_PATH) - dialog.set_connect_manager(self._connect) - dialog.doModal() - - self.update_state() - - if dialog.is_logged_in(): - return dialog.get_user() - else: - raise RuntimeError("Connect user is not logged in") - - def login(self, server=None): - - """ - ga = GoogleAnalytics() - ga.sendEventData("Connect", "UserLogin") - """ - - # Return user or raise error - server = server or self.state['Servers'][0] - server_address = connectionmanager.getServerAddress(server, server['LastConnectionMode']) - - users = ""; - try: - users = self.emby.getUsers(server_address) - except Exception as error: - log.info("Error getting users from server: " + str(error)) - - if not users: - try: - return self.login_manual(server_address) - except RuntimeError: - raise RuntimeError("No user selected") - - dialog = UsersConnect("script-emby-connect-users.xml", *XML_PATH) - dialog.set_server(server_address) - dialog.set_users(users) - dialog.doModal() - - if dialog.is_user_selected(): - - user = dialog.get_user() - username = user['Name'] - - if user['HasPassword']: - log.debug("User has password, present manual login") - try: - return self.login_manual(server_address, username) - except RuntimeError: - return self.login(server) - else: - try: - user = self.emby.loginUser(server_address, username) - except Exception as error: - log.info("Error logging in user: " + str(error)) - raise - - self._connect.onAuthenticated(user) - return user - - elif dialog.is_manual_login(): - try: - return self.login_manual(server_address) - except RuntimeError: - return self.login(server) - else: - raise RuntimeError("No user selected") - - def login_manual(self, server, user=None): - # Return manual login user authenticated or raise error - dialog = LoginManual("script-emby-connect-login-manual.xml", *XML_PATH) - dialog.set_server(server) - dialog.set_user(user) - dialog.doModal() - - if dialog.is_logged_in(): - user = dialog.get_user() - self._connect.onAuthenticated(user) - return user - else: - raise RuntimeError("User is not authenticated") - - def update_token(self, server): - - credentials = self._connect.credentialProvider.getCredentials() - self._connect.credentialProvider.addOrUpdateServer(credentials['Servers'], server) - - for server in self.get_state()['Servers']: - for cred_server in credentials['Servers']: - if server['Id'] == cred_server['Id']: - # Update token saved in current state - server.update(cred_server) - # Update the token in data.txt - self._connect.credentialProvider.getCredentials(credentials) - - def get_connect_servers(self): - - connect_servers = [] - servers = self._connect.getAvailableServers() - for server in servers: - if 'ExchangeToken' in server: - result = self.connect_server(server) - if result['State'] == STATE['SignedIn']: - connect_servers.append(server) - - log.info(connect_servers) - return connect_servers - - def connect_server(self, server): - return self._connect.connectToServer(server, {'updateDateLastAccessed': False}) diff --git a/resources/lib/context_entry.py b/resources/lib/context_entry.py deleted file mode 100644 index e46eac48..00000000 --- a/resources/lib/context_entry.py +++ /dev/null @@ -1,204 +0,0 @@ -# -*- coding: utf-8 -*- - -################################################################################################# - -import logging -import sys -from datetime import timedelta - -import xbmc -import xbmcaddon - -import api -import read_embyserver as embyserver -import playbackutils as pbutils -import embydb_functions as embydb -import musicutils as musicutils -from utils import settings, dialog, language as lang -from dialogs import context, resume -from database import DatabaseConn - -################################################################################################# - -log = logging.getLogger("EMBY."+__name__) -addon = xbmcaddon.Addon('plugin.video.emby') - -XML_PATH = (addon.getAddonInfo('path'), "default", "1080i") -OPTIONS = { - - 'Refresh': lang(30410), - 'Delete': lang(30409), - 'Addon': lang(30408), - 'AddFav': lang(30405), - 'RemoveFav': lang(30406), - 'RateSong': lang(30407), - 'Transcode': lang(30412) -} - -################################################################################################# - - -class ContextMenu(object): - - _selected_option = None - - - def __init__(self, force_transcode=False): - - self.emby = embyserver.Read_EmbyServer() - - self.kodi_id = sys.listitem.getVideoInfoTag().getDbId() - self.item_type = self._get_item_type() - - self.item_id = self._get_item_id(self.kodi_id, self.item_type) - log.info("Found item_id: %s item_type: %s", self.item_id, self.item_type) - - if self.item_id: - - self.item = self.emby.getItem(self.item_id) - self.api = api.API(self.item) - - if force_transcode: - self._force_transcode() - - elif self._select_menu(): - self._action_menu() - - if self._selected_option in (OPTIONS['Delete'], OPTIONS['AddFav'], - OPTIONS['RemoveFav'], OPTIONS['RateSong']): - log.info("refreshing container") - xbmc.sleep(500) - xbmc.executebuiltin('Container.Refresh') - - @classmethod - def _get_item_type(cls): - - item_type = sys.listitem.getVideoInfoTag().getMediaType() - - if not item_type: - - if xbmc.getCondVisibility('Container.Content(albums)'): - item_type = "album" - elif xbmc.getCondVisibility('Container.Content(artists)'): - item_type = "artist" - elif xbmc.getCondVisibility('Container.Content(songs)'): - item_type = "song" - elif xbmc.getCondVisibility('Container.Content(pictures)'): - item_type = "picture" - else: - log.info("item_type is unknown") - - return item_type.decode('utf-8') - - @classmethod - def _get_item_id(cls, kodi_id, item_type): - - item_id = xbmc.getInfoLabel('ListItem.Property(embyid)') - - if not item_id and kodi_id and item_type: - - with DatabaseConn('emby') as cursor: - emby_db = embydb.Embydb_Functions(cursor) - item = emby_db.getItem_byKodiId(kodi_id, item_type) - try: - item_id = item[0] - except TypeError: - pass - - return item_id - - def _select_menu(self): - # Display select dialog - userdata = self.api.get_userdata() - options = [] - - if userdata['Favorite']: - # Remove from emby favourites - options.append(OPTIONS['RemoveFav']) - else: - # Add to emby favourites - options.append(OPTIONS['AddFav']) - - if self.item_type == "song": - # Set custom song rating - options.append(OPTIONS['RateSong']) - - # Refresh item - options.append(OPTIONS['Refresh']) - # Delete item - options.append(OPTIONS['Delete']) - # Addon settings - options.append(OPTIONS['Addon']) - - context_menu = context.ContextMenu("script-emby-context.xml", *XML_PATH) - context_menu.set_options(options) - context_menu.doModal() - - if context_menu.is_selected(): - self._selected_option = context_menu.get_selected() - - return self._selected_option - - def _action_menu(self): - - selected = self._selected_option.decode('utf-8') - - if selected == OPTIONS['Refresh']: - self.emby.refreshItem(self.item_id) - - elif selected == OPTIONS['AddFav']: - self.emby.updateUserRating(self.item_id, favourite=True) - - elif selected == OPTIONS['RemoveFav']: - self.emby.updateUserRating(self.item_id, favourite=False) - - elif selected == OPTIONS['RateSong']: - self._rate_song() - - elif selected == OPTIONS['Addon']: - xbmc.executebuiltin('Addon.OpenSettings(plugin.video.emby)') - - elif selected == OPTIONS['Delete']: - self._delete_item() - - def _rate_song(self): - - with DatabaseConn('music') as cursor_music: - query = "SELECT rating FROM song WHERE idSong = ?" - cursor_music.execute(query, (self.kodi_id,)) - try: - value = cursor_music.fetchone()[0] - current_value = int(round(float(value), 0)) - except TypeError: - pass - else: - new_value = dialog("numeric", 0, lang(30411), str(current_value)) - if new_value > -1: - - new_value = int(new_value) - if new_value > 5: - new_value = 5 - - if settings('enableUpdateSongRating') == "true": - musicutils.updateRatingToFile(new_value, self.api.get_file_path()) - - query = "UPDATE song SET rating = ? WHERE idSong = ?" - cursor_music.execute(query, (new_value, self.kodi_id,)) - - def _delete_item(self): - - delete = True - if settings('skipContextMenu') != "true": - - if not dialog(type_="yesno", heading="{emby}", line1=lang(33041)): - log.info("User skipped deletion for: %s", self.item_id) - delete = False - - if delete: - log.info("Deleting request: %s", self.item_id) - self.emby.deleteItem(self.item_id) - - def _force_transcode(self): - - log.info("Force transcode called.") - pbutils.PlaybackUtils(self.item).play(self.item['Id'], self.kodi_id, True) diff --git a/resources/lib/database.py b/resources/lib/database.py deleted file mode 100644 index 9c7b85b2..00000000 --- a/resources/lib/database.py +++ /dev/null @@ -1,239 +0,0 @@ -# -*- coding: utf-8 -*- - -################################################################################################# - -import logging -import sqlite3 -import sys -import traceback - -import xbmc -import xbmcgui -import xbmcplugin -import xbmcvfs - -from views import Playlist, VideoNodes -from utils import window, should_stop, settings, language - -################################################################################################# - -log = logging.getLogger("EMBY."+__name__) -KODI = xbmc.getInfoLabel('System.BuildVersion')[:2] - -################################################################################################# - -def video_database(): - db_version = { - - '17': 107,# Krypton - '18': 110 # Leia - } - return xbmc.translatePath("special://database/MyVideos%s.db" - % db_version.get(KODI, "")).decode('utf-8') - -def music_database(): - db_version = { - - '17': 60, # Krypton - '18': 72 # Leia - } - return xbmc.translatePath("special://database/MyMusic%s.db" - % db_version.get(KODI, "")).decode('utf-8') - -def texture_database(): - return xbmc.translatePath("special://database/Textures13.db").decode('utf-8') - -def emby_database(): - return xbmc.translatePath("special://database/emby.db").decode('utf-8') - -def kodi_commit(): - # verification for the Kodi video scan - kodi_scan = window('emby_kodiScan') == "true" - count = 0 - - while kodi_scan: - log.info("kodi scan is running, waiting...") - - if count == 10: - log.info("flag still active, but will try to commit") - window('emby_kodiScan', clear=True) - - elif should_stop() or xbmc.Monitor().waitForAbort(1): - log.info("commit unsuccessful. sync terminating") - return False - - kodi_scan = window('emby_kodiScan') == "true" - count += 1 - - return True - - -class DatabaseConn(object): - # To be called as context manager - i.e. with DatabaseConn() as conn: #dostuff - - def __init__(self, database_file="video", commit_on_close=True, timeout=120): - """ - database_file can be custom: emby, texture, music, video, :memory: or path to the file - commit_mode set to None to autocommit (isolation_level). See python documentation. - """ - self.db_file = database_file - self.commit_on_close = commit_on_close - self.timeout = timeout - - def __enter__(self): - # Open the connection - self.path = self._SQL(self.db_file) - #traceback.print_stack() - - if settings('dblock') == "true": - self.conn = sqlite3.connect(self.path, isolation_level=None, timeout=self.timeout) - else: - self.conn = sqlite3.connect(self.path, timeout=self.timeout) - - log.info("opened: %s - %s", self.path, id(self.conn)) - self.cursor = self.conn.cursor() - - if self.db_file == "emby": - verify_emby_database(self.cursor) - self.conn.commit() - - return self.cursor - - def _SQL(self, media_type): - - databases = { - 'emby': emby_database, - 'texture': texture_database, - 'music': music_database, - 'video': video_database - } - return databases[media_type]() if media_type in databases else self.db_file - - def __exit__(self, exc_type, exc_val, exc_tb): - # Close the connection - changes = self.conn.total_changes - - if exc_type is not None: - # Errors were raised in the with statement - log.error("Type: %s Value: %s", exc_type, exc_val) - - if self.commit_on_close == True and changes: - log.info("number of rows updated: %s", changes) - if self.db_file == "video": - kodi_commit() - self.conn.commit() - log.info("commit: %s", self.path) - - log.info("closing: %s - %s", self.path, id(self.conn)) - self.cursor.close() - self.conn.close() - - -def verify_emby_database(cursor): - # Create the tables for the emby database - # emby, view, version - - log.info("Verifying emby DB") - cursor.execute( - """CREATE TABLE IF NOT EXISTS emby( - emby_id TEXT UNIQUE, media_folder TEXT, emby_type TEXT, media_type TEXT, - kodi_id INTEGER, kodi_fileid INTEGER, kodi_pathid INTEGER, parent_id INTEGER, - checksum INTEGER)""") - cursor.execute( - """CREATE TABLE IF NOT EXISTS view( - view_id TEXT UNIQUE, view_name TEXT, media_type TEXT, kodi_tagid INTEGER, group_series TEXT)""") - cursor.execute("CREATE TABLE IF NOT EXISTS version(idVersion TEXT)") - - columns = cursor.execute("SELECT * FROM view") - if 'group_series' not in [description[0] for description in columns.description]: - log.info("Add missing column group_series") - cursor.execute("ALTER TABLE view ADD COLUMN group_series 'TEXT'") - - -def db_reset(): - - dialog = xbmcgui.Dialog() - - if not dialog.yesno(language(29999), language(33074)): - return - - # first stop any db sync - window('emby_online', value="reset") - window('emby_shouldStop', value="true") - count = 10 - while window('emby_dbScan') == "true": - log.info("Sync is running, will retry: %s..." % count) - count -= 1 - if count == 0: - dialog.ok(language(29999), language(33085)) - return - xbmc.sleep(1000) - - # Clean up the playlists - Playlist().delete_playlists() - - # Clean up the video nodes - VideoNodes().deleteNodes() - - # Wipe the kodi databases - log.warn("Resetting the Kodi video database.") - with DatabaseConn('video') as cursor: - cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"') - rows = cursor.fetchall() - for row in rows: - tablename = row[0] - if tablename != "version": - cursor.execute("DELETE FROM " + tablename) - - if settings('enableMusic') == "true": - log.warn("Resetting the Kodi music database.") - with DatabaseConn('music') as cursor: - cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"') - rows = cursor.fetchall() - for row in rows: - tablename = row[0] - if tablename != "version": - cursor.execute("DELETE FROM " + tablename) - - # Wipe the emby database - log.warn("Resetting the Emby database.") - with DatabaseConn('emby') as cursor: - cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"') - rows = cursor.fetchall() - for row in rows: - tablename = row[0] - if tablename != "version": - cursor.execute("DELETE FROM " + tablename) - cursor.execute('DROP table IF EXISTS emby') - cursor.execute('DROP table IF EXISTS view') - cursor.execute("DROP table IF EXISTS version") - - # Offer to wipe cached thumbnails - if dialog.yesno(language(29999), language(33086)): - log.warn("Resetting all cached artwork") - # Remove all existing textures first - import artwork - artwork.Artwork().delete_cache() - - # reset the install run flag - settings('SyncInstallRunDone', value="false") - - # Remove emby info - resp = dialog.yesno(language(29999), language(33087)) - if resp: - import connectmanager - # Delete the settings - addondir = xbmc.translatePath( - "special://profile/addon_data/plugin.video.emby/").decode('utf-8') - dataPath = "%ssettings.xml" % addondir - xbmcvfs.delete(dataPath) - connectmanager.ConnectManager().clear_data() - - dialog.ok(heading=language(29999), line1=language(33088)) - xbmc.executebuiltin('RestartApp') - try: - xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, xbmcgui.ListItem()) - except: - pass - - diff --git a/resources/lib/database/__init__.py b/resources/lib/database/__init__.py new file mode 100644 index 00000000..45abd2c9 --- /dev/null +++ b/resources/lib/database/__init__.py @@ -0,0 +1,284 @@ +# -*- coding: utf-8 -*- + +################################################################################################# + +import datetime +import logging +import json +import os +import sqlite3 + +import xbmc +import xbmcvfs + +import emby_db +from helper import _, settings, window, dialog +from objects import obj + +################################################################################################# + +LOG = logging.getLogger("EMBY."+__name__) + +################################################################################################# + + +class Database(object): + + ''' This should be called like a context. + i.e. with Database('emby') as db: + db.cursor + db.conn.commit() + ''' + timeout = 120 + + def __init__(self, file=None, commit_close=True): + + ''' file: emby, texture, music, video, :memory: or path to file + ''' + self.db_file = file or "video" + self.commit_close = commit_close + + def __enter__(self): + + ''' Open the connection and return the Database class. + This is to allow for both the cursor and conn to be accessible. + at any time. + ''' + self.conn = sqlite3.connect(self._sql(self.db_file), timeout=self.timeout) + self.cursor = self.conn.cursor() + + if self.db_file in ('video', 'music', 'texture'): + self.conn.execute("PRAGMA journal_mode=WAL") + + LOG.debug("--->[ database: %s ] %s", self.db_file, id(self.conn)) + + if self.db_file == 'emby': + + emby_tables(self.cursor) + self.conn.commit() + + return self + + def _sql(self, file): + + databases = obj.Objects().objects + + return xbmc.translatePath(databases[file]).decode('utf-8') if file in databases else file + + def __exit__(self, exc_type, exc_val, exc_tb): + + ''' Close the connection and cursor. + ''' + changes = self.conn.total_changes + + if exc_type is not None: # errors raised + LOG.error("type: %s value: %s", exc_type, exc_val) + + if self.commit_close and changes: + + LOG.info("[%s] %s rows updated.", self.db_file, changes) + self.conn.commit() + + LOG.debug("---<[ database: %s ] %s", self.db_file, id(self.conn)) + self.cursor.close() + self.conn.close() + +def emby_tables(cursor): + + ''' Create the tables for the emby database. + emby, view, version + ''' + cursor.execute( + """CREATE TABLE IF NOT EXISTS emby( + emby_id TEXT UNIQUE, media_folder TEXT, emby_type TEXT, media_type TEXT, + kodi_id INTEGER, kodi_fileid INTEGER, kodi_pathid INTEGER, parent_id INTEGER, + checksum INTEGER)""") + cursor.execute( + """CREATE TABLE IF NOT EXISTS view( + view_id TEXT UNIQUE, view_name TEXT, media_type TEXT)""") + cursor.execute("CREATE TABLE IF NOT EXISTS version(idVersion TEXT)") + +def reset(): + + ''' Reset both the emby database and the kodi database. + ''' + from views import Views + views = Views() + + if not dialog("yesno", heading="{emby}", line1=_(33074)): + return + + window('emby_should_stop.bool', True) + count = 10 + + while window('emby_sync.bool'): + + LOG.info("Sync is running...") + count -= 1 + + if not count: + dialog("ok", heading="{emby}", line1=_(33085)) + + return + + if xbmc.Monitor().waitForAbort(1): + return + + reset_kodi() + reset_emby() + views.delete_playlists() + views.delete_nodes() + + if dialog("yesno", heading="{emby}", line1=_(33086)): + reset_artwork() + + addon_data = xbmc.translatePath("special://profile/addon_data/plugin.video.emby/").decode('utf-8') + + if dialog("yesno", heading="{emby}", line1=_(33087)): + + xbmcvfs.delete(os.path.join(addon_data, "settings.xml")) + xbmcvfs.delete(os.path.join(addon_data, "data.json")) + LOG.info("[ reset settings ]") + + if xbmcvfs.exists(os.path.join(addon_data, "sync.json")): + xbmcvfs.delete(os.path.join(addon_data, "sync.json")) + + settings('MinimumSetup.bool', False) + settings('SyncInstallRunDone.bool', False) + dialog("ok", heading="{emby}", line1=_(33088)) + xbmc.executebuiltin('RestartApp') + +def reset_kodi(): + + with Database() as videodb: + videodb.cursor.execute("SELECT tbl_name FROM sqlite_master WHERE type='table'") + + for table in videodb.cursor.fetchall(): + name = table[0] + + if name != 'version': + videodb.cursor.execute("DELETE FROM " + name) + + if settings('enableMusic.bool'): + + with Database('music') as musicdb: + musicdb.cursor.execute("SELECT tbl_name FROM sqlite_master WHERE type='table'") + + for table in musicdb.cursor.fetchall(): + name = table[0] + + if name != 'version': + musicdb.cursor.execute("DELETE FROM " + name) + + LOG.warn("[ reset kodi ]") + +def reset_emby(): + + with Database('emby') as embydb: + embydb.cursor.execute("SELECT tbl_name FROM sqlite_master WHERE type='table'") + + for table in embydb.cursor.fetchall(): + name = table[0] + + if name not in ('version', 'view'): + embydb.cursor.execute("DELETE FROM " + name) + + embydb.cursor.execute("DROP table IF EXISTS emby") + embydb.cursor.execute("DROP table IF EXISTS view") + embydb.cursor.execute("DROP table IF EXISTS version") + + LOG.warn("[ reset emby ]") + +def reset_artwork(): + + ''' Remove all existing texture. + ''' + thumbnails = xbmc.translatePath('special://thumbnails/').decode('utf-8') + + if xbmcvfs.exists(thumbnails): + dirs, ignore = xbmcvfs.listdir(thumbnails) + + for directory in dirs: + ignore, thumbs = xbmcvfs.listdir(os.path.join(thumbnails, directory.decode('utf-8'))) + + for thumb in thumbs: + LOG.debug("DELETE thumbnail %s", thumb) + xbmcvfs.delete(os.path.join(thumbnails, directory.decode('utf-8'), thumb.decode('utf-8'))) + + with Database('texture') as texdb: + texdb.cursor.execute("SELECT tbl_name FROM sqlite_master WHERE type='table'") + + for table in texdb.cursor.fetchall(): + name = table[0] + + if name != 'version': + texdb.cursor.execute("DELETE FROM " + name) + + LOG.warn("[ reset artwork ]") + +def get_sync(): + + path = xbmc.translatePath("special://profile/addon_data/plugin.video.emby/").decode('utf-8') + + if not xbmcvfs.exists(path): + xbmcvfs.mkdirs(path) + + try: + with open(os.path.join(path, 'sync.json')) as infile: + sync = json.load(infile) + except Exception: + sync = {'Libraries': [], 'RestorePoint': {}, 'Whitelist': []} + + return sync + +def save_sync(sync): + + path = xbmc.translatePath("special://profile/addon_data/plugin.video.emby/").decode('utf-8') + + if not xbmcvfs.exists(path): + xbmcvfs.mkdirs(path) + + sync['Date'] = datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%SZ') + + with open(os.path.join(path, 'sync.json'), 'w') as outfile: + json.dump(sync, outfile, sort_keys=True, indent=4, ensure_ascii=False) + +def get_credentials(): + + path = xbmc.translatePath("special://profile/addon_data/plugin.video.emby/").decode('utf-8') + + if not xbmcvfs.exists(path): + xbmcvfs.mkdirs(path) + + try: + with open(os.path.join(path, 'data.json')) as infile: + credentials = json.load(infile) + except Exception: + credentials = {'Servers': []} + + return credentials + +def save_credentials(credentials): + + credentials = credentials or {} + path = xbmc.translatePath("special://profile/addon_data/plugin.video.emby/").decode('utf-8') + + if not xbmcvfs.exists(path): + xbmcvfs.mkdirs(path) + + with open(os.path.join(path, 'data.json'), 'w') as outfile: + json.dump(credentials, outfile, sort_keys=True, indent=4, ensure_ascii=False) + +def get_item(kodi_id, media): + + ''' Get emby item based on kodi id and media. + ''' + with Database('emby') as embydb: + item = emby_db.EmbyDatabase(embydb.cursor).get_full_item_by_kodi_id(kodi_id, media) + + if not item: + LOG.debug("Not an emby item") + + return + + return item diff --git a/resources/lib/embydb_functions.py b/resources/lib/database/emby_db.py similarity index 64% rename from resources/lib/embydb_functions.py rename to resources/lib/database/emby_db.py index 27546f8c..8f8ab37a 100644 --- a/resources/lib/embydb_functions.py +++ b/resources/lib/database/emby_db.py @@ -1,409 +1,417 @@ -# -*- coding: utf-8 -*- - -################################################################################################# - -import logging -from sqlite3 import OperationalError - -import downloadutils - -################################################################################################## - -log = logging.getLogger("EMBY."+__name__) - -################################################################################################## - - -class Embydb_Functions(): - - - def __init__(self, embycursor): - - self.embycursor = embycursor - self.download = downloadutils.DownloadUtils().downloadUrl - - - def get_version(self, version=None): - - if version is not None: - self.embycursor.execute("DELETE FROM version") - query = "INSERT INTO version(idVersion) VALUES (?)" - self.embycursor.execute(query, (version,)) - else: - query = "SELECT idVersion FROM version" - self.embycursor.execute(query) - try: - version = self.embycursor.fetchone()[0] - except TypeError: - pass - - return version - - def getViews(self): - - views = [] - - query = ' '.join(( - - "SELECT view_id", - "FROM view" - )) - self.embycursor.execute(query) - rows = self.embycursor.fetchall() - for row in rows: - views.append(row[0]) - - return views - - def getView_embyId(self, item_id): - # Returns ancestors using embyId - url = "{server}/emby/Items/%s/Ancestors?UserId={UserId}&format=json" % item_id - - try: - view_list = self.download(url) - except Exception as error: - log.info("Error getting views: " + str(error)) - view_list = [] - - if view_list is None: - view_list = [] - - for view in view_list: - - if view['Type'] == "CollectionFolder": - # Found view - view_id = view['Id'] - break - else: # No view found - return [None, None] - - # Compare to view table in emby database - query = ' '.join(( - - "SELECT view_name", - "FROM view", - "WHERE view_id = ?" - )) - self.embycursor.execute(query, (view_id,)) - try: - view_name = self.embycursor.fetchone()[0] - except TypeError: - view_name = None - - return [view_name, view_id] - - def getView_byId(self, viewid): - - - query = ' '.join(( - - "SELECT view_name, media_type, kodi_tagid", - "FROM view", - "WHERE view_id = ?" - )) - self.embycursor.execute(query, (viewid,)) - view = self.embycursor.fetchone() - - return view - - def getView_byType(self, mediatype): - - views = [] - - query = ' '.join(( - - "SELECT view_id, view_name", - "FROM view", - "WHERE media_type = ?" - )) - self.embycursor.execute(query, (mediatype,)) - rows = self.embycursor.fetchall() - for row in rows: - views.append({ - - 'id': row[0], - 'name': row[1], - 'mediatype': mediatype - }) - - if mediatype in ('tvshows', 'movies'): - query = ' '.join(( - "SELECT view_id, view_name", - "FROM view", - "WHERE media_type = ?" - )) - - self.embycursor.execute(query, ("mixed",)) - rows = self.embycursor.fetchall() - for row in rows: - views.append({ - - 'id': row[0], - 'name': row[1], - 'mediatype': "mixed" - }) - - return views - - def getView_byName(self, tagname): - - query = ' '.join(( - - "SELECT view_id", - "FROM view", - "WHERE view_name = ?" - )) - self.embycursor.execute(query, (tagname,)) - try: - view = self.embycursor.fetchone()[0] - - except TypeError: - view = None - - return view - - def addView(self, embyid, name, mediatype, tagid, group_series): - - query = ( - ''' - INSERT INTO view( - view_id, view_name, media_type, kodi_tagid, group_series) - - VALUES (?, ?, ?, ?, ?) - ''' - ) - self.embycursor.execute(query, (embyid, name, mediatype, tagid, group_series)) - - def get_view_grouped_series(self, view_id): - - query = ' '.join(( - - "SELECT group_series", - "FROM view", - "WHERE view_id = ?" - )) - try: - self.embycursor.execute(query, (view_id,)) - return self.embycursor.fetchone() - except: return False - - def update_view_grouped_series(self, view_id, group_series): - - query = ' '.join(( - - "UPDATE view", - "SET group_series = ?", - "WHERE view_id = ?" - )) - self.embycursor.execute(query, (group_series, view_id)) - - def updateView(self, name, tagid, mediafolderid): - - query = ' '.join(( - - "UPDATE view", - "SET view_name = ?, kodi_tagid = ?", - "WHERE view_id = ?" - )) - self.embycursor.execute(query, (name, tagid, mediafolderid)) - - def removeView(self, viewid): - - query = ' '.join(( - - "DELETE FROM view", - "WHERE view_id = ?" - )) - self.embycursor.execute(query, (viewid,)) - - def getItem_byId(self, embyid): - - query = ' '.join(( - - "SELECT kodi_id, kodi_fileid, kodi_pathid, parent_id, media_type, emby_type", - "FROM emby", - "WHERE emby_id = ?" - )) - try: - self.embycursor.execute(query, (embyid,)) - item = self.embycursor.fetchone() - return item - except: return None - - def getItem_byWildId(self, embyid): - - query = ' '.join(( - - "SELECT kodi_id, media_type", - "FROM emby", - "WHERE emby_id LIKE ?" - )) - self.embycursor.execute(query, (embyid+"%",)) - return self.embycursor.fetchall() - - def getItem_byView(self, mediafolderid): - - query = ' '.join(( - - "SELECT kodi_id", - "FROM emby", - "WHERE media_folder = ?" - )) - self.embycursor.execute(query, (mediafolderid,)) - return self.embycursor.fetchall() - - def get_item_by_view(self, view_id): - - query = ' '.join(( - - "SELECT emby_id", - "FROM emby", - "WHERE media_folder = ?" - )) - self.embycursor.execute(query, (view_id,)) - return self.embycursor.fetchall() - - def getItem_byKodiId(self, kodiid, mediatype): - - query = ' '.join(( - - "SELECT emby_id, parent_id, media_folder", - "FROM emby", - "WHERE kodi_id = ?", - "AND media_type = ?" - )) - self.embycursor.execute(query, (kodiid, mediatype,)) - return self.embycursor.fetchone() - - def getItem_byParentId(self, parentid, mediatype): - - query = ' '.join(( - - "SELECT emby_id, kodi_id, kodi_fileid", - "FROM emby", - "WHERE parent_id = ?", - "AND media_type = ?" - )) - self.embycursor.execute(query, (parentid, mediatype,)) - return self.embycursor.fetchall() - - def getItemId_byParentId(self, parentid, mediatype): - - query = ' '.join(( - - "SELECT emby_id, kodi_id", - "FROM emby", - "WHERE parent_id = ?", - "AND media_type = ?" - )) - self.embycursor.execute(query, (parentid, mediatype,)) - return self.embycursor.fetchall() - - def get_checksum(self, mediatype): - - query = ' '.join(( - - "SELECT emby_id, checksum", - "FROM emby", - "WHERE emby_type = ?" - )) - self.embycursor.execute(query, (mediatype,)) - return self.embycursor.fetchall() - - def get_checksum_by_view(self, media_type, view_id): - - query = ' '.join(( - - "SELECT emby_id, checksum", - "FROM emby", - "WHERE emby_type = ?", - "AND media_folder = ?" - )) - self.embycursor.execute(query, (media_type, view_id,)) - return self.embycursor.fetchall() - - def getMediaType_byId(self, embyid): - - query = ' '.join(( - - "SELECT emby_type", - "FROM emby", - "WHERE emby_id = ?" - )) - self.embycursor.execute(query, (embyid,)) - try: - itemtype = self.embycursor.fetchone()[0] - - except TypeError: - itemtype = None - - return itemtype - - def sortby_mediaType(self, itemids, unsorted=True): - - sorted_items = {} - - for itemid in itemids: - - mediatype = self.getMediaType_byId(itemid) - if mediatype: - sorted_items.setdefault(mediatype, []).append(itemid) - elif unsorted: - sorted_items.setdefault('Unsorted', []).append(itemid) - - return sorted_items - - def addReference(self, embyid, kodiid, embytype, mediatype, fileid=None, pathid=None, - parentid=None, checksum=None, mediafolderid=None): - query = ( - ''' - INSERT OR REPLACE INTO emby( - emby_id, kodi_id, kodi_fileid, kodi_pathid, emby_type, media_type, parent_id, - checksum, media_folder) - - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) - ''' - ) - self.embycursor.execute(query, (embyid, kodiid, fileid, pathid, embytype, mediatype, - parentid, checksum, mediafolderid)) - - def updateReference(self, embyid, checksum): - - query = "UPDATE emby SET checksum = ? WHERE emby_id = ?" - self.embycursor.execute(query, (checksum, embyid)) - - def updateParentId(self, embyid, parent_kodiid): - - query = "UPDATE emby SET parent_id = ? WHERE emby_id = ?" - self.embycursor.execute(query, (parent_kodiid, embyid)) - - def removeItems_byParentId(self, parent_kodiid, mediatype): - - query = ' '.join(( - - "DELETE FROM emby", - "WHERE parent_id = ?", - "AND media_type = ?" - )) - self.embycursor.execute(query, (parent_kodiid, mediatype,)) - - def removeItem_byKodiId(self, kodiid, mediatype): - - query = ' '.join(( - - "DELETE FROM emby", - "WHERE kodi_id = ?", - "AND media_type = ?" - )) - self.embycursor.execute(query, (kodiid, mediatype,)) - - def removeItem(self, embyid): - - query = "DELETE FROM emby WHERE emby_id = ?" - self.embycursor.execute(query, (embyid,)) - - def removeWildItem(self, embyid): - - query = "DELETE FROM emby WHERE emby_id LIKE ?" - self.embycursor.execute(query, (embyid+"%",)) +# -*- coding: utf-8 -*- + +################################################################################################# + +import logging + +import queries as QU + +################################################################################################## + +LOG = logging.getLogger("EMBY."+__name__) + +################################################################################################## + + +class EmbyDatabase(): + + + def __init__(self, cursor): + self.cursor = cursor + + def get_item_by_id(self, *args): + self.cursor.execute(QU.get_item, args) + + return self.cursor.fetchone() + + def add_reference(self, *args): + self.cursor.execute(QU.add_reference, args) + + def update_reference(self, *args): + self.cursor.execute(QU.update_reference, args) + + def update_parent_id(self, *args): + + ''' Parent_id is the parent Kodi id. + ''' + self.cursor.execute(QU.update_parent, args) + + def get_item_id_by_parent_id(self, *args): + self.cursor.execute(QU.get_item_id_by_parent, args) + + return self.cursor.fetchall() + + def get_item_by_parent_id(self, *args): + self.cursor.execute(QU.get_item_by_parent, args) + + return self.cursor.fetchall() + + def get_item_by_media_folder(self, *args): + self.cursor.execute(QU.get_item_by_media_folder, args) + + return self.cursor.fetchall() + + def get_item_by_wild_id(self, item_id): + self.cursor.execute(QU.get_item_by_wild, (item_id + "%",)) + + return self.cursor.fetchall() + + def get_checksum(self, *args): + self.cursor.execute(QU.get_checksum, args) + + return self.cursor.fetchall() + + def get_item_by_kodi_id(self, *args): + + try: + self.cursor.execute(QU.get_item_by_kodi, args) + + return self.cursor.fetchone()[0] + except TypeError: + return + + def get_full_item_by_kodi_id(self, *args): + + try: + self.cursor.execute(QU.get_item_by_kodi, args) + + return self.cursor.fetchone() + except TypeError: + return + + def get_media_by_id(self, *args): + + try: + self.cursor.execute(QU.get_media_by_id, args) + + return self.cursor.fetchone()[0] + except TypeError: + return + + def remove_item(self, *args): + self.cursor.execute(QU.delete_item, args) + + def remove_items_by_parent_id(self, *args): + self.cursor.execute(QU.delete_item_by_parent, args) + + def remove_item_by_kodi_id(self, *args): + self.cursor.execute(QU.delete_item_by_kodi, args) + + def remove_wild_item(self, item_id): + self.cursor.execute(QU.delete_item_by_wild, (item_id + "%",)) + + + def get_view_name(self, item_id): + + try: + self.cursor.execute(QU.get_view_name, (item_id,)) + + return self.cursor.fetchone()[0] + except Exception as error: + return + + def get_view(self, *args): + + try: + self.cursor.execute(QU.get_view, args) + + return self.cursor.fetchone() + except TypeError: + return + + def add_view(self, *args): + self.cursor.execute(QU.add_view, args) + + def remove_view(self, *args): + self.cursor.execute(QU.delete_view, args) + + def get_views(self, *args): + self.cursor.execute(QU.get_views, args) + + return self.cursor.fetchall() + + def get_items_by_media(self, *args): + self.cursor.execute(QU.get_items_by_media, args) + + return self.cursor.fetchall() + + + + + + """ + def get_version(self, version=None): + + if version is not None: + self.embycursor.execute("DELETE FROM version") + query = "INSERT INTO version(idVersion) VALUES (?)" + self.embycursor.execute(query, (version,)) + else: + query = "SELECT idVersion FROM version" + self.embycursor.execute(query) + try: + version = self.embycursor.fetchone()[0] + except TypeError: + pass + + return version + + def getViews(self): + + views = [] + + query = ' '.join(( + + "SELECT view_id", + "FROM view" + )) + self.embycursor.execute(query) + rows = self.embycursor.fetchall() + for row in rows: + views.append(row[0]) + + return views + + def getView_embyId(self, item_id): + # Returns ancestors using embyId + url = "{server}/emby/Items/%s/Ancestors?UserId={UserId}&format=json" % item_id + + try: + view_list = self.download(url) + except Exception as error: + log.info("Error getting views: " + str(error)) + view_list = [] + + if view_list is None: + view_list = [] + + for view in view_list: + + if view['Type'] == "CollectionFolder": + # Found view + view_id = view['Id'] + break + else: # No view found + return [None, None] + + # Compare to view table in emby database + query = ' '.join(( + + "SELECT view_name", + "FROM view", + "WHERE view_id = ?" + )) + self.embycursor.execute(query, (view_id,)) + try: + view_name = self.embycursor.fetchone()[0] + except TypeError: + view_name = None + + return [view_name, view_id] + + def getView_byId(self, viewid): + + + query = ' '.join(( + + "SELECT view_name, media_type, kodi_tagid", + "FROM view", + "WHERE view_id = ?" + )) + self.embycursor.execute(query, (viewid,)) + view = self.embycursor.fetchone() + + return view + + def getView_byType(self, mediatype): + + views = [] + + query = ' '.join(( + + "SELECT view_id, view_name", + "FROM view", + "WHERE media_type = ?" + )) + self.embycursor.execute(query, (mediatype,)) + rows = self.embycursor.fetchall() + for row in rows: + views.append({ + + 'id': row[0], + 'name': row[1], + 'mediatype': mediatype + }) + + if mediatype in ('tvshows', 'movies'): + query = ' '.join(( + "SELECT view_id, view_name", + "FROM view", + "WHERE media_type = ?" + )) + + self.embycursor.execute(query, ("mixed",)) + rows = self.embycursor.fetchall() + for row in rows: + views.append({ + + 'id': row[0], + 'name': row[1], + 'mediatype': "mixed" + }) + + return views + + def getView_byName(self, tagname): + + query = ' '.join(( + + "SELECT view_id", + "FROM view", + "WHERE view_name = ?" + )) + self.embycursor.execute(query, (tagname,)) + try: + view = self.embycursor.fetchone()[0] + + except TypeError: + view = None + + return view + + def addView(self, embyid, name, mediatype, tagid, group_series): + + query = ( + ''' + INSERT INTO view( + view_id, view_name, media_type, kodi_tagid, group_series) + + VALUES (?, ?, ?, ?, ?) + ''' + ) + self.embycursor.execute(query, (embyid, name, mediatype, tagid, group_series)) + + def get_view_grouped_series(self, view_id): + + query = ' '.join(( + + "SELECT group_series", + "FROM view", + "WHERE view_id = ?" + )) + try: + self.embycursor.execute(query, (view_id,)) + return self.embycursor.fetchone() + except: return False + + def update_view_grouped_series(self, view_id, group_series): + + query = ' '.join(( + + "UPDATE view", + "SET group_series = ?", + "WHERE view_id = ?" + )) + self.embycursor.execute(query, (group_series, view_id)) + + def updateView(self, name, tagid, mediafolderid): + + query = ' '.join(( + + "UPDATE view", + "SET view_name = ?, kodi_tagid = ?", + "WHERE view_id = ?" + )) + self.embycursor.execute(query, (name, tagid, mediafolderid)) + + def removeView(self, viewid): + + query = ' '.join(( + + "DELETE FROM view", + "WHERE view_id = ?" + )) + self.embycursor.execute(query, (viewid,)) + + def getItem_byId(self, embyid): + + query = ' '.join(( + + "SELECT kodi_id, kodi_fileid, kodi_pathid, parent_id, media_type, emby_type", + "FROM emby", + "WHERE emby_id = ?" + )) + try: + self.embycursor.execute(query, (embyid,)) + item = self.embycursor.fetchone() + return item + except: return None + + def getItem_byView(self, mediafolderid): + + query = ' '.join(( + + "SELECT kodi_id", + "FROM emby", + "WHERE media_folder = ?" + )) + self.embycursor.execute(query, (mediafolderid,)) + return self.embycursor.fetchall() + + def get_item_by_view(self, view_id): + + query = ' '.join(( + + "SELECT emby_id", + "FROM emby", + "WHERE media_folder = ?" + )) + self.embycursor.execute(query, (view_id,)) + return self.embycursor.fetchall() + + + def get_checksum_by_view(self, media_type, view_id): + + query = ' '.join(( + + "SELECT emby_id, checksum", + "FROM emby", + "WHERE emby_type = ?", + "AND media_folder = ?" + )) + self.embycursor.execute(query, (media_type, view_id,)) + return self.embycursor.fetchall() + + def getMediaType_byId(self, embyid): + + query = ' '.join(( + + "SELECT emby_type", + "FROM emby", + "WHERE emby_id = ?" + )) + self.embycursor.execute(query, (embyid,)) + try: + itemtype = self.embycursor.fetchone()[0] + + except TypeError: + itemtype = None + + return itemtype + + def sortby_mediaType(self, itemids, unsorted=True): + + sorted_items = {} + + for itemid in itemids: + + mediatype = self.getMediaType_byId(itemid) + if mediatype: + sorted_items.setdefault(mediatype, []).append(itemid) + elif unsorted: + sorted_items.setdefault('Unsorted', []).append(itemid) + + return sorted_items + + """ \ No newline at end of file diff --git a/resources/lib/database/queries.py b/resources/lib/database/queries.py new file mode 100644 index 00000000..0e54ecbf --- /dev/null +++ b/resources/lib/database/queries.py @@ -0,0 +1,158 @@ + +get_item = """ SELECT kodi_id, kodi_fileid, kodi_pathid, parent_id, media_type, + emby_type, media_folder + FROM emby + WHERE emby_id = ? + """ +get_item_obj = [ "{Id}" + ] +get_item_series_obj = [ "{SeriesId}" + ] +get_item_song_obj = [ "{SongAlbumId}" + ] +get_item_id_by_parent = """ SELECT emby_id, kodi_id + FROM emby + WHERE parent_id = ? + AND media_type = ? + """ +get_item_id_by_parent_boxset_obj = [ "{SetId}","movie" + ] +get_item_by_parent = """ SELECT emby_id, kodi_id, kodi_fileid + FROM emby + WHERE parent_id = ? + AND media_type = ? + """ +get_item_by_media_folder = """ SELECT emby_id, emby_type + FROM emby + WHERE media_folder = ? + """ +get_item_by_parent_movie_obj = [ "{KodiId}","movie" + ] +get_item_by_parent_tvshow_obj = [ "{ParentId}","tvshow" + ] +get_item_by_parent_season_obj = [ "{ParentId}","season" + ] +get_item_by_parent_episode_obj = [ "{ParentId}","episode" + ] +get_item_by_parent_album_obj = [ "{ParentId}","album" + ] +get_item_by_parent_song_obj = [ "{ParentId}","song" + ] +get_item_by_wild = """ SELECT kodi_id, media_type + FROM emby + WHERE emby_id LIKE ? + """ +get_item_by_wild_obj = [ "{Id}" + ] +get_item_by_kodi = """ SELECT emby_id, parent_id, media_folder, emby_type, checksum + FROM emby + WHERE kodi_id = ? + AND media_type = ? + """ +get_checksum = """ SELECT emby_id, checksum + FROM emby + WHERE emby_type = ? + """ +get_view_name = """ SELECT view_name + FROM view + WHERE view_id = ? + """ +get_media_by_id = """ SELECT emby_type + FROM emby + WHERE emby_id = ? + """ +get_view = """ SELECT view_name, media_type + FROM view + WHERE view_id = ? + """ +get_views = """ SELECT * + FROM view + """ +get_items_by_media = """ SELECT emby_id + FROM emby + WHERE media_type = ? + """ + + + +add_reference = """ INSERT OR REPLACE INTO emby(emby_id, kodi_id, kodi_fileid, kodi_pathid, emby_type, + media_type, parent_id, checksum, media_folder) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) + """ +add_reference_movie_obj = [ "{Id}","{MovieId}","{FileId}","{PathId}","Movie","movie", None,"{Checksum}","{LibraryId}" + ] +add_reference_boxset_obj = [ "{Id}","{SetId}",None,None,"BoxSet","set",None,"{Checksum}",None + ] +add_reference_tvshow_obj = [ "{Id}","{ShowId}",None,"{PathId}","Series","tvshow",None,"{Checksum}","{LibraryId}" + ] +add_reference_season_obj = [ "{Id}","{SeasonId}",None,None,"Season","season","{ShowId}",None,None + ] +add_reference_pool_obj = [ "{SeriesId}","{ShowId}",None,"{PathId}","Series","tvshow",None,"{Checksum}","{LibraryId}" + ] +add_reference_episode_obj = [ "{Id}","{EpisodeId}","{FileId}","{PathId}","Episode","episode","{SeasonId}","{Checksum}",None + ] +add_reference_mvideo_obj = [ "{Id}","{MvideoId}","{FileId}","{PathId}","MusicVideo","musicvideo",None,"{Checksum}", + "{LibraryId}" + ] +add_reference_artist_obj = [ "{Id}","{ArtistId}",None,None,"{ArtistType}","artist",None,"{Checksum}","{LibraryId}" + ] +add_reference_album_obj = [ "{Id}","{AlbumId}",None,None,"MusicAlbum","album",None,"{Checksum}",None + ] +add_reference_song_obj = [ "{Id}","{SongId}",None,"{PathId}","Audio","song","{AlbumId}","{Checksum}",None + ] +add_view = """ INSERT OR REPLACE INTO view(view_id, view_name, media_type) + VALUES (?, ?, ?) + """ + + +update_reference = """ UPDATE emby + SET checksum = ? + WHERE emby_id = ? + """ +update_reference_obj = [ "{Checksum}", "{Id}" + ] +update_parent = """ UPDATE emby + SET parent_id = ? + WHERE emby_id = ? + """ +update_parent_movie_obj = [ "{SetId}","{Id}" + ] +update_parent_episode_obj = [ "{SeasonId}","{Id}" + ] +update_parent_album_obj = [ "{ArtistId}","{AlbumId}"] + + + +delete_item = """ DELETE FROM emby + WHERE emby_id = ? + """ +delete_item_obj = [ "{Id}" + ] +delete_item_by_parent = """ DELETE FROM emby + WHERE parent_id = ? + AND media_type = ? + """ +delete_item_by_parent_tvshow_obj = [ "{ParentId}","tvshow" + ] +delete_item_by_parent_season_obj = [ "{ParentId}","season" + ] +delete_item_by_parent_episode_obj = [ "{ParentId}","episode" + ] +delete_item_by_parent_song_obj = [ "{ParentId}","song" + ] +delete_item_by_parent_artist_obj = [ "{ParentId}","artist" + ] +delete_item_by_parent_album_obj = [ "{KodiId}","album" + ] +delete_item_by_kodi = """ DELETE FROM emby + WHERE kodi_id = ? + AND media_type = ? + """ +delete_item_by_wild = """ DELETE FROM emby + WHERE emby_id LIKE ? + """ +delete_view = """ DELETE FROM view + WHERE view_id = ? + """ +delete_parent_boxset_obj = [ None, "{Movie}" + ] diff --git a/resources/lib/dialogs/__init__.py b/resources/lib/dialogs/__init__.py index b6c69bf6..a0208889 100644 --- a/resources/lib/dialogs/__init__.py +++ b/resources/lib/dialogs/__init__.py @@ -1,4 +1,3 @@ -# Dummy file to make this directory a package. from serverconnect import ServerConnect from usersconnect import UsersConnect from loginconnect import LoginConnect diff --git a/resources/lib/dialogs/context.py b/resources/lib/dialogs/context.py index a0b65c2c..562809ac 100644 --- a/resources/lib/dialogs/context.py +++ b/resources/lib/dialogs/context.py @@ -8,13 +8,11 @@ import os import xbmcgui import xbmcaddon -from utils import window +from helper import window, addon_id ################################################################################################## -log = logging.getLogger("EMBY."+__name__) -addon = xbmcaddon.Addon('plugin.video.emby') - +LOG = logging.getLogger("EMBY."+__name__) ACTION_PARENT_DIR = 9 ACTION_PREVIOUS_MENU = 10 ACTION_BACK = 92 @@ -51,7 +49,7 @@ class ContextMenu(xbmcgui.WindowXMLDialog): self.getControl(USER_IMAGE).setImage(window('EmbyUserImage')) height = 479 + (len(self._options) * 55) - log.info("options: %s", self._options) + LOG.info("options: %s", self._options) self.list_ = self.getControl(LIST) for option in self._options: @@ -69,10 +67,24 @@ class ContextMenu(xbmcgui.WindowXMLDialog): if self.getFocusId() == LIST: option = self.list_.getSelectedItem() self.selected_option = option.getLabel() - log.info('option selected: %s', self.selected_option) + LOG.info('option selected: %s', self.selected_option) self.close() + def _add_editcontrol(self, x, y, height, width, password=0): + + media = os.path.join(xbmcaddon.Addon(addon_id()).getAddonInfo('path'), 'resources', 'skins', 'default', 'media') + control = xbmcgui.ControlImage(0, 0, 0, 0, + filename=os.path.join(media, "white.png"), + aspectRatio=0, + colorDiffuse="ff111111") + control.setPosition(x, y) + control.setHeight(height) + control.setWidth(width) + + self.addControl(control) + return control + @classmethod def _add_listitem(cls, label): return xbmcgui.ListItem(label) diff --git a/resources/lib/dialogs/loginconnect.py b/resources/lib/dialogs/loginconnect.py index 70d4591d..ed490422 100644 --- a/resources/lib/dialogs/loginconnect.py +++ b/resources/lib/dialogs/loginconnect.py @@ -8,13 +8,11 @@ import os import xbmcgui import xbmcaddon -from utils import language as lang +from helper import _, addon_id, settings, dialog ################################################################################################## -log = logging.getLogger("EMBY."+__name__) -addon = xbmcaddon.Addon('plugin.video.emby') - +LOG = logging.getLogger("EMBY."+__name__) ACTION_PARENT_DIR = 9 ACTION_PREVIOUS_MENU = 10 ACTION_BACK = 92 @@ -40,8 +38,10 @@ class LoginConnect(xbmcgui.WindowXMLDialog): xbmcgui.WindowXMLDialog.__init__(self, *args, **kwargs) - def set_connect_manager(self, connect_manager): - self.connect_manager = connect_manager + def set_args(self, **kwargs): + # connect_manager, user_image, servers, emby_connect + for key, value in kwargs.iteritems(): + setattr(self, key, value) def is_logged_in(self): return True if self._user else False @@ -78,8 +78,8 @@ class LoginConnect(xbmcgui.WindowXMLDialog): if not user or not password: # Display error - self._error(ERROR['Empty'], lang(30608)) - log.error("Username or password cannot be null") + self._error(ERROR['Empty'], _('empty_user_pass')) + LOG.error("Username or password cannot be null") elif self._login(user, password): self.close() @@ -99,7 +99,7 @@ class LoginConnect(xbmcgui.WindowXMLDialog): def _add_editcontrol(self, x, y, height, width, password=0): - media = os.path.join(addon.getAddonInfo('path'), 'resources', 'skins', 'default', 'media') + media = os.path.join(xbmcaddon.Addon(addon_id()).getAddonInfo('path'), 'resources', 'skins', 'default', 'media') control = xbmcgui.ControlEdit(0, 0, 0, 0, label="User", font="font13", @@ -117,13 +117,23 @@ class LoginConnect(xbmcgui.WindowXMLDialog): def _login(self, username, password): - result = self.connect_manager.loginToConnect(username, password) + result = self.connect_manager['login-connect'](username, password) if result is False: - self._error(ERROR['Invalid'], lang(33009)) + self._error(ERROR['Invalid'], _('invalid_auth')) + return False - else: - self._user = result - return True + + self._user = result + username = result['User']['Name'] + settings('connectUsername', value=username) + settings('idMethod', value="1") + + dialog("notification", heading="{emby}", message="%s %s" % (_(33000), username.decode('utf-8')), + icon=result['User'].get('ImageUrl') or "{emby}", + time=2000, + sound=False) + + return True def _error(self, state, message): diff --git a/resources/lib/dialogs/loginmanual.py b/resources/lib/dialogs/loginmanual.py index a0be85c5..0a1125be 100644 --- a/resources/lib/dialogs/loginmanual.py +++ b/resources/lib/dialogs/loginmanual.py @@ -8,14 +8,11 @@ import os import xbmcgui import xbmcaddon -import read_embyserver as embyserver -from utils import language as lang +from helper import _, addon_id ################################################################################################## -log = logging.getLogger("EMBY."+__name__) -addon = xbmcaddon.Addon('plugin.video.emby') - +LOG = logging.getLogger("EMBY."+__name__) ACTION_PARENT_DIR = 9 ACTION_PREVIOUS_MENU = 10 ACTION_BACK = 92 @@ -23,10 +20,7 @@ SIGN_IN = 200 CANCEL = 201 ERROR_TOGGLE = 202 ERROR_MSG = 203 -ERROR = { - 'Invalid': 1, - 'Empty': 2 -} +ERROR = {'Invalid': 1, 'Empty': 2} ################################################################################################## @@ -39,19 +33,16 @@ class LoginManual(xbmcgui.WindowXMLDialog): def __init__(self, *args, **kwargs): - - self.emby = embyserver.Read_EmbyServer() xbmcgui.WindowXMLDialog.__init__(self, *args, **kwargs) + def set_args(self, **kwargs): + # connect_manager, user_image, servers, emby_connect + for key, value in kwargs.iteritems(): + setattr(self, key, value) + def is_logged_in(self): return True if self._user else False - def set_server(self, server): - self.server = server - - def set_user(self, user): - self.username = user or {} - def get_user(self): return self._user @@ -65,6 +56,7 @@ class LoginManual(xbmcgui.WindowXMLDialog): self.password_field = self._add_editcontrol(755, 543, 40, 415, password=1) if self.username: + self.user_field.setText(self.username) self.setFocus(self.password_field) else: @@ -88,8 +80,8 @@ class LoginManual(xbmcgui.WindowXMLDialog): if not user: # Display error - self._error(ERROR['Empty'], lang(30613)) - log.error("Username cannot be null") + self._error(ERROR['Empty'], _('empty_user')) + LOG.error("Username cannot be null") elif self._login(user, password): self.close() @@ -108,7 +100,7 @@ class LoginManual(xbmcgui.WindowXMLDialog): def _add_editcontrol(self, x, y, height, width, password=0): - media = os.path.join(addon.getAddonInfo('path'), 'resources', 'skins', 'default', 'media') + media = os.path.join(xbmcaddon.Addon(addon_id()).getAddonInfo('path'), 'resources', 'skins', 'default', 'media') control = xbmcgui.ControlEdit(0, 0, 0, 0, label="User", font="font13", @@ -122,18 +114,15 @@ class LoginManual(xbmcgui.WindowXMLDialog): control.setWidth(width) self.addControl(control) + return control def _login(self, username, password): - try: - result = self.emby.loginUser(self.server, username, password) - except Exception as error: - log.info("Error doing login: " + str(error)) - result = None + result = self.connect_manager['login'](self.connect_manager['server-address'], username, password) - if result is None: - self._error(ERROR['Invalid'], lang(33009)) + if not result: + self._error(ERROR['Invalid'], _('invalid_auth')) return False else: self._user = result diff --git a/resources/lib/dialogs/resume.py b/resources/lib/dialogs/resume.py index fe51ea5c..9dfe8610 100644 --- a/resources/lib/dialogs/resume.py +++ b/resources/lib/dialogs/resume.py @@ -10,9 +10,7 @@ import xbmcaddon ################################################################################################## -log = logging.getLogger("EMBY."+__name__) -addon = xbmcaddon.Addon('plugin.video.emby') - +LOG = logging.getLogger("EMBY."+__name__) ACTION_PARENT_DIR = 9 ACTION_PREVIOUS_MENU = 10 ACTION_BACK = 92 diff --git a/resources/lib/dialogs/serverconnect.py b/resources/lib/dialogs/serverconnect.py index 103559f1..2b79c497 100644 --- a/resources/lib/dialogs/serverconnect.py +++ b/resources/lib/dialogs/serverconnect.py @@ -7,14 +7,12 @@ import logging import xbmc import xbmcgui -import connect.connectionmanager as connectionmanager -from utils import language as lang +from helper import _ +from emby.core.connection_manager import CONNECTION_STATE ################################################################################################## -log = logging.getLogger("EMBY."+__name__) - -CONN_STATE = connectionmanager.ConnectionState +LOG = logging.getLogger("EMBY."+__name__) ACTION_PARENT_DIR = 9 ACTION_PREVIOUS_MENU = 10 ACTION_BACK = 92 @@ -79,7 +77,7 @@ class ServerConnect(xbmcgui.WindowXMLDialog): self.getControl(USER_IMAGE).setImage(self.user_image) if not self.emby_connect: # Change connect user - self.getControl(EMBY_CONNECT).setLabel("[B]%s[/B]" % lang(30618)) + self.getControl(EMBY_CONNECT).setLabel("[B]%s[/B]" % _(30618)) if self.servers: self.setFocus(self.list_) @@ -103,7 +101,7 @@ class ServerConnect(xbmcgui.WindowXMLDialog): if self.getFocusId() == LIST: server = self.list_.getSelectedItem() selected_id = server.getProperty('id') - log.info('Server Id selected: %s', selected_id) + LOG.info('Server Id selected: %s', selected_id) if self._connect_server(selected_id): self.message_box.setVisibleCondition('false') @@ -112,7 +110,7 @@ class ServerConnect(xbmcgui.WindowXMLDialog): def onClick(self, control): if control == EMBY_CONNECT: - self.connect_manager.clearData() + self.connect_manager.clear_data() self._connect_login = True self.close() @@ -125,15 +123,18 @@ class ServerConnect(xbmcgui.WindowXMLDialog): def _connect_server(self, server_id): - server = self.connect_manager.getServerInfo(server_id) - self.message.setLabel("%s %s..." % (lang(30610), server['Name'])) + server = self.connect_manager.get_server_info(server_id) + self.message.setLabel("%s %s..." % (_(30610), server['Name'])) + self.message_box.setVisibleCondition('true') self.busy.setVisibleCondition('true') - result = self.connect_manager.connectToServer(server) - if result['State'] == CONN_STATE['Unavailable']: + result = self.connect_manager['connect-to-server'](server) + + if result['State'] == CONNECTION_STATE['Unavailable']: self.busy.setVisibleCondition('false') - self.message.setLabel(lang(30609)) + + self.message.setLabel(_(30609)) return False else: xbmc.sleep(1000) diff --git a/resources/lib/dialogs/servermanual.py b/resources/lib/dialogs/servermanual.py index ab5d6377..45eebcf3 100644 --- a/resources/lib/dialogs/servermanual.py +++ b/resources/lib/dialogs/servermanual.py @@ -8,15 +8,12 @@ import os import xbmcgui import xbmcaddon -import connect.connectionmanager as connectionmanager -from utils import language as lang +from helper import _, addon_id +from emby.core.connection_manager import CONNECTION_STATE ################################################################################################## -log = logging.getLogger("EMBY."+__name__) -addon = xbmcaddon.Addon('plugin.video.emby') - -CONN_STATE = connectionmanager.ConnectionState +LOG = logging.getLogger("EMBY."+__name__) ACTION_PARENT_DIR = 9 ACTION_PREVIOUS_MENU = 10 ACTION_BACK = 92 @@ -42,8 +39,10 @@ class ServerManual(xbmcgui.WindowXMLDialog): xbmcgui.WindowXMLDialog.__init__(self, *args, **kwargs) - def set_connect_manager(self, connect_manager): - self.connect_manager = connect_manager + def set_args(self, **kwargs): + # connect_manager, user_image, servers, emby_connect + for key, value in kwargs.iteritems(): + setattr(self, key, value) def is_connected(self): return True if self._server else False @@ -81,8 +80,8 @@ class ServerManual(xbmcgui.WindowXMLDialog): if not server: # Display error - self._error(ERROR['Empty'], lang(30617)) - log.error("Server cannot be null") + self._error(ERROR['Empty'], _('empty_server')) + LOG.error("Server cannot be null") elif self._connect_to_server(server, port): self.close() @@ -101,7 +100,7 @@ class ServerManual(xbmcgui.WindowXMLDialog): def _add_editcontrol(self, x, y, height, width): - media = os.path.join(addon.getAddonInfo('path'), 'resources', 'skins', 'default', 'media') + media = os.path.join(xbmcaddon.Addon(addon_id()).getAddonInfo('path'), 'resources', 'skins', 'default', 'media') control = xbmcgui.ControlEdit(0, 0, 0, 0, label="User", font="font13", @@ -119,11 +118,11 @@ class ServerManual(xbmcgui.WindowXMLDialog): def _connect_to_server(self, server, port): server_address = "%s:%s" % (server, port) if port else server - self._message("%s %s..." % (lang(30610), server_address)) - result = self.connect_manager.connectToAddress(server_address) + self._message("%s %s..." % (_(30610), server_address)) + result = self.connect_manager['manual-server'](server_address) - if result['State'] == CONN_STATE['Unavailable']: - self._message(lang(30609)) + if result['State'] == CONNECTION_STATE['Unavailable']: + self._message(_(30609)) return False else: self._server = result['Servers'][0] diff --git a/resources/lib/dialogs/usersconnect.py b/resources/lib/dialogs/usersconnect.py index b91ff69d..5c4a55b0 100644 --- a/resources/lib/dialogs/usersconnect.py +++ b/resources/lib/dialogs/usersconnect.py @@ -9,8 +9,7 @@ import xbmcgui ################################################################################################## -log = logging.getLogger("EMBY."+__name__) - +LOG = logging.getLogger("EMBY."+__name__) ACTION_PARENT_DIR = 9 ACTION_PREVIOUS_MENU = 10 ACTION_BACK = 92 @@ -34,11 +33,10 @@ class UsersConnect(xbmcgui.WindowXMLDialog): self.kodi_version = int(xbmc.getInfoLabel('System.BuildVersion')[:2]) xbmcgui.WindowXMLDialog.__init__(self, *args, **kwargs) - def set_server(self, server): - self.server = server - - def set_users(self, users): - self.users = users + def set_args(self, **kwargs): + # connect_manager, user_image, servers, emby_connect + for key, value in kwargs.iteritems(): + setattr(self, key, value) def is_user_selected(self): return True if self._user else False @@ -81,7 +79,7 @@ class UsersConnect(xbmcgui.WindowXMLDialog): if self.getFocusId() == LIST: user = self.list_.getSelectedItem() selected_id = user.getProperty('id') - log.info('User Id selected: %s', selected_id) + LOG.info('User Id selected: %s', selected_id) for user in self.users: if user['Id'] == selected_id: diff --git a/resources/lib/downloader.py b/resources/lib/downloader.py new file mode 100644 index 00000000..6d1e51f2 --- /dev/null +++ b/resources/lib/downloader.py @@ -0,0 +1,318 @@ +# -*- coding: utf-8 -*- + +################################################################################################# + +import json +import logging +import Queue +import threading +import os + +import xbmc +import xbmcvfs + +from libraries import requests +from helper.utils import should_stop, delete_build +from helper import settings, stop, event, window +from emby import Emby +from emby.core import api +from emby.core.exceptions import HTTPException + +################################################################################################# + +LOG = logging.getLogger("EMBY."+__name__) +LIMIT = min(int(settings('limitIndex') or 50), 50) + +################################################################################################# + +def get_embyserver_url(handler): + + if handler.startswith('/'): + + handler = handler[1:] + LOG.warn("handler starts with /: %s", handler) + + return "{server}/emby/%s" % handler + +def browse_info(): + return ( + "DateCreated,EpisodeCount,SeasonCount,Path,Genres,Studios,Taglines,MediaStreams,Overview,Etag," + "ProductionLocations,Width,Height,RecursiveItemCount,ChildCount" + ) + +def _http(action, url, request={}, server_id=None): + request.update({'url': url, 'type': action}) + + return Emby(server_id)['http/request'](request) + +def _get(handler, params=None, server_id=None): + return _http("GET", get_embyserver_url(handler), {'params': params}, server_id) + +def _post(handler, json=None, params=None, server_id=None): + return _http("POST", get_embyserver_url(handler), {'params': params, 'json': json}, server_id) + +def _delete(handler, params=None, server_id=None): + return _http("DELETE", get_embyserver_url(handler), {'params': params}, server_id) + +def validate_view(library_id, item_id): + + ''' This confirms a single item from the library matches the view it belongs to. + Used to detect grouped libraries. + ''' + try: + result = _get("Users/{UserId}/Items", { + 'ParentId': library_id, + 'Recursive': True, + 'Ids': item_id + }) + except Exception: + return False + + return True if len(result['Items']) else False + +def get_single_item(parent_id, media): + return _get("Users/{UserId}/Items", { + 'ParentId': parent_id, + 'Recursive': True, + 'Limit': 1, + 'IncludeItemTypes': media + }) + +def get_filtered_section(parent_id, media=None, limit=None, recursive=None, sort=None, sort_order=None, + filters=None, server_id=None): + + ''' Get dynamic listings. + ''' + params = { + 'ParentId': parent_id, + 'IncludeItemTypes': media, + 'IsMissing': False, + 'Recursive': recursive if recursive is not None else True, + 'Limit': limit, + 'SortBy': sort or "SortName", + 'SortOrder': sort_order or "Ascending", + 'Filters': filters, + 'ImageTypeLimit': 1, + 'IsVirtualUnaired': False, + 'CollapseBoxSetItems': not settings('groupedSets.bool'), + 'Fields': browse_info() + } + if settings('getCast.bool'): + params['Fields'] += ",People" + + if media and 'Photo' in media: + params['Fields'] += ",Width,Height" + + return _get("Users/{UserId}/Items", params, server_id) + +def get_movies_by_boxset(boxset_id, server_id=None): + + for items in get_items(boxset_id, "Movie", server_id=server_id): + yield items + +def get_episode_by_show(show_id, server_id=None): + + for items in get_items(show_id, "Episode", server_id=server_id): + yield items + +def get_items(parent_id, item_type=None, basic=False, params=None, server_id=None): + + query = { + 'url': "Users/{UserId}/Items", + 'params': { + 'ParentId': parent_id, + 'IncludeItemTypes': item_type, + 'SortBy': "SortName", + 'SortOrder': "Ascending", + 'Fields': api.basic_info() if basic else api.info() + } + } + + if params: + query['params'].update(params) + + for items in _get_items(query, server_id): + yield items + +def get_artists(parent_id=None, basic=False, params=None, server_id=None): + + query = { + 'url': "Artists", + 'params': { + 'UserId': "{UserId}", + 'ParentId': parent_id, + 'SortBy': "SortName", + 'SortOrder': "Ascending", + 'Fields': api.basic_info() if basic else api.music_info() + } + } + + if params: + query['params'].update(params) + + for items in _get_items(query, server_id): + yield items + +def get_albums_by_artist(artist_id, basic=False, server_id=None): + + params = { + 'SortBy': "DateCreated", + 'ArtistIds': artist_id + } + for items in get_items(None, "MusicAlbum", basic, params, server_id): + yield items + +@stop() +def _get_items(query, server_id=None): + + ''' query = { + 'url': string, + 'params': dict -- opt, include StartIndex to resume + } + ''' + items = { + 'Items': [], + 'TotalRecordCount': 0, + 'RestorePoint': {} + } + + url = query['url'] + params = query.get('params', {}) + params.update({ + 'CollapseBoxSetItems': False, + 'IsVirtualUnaired': False, + 'EnableTotalRecordCount': False, + 'LocationTypes': "FileSystem,Remote,Offline", + 'IsMissing': False, + 'Recursive': True + }) + + try: + test_params = dict(params) + test_params['Limit'] = 1 + test_params['EnableTotalRecordCount'] = True + + items['TotalRecordCount'] = _get(url, test_params, server_id=server_id)['TotalRecordCount'] + + except Exception as error: + LOG.error("Failed to retrieve the server response %s: %s params:%s", url, error, params) + + else: + index = params.get('StartIndex', 0) + total = items['TotalRecordCount'] + + while index < total: + + params['StartIndex'] = index + params['Limit'] = LIMIT + result = _get(url, params, server_id=server_id) + + items['Items'].extend(result['Items']) + items['RestorePoint'] = query + yield items + + del items['Items'][:] + index += LIMIT + +class GetItemWorker(threading.Thread): + + is_done = False + + def __init__(self, server, queue, output): + + self.server = server + self.queue = queue + self.output = output + threading.Thread.__init__(self) + + def run(self): + + with requests.Session() as s: + while True: + + try: + item_id = self.queue.get(timeout=1) + except Queue.Empty: + + self.is_done = True + LOG.info("--<[ q:download/%s ]", id(self)) + + return + + request = {'type': "GET", 'handler': "Users/{UserId}/Items/%s" % item_id} + try: + result = self.server['http/request'](request, s) + + if result['Type'] in self.output: + self.output[result['Type']].put(result) + except HTTPException as error: + LOG.error("--[ http status: %s ]", error.status) + + if error.status != 500: # to retry + continue + + except Exception as error: + LOG.exception(error) + + self.queue.task_done() + + if xbmc.Monitor().abortRequested(): + break + +class TheVoid(object): + + def __init__(self, method, data): + + ''' This will block until response is received. + This is meant to go as fast as possible, a response will always be returned. + ''' + if type(data) != dict: + raise Exception("unexpected data format") + + data['VoidName'] = id(self) + LOG.info("---[ contact mothership/%s ]", method) + LOG.debug(data) + + event(method, data) + self.method = method + self.data = data + + def get(self): + + while True: + response = window('emby_%s.json' % self.data['VoidName']) + + if response != "": + + LOG.debug("--<[ beacon/emby_%s.json ]", self.data['VoidName']) + window('emby_%s' % self.data['VoidName'], clear=True) + + return response + + if xbmc.Monitor().abortRequested(): + break + +def get_objects(src, filename): + + ''' Download objects dependency to temp cache folder. + ''' + temp = xbmc.translatePath('special://temp/emby/').decode('utf-8') + + if not xbmcvfs.exists(temp): + xbmcvfs.mkdir(temp) + else: + delete_build(temp) + + path = os.path.join(temp, filename) + try: + response = requests.get(src, stream=True) + response.raise_for_status() + except Exception as error: + raise + else: + with open(path, 'wb') as f: + f.write(response.content) + del response + + xbmc.executebuiltin('Extract(%s, %s)' % (path, temp)) + xbmcvfs.delete(path) diff --git a/resources/lib/downloadutils.py b/resources/lib/downloadutils.py deleted file mode 100644 index 9e96e85c..00000000 --- a/resources/lib/downloadutils.py +++ /dev/null @@ -1,383 +0,0 @@ -# -*- coding: utf-8 -*- - -################################################################################################## - -import json -import logging -import requests - -import xbmcgui - -import clientinfo -import connect.connectionmanager as connectionmanager -from utils import window, settings, language as lang - -################################################################################################## - -# Disable requests logging -from requests.packages.urllib3.exceptions import InsecureRequestWarning, InsecurePlatformWarning -requests.packages.urllib3.disable_warnings(InsecureRequestWarning) -requests.packages.urllib3.disable_warnings(InsecurePlatformWarning) - -log = logging.getLogger("EMBY."+__name__) - -################################################################################################## - -class HTTPException(Exception): - # Emby HTTP exception - def __init__(self, status): - self.status = status - - -class DownloadUtils(object): - - # Borg - multiple instances, shared state - _shared_state = {} - - # Requests session - session = {} - session_requests = None - servers = {} # Multi server setup - default_timeout = 30 - - - def __init__(self): - - self.__dict__ = self._shared_state - self.client_info = clientinfo.ClientInfo() - - - def set_session(self, **kwargs): - # Reserved for userclient only - info = {} - for key in kwargs: - info[key] = kwargs[key] - - self.session.update(info) - window('emby_server.json', value=self.session) - - log.debug("Set info for server %s: %s", self.session['ServerId'], self.session) - - def get_token(self): - return self._get_session_info()['Token'] - - def add_server(self, server, ssl): - # Reserved for userclient only - server_id = server['Id'] - info = { - 'UserId': server['UserId'], - 'Server': connectionmanager.getServerAddress(server, server['LastConnectionMode']), - 'Token': server['AccessToken'], - 'SSL': ssl - } - for server_info in self.servers: - if server_info == server_id: - server_info.update(info) - # Set window prop - self._set_server_properties(server_id, server['Name'], info) - log.info("updating %s to available servers: %s", server_id, self.servers) - break - else: - self.servers[server_id] = info - self._set_server_properties(server_id, server['Name'], json.dumps(info)) - log.info("adding %s to available servers: %s", server_id, self.servers) - - def reset_server(self, server_id): - # Reserved for userclient only - for server in self.servers: - if server['ServerId'] == server_id: - self.servers.pop(server) - window('emby_server%s.json' % server_id, clear=True) - window('emby_server%s.name' % server_id, clear=True) - log.info("removing %s from available servers", server_id) - - @staticmethod - def _set_server_properties(server_id, name, info): - window('emby_server%s.json' % server_id, value=info) - window('emby_server%s.name' % server_id, value=name) - - def post_capabilities(self, device_id=clientinfo.ClientInfo.get_device_id()): - # Post settings to session - url = "{server}/emby/Sessions/Capabilities/Full?format=json" - data = { - - 'PlayableMediaTypes': "Audio,Video", - 'SupportsMediaControl': True, - 'SupportedCommands': ( - - "MoveUp,MoveDown,MoveLeft,MoveRight,Select," - "Back,ToggleContextMenu,ToggleFullscreen,ToggleOsdMenu," - "GoHome,PageUp,NextLetter,GoToSearch," - "GoToSettings,PageDown,PreviousLetter,TakeScreenshot," - "VolumeUp,VolumeDown,ToggleMute,SendString,DisplayMessage," - "SetAudioStreamIndex,SetSubtitleStreamIndex," - "SetRepeatMode," - "Mute,Unmute,SetVolume," - "Play,Playstate,PlayNext,PlayMediaSource" - ), - 'IconUrl': "https://raw.githubusercontent.com/MediaBrowser/plugin.video.emby/develop/kodi_icon.png", - } - - try: - self.downloadUrl(url, postBody=data, action_type="POST") - log.debug("Posted capabilities to %s", self.session['Server']) - - # Attempt at getting sessionId - url = "{server}/emby/Sessions?DeviceId=%s&format=json" % device_id - result = self.downloadUrl(url) - session_id = result[0]['Id'] - - except Exception as error: - log.error("Failed to retrieve the session id: " + str(error)) - return False - - else: - log.info("SessionId: %s", session_id) - window('emby_sessionId', value=session_id) - - # Post any permanent additional users - additional_users = settings('additionalUsers') - if additional_users: - - additional_users = additional_users.split(',') - log.info("List of permanent users added to the session: %s", additional_users) - - # Get the user list from server to get the userId - url = "{server}/emby/Users?format=json" - result = self.downloadUrl(url) - - for additional in additional_users: - add_user = additional.decode('utf-8').lower() - - # Compare to server users to list of permanent additional users - for user in result: - username = user['Name'].lower() - - if username in add_user: - user_id = user['Id'] - url = ("{server}/emby/Sessions/%s/Users/%s?format=json" - % (session_id, user_id)) - self.downloadUrl(url, postBody={}, action_type="POST") - - return True - - def start_session(self): - # User is identified from this point - # Attach authenticated header to the session - session = requests.Session() - session.headers = self.get_header() - session.verify = self.session['SSL'] - # Retry connections to the server - session.mount("http://", requests.adapters.HTTPAdapter(max_retries=3)) - session.mount("https://", requests.adapters.HTTPAdapter(max_retries=3)) - self.session_requests = session - - log.info("requests session started on: %s", self.session['Server']) - - def stop_session(self): - try: - self.session_requests.close() - except Exception as error: - log.error(error) - log.warn("requests session could not be terminated") - - def get_header(self, server_id=None, authenticate=True): - - device_name = self.client_info.get_device_name().encode('utf-8') - device_id = self.client_info.get_device_id().encode('utf-8') - version = self.client_info.get_version().encode('utf-8') - - if authenticate: - - user = self._get_session_info(server_id) - user_id = user['UserId'].encode('utf-8') - token = user['Token'] - - auth = ( - 'MediaBrowser UserId="%s", Client="Kodi", Device="%s", DeviceId="%s", Version="%s"' - % (user_id, device_name, device_id, version) - ) - header = { - 'Authorization': auth, - 'X-MediaBrowser-Token': token - } - else: - auth = ( - 'MediaBrowser Client="Kodi", Device="%s", DeviceId="%s", Version="%s"' - % (device_name, device_id, version) - ) - header = {'Authorization': auth} - - header.update({ - 'Content-type': 'application/json', - 'Accept-encoding': 'gzip', - 'Accept-Charset': 'UTF-8,*', - 'User-Agent': 'Emby-Kodi (%s)' % version - }) - return header - - - def downloadUrl(self, url, postBody=None, action_type="GET", parameters=None, - authenticate=True, server_id=None): - - log.debug("===== ENTER downloadUrl =====") - - kwargs = {} - - try: - # Ensure server info is loaded - self._ensure_server(server_id) - server = self.session if server_id is None else self.servers[server_id] - - requires_server = False - if url.find("{server}") > -1 or url.find("{UserId}") > -1: - requires_server = True - - if requires_server and (not server or not server.get("Server") or not server.get("UserId")): - #xbmcgui.Dialog().ok('Emby for Kodi', "You are not connected to your emby server.") - #raise Exception("Aborting download, Server Details Error: %s url=%s" % (server, url)) - log.error("Aborting download, Server Details Error: %s url=%s", server, url) - return - - if server_id is None and self.session_requests is not None: # Main server - session = self.session_requests - else: - session = requests - kwargs.update({ - 'verify': server.get('SSL', False), - 'headers': self.get_header(server_id, authenticate) - }) - - # Replace for the real values - if requires_server: - url = url.replace("{server}", server['Server']) - url = url.replace("{UserId}", server['UserId']) - - # does the URL look ok - if url.startswith('/'): - raise Exception("URL Error: " + url) - - ##### PREPARE REQUEST ##### - kwargs.update({ - 'url': url, - 'timeout': self.default_timeout, - 'json': postBody, - 'params': parameters - }) - - ##### THE RESPONSE ##### - log.debug(kwargs) - response = self._requests(action_type, session, **kwargs) - #response = requests.get('http://httpbin.org/status/400') - - if response.status_code == 204: - # No body in the response - log.debug("====== 204 Success ======") - # Read response to release connection - response.content - if action_type == "GET": - raise Exception("Response Code 204 for GET request") - else: - # this is probably valid for DELETE and PUT - return None - - elif response.status_code == requests.codes.ok: - # UNICODE - JSON object - json_data = response.json() - log.debug("====== 200 Success ======") - log.debug("Response: %s", json_data) - return json_data - - else: # Bad status code - log.error("=== Bad status response: %s ===", response.status_code) - response.raise_for_status() - - ##### EXCEPTIONS ##### - - except requests.exceptions.ConnectionError as error: - # Make the addon aware of status - if window('emby_online') != "false": - log.error("Server unreachable at: %s", url) - window('emby_online', value="false") - - raise HTTPException(None) - - except requests.exceptions.ReadTimeout as error: - log.error("ReadTimeout at: %s", url) - - raise HTTPException(None) - - except requests.exceptions.HTTPError as error: - - if response.status_code == 401: - # Unauthorized - status = window('emby_serverStatus') - - if 'X-Application-Error-Code' in response.headers: - # Emby server errors - if response.headers['X-Application-Error-Code'] == "ParentalControl": - # Parental control - access restricted - if status != "restricted": - xbmcgui.Dialog().notification(heading=lang(29999), - message="Access restricted.", - icon=xbmcgui.NOTIFICATION_ERROR, - time=5000) - window('emby_serverStatus', value="restricted") - - elif status not in ("401", "Auth"): - # Tell userclient token has been revoked. - window('emby_serverStatus', value="401") - log.error("HTTP Error: %s", error) - xbmcgui.Dialog().notification(heading="Error connecting", - message="Unauthorized.", - icon=xbmcgui.NOTIFICATION_ERROR) - - raise HTTPException(response.status_code) - - # if we got to here and did not process the download for some reason then that is bad - raise Exception("Unhandled Download : %s", url) - - def _ensure_server(self, server_id=None): - - if server_id is None and self.session_requests is None: - if not self.session: - server = self._get_session_info() - self.session = server - - elif server_id and server_id not in self.servers: - if server_id not in self.servers: - server = self._get_session_info(server_id) - self.servers[server_id] = server - - return True - - @classmethod - def _get_session_info(cls, server_id=None): - - info = { - 'UserId': "", - 'Server': "", - 'Token': "" - } - - if server_id is None: # Main server - server = window('emby_server.json') - else: # Other connect servers - server = window('emby_server%s.json' % server_id) - - if server: - info.update(server) - - return info - - @classmethod - def _requests(cls, action, session, **kwargs): - - if action == "GET": - response = session.get(**kwargs) - elif action == "POST": - response = session.post(**kwargs) - elif action == "DELETE": - response = session.delete(**kwargs) - - return response diff --git a/resources/lib/emby.py b/resources/lib/emby.py deleted file mode 100644 index 317073d9..00000000 --- a/resources/lib/emby.py +++ /dev/null @@ -1,280 +0,0 @@ -# -*- coding: utf-8 -*- - - -''' The goal is to reduce memory usage. - Generators to prevent having to hold all the info in memory - while downloading from emby servers. - - Working with json, so we can resume where we left off. -''' -################################################################################################# - -import json -import logging -import hashlib -import threading -import Queue - -import xbmc - -import downloadutils -import database -from utils import window, settings, should_stop -from contextlib import closing - -################################################################################################# - -log = logging.getLogger("EMBY."+__name__) -limit = min(int(settings('limitIndex')), 50) -do = downloadutils.DownloadUtils() - -################################################################################################# - -def get_embyserver_url(handler): - return "{server}/emby/%s" % handler - -def basic_info(): - return "Etag" - -def complete_info(): - return ( - "Path,Genres,SortName,Studios,Writer,ProductionYear,Taglines," - "CommunityRating,OfficialRating,CumulativeRunTimeTicks," - "Metascore,AirTime,DateCreated,MediaStreams,People,Overview," - "CriticRating,CriticRatingSummary,Etag,ShortOverview,ProductionLocations," - "Tags,ProviderIds,ParentId,RemoteTrailers,SpecialEpisodeNumbers," - "MediaSources,VoteCount,ItemCounts" - ) - -def _http(action, url, request={}): - #request.update({'type': action, 'url': url}) - #return HTTP.request_url(request) - - while True: - - try: - return do.downloadUrl(url, action_type=action, parameters=request['params']) - except downloadutils.HTTPException as error: - - if error.status is None or error.status == 502: - while True: - - if xbmc.Monitor().waitForAbort(15): - raise - - if should_stop(): - raise - - if window('emby_online') == "true": - log.info("Retrying http query...") - break - else: - raise - -def _get(handler, params=None): - return _http("GET", get_embyserver_url(handler), {'params': params}) - -def _post(handler, json=None, params=None): - return _http("POST", get_embyserver_url(handler), {'params': params, 'json': json}) - -def _delete(handler, params=None): - return _http("DELETE", get_embyserver_url(handler), {'params': params}) - - -def emby_session(handler="", params=None, action="GET", json=None): - - if action == "POST": - return _post("Sessions%s" % handler, json, params) - elif action == "DELETE": - return _delete("Sessions%s" % handler, params) - else: - return _get("Sessions%s" % handler, params) - -def user(handler="", params=None, action="GET", json=None): - - if action == "POST": - return _post("Users/{UserId}%s" % handler, json, params) - elif action == "DELETE": - return _delete(session, "Users/{UserId}%s" % handler, params) - else: - return _get(session, "Users/{UserId}%s" % handler, params) - -def item(handler="", params=None): - return user("/Items%s" % handler, params) - -def show(handler, params): - return _get("Shows%s" % handler, params) - -################################################################################################# - -# Single result functions - -################################################################################################# - -def get_item(item_id, fields=None): - return item(params={ - 'Ids': item_id, - 'EnableTotalRecordCount': False, - 'Fields': fields - }) - -def get_seasons(self, show_id): - return show("/%s/Seasons?UserId={UserId}" % show_id, { - 'IsVirtualUnaired': False, - 'Fields': "Etag" - }) - -################################################################################################# - -# Multiple calls to get multiple items (Generator) - -''' This should help with memory issues. - for items in generator(...): - #do something - - If all items are required at once: - a = (items['Items'] for items in generator(...)) -''' - -################################################################################################# - -def get_all(generator): - - items = [] - - for item in generator: - items.extend(item['Items']) - - return items - -def get_items(parent_id, item_type=None, basic=False, params=None): - - query = { - 'url': "Users/{UserId}/Items", - 'params': { - 'ParentId': parent_id, - 'IncludeItemTypes': item_type, - 'SortBy': "SortName", - 'SortOrder': "Ascending", - 'Fields': basic_info() if basic else complete_info() - } - } - - if params: - query['params'].update(params) - - for items in _get_items(query): - yield items - -def get_item_list(item_list, basic=False): - - for item_ids in _split_list(item_list[:], limit): - - query = { - 'url': "Users/{UserId}/Items", - 'params': { - "Ids": ",".join(item_ids), - 'Fields': basic_info() if basic else complete_info() - } - } - - for items in _get_items(query): - yield items - -def get_artists(parent_id=None): - - query = { - 'url': "Artists?UserId={UserId}", - 'params': { - 'ParentId': parent_id, - 'SortBy': "SortName", - 'SortOrder': "Ascending", - 'Fields': ( - "Etag,Genres,SortName,Studios,Writer,ProductionYear," - "CommunityRating,OfficialRating,CumulativeRunTimeTicks,Metascore," - "AirTime,DateCreated,MediaStreams,People,ProviderIds,Overview,ItemCounts" - ) - } - } - for items in _get_items(query): - yield items - -def get_albums_by_artist(artist_id): - - params = { - 'SortBy': "DateCreated", - 'ArtistIds': artist_id - } - for items in get_items(None, "MusicAlbum", params=params): - yield items - -def sortby_mediatype(item_ids): - - sorted_items = {} - - items = get_all(get_item_list(item_ids)) - for item in items: - - mediatype = item.get('Type') - if mediatype: - sorted_items.setdefault(mediatype, []).append(item) - - return sorted_items - - -def _split_list(item_list, size): - # Split up list in pieces of size. Will generate a list of lists - return [item_list[i:i + size] for i in range(0, len(item_list), size)] - -def _test_params(url, params): - - params['Limit'] = 1 - params['EnableTotalRecordCount'] = True - - try: - return _get(url, params) - - except Exception as error: - raise - -def _get_items(query): - - ''' query = { - 'url': string, - 'params': dict -- opt, include StartIndex to resume - } - ''' - items = { - 'Items': [], - 'TotalRecordCount': 0, - 'RestorePoint': {} - } - - url = query['url'] - params = query.get('params', {}) - params.update({ - 'CollapseBoxSetItems': False, - 'IsVirtualUnaired': False, - 'EnableTotalRecordCount': False, - 'LocationTypes': "FileSystem,Remote,Offline", - 'IsMissing': False, - 'Recursive': True - }) - - items['TotalRecordCount'] = _test_params(url, dict(params))['TotalRecordCount'] - - index = params.get('StartIndex', 0) - total = items['TotalRecordCount'] - - while index < total: - - params['StartIndex'] = index - params['Limit'] = limit - result = _get(url, params) # Could raise an HTTP error. - - items['Items'].extend(result['Items']) - items['RestorePoint'] = query - yield items - - del items['Items'][:] - index += limit diff --git a/resources/lib/emby/__init__.py b/resources/lib/emby/__init__.py new file mode 100644 index 00000000..fc478bd2 --- /dev/null +++ b/resources/lib/emby/__init__.py @@ -0,0 +1,118 @@ +# -*- coding: utf-8 -*- + +################################################################################################# + +import logging + +from client import EmbyClient +from helpers import has_attribute + +################################################################################################# + +class NullHandler(logging.Handler): + def emit(self, record): + print(self.format(record)) + +loghandler = NullHandler +LOG = logging.getLogger('Emby') + +################################################################################################# + +def config(level=logging.INFO): + + logger = logging.getLogger('Emby') + logger.addHandler(Emby.loghandler()) + logger.setLevel(level) + +def ensure_client(): + + def decorator(func): + def wrapper(self, *args, **kwargs): + + if self.client.get(self.server_id) is None: + self.construct() + + return func(self, *args, **kwargs) + + return wrapper + return decorator + + +class Emby(object): + + ''' This is your Embyclient, you can create more than one. The server_id is only a temporary thing. + from emby import Emby + + default_client = Emby()['config/app'] + another_client = Emby('123456')['config/app'] + ''' + + # Borg - multiple instances, shared state + _shared_state = {} + client = {} + server_id = "default" + loghandler = loghandler + + def __init__(self, server_id=None): + self.__dict__ = self._shared_state + self.server_id = server_id or "default" + + @classmethod + def set_loghandler(cls, func=loghandler, level=logging.INFO): + + for handler in logging.getLogger('Emby').handlers: + if isinstance(handler, cls.loghandler): + logging.getLogger('Emby').removeHandler(handler) + + cls.loghandler = func + config(level) + + def close(self): + + if self.server_id not in self.client: + return + + self.client[self.server_id].stop() + self.client.pop(self.server_id, None) + + LOG.info("---[ STOPPED EMBYCLIENT: %s ]---", self.server_id) + + @classmethod + def close_all(cls): + + for client in cls.client: + cls.client[client].stop() + + cls.client = {} + LOG.info("---[ STOPPED ALL EMBYCLIENTS ]---") + + @classmethod + def get_active_clients(cls): + return cls.client + + @ensure_client() + def __setattr__(self, name, value): + + if has_attribute(self, name): + return super(Emby, self).__setattr__(name, value) + + setattr(self.client[self.server_id], name, value) + + @ensure_client() + def __getattr__(self, name): + return getattr(self.client[self.server_id], name) + + @ensure_client() + def __getitem__(self, key): + return self.client[self.server_id][key] + + def construct(self): + + self.client[self.server_id] = EmbyClient() + + if self.server_id == 'default': + LOG.info("---[ START EMBYCLIENT ]---") + else: + LOG.info("---[ START EMBYCLIENT: %s ]---", self.server_id) + +config() \ No newline at end of file diff --git a/resources/lib/emby/client.py b/resources/lib/emby/client.py new file mode 100644 index 00000000..2209b069 --- /dev/null +++ b/resources/lib/emby/client.py @@ -0,0 +1,107 @@ +# -*- coding: utf-8 -*- + +################################################################################################# + +import logging + +import core.api as api +from core.configuration import Config +from core.http import HTTP +from core.ws_client import WSClient +from core.connection_manager import ConnectionManager, CONNECTION_STATE + +################################################################################################# + +LOG = logging.getLogger('Emby.'+__name__) + +################################################################################################# + +def callback(message, data): + ''' Callback function should received message, data + message: string + data: json dictionary + ''' + pass + + +class EmbyClient(object): + + logged_in = False + + def __init__(self): + LOG.debug("EmbyClient initializing...") + + self.config = Config() + self.http = HTTP(self) + self.wsc = WSClient(self) + self.auth = ConnectionManager(self) + self.emby = api + self.emby.client = self.http + self.callback_ws = callback + self.callback = callback + + def set_credentials(self, credentials=None): + self.auth.credentials.set_credentials(credentials or {}) + + def get_credentials(self): + return self.auth.credentials.get_credentials() + + def authenticate(self, credentials=None, options=None): + + self.set_credentials(credentials or {}) + state = self.auth.connect(options or {}) + + if state['State'] == CONNECTION_STATE['SignedIn']: + + LOG.info("User is authenticated.") + self.logged_in = True + self.callback("ServerOnline", {'Id': self['auth/server-id']}) + + state['Credentials'] = self.get_credentials() + + return state + + def start(self, websocket=False): + + if not self.logged_in: + raise ValueError("User is not authenticated.") + + self.http.start_session() + + if websocket: + self.start_wsc() + + def start_wsc(self): + self.wsc.start() + + def stop(self): + + self.wsc.stop_client() + self.http.stop_session() + + def __getitem__(self, key): + + if key.startswith('config'): + return self.config[key.replace('config/', "", 1)] if "/" in key else self.config + + elif key.startswith('http'): + return self.http.__shortcuts__(key.replace('http/', "", 1)) + + elif key.startswith('websocket'): + return self.wsc.__shortcuts__(key.replace('websocket/', "", 1)) + + elif key.startswith('callback'): + return self.callback_ws if 'ws' in key else self.callback + + elif key.startswith('auth'): + return self.auth.__shortcuts__(key.replace('auth/', "", 1)) + + elif key.startswith('api'): + self.emby.client = self.http # Since api is not a class, re-assign global var to correct http adapter + + return self.emby + + elif key == 'connected': + return self.logged_in + + return diff --git a/resources/lib/emby/core/__init__.py b/resources/lib/emby/core/__init__.py new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/resources/lib/emby/core/__init__.py @@ -0,0 +1 @@ + diff --git a/resources/lib/emby/core/api.py b/resources/lib/emby/core/api.py new file mode 100644 index 00000000..8c5159d4 --- /dev/null +++ b/resources/lib/emby/core/api.py @@ -0,0 +1,287 @@ +# -*- coding: utf-8 -*- + +################################################################################################# + +client = None + +################################################################################################# + +def _http(action, url, request={}): + request.update({'type': action, 'handler': url}) + + return client.request(request) + +def _get(handler, params=None): + return _http("GET", handler, {'params': params}) + +def _post(handler, json=None, params=None): + return _http("POST", handler, {'params': params, 'json': json}) + +def _delete(handler, params=None): + return _http("DELETE", handler, {'params': params}) + +def emby_url(handler): + return "%s/emby/%s" % (client.config['auth.server'], handler) + +def basic_info(): + return "Etag" + +def info(): + return ( + "Path,Genres,SortName,Studios,Writer,Taglines," + "OfficialRating,CumulativeRunTimeTicks," + "Metascore,AirTime,DateCreated,People,Overview," + "CriticRating,CriticRatingSummary,Etag,ShortOverview,ProductionLocations," + "Tags,ProviderIds,ParentId,RemoteTrailers,SpecialEpisodeNumbers," + "MediaSources,VoteCount,RecursiveItemCount,PrimaryImageAspectRatio" + ) + +def music_info(): + return ( + "Etag,Genres,SortName,Studios,Writer," + "OfficialRating,CumulativeRunTimeTicks,Metascore," + "AirTime,DateCreated,MediaStreams,People,ProviderIds,Overview,ItemCounts" + ) + +################################################################################################# + +# Bigger section of the Emby api + +################################################################################################# + +def try_server(): + return _get("System/Info/Public") + +def sessions(handler="", action="GET", params=None, json=None): + + if action == "POST": + return _post("Sessions%s" % handler, json, params) + elif action == "DELETE": + return _delete("Sessions%s" % handler, params) + else: + return _get("Sessions%s" % handler, params) + +def users(handler="", action="GET", params=None, json=None): + + if action == "POST": + return _post("Users/{UserId}%s" % handler, json, params) + elif action == "DELETE": + return _delete("Users/{UserId}%s" % handler, params) + else: + return _get("Users/{UserId}%s" % handler, params) + +def items(handler="", action="GET", params=None, json=None): + + if action == "POST": + return _post("Items%s" % handler, json, params) + elif action == "DELETE": + return _delete("Items%s" % handler, params) + else: + return _get("Items%s" % handler, params) + +def user_items(handler="", params=None): + return users("/Items%s" % handler, params) + +def shows(handler, params): + return _get("Shows%s" % handler, params) + +def videos(handler): + return _get("Videos%s" % handler) + +def artwork(item_id, art, max_width, ext="jpg", index=None): + + if index is None: + return emby_url("Items/%s/Images/%s?MaxWidth=%s&format=%s" % (item_id, art, max_width, ext)) + + return emby_url("Items/%s/Images/%s/%s?MaxWidth=%s&format=%s" % (item_id, art, index, max_width, ext)) + +################################################################################################# + +# More granular api + +################################################################################################# + +def get_users(disabled=False, hidden=False): + return _get("Users", params={ + 'IsDisabled': disabled, + 'IsHidden': hidden + }) + +def get_public_users(): + return _get("Users/Public") + +def get_user(user_id=None): + return users() if user_id is None else _get("Users/%s" % user_id) + +def get_views(): + return users("/Views") + +def get_media_folders(): + return users("/Items") + +def get_item(item_id): + return users("/Items/%s" % item_id) + +def get_items(item_ids): + return users("/Items", params={ + 'Ids': ','.join(str(x) for x in item_ids), + 'Fields': info() + }) + +def get_sessions(): + return sessions(params={'ControllableByUserId': "{UserId}"}) + +def get_device(device_id): + return sessions(params={'DeviceId': device_id}) + +def post_session(session_id, url, params=None, data=None): + return sessions("/%s/%s" % (session_id, url), "POST", params, data) + +def get_images(item_id): + return items("/%s/Images" % item_id) + +def get_suggestion(media="Movie,Episode", limit=1): + return users("/Suggestions", { + 'Type': media, + 'Limit': limit + }) + +def get_recently_added(media=None, limit=20): + return user_items("/Latest", { + 'Limit': limit, + 'UserId': "{UserId}", + 'IncludeItemTypes': media + }) + +def get_next(index=None, limit=1): + return shows("/NextUp", { + 'Limit': limit, + 'UserId': "{UserId}", + 'StartIndex': None if index is None else int(index) + }) + +def get_intros(item_id): + return user_items("/%s/Intros" % item_id) + +def get_additional_parts(item_id): + return videos("/%s/AdditionalParts" % item_id) + +def delete_item(item_id): + return items("/%s" % item_id, "DELETE") + +def get_local_trailers(item_id): + return user_items("/%s/LocalTrailers" % item_id) + +def get_ancestors(item_id): + return items("/%s/Ancestors" % item_id, params={ + 'UserId': "{UserId}" + }) + +def get_items_theme_video(parent_id): + return users("/Items", params={ + 'HasThemeVideo': True, + 'ParentId': parent_id + }) + +def get_themes(item_id): + return items("/%s/ThemeMedia" % item_id, params={ + 'UserId': "{UserId}", + 'InheritFromParent': True + }) + +def get_items_theme_song(parent_id): + return users("/Items", params={ + 'HasThemeSong': True, + 'ParentId': parent_id + }) + +def get_plugins(): + return _get("Plugins") + +def get_seasons(show_id): + return shows("/%s/Seasons" % show_id, params={ + 'UserId': "{UserId}", + 'Fields': basic_info() + }) + +def get_date_modified(date, parent_id, media=None): + return users("/Items", params={ + 'ParentId': parent_id, + 'Recursive': False, + 'IsMissing': False, + 'IsVirtualUnaired': False, + 'IncludeItemTypes': media or None, + #'MinDateLastSavedForUser': date, + 'MinDateLastSaved': date, + 'Fields': info() + }) + +def refresh_item(item_id): + return items("/%s/Refresh" % item_id, "POST", json={ + 'Recursive': True, + 'ImageRefreshMode': "FullRefresh", + 'MetadataRefreshMode': "FullRefresh", + 'ReplaceAllImages': False, + 'ReplaceAllMetadata': True + }) + +def favorite(item_id, option=True): + return users("/FavoriteItems/%s" % item_id, "POST" if option else "DELETE") + +def get_system_info(): + return _get("System/Configuration") + +def post_capabilities(data): + return sessions("/Capabilities/Full", "POST", json=data) + +def session_add_user(session_id, user_id, option=True): + return sessions("/%s/Users/%s" % (session_id, user_id), "POST" if option else "DELETE") + +def session_playing(data): + return sessions("/Playing", "POST", json=data) + +def session_progress(data): + return sessions("/Playing/Progress", "POST", json=data) + +def session_stop(data): + return sessions("/Playing/Stopped", "POST", json=data) + +def item_played(item_id, watched): + return users("/PlayedItems/%s" % item_id, "POST" if watched else "DELETE") + +def get_sync_queue(date, filters=None): + return _get("Emby.Kodi.SyncQueue/{UserId}/GetItems", params={ + 'LastUpdateDT': date, + 'filter': filters or None + }) + +def get_server_time(): + return _get("Emby.Kodi.SyncQueue/GetServerDateTime") + +def get_play_info(item_id, profile): + return items("/%s/PlaybackInfo" % item_id, "POST", json={ + 'UserId': "{UserId}", + 'DeviceProfile': profile + }) + +def get_live_stream(item_id, play_id, token, profile): + return _post("LiveStreams/Open", json={ + 'UserId': "{UserId}", + 'DeviceProfile': profile, + 'OpenToken': token, + 'PlaySessionId': play_id, + 'ItemId': item_id + }) + +def close_live_stream(live_id): + return _post("LiveStreams/Close", json={ + 'LiveStreamId': live_id + }) + +def close_transcode(device_id): + return _delete("Videos/ActiveEncodings", params={ + 'DeviceId': device_id + }) + +def delete_item(item_id): + return items("/%s" % item_id, "DELETE") diff --git a/resources/lib/emby/core/configuration.py b/resources/lib/emby/core/configuration.py new file mode 100644 index 00000000..6966c85f --- /dev/null +++ b/resources/lib/emby/core/configuration.py @@ -0,0 +1,73 @@ +# -*- coding: utf-8 -*- + +''' This will hold all configs from the client. + Configuration set here will be used for the HTTP client. +''' + +################################################################################################# + +import logging + +################################################################################################# + +DEFAULT_HTTP_MAX_RETRIES = 3 +DEFAULT_HTTP_TIMEOUT = 30 +LOG = logging.getLogger('Emby.'+__name__) + +################################################################################################# + +class Config(object): + + def __init__(self): + + LOG.debug("Configuration initializing...") + self.data = {} + self.http() + + def __shortcuts__(self, key): + + if key == "auth": + return self.auth + elif key == "app": + return self.app + elif key == "http": + return self.http + elif key == "data": + return self + + return + + def __getitem__(self, key): + return self.data.get(key, self.__shortcuts__(key)) + + def __setitem__(self, key, value): + self.data[key] = value + + def app(self, name, version, device_name, device_id, capabilities=None, device_pixel_ratio=None): + + LOG.info("Begin app constructor.") + + self.data['app.name'] = name + self.data['app.version'] = version + self.data['app.device_name'] = device_name + self.data['app.device_id'] = device_id + self.data['app.capabilities'] = capabilities + self.data['app.device_pixel_ratio'] = device_pixel_ratio + self.data['app.default'] = False + + def auth(self, server, user_id, token=None, ssl=None): + + LOG.info("Begin auth constructor.") + + self.data['auth.server'] = server + self.data['auth.user_id'] = user_id + self.data['auth.token'] = token + self.data['auth.ssl'] = ssl + + def http(self, user_agent=None, max_retries=DEFAULT_HTTP_MAX_RETRIES, timeout=DEFAULT_HTTP_TIMEOUT): + + LOG.info("Begin http constructor.") + + self.data['http.max_retries'] = max_retries + self.data['http.timeout'] = timeout + self.data['http.user_agent'] = user_agent diff --git a/resources/lib/emby/core/connection_manager.py b/resources/lib/emby/core/connection_manager.py new file mode 100644 index 00000000..c96f4943 --- /dev/null +++ b/resources/lib/emby/core/connection_manager.py @@ -0,0 +1,846 @@ +# -*- coding: utf-8 -*- + +################################################################################################# + +import json +import logging +import hashlib +import socket +import time +from datetime import datetime + +from credentials import Credentials +from http import HTTP + +################################################################################################# + +LOG = logging.getLogger('Emby.'+__name__) +CONNECTION_STATE = { + 'Unavailable': 0, + 'ServerSelection': 1, + 'ServerSignIn': 2, + 'SignedIn': 3, + 'ConnectSignIn': 4, + 'ServerUpdateNeeded': 5 +} +CONNECTION_MODE = { + 'Local': 0, + 'Remote': 1, + 'Manual': 2 +} + +################################################################################################# + +def get_server_address(server, mode): + + modes = { + CONNECTION_MODE['Local']: server.get('LocalAddress'), + CONNECTION_MODE['Remote']: server.get('RemoteAddress'), + CONNECTION_MODE['Manual']: server.get('ManualAddress') + } + return modes.get(mode) or server.get('ManualAddress', server.get('LocalAddress', server.get('RemoteAddress'))) + + +class ConnectionManager(object): + + min_server_version = "3.0.5930" + server_version = min_server_version + user = {} + server_id = None + timeout = 10 + + def __init__(self, client): + + LOG.debug("ConnectionManager initializing...") + + self.client = client + self.config = client.config + self.credentials = Credentials() + + self.http = HTTP(client) + + def __shortcuts__(self, key): + + if key == "clear": + return self.clear_data + elif key == "servers": + return self.get_available_servers() + elif key in ("reconnect", "refresh"): + return self.connect + elif key == "login": + return self.login + elif key == "login-connect": + return self.login_to_connect + elif key == "connect-user": + return self.connect_user() + elif key == "connect-token": + return self.connect_token() + elif key == "connect-user-id": + return self.connect_user_id() + elif key == "server": + return self.get_server_info(self.server_id) + elif key == "server-id": + return self.server_id + elif key == "server-version": + return self.server_version + elif key == "user-id": + return self.emby_user_id() + elif key == "public-users": + return self.get_public_users() + elif key == "token": + return self.emby_token() + elif key == "manual-server": + return self.connect_to_address + elif key == "connect-to-server": + return self.connect_to_server + elif key == "server-address": + server = self.get_server_info(self.server_id) + return get_server_address(server, server['LastConnectionMode']) + elif key == "revoke-token": + return self.revoke_token() + + return + + def __getitem__(self, key): + return self.__shortcuts__(key) + + def clear_data(self): + + LOG.info("connection manager clearing data") + + self.user = None + credentials = self.credentials.get_credentials() + credentials['ConnectAccessToken'] = None + credentials['ConnectUserId'] = None + credentials['Servers'] = list() + self.credentials.get_credentials(credentials) + + self.config.auth(None, None) + + def revoke_token(self): + + LOG.info("revoking token") + + self['server']['AccessToken'] = None + self.credentials.get_credentials(self.credentials.get_credentials()) + + self.config['auth.token'] = None + + def get_available_servers(self): + + LOG.info("Begin getAvailableServers") + + # Clone the credentials + credentials = self.credentials.get_credentials() + connect_servers = self._get_connect_servers(credentials) + found_servers = self._find_servers(self._server_discovery()) + + if not connect_servers and not found_servers and not credentials['Servers']: # back out right away, no point in continuing + LOG.info("Found no servers") + + return list() + + servers = list(credentials['Servers']) + self._merge_servers(servers, found_servers) + self._merge_servers(servers, connect_servers) + + servers = self._filter_servers(servers, connect_servers) + + try: + servers.sort(key=lambda x: datetime.strptime(x['DateLastAccessed'], "%Y-%m-%dT%H:%M:%SZ"), reverse=True) + except TypeError: + servers.sort(key=lambda x: datetime(*(time.strptime(x['DateLastAccessed'], "%Y-%m-%dT%H:%M:%SZ")[0:6])), reverse=True) + + credentials['Servers'] = servers + self.credentials.get_credentials(credentials) + + return servers + + def login_to_connect(self, username, password): + + if not username: + raise AttributeError("username cannot be empty") + + if not password: + raise AttributeError("password cannot be empty") + + try: + result = self._request_url({ + 'type': "POST", + 'url': self.get_connect_url("user/authenticate"), + 'data': { + 'nameOrEmail': username, + 'password': self._get_connect_password_hash(password) + }, + 'dataType': "json" + }) + except Exception as error: # Failed to login + LOG.error(error) + return False + else: + credentials = self.credentials.get_credentials() + credentials['ConnectAccessToken'] = result['AccessToken'] + credentials['ConnectUserId'] = result['User']['Id'] + credentials['ConnectUser'] = result['User']['DisplayName'] + self.credentials.get_credentials(credentials) + # Signed in + self._on_connect_user_signin(result['User']) + + return result + + def login(self, server, username, password="", options={}): + + if not username: + raise AttributeError("username cannot be empty") + + if not server: + raise AttributeError("server cannot be empty") + + try: + result = self._request_url({ + 'type': "POST", + 'url': self.get_emby_url(server, "Users/AuthenticateByName"), + 'json': { + 'username': username, + 'password': hashlib.sha1(password or "").hexdigest() + } + }, False) + except Exception as error: # Failed to login + LOG.error(error) + return False + else: + self._on_authenticated(result, options) + + return result + + def connect_to_address(self, address, options={}): + + if not address: + return False + + address = self._normalize_address(address) + + def _on_fail(): + LOG.error("connectToAddress %s failed", address) + return self._resolve_failure() + + try: + public_info = self._try_connect(address, options=options) + except Exception: + return _on_fail() + else: + LOG.info("connectToAddress %s succeeded", address) + server = { + 'ManualAddress': address, + 'LastConnectionMode': CONNECTION_MODE['Manual'] + } + self._update_server_info(server, public_info) + server = self.connect_to_server(server, options) + if server is False: + return _on_fail() + + return server + + def connect_to_server(self, server, options={}): + + LOG.info("begin connectToServer") + + tests = [] + + if server.get('LastConnectionMode') is not None: + #tests.append(server['LastConnectionMode']) + pass + if CONNECTION_MODE['Manual'] not in tests: + tests.append(CONNECTION_MODE['Manual']) + if CONNECTION_MODE['Local'] not in tests: + tests.append(CONNECTION_MODE['Local']) + if CONNECTION_MODE['Remote'] not in tests: + tests.append(CONNECTION_MODE['Remote']) + + # TODO: begin to wake server + + LOG.info("beginning connection tests") + return self._test_next_connection_mode(tests, 0, server, options) + + def connect(self, options={}): + + LOG.info("Begin connect") + return self._connect_to_servers(self.get_available_servers(), options) + + def connect_user(self): + return self.user + + def connect_user_id(self): + return self.credentials.get_credentials().get('ConnectUserId') + + def connect_token(self): + return self.credentials.get_credentials().get('ConnectAccessToken') + + def emby_user_id(self): + return self.get_server_info(self.server_id)['UserId'] + + def emby_token(self): + return self.get_server_info(self.server_id)['AccessToken'] + + def get_server_info(self, server_id): + + if server_id is None: + LOG.info("server_id is empty") + return {} + + servers = self.credentials.get_credentials()['Servers'] + + for server in servers: + if server['Id'] == server_id: + return server + + def get_public_users(self): + return self.client.emby.get_public_users() + + def get_connect_url(self, handler): + return "https://connect.emby.media/service/%s" % handler + + def get_emby_url(self, base, handler): + return "%s/emby/%s" % (base, handler) + + def _request_url(self, request, headers=True): + + request['timeout'] = request.get('timeout') or self.timeout + if headers: + self._get_headers(request) + + try: + return self.http.request(request) + except Exception as error: + LOG.error(error) + raise + + def _add_app_info(self): + return "%s/%s" % (self.config['app.name'], self.config['app.version']) + + def _get_headers(self, request): + + headers = request.setdefault('headers', {}) + + if request.get('dataType') == "json": + headers['Accept'] = "application/json" + request.pop('dataType') + + headers['X-Application'] = self._add_app_info() + headers['Content-type'] = request.get('contentType', + 'application/x-www-form-urlencoded; charset=UTF-8') + + def _connect_to_servers(self, servers, options): + + LOG.info("Begin connectToServers, with %s servers", len(servers)) + result = {} + + if len(servers) == 1: + result = self.connect_to_server(servers[0], options) + + """ + if result['State'] == CONNECTION_STATE['Unavailable']: + result['State'] = CONNECTION_STATE['ConnectSignIn'] if result['ConnectUser'] is None else CONNECTION_STATE['ServerSelection'] + """ + + LOG.debug("resolving connectToServers with result['State']: %s", result) + + return result + + first_server = self._get_last_used_server() + # See if we have any saved credentials and can auto sign in + if first_server is not None and first_server['DateLastAccessed'] != "2001-01-01T00:00:00Z": + result = self.connect_to_server(first_server, options) + + if result['State'] == CONNECTION_STATE['SignedIn']: + return result + + # Return loaded credentials if exists + credentials = self.credentials.get_credentials() + self._ensure_connect_user(credentials) + + return { + 'Servers': servers, + 'State': CONNECTION_STATE['ConnectSignIn'] if (not len(servers) and not self.connect_user()) else (result.get('State') or CONNECTION_STATE['ServerSelection']), + 'ConnectUser': self.connect_user() + } + + def _try_connect(self, url, timeout=None, options={}): + + url = self.get_emby_url(url, "system/info/public") + LOG.info("tryConnect url: %s", url) + + return self._request_url({ + 'type': "GET", + 'url': url, + 'dataType': "json", + 'timeout': timeout, + 'verify': options.get('ssl'), + 'retry': False + }) + + def _test_next_connection_mode(self, tests, index, server, options): + + if index >= len(tests): + LOG.info("Tested all connection modes. Failing server connection.") + return self._resolve_failure() + + mode = tests[index] + address = get_server_address(server, mode) + enable_retry = False + skip_test = False + timeout = self.timeout + + LOG.info("testing connection mode %s with server %s", mode, server.get('Name')) + + if mode == CONNECTION_MODE['Local']: + enable_retry = True + timeout = 8 + + if self._string_equals_ignore_case(address, server.get('ManualAddress')): + LOG.info("skipping LocalAddress test because it is the same as ManualAddress") + skip_test = True + + elif mode == CONNECTION_MODE['Manual']: + if self._string_equals_ignore_case(address, server.get('LocalAddress')): + enable_retry = True + timeout = 8 + + if skip_test or not address: + LOG.info("skipping test at index: %s", index) + return self._test_next_connection_mode(tests, index + 1, server, options) + + try: + result = self._try_connect(address, timeout, options) + + except Exception: + LOG.error("test failed for connection mode %s with server %s", mode, server.get('Name')) + + if enable_retry: + # TODO: wake on lan and retry + return self._test_next_connection_mode(tests, index + 1, server, options) + else: + return self._test_next_connection_mode(tests, index + 1, server, options) + else: + if self._compare_versions(self._get_min_server_version(), result['Version']) == 1: + LOG.warn("minServerVersion requirement not met. Server version: %s", result['Version']) + return { + 'State': CONNECTION_STATE['ServerUpdateNeeded'], + 'Servers': [server] + } + else: + LOG.info("calling onSuccessfulConnection with connection mode %s with server %s", mode, server.get('Name')) + return self._on_successful_connection(server, result, mode, options) + + def _on_successful_connection(self, server, system_info, connection_mode, options): + + credentials = self.credentials.get_credentials() + + if credentials.get('ConnectAccessToken') and options.get('enableAutoLogin') is not False: + + if self._ensure_connect_user(credentials) is not False: + + if server.get('ExchangeToken'): + self._add_authentication_info_from_connect(server, connection_mode, credentials, options) + + return self._after_connect_validated(server, credentials, system_info, connection_mode, True, options) + + def _resolve_failure(self): + return { + 'State': CONNECTION_STATE['Unavailable'], + 'ConnectUser': self.connect_user() + } + + def _get_min_server_version(self, val=None): + + if val is not None: + LOG.info("hello?") + self.min_server_version = val + + return self.min_server_version + + def _compare_versions(self, a, b): + + ''' -1 a is smaller + 1 a is larger + 0 equal + ''' + a = a.split('.') + b = b.split('.') + + for i in range(0, max(len(a), len(b)), 1): + try: + aVal = a[i] + except IndexError: + aVal = 0 + + try: + bVal = b[i] + except IndexError: + bVal = 0 + + if aVal < bVal: + return -1 + + if aVal > bVal: + return 1 + + return 0 + + def _string_equals_ignore_case(self, str1, str2): + return (str1 or "").lower() == (str2 or "").lower() + + def _get_connect_user(self, user_id, access_token): + + if not user_id: + raise AttributeError("null userId") + + if not access_token: + raise AttributeError("null accessToken") + + return self._request_url({ + 'type': "GET", + 'url': self.get_connect_url('user?id=%s' % user_id), + 'dataType': "json", + 'headers': { + 'X-Connect-UserToken': access_token + } + }) + + def _server_discovery(self): + + MULTI_GROUP = ("<broadcast>", 7359) + MESSAGE = "who is EmbyServer?" + + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + sock.settimeout(1.0) # This controls the socket.timeout exception + + sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 20) + sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) + sock.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_LOOP, 1) + sock.setsockopt(socket.IPPROTO_IP, socket.SO_REUSEADDR, 1) + + LOG.debug("MultiGroup : %s", str(MULTI_GROUP)) + LOG.debug("Sending UDP Data: %s", MESSAGE) + + servers = [] + + try: + sock.sendto(MESSAGE, MULTI_GROUP) + except Exception as error: + LOG.error(error) + return servers + + while True: + try: + data, addr = sock.recvfrom(1024) # buffer size + servers.append(json.loads(data)) + + except socket.timeout: + LOG.info("Found Servers: %s", servers) + return servers + + except Exception as e: + LOG.error("Error trying to find servers: %s", e) + return servers + + def _get_connect_servers(self, credentials): + + LOG.info("Begin getConnectServers") + + servers = list() + + if not credentials.get('ConnectAccessToken') or not credentials.get('ConnectUserId'): + return servers + + url = self.get_connect_url("servers?userId=%s" % credentials['ConnectUserId']) + request = { + 'type': "GET", + 'url': url, + 'dataType': "json", + 'headers': { + 'X-Connect-UserToken': credentials['ConnectAccessToken'] + } + } + for server in self._request_url(request): + servers.append({ + 'ExchangeToken': server['AccessKey'], + 'ConnectServerId': server['Id'], + 'Id': server['SystemId'], + 'Name': server['Name'], + 'RemoteAddress': server['Url'], + 'LocalAddress': server['LocalAddress'], + 'UserLinkType': "Guest" if server['UserType'].lower() == "guest" else "LinkedUser", + }) + + return servers + + def _get_last_used_server(self): + + servers = self.credentials.get_credentials()['Servers'] + + if not len(servers): + return + + try: + servers.sort(key=lambda x: datetime.strptime(x['DateLastAccessed'], "%Y-%m-%dT%H:%M:%SZ"), reverse=True) + except TypeError: + servers.sort(key=lambda x: datetime(*(time.strptime(x['DateLastAccessed'], "%Y-%m-%dT%H:%M:%SZ")[0:6])), reverse=True) + + return servers[0] + + def _merge_servers(self, list1, list2): + + for i in range(0, len(list2), 1): + try: + self.credentials.add_update_server(list1, list2[i]) + except KeyError: + continue + + return list1 + + def _find_servers(self, found_servers): + + servers = [] + + for found_server in found_servers: + + server = self._convert_endpoint_address_to_manual_address(found_server) + + info = { + 'Id': found_server['Id'], + 'LocalAddress': server or found_server['Address'], + 'Name': found_server['Name'] + } #TODO + info['LastConnectionMode'] = CONNECTION_MODE['Manual'] if info.get('ManualAddress') else CONNECTION_MODE['Local'] + + servers.append(info) + else: + return servers + + def _filter_servers(self, servers, connect_servers): + + filtered = list() + for server in servers: + if server.get('ExchangeToken') is None: + # It's not a connect server, so assume it's still valid + filtered.append(server) + continue + + for connect_server in connect_servers: + if server['Id'] == connect_server['Id']: + filtered.append(server) + break + + return filtered + + def _convert_endpoint_address_to_manual_address(self, info): + + if info.get('Address') and info.get('EndpointAddress'): + address = info['EndpointAddress'].split(':')[0] + + # Determine the port, if any + parts = info['Address'].split(':') + if len(parts) > 1: + port_string = parts[len(parts)-1] + + try: + address += ":%s" % int(port_string) + return self._normalize_address(address) + except ValueError: + pass + + return None + + def _normalize_address(self, address): + # Attempt to correct bad input + address = address.strip() + address = address.lower() + + if 'http' not in address: + address = "http://%s" % address + + return address + + def _get_connect_password_hash(self, password): + + password = self._clean_connect_password(password) + return hashlib.md5(password).hexdigest() + + def _clean_connect_password(self, password): + + password = password or "" + + password = password.replace("&", '&') + password = password.replace("/", '\') + password = password.replace("!", '!') + password = password.replace("$", '$') + password = password.replace("\"", '"') + password = password.replace("<", '<') + password = password.replace(">", '>') + password = password.replace("'", ''') + + return password + + def _ensure_connect_user(self, credentials): + + if self.user and self.user['Id'] == credentials['ConnectUserId']: + return + + elif credentials.get('ConnectUserId') and credentials.get('ConnectAccessToken'): + self.user = None + + try: + result = self._get_connect_user(credentials['ConnectUserId'], credentials['ConnectAccessToken']) + self._on_connect_user_signin(result) + except Exception: + return False + + def _on_connect_user_signin(self, user): + + self.user = user + LOG.info("connectusersignedin %s", user) + + def _save_user_info_into_credentials(self, server, user): + + info = { + 'Id': user['Id'], + 'IsSignedInOffline': True + } + self.credentials.add_update_user(server, info) + + def _add_authentication_info_from_connect(self, server, connection_mode, credentials, options={}): + + if not server.get('ExchangeToken'): + raise KeyError("server['ExchangeToken'] cannot be null") + + if not credentials.get('ConnectUserId'): + raise KeyError("credentials['ConnectUserId'] cannot be null") + + auth = "MediaBrowser " + auth += "Client='%s', " % self.config['app.name'] + auth += "Device='%s', " % self.config['app.device_name'] + auth += "DeviceId='%s', " % self.config['app.device_id'] + auth += "Version='%s' " % self.config['app.version'] + + try: + auth = self._request_url({ + 'url': self.get_emby_url(get_server_address(server, connection_mode), "Connect/Exchange"), + 'type': "GET", + 'dataType': "json", + 'verify': options.get('ssl'), + 'params': { + 'ConnectUserId': credentials['ConnectUserId'] + }, + 'headers': { + 'X-MediaBrowser-Token': server['ExchangeToken'], + 'X-Emby-Authorization': auth + } + }) + except Exception: + server['UserId'] = None + server['AccessToken'] = None + return False + else: + server['UserId'] = auth['LocalUserId'] + server['AccessToken'] = auth['AccessToken'] + return auth + + def _after_connect_validated(self, server, credentials, system_info, connection_mode, verify_authentication, options): + + if options.get('enableAutoLogin') == False: + + self.config['auth.user_id'] = server.pop('UserId', None) + self.config['auth.token'] = server.pop('AccessToken', None) + + elif verify_authentication and server.get('AccessToken'): + + if self._validate_authentication(server, connection_mode, options) is not False: + + self.config['auth.user_id'] = server['UserId'] + self.config['auth.token'] = server['AccessToken'] + return self._after_connect_validated(server, credentials, system_info, connection_mode, False, options) + + return self._resolve_failure() + + self._update_server_info(server, system_info) + self.server_version = system_info['Version'] + server['LastConnectionMode'] = connection_mode + + if options.get('updateDateLastAccessed') is not False: + server['DateLastAccessed'] = datetime.now().strftime('%Y-%m-%dT%H:%M:%SZ') + + self.credentials.add_update_server(credentials['Servers'], server) + self.credentials.get_credentials(credentials) + self.server_id = server['Id'] + + # Update configs + self.config['auth.server'] = get_server_address(server, connection_mode) + self.config['auth.server-name'] = server['Name'] + self.config['auth.server=id'] = server['Id'] + self.config['auth.ssl'] = options.get('ssl', self.config['auth.ssl']) + + result = { + 'Servers': [server], + 'ConnectUser': self.connect_user() + } + + result['State'] = CONNECTION_STATE['SignedIn'] if server.get('AccessToken') else CONNECTION_STATE['ServerSignIn'] + # Connected + return result + + def _validate_authentication(self, server, connection_mode, options={}): + + try: + system_info = self._request_url({ + 'type': "GET", + 'url': self.get_emby_url(get_server_address(server, connection_mode), "System/Info"), + 'verify': options.get('ssl'), + 'dataType': "json", + 'headers': { + 'X-MediaBrowser-Token': server['AccessToken'] + } + }) + self._update_server_info(server, system_info) + except Exception: + server['UserId'] = None + server['AccessToken'] = None + return False + + def _update_server_info(self, server, system_info): + + if server is None or system_info is None: + return + + server['Name'] = system_info['ServerName'] + server['Id'] = system_info['Id'] + + if system_info.get('LocalAddress'): + server['LocalAddress'] = system_info['LocalAddress'] + if system_info.get('WanAddress'): + server['RemoteAddress'] = system_info['WanAddress'] + if 'MacAddress' in system_info: + server['WakeOnLanInfos'] = [{'MacAddress': system_info['MacAddress']}] + + def _on_authenticated(self, result, options={}): + + credentials = self.credentials.get_credentials() + + self.config['auth.user_id'] = result['User']['Id'] + self.config['auth.token'] = result['AccessToken'] + + for server in credentials['Servers']: + if server['Id'] == result['ServerId']: + found_server = server + break + else: return # No server found + + if options.get('updateDateLastAccessed') is not False: + found_server['DateLastAccessed'] = datetime.now().strftime('%Y-%m-%dT%H:%M:%SZ') + + found_server['UserId'] = result['User']['Id'] + found_server['AccessToken'] = result['AccessToken'] + + self.credentials.add_update_server(credentials['Servers'], found_server) + self._save_user_info_into_credentials(found_server, result['User']) + self.credentials.get_credentials(credentials) diff --git a/resources/lib/connect/credentials.py b/resources/lib/emby/core/credentials.py similarity index 73% rename from resources/lib/connect/credentials.py rename to resources/lib/emby/core/credentials.py index 64f40995..6e47e342 100644 --- a/resources/lib/connect/credentials.py +++ b/resources/lib/emby/core/credentials.py @@ -10,73 +10,70 @@ from datetime import datetime ################################################################################################# -log = logging.getLogger("EMBY."+__name__.split('.')[-1]) +LOG = logging.getLogger('Emby.'+__name__) ################################################################################################# - class Credentials(object): - _shared_state = {} # Borg credentials = None - path = "" - def __init__(self): - self.__dict__ = self._shared_state + LOG.debug("Credentials initializing...") - def setPath(self, path): - # Path to save persistant data.txt - self.path = path + def set_credentials(self, credentials): + self.credentials = credentials - def _ensure(self): - - if self.credentials is None: - try: - with open(os.path.join(self.path, 'data.txt')) as infile: - self.credentials = json.load(infile) - - if not isinstance(self.credentials, dict): - raise ValueError("invalid credentials format") - - except Exception as e: # File is either empty or missing - log.warn(e) - self.credentials = {} - - log.debug("credentials initialized with: %s" % self.credentials) - self.credentials['Servers'] = self.credentials.setdefault('Servers', []) - - def _get(self): - - self._ensure() - return self.credentials - - def _set(self, data): - - if data: - self.credentials = data - # Set credentials to file - with open(os.path.join(self.path, 'data.txt'), 'w') as outfile: - json.dump(data, outfile, ensure_ascii=True) - else: - self._clear() - - log.info("credentialsupdated") - - def _clear(self): - - self.credentials = None - # Remove credentials from file - with open(os.path.join(self.path, 'data.txt'), 'w'): pass - - def getCredentials(self, data=None): + def get_credentials(self, data=None): if data is not None: self._set(data) return self._get() - def addOrUpdateServer(self, list_, server): + def _ensure(self): + + if not self.credentials: + try: + LOG.info(self.credentials) + if not isinstance(self.credentials, dict): + raise ValueError("invalid credentials format") + + except Exception as e: # File is either empty or missing + LOG.warn(e) + self.credentials = {} + + LOG.debug("credentials initialized with: %s", self.credentials) + self.credentials['Servers'] = self.credentials.setdefault('Servers', []) + + def _get(self): + self._ensure() + + return self.credentials + + def _set(self, data): + + if data: + self.credentials.update(data) + else: + self._clear() + + LOG.debug("credentialsupdated") + + def _clear(self): + self.credentials.clear() + + def add_update_user(self, server, user): + + for existing in server.setdefault('Users', []): + if existing['Id'] == user['Id']: + # Merge the data + existing['IsSignedInOffline'] = True + break + else: + server['Users'].append(user) + + def add_update_server(self, servers, server): if server.get('Id') is None: raise KeyError("Server['Id'] cannot be null or empty") @@ -84,12 +81,12 @@ class Credentials(object): # Add default DateLastAccessed if doesn't exist. server.setdefault('DateLastAccessed', "2001-01-01T00:00:00Z") - for existing in list_: + for existing in servers: if existing['Id'] == server['Id']: # Merge the data if server.get('DateLastAccessed'): - if self._dateObject(server['DateLastAccessed']) > self._dateObject(existing['DateLastAccessed']): + if self._date_object(server['DateLastAccessed']) > self._date_object(existing['DateLastAccessed']): existing['DateLastAccessed'] = server['DateLastAccessed'] if server.get('UserLinkType'): @@ -125,20 +122,10 @@ class Credentials(object): return existing else: - list_.append(server) + servers.append(server) return server - def addOrUpdateUser(self, server, user): - - for existing in server.setdefault('Users', []): - if existing['Id'] == user['Id']: - # Merge the data - existing['IsSignedInOffline'] = True - break - else: - server['Users'].append(user) - - def _dateObject(self, date): + def _date_object(self, date): # Convert string to date try: date_obj = time.strptime(date, "%Y-%m-%dT%H:%M:%SZ") @@ -147,4 +134,4 @@ class Credentials(object): # Known Kodi/python error date_obj = datetime(*(time.strptime(date, "%Y-%m-%dT%H:%M:%SZ")[0:6])) - return date_obj \ No newline at end of file + return date_obj diff --git a/resources/lib/emby/core/exceptions.py b/resources/lib/emby/core/exceptions.py new file mode 100644 index 00000000..2a00a336 --- /dev/null +++ b/resources/lib/emby/core/exceptions.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- + +################################################################################################# + +class HTTPException(Exception): + # Emby HTTP exception + def __init__(self, status, message): + self.status = status + self.message = message + + diff --git a/resources/lib/emby/core/http.py b/resources/lib/emby/core/http.py new file mode 100644 index 00000000..24a2c16e --- /dev/null +++ b/resources/lib/emby/core/http.py @@ -0,0 +1,233 @@ +# -*- coding: utf-8 -*- + +################################################################################################# + +import json +import logging +import time + +from libraries import requests +from exceptions import HTTPException + +################################################################################################# + +LOG = logging.getLogger('Emby.'+__name__) + +################################################################################################# + +class HTTP(object): + + session = None + keep_alive = False + + def __init__(self, client): + + self.client = client + self.config = client['config'] + + def __shortcuts__(self, key): + + if key == "request": + return self.request + + return + + def start_session(self): + + self.session = requests.Session() + + max_retries = self.config['http.max_retries'] + self.session.mount("http://", requests.adapters.HTTPAdapter(max_retries=max_retries)) + self.session.mount("https://", requests.adapters.HTTPAdapter(max_retries=max_retries)) + + def stop_session(self): + + if self.session is None: + return + + try: + self.session.close() + except Exception as error: + LOG.warn("The requests session could not be terminated: %s", error) + + def _replace_user_info(self, string): + + if self.config['auth.server']: + string = string.replace("{server}", self.config['auth.server']) + + if self.config['auth.user_id']: + string = string.replace("{UserId}", self.config['auth.user_id']) + + return string + + def request(self, data, session=None): + + ''' Give a chance to retry the connection. Emby sometimes can be slow to answer back + data dictionary can contain: + type: GET, POST, etc. + url: (optional) + handler: not considered when url is provided (optional) + params: request parameters (optional) + json: request body (optional) + headers: (optional), + verify: ssl certificate, True (verify using device built-in library) or False + ''' + if not data: + raise AttributeError("Request cannot be empty") + + data = self._request(data) + LOG.debug("--->[ http ] %s", json.dumps(data, indent=4)) + retry = data.pop('retry', 5) + + while True: + + try: + r = self._requests(session or self.session or requests, data.pop('type', "GET"), **data) + r.content # release the connection + + if not self.keep_alive and self.session is not None: + self.stop_session() + + r.raise_for_status() + + except requests.exceptions.ConnectionError as error: + if retry: + + retry -= 1 + time.sleep(1) + + continue + + LOG.error(error) + self.client['callback']("ServerUnreachable", {'ServerId': self.config['auth.server-id']}) + + raise HTTPException("ServerUnreachable", error) + + except requests.exceptions.ReadTimeout as error: + if retry: + + retry -= 1 + time.sleep(1) + + continue + + LOG.error(error) + + raise HTTPException("ReadTimeout", error) + + except requests.exceptions.HTTPError as error: + LOG.error(error) + + if r.status_code == 401: + + if 'X-Application-Error-Code' in r.headers: + self.client['callback']("AccessRestricted", {'ServerId': self.config['auth.server-id']}) + + raise HTTPException("AccessRestricted", error) + else: + self.client['callback']("Unauthorized", {'ServerId': self.config['auth.server-id']}) + self.client['auth/revoke-token'] + + raise HTTPException("Unauthorized", error) + + elif r.status_code == 500: # log and ignore. + LOG.error("--[ 500 response ] %s", error) + + return + + elif r.status_code == 502: + if retry: + + retry -= 1 + time.sleep(1) + + continue + + raise HTTPException(r.status_code, error) + + except requests.exceptions.MissingSchema as error: + raise HTTPException("MissingSchema", {'Id': self.config['auth.server']}) + + except Exception as error: + raise + + else: + try: + elapsed = int(r.elapsed.total_seconds() * 1000) + response = r.json() + LOG.debug("---<[ http ][%s ms]", elapsed) + LOG.debug(json.dumps(response, indent=4)) + + return response + except ValueError: + return + + def _request(self, data): + + if 'url' not in data: + data['url'] = "%s/emby/%s" % (self.config['auth.server'], data.pop('handler', "")) + + self._get_header(data) + data['timeout'] = data.get('timeout') or self.config['http.timeout'] + data['verify'] = data.get('verify') or self.config['auth.ssl'] or False + data['url'] = self._replace_user_info(data['url']) + self._process_params(data.get('params') or {}) + self._process_params(data.get('json') or {}) + + return data + + def _process_params(self, params): + + for key in params: + value = params[key] + + if isinstance(value, dict): + self._process_params(value) + + if isinstance(value, str): + params[key] = self._replace_user_info(value) + + def _get_header(self, data): + + data['headers'] = data.setdefault('headers', {}) + + if not data['headers']: + data['headers'].update({ + 'Content-type': "application/json", + 'Accept-Charset': "UTF-8,*", + 'Accept-encoding': "gzip", + 'User-Agent': self.config['http.user_agent'] or "%s/%s" % (self.config['app.name'], self.config['app.version']) + }) + + if 'Authorization' not in data['headers']: + self._authorization(data) + + return data + + def _authorization(self, data): + + auth = "MediaBrowser " + auth += "Client=%s, " % self.config['app.name'] + auth += "Device=%s, " % self.config['app.device_name'] + auth += "DeviceId=%s, " % self.config['app.device_id'] + auth += "Version=%s" % self.config['app.version'] + + data['headers'].update({'Authorization': auth}) + + if self.config['auth.token']: + + auth += ', UserId=%s' % self.config['auth.user_id'] + data['headers'].update({'Authorization': auth, 'X-MediaBrowser-Token': self.config['auth.token']}) + + return data + + def _requests(self, session, action, **kwargs): + + if action == "GET": + return session.get(**kwargs) + elif action == "POST": + return session.post(**kwargs) + elif action == "HEAD": + return session.head(**kwargs) + elif action == "DELETE": + return session.delete(**kwargs) diff --git a/resources/lib/emby/core/ws_client.py b/resources/lib/emby/core/ws_client.py new file mode 100644 index 00000000..ce48867e --- /dev/null +++ b/resources/lib/emby/core/ws_client.py @@ -0,0 +1,92 @@ +# -*- coding: utf-8 -*- + +################################################################################################# + +import json +import logging +import threading +import time + +from ..resources import websocket + +################################################################################################## + +LOG = logging.getLogger('Emby.'+__name__) + +################################################################################################## + + +class WSClient(threading.Thread): + + wsc = None + stop = False + + def __init__(self, client): + + LOG.debug("WSClient initializing...") + + self.client = client + threading.Thread.__init__(self) + + def __shortcuts__(self, key): + + if key == "send": + return self.send + elif key == "stop": + return self.stop_client() + + return + + def send(self, message, data=""): + + if self.wsc is None: + raise ValueError("The websocket client is not started.") + + self.wsc.send(json.dumps({'MessageType': message, "Data": data})) + + def run(self): + + token = self.client['config/auth.token'] + device_id = self.client['config/app.device_id'] + server = self.client['config/auth.server'] + server = server.replace('https', "wss") if server.startswith('https') else server.replace('http', "ws") + wsc_url = "%s/embywebsocket?api_key=%s&device_id=%s" % (server, token, device_id) + + LOG.info("Websocket url: %s", wsc_url) + + self.wsc = websocket.WebSocketApp(wsc_url, + on_message=self.on_message, + on_error=self.on_error) + self.wsc.on_open = self.on_open + + while not self.stop: + + self.wsc.run_forever(ping_interval=10) + + if not self.stop: + time.sleep(5) + + LOG.info("---<[ websocket ]") + + def on_error(self, ws, error): + LOG.error(error) + + def on_open(self, ws): + LOG.info("--->[ websocket ]") + + def on_message(self, ws, message): + + message = json.loads(message) + data = message.get('Data', {}) + + if not self.client['config/app.default']: + data['ServerId'] = self.client['auth/server-id'] + + self.client['callback_ws'](message['MessageType'], data) + + def stop_client(self): + + self.stop = True + + if self.wsc is not None: + self.wsc.close() diff --git a/resources/lib/emby/helpers/__init__.py b/resources/lib/emby/helpers/__init__.py new file mode 100644 index 00000000..453fa606 --- /dev/null +++ b/resources/lib/emby/helpers/__init__.py @@ -0,0 +1,7 @@ + +def has_attribute(obj, name): + try: + object.__getattribute__(obj, name) + return True + except AttributeError: + return False diff --git a/resources/lib/emby/helpers/utils.py b/resources/lib/emby/helpers/utils.py new file mode 100644 index 00000000..3faa66c4 --- /dev/null +++ b/resources/lib/emby/helpers/utils.py @@ -0,0 +1,15 @@ +# -*- coding: utf-8 -*- + +################################################################################################# + +import logging +from uuid import uuid4 + +################################################################################################# + +LOG = logging.getLogger('Emby.'+__name__) + +################################################################################################# + +def generate_client_id(): + return str("%012X" % uuid4()) diff --git a/resources/lib/emby/resources/__init__.py b/resources/lib/emby/resources/__init__.py new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/resources/lib/emby/resources/__init__.py @@ -0,0 +1 @@ + diff --git a/resources/lib/websocket.py b/resources/lib/emby/resources/websocket.py similarity index 96% rename from resources/lib/websocket.py rename to resources/lib/emby/resources/websocket.py index 3c171e55..00733296 100644 --- a/resources/lib/websocket.py +++ b/resources/lib/emby/resources/websocket.py @@ -1,930 +1,930 @@ -""" -websocket - WebSocket client library for Python - -Copyright (C) 2010 Hiroki Ohtani(liris) - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -""" - - -import socket - -try: - import ssl - from ssl import SSLError - HAVE_SSL = True -except ImportError: - # dummy class of SSLError for ssl none-support environment. - class SSLError(Exception): - pass - - HAVE_SSL = False - -from urlparse import urlparse -import os -import array -import struct -import uuid -import hashlib -import base64 -import threading -import time -import logging -import traceback -import sys - -""" -websocket python client. -========================= - -This version support only hybi-13. -Please see http://tools.ietf.org/html/rfc6455 for protocol. -""" - - -# websocket supported version. -VERSION = 13 - -# closing frame status codes. -STATUS_NORMAL = 1000 -STATUS_GOING_AWAY = 1001 -STATUS_PROTOCOL_ERROR = 1002 -STATUS_UNSUPPORTED_DATA_TYPE = 1003 -STATUS_STATUS_NOT_AVAILABLE = 1005 -STATUS_ABNORMAL_CLOSED = 1006 -STATUS_INVALID_PAYLOAD = 1007 -STATUS_POLICY_VIOLATION = 1008 -STATUS_MESSAGE_TOO_BIG = 1009 -STATUS_INVALID_EXTENSION = 1010 -STATUS_UNEXPECTED_CONDITION = 1011 -STATUS_TLS_HANDSHAKE_ERROR = 1015 - -logger = logging.getLogger() - - -class WebSocketException(Exception): - """ - websocket exeception class. - """ - pass - - -class WebSocketConnectionClosedException(WebSocketException): - """ - If remote host closed the connection or some network error happened, - this exception will be raised. - """ - pass - -class WebSocketTimeoutException(WebSocketException): - """ - WebSocketTimeoutException will be raised at socket timeout during read/write data. - """ - pass - -default_timeout = None -traceEnabled = False - - -def enableTrace(tracable): - """ - turn on/off the tracability. - - tracable: boolean value. if set True, tracability is enabled. - """ - global traceEnabled - traceEnabled = tracable - if tracable: - if not logger.handlers: - logger.addHandler(logging.StreamHandler()) - logger.setLevel(logging.DEBUG) - - -def setdefaulttimeout(timeout): - """ - Set the global timeout setting to connect. - - timeout: default socket timeout time. This value is second. - """ - global default_timeout - default_timeout = timeout - - -def getdefaulttimeout(): - """ - Return the global timeout setting(second) to connect. - """ - return default_timeout - - -def _wrap_sni_socket(sock, sslopt, hostname): - context = ssl.SSLContext(sslopt.get('ssl_version', ssl.PROTOCOL_SSLv23)) - - if sslopt.get('cert_reqs', ssl.CERT_NONE) != ssl.CERT_NONE: - capath = ssl.get_default_verify_paths().capath - context.load_verify_locations(cafile=sslopt.get('ca_certs', None), - capath=sslopt.get('ca_cert_path', capath)) - - return context.wrap_socket( - sock, - do_handshake_on_connect=sslopt.get('do_handshake_on_connect', True), - suppress_ragged_eofs=sslopt.get('suppress_ragged_eofs', True), - server_hostname=hostname, - ) - - -def _parse_url(url): - """ - parse url and the result is tuple of - (hostname, port, resource path and the flag of secure mode) - - url: url string. - """ - if ":" not in url: - raise ValueError("url is invalid") - - scheme, url = url.split(":", 1) - - parsed = urlparse(url, scheme="http") - if parsed.hostname: - hostname = parsed.hostname - else: - raise ValueError("hostname is invalid") - port = 0 - if parsed.port: - port = parsed.port - - is_secure = False - if scheme == "ws": - if not port: - port = 80 - elif scheme == "wss": - is_secure = True - if not port: - port = 443 - else: - raise ValueError("scheme %s is invalid" % scheme) - - if parsed.path: - resource = parsed.path - else: - resource = "/" - - if parsed.query: - resource += "?" + parsed.query - - return (hostname, port, resource, is_secure) - - -def create_connection(url, timeout=None, **options): - """ - connect to url and return websocket object. - - Connect to url and return the WebSocket object. - Passing optional timeout parameter will set the timeout on the socket. - If no timeout is supplied, the global default timeout setting returned by getdefauttimeout() is used. - You can customize using 'options'. - If you set "header" list object, you can set your own custom header. - - >>> conn = create_connection("ws://echo.websocket.org/", - ... header=["User-Agent: MyProgram", - ... "x-custom: header"]) - - - timeout: socket timeout time. This value is integer. - if you set None for this value, it means "use default_timeout value" - - options: current support option is only "header". - if you set header as dict value, the custom HTTP headers are added. - """ - sockopt = options.get("sockopt", []) - sslopt = options.get("sslopt", {}) - websock = WebSocket(sockopt=sockopt, sslopt=sslopt) - websock.settimeout(timeout if timeout is not None else default_timeout) - websock.connect(url, **options) - return websock - -_MAX_INTEGER = (1 << 32) -1 -_AVAILABLE_KEY_CHARS = range(0x21, 0x2f + 1) + range(0x3a, 0x7e + 1) -_MAX_CHAR_BYTE = (1<<8) -1 - -# ref. Websocket gets an update, and it breaks stuff. -# http://axod.blogspot.com/2010/06/websocket-gets-update-and-it-breaks.html - - -def _create_sec_websocket_key(): - uid = uuid.uuid4() - return base64.encodestring(uid.bytes).strip() - - -_HEADERS_TO_CHECK = { - "upgrade": "websocket", - "connection": "upgrade", - } - - -class ABNF(object): - """ - ABNF frame class. - see http://tools.ietf.org/html/rfc5234 - and http://tools.ietf.org/html/rfc6455#section-5.2 - """ - - # operation code values. - OPCODE_CONT = 0x0 - OPCODE_TEXT = 0x1 - OPCODE_BINARY = 0x2 - OPCODE_CLOSE = 0x8 - OPCODE_PING = 0x9 - OPCODE_PONG = 0xa - - # available operation code value tuple - OPCODES = (OPCODE_CONT, OPCODE_TEXT, OPCODE_BINARY, OPCODE_CLOSE, - OPCODE_PING, OPCODE_PONG) - - # opcode human readable string - OPCODE_MAP = { - OPCODE_CONT: "cont", - OPCODE_TEXT: "text", - OPCODE_BINARY: "binary", - OPCODE_CLOSE: "close", - OPCODE_PING: "ping", - OPCODE_PONG: "pong" - } - - # data length threashold. - LENGTH_7 = 0x7d - LENGTH_16 = 1 << 16 - LENGTH_63 = 1 << 63 - - def __init__(self, fin=0, rsv1=0, rsv2=0, rsv3=0, - opcode=OPCODE_TEXT, mask=1, data=""): - """ - Constructor for ABNF. - please check RFC for arguments. - """ - self.fin = fin - self.rsv1 = rsv1 - self.rsv2 = rsv2 - self.rsv3 = rsv3 - self.opcode = opcode - self.mask = mask - self.data = data - self.get_mask_key = os.urandom - - def __str__(self): - return "fin=" + str(self.fin) \ - + " opcode=" + str(self.opcode) \ - + " data=" + str(self.data) - - @staticmethod - def create_frame(data, opcode): - """ - create frame to send text, binary and other data. - - data: data to send. This is string value(byte array). - if opcode is OPCODE_TEXT and this value is uniocde, - data value is conveted into unicode string, automatically. - - opcode: operation code. please see OPCODE_XXX. - """ - if opcode == ABNF.OPCODE_TEXT and isinstance(data, unicode): - data = data.encode("utf-8") - # mask must be set if send data from client - return ABNF(1, 0, 0, 0, opcode, 1, data) - - def format(self): - """ - format this object to string(byte array) to send data to server. - """ - if any(x not in (0, 1) for x in [self.fin, self.rsv1, self.rsv2, self.rsv3]): - raise ValueError("not 0 or 1") - if self.opcode not in ABNF.OPCODES: - raise ValueError("Invalid OPCODE") - length = len(self.data) - if length >= ABNF.LENGTH_63: - raise ValueError("data is too long") - - frame_header = chr(self.fin << 7 - | self.rsv1 << 6 | self.rsv2 << 5 | self.rsv3 << 4 - | self.opcode) - if length < ABNF.LENGTH_7: - frame_header += chr(self.mask << 7 | length) - elif length < ABNF.LENGTH_16: - frame_header += chr(self.mask << 7 | 0x7e) - frame_header += struct.pack("!H", length) - else: - frame_header += chr(self.mask << 7 | 0x7f) - frame_header += struct.pack("!Q", length) - - if not self.mask: - return frame_header + self.data - else: - mask_key = self.get_mask_key(4) - return frame_header + self._get_masked(mask_key) - - def _get_masked(self, mask_key): - s = ABNF.mask(mask_key, self.data) - return mask_key + "".join(s) - - @staticmethod - def mask(mask_key, data): - """ - mask or unmask data. Just do xor for each byte - - mask_key: 4 byte string(byte). - - data: data to mask/unmask. - """ - _m = array.array("B", mask_key) - _d = array.array("B", data) - for i in xrange(len(_d)): - _d[i] ^= _m[i % 4] - return _d.tostring() - - -class WebSocket(object): - """ - Low level WebSocket interface. - This class is based on - The WebSocket protocol draft-hixie-thewebsocketprotocol-76 - http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76 - - We can connect to the websocket server and send/recieve data. - The following example is a echo client. - - >>> import websocket - >>> ws = websocket.WebSocket() - >>> ws.connect("ws://echo.websocket.org") - >>> ws.send("Hello, Server") - >>> ws.recv() - 'Hello, Server' - >>> ws.close() - - get_mask_key: a callable to produce new mask keys, see the set_mask_key - function's docstring for more details - sockopt: values for socket.setsockopt. - sockopt must be tuple and each element is argument of sock.setscokopt. - sslopt: dict object for ssl socket option. - """ - - def __init__(self, get_mask_key=None, sockopt=None, sslopt=None): - """ - Initalize WebSocket object. - """ - if sockopt is None: - sockopt = [] - if sslopt is None: - sslopt = {} - self.connected = False - self.sock = socket.socket() - for opts in sockopt: - self.sock.setsockopt(*opts) - self.sslopt = sslopt - self.get_mask_key = get_mask_key - # Buffers over the packets from the layer beneath until desired amount - # bytes of bytes are received. - self._recv_buffer = [] - # These buffer over the build-up of a single frame. - self._frame_header = None - self._frame_length = None - self._frame_mask = None - self._cont_data = None - - def fileno(self): - return self.sock.fileno() - - def set_mask_key(self, func): - """ - set function to create musk key. You can custumize mask key generator. - Mainly, this is for testing purpose. - - func: callable object. the fuct must 1 argument as integer. - The argument means length of mask key. - This func must be return string(byte array), - which length is argument specified. - """ - self.get_mask_key = func - - def gettimeout(self): - """ - Get the websocket timeout(second). - """ - return self.sock.gettimeout() - - def settimeout(self, timeout): - """ - Set the timeout to the websocket. - - timeout: timeout time(second). - """ - self.sock.settimeout(timeout) - - timeout = property(gettimeout, settimeout) - - def connect(self, url, **options): - """ - Connect to url. url is websocket url scheme. ie. ws://host:port/resource - You can customize using 'options'. - If you set "header" dict object, you can set your own custom header. - - >>> ws = WebSocket() - >>> ws.connect("ws://echo.websocket.org/", - ... header={"User-Agent: MyProgram", - ... "x-custom: header"}) - - timeout: socket timeout time. This value is integer. - if you set None for this value, - it means "use default_timeout value" - - options: current support option is only "header". - if you set header as dict value, - the custom HTTP headers are added. - - """ - hostname, port, resource, is_secure = _parse_url(url) - # TODO: we need to support proxy - self.sock.connect((hostname, port)) - if is_secure: - if HAVE_SSL: - if self.sslopt is None: - sslopt = {} - else: - sslopt = self.sslopt - if ssl.HAS_SNI: - self.sock = _wrap_sni_socket(self.sock, sslopt, hostname) - else: - self.sock = ssl.wrap_socket(self.sock, **sslopt) - else: - raise WebSocketException("SSL not available.") - - self._handshake(hostname, port, resource, **options) - - def _handshake(self, host, port, resource, **options): - headers = [] - headers.append("GET %s HTTP/1.1" % resource) - headers.append("Upgrade: websocket") - headers.append("Connection: Upgrade") - if port == 80: - hostport = host - else: - hostport = "%s:%d" % (host, port) - headers.append("Host: %s" % hostport) - - if "origin" in options: - headers.append("Origin: %s" % options["origin"]) - else: - headers.append("Origin: http://%s" % hostport) - - key = _create_sec_websocket_key() - headers.append("Sec-WebSocket-Key: %s" % key) - headers.append("Sec-WebSocket-Version: %s" % VERSION) - if "header" in options: - headers.extend(options["header"]) - - headers.append("") - headers.append("") - - header_str = "\r\n".join(headers) - self._send(header_str) - if traceEnabled: - logger.debug("--- request header ---") - logger.debug(header_str) - logger.debug("-----------------------") - - status, resp_headers = self._read_headers() - if status != 101: - self.close() - raise WebSocketException("Handshake Status %d" % status) - - success = self._validate_header(resp_headers, key) - if not success: - self.close() - raise WebSocketException("Invalid WebSocket Header") - - self.connected = True - - def _validate_header(self, headers, key): - for k, v in _HEADERS_TO_CHECK.iteritems(): - r = headers.get(k, None) - if not r: - return False - r = r.lower() - if v != r: - return False - - result = headers.get("sec-websocket-accept", None) - if not result: - return False - result = result.lower() - - value = key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" - hashed = base64.encodestring(hashlib.sha1(value).digest()).strip().lower() - return hashed == result - - def _read_headers(self): - status = None - headers = {} - if traceEnabled: - logger.debug("--- response header ---") - - while True: - line = self._recv_line() - if line == "\r\n": - break - line = line.strip() - if traceEnabled: - logger.debug(line) - if not status: - status_info = line.split(" ", 2) - status = int(status_info[1]) - else: - kv = line.split(":", 1) - if len(kv) == 2: - key, value = kv - headers[key.lower()] = value.strip().lower() - else: - raise WebSocketException("Invalid header") - - if traceEnabled: - logger.debug("-----------------------") - - return status, headers - - def send(self, payload, opcode=ABNF.OPCODE_TEXT): - """ - Send the data as string. - - payload: Payload must be utf-8 string or unicoce, - if the opcode is OPCODE_TEXT. - Otherwise, it must be string(byte array) - - opcode: operation code to send. Please see OPCODE_XXX. - """ - frame = ABNF.create_frame(payload, opcode) - if self.get_mask_key: - frame.get_mask_key = self.get_mask_key - data = frame.format() - length = len(data) - if traceEnabled: - logger.debug("send: " + repr(data)) - while data: - l = self._send(data) - data = data[l:] - return length - - def send_binary(self, payload): - return self.send(payload, ABNF.OPCODE_BINARY) - - def ping(self, payload=""): - """ - send ping data. - - payload: data payload to send server. - """ - self.send(payload, ABNF.OPCODE_PING) - - def pong(self, payload): - """ - send pong data. - - payload: data payload to send server. - """ - self.send(payload, ABNF.OPCODE_PONG) - - def recv(self): - """ - Receive string data(byte array) from the server. - - return value: string(byte array) value. - """ - opcode, data = self.recv_data() - return data - - def recv_data(self): - """ - Recieve data with operation code. - - return value: tuple of operation code and string(byte array) value. - """ - while True: - frame = self.recv_frame() - if not frame: - # handle error: - # 'NoneType' object has no attribute 'opcode' - raise WebSocketException("Not a valid frame %s" % frame) - elif frame.opcode in (ABNF.OPCODE_TEXT, ABNF.OPCODE_BINARY, ABNF.OPCODE_CONT): - if frame.opcode == ABNF.OPCODE_CONT and not self._cont_data: - raise WebSocketException("Illegal frame") - if self._cont_data: - self._cont_data[1] += frame.data - else: - self._cont_data = [frame.opcode, frame.data] - - if frame.fin: - data = self._cont_data - self._cont_data = None - return data - elif frame.opcode == ABNF.OPCODE_CLOSE: - self.send_close() - return (frame.opcode, None) - elif frame.opcode == ABNF.OPCODE_PING: - self.pong(frame.data) - - def recv_frame(self): - """ - recieve data as frame from server. - - return value: ABNF frame object. - """ - # Header - if self._frame_header is None: - self._frame_header = self._recv_strict(2) - b1 = ord(self._frame_header[0]) - fin = b1 >> 7 & 1 - rsv1 = b1 >> 6 & 1 - rsv2 = b1 >> 5 & 1 - rsv3 = b1 >> 4 & 1 - opcode = b1 & 0xf - b2 = ord(self._frame_header[1]) - has_mask = b2 >> 7 & 1 - # Frame length - if self._frame_length is None: - length_bits = b2 & 0x7f - if length_bits == 0x7e: - length_data = self._recv_strict(2) - self._frame_length = struct.unpack("!H", length_data)[0] - elif length_bits == 0x7f: - length_data = self._recv_strict(8) - self._frame_length = struct.unpack("!Q", length_data)[0] - else: - self._frame_length = length_bits - # Mask - if self._frame_mask is None: - self._frame_mask = self._recv_strict(4) if has_mask else "" - # Payload - payload = self._recv_strict(self._frame_length) - if has_mask: - payload = ABNF.mask(self._frame_mask, payload) - # Reset for next frame - self._frame_header = None - self._frame_length = None - self._frame_mask = None - return ABNF(fin, rsv1, rsv2, rsv3, opcode, has_mask, payload) - - - def send_close(self, status=STATUS_NORMAL, reason=""): - """ - send close data to the server. - - status: status code to send. see STATUS_XXX. - - reason: the reason to close. This must be string. - """ - if status < 0 or status >= ABNF.LENGTH_16: - raise ValueError("code is invalid range") - self.send(struct.pack('!H', status) + reason, ABNF.OPCODE_CLOSE) - - def close(self, status=STATUS_NORMAL, reason=""): - """ - Close Websocket object - - status: status code to send. see STATUS_XXX. - - reason: the reason to close. This must be string. - """ - - try: - self.sock.shutdown(socket.SHUT_RDWR) - except: - pass - - ''' - if self.connected: - if status < 0 or status >= ABNF.LENGTH_16: - raise ValueError("code is invalid range") - - try: - self.send(struct.pack('!H', status) + reason, ABNF.OPCODE_CLOSE) - timeout = self.sock.gettimeout() - self.sock.settimeout(3) - try: - frame = self.recv_frame() - if logger.isEnabledFor(logging.ERROR): - recv_status = struct.unpack("!H", frame.data)[0] - if recv_status != STATUS_NORMAL: - logger.error("close status: " + repr(recv_status)) - except: - pass - self.sock.settimeout(timeout) - self.sock.shutdown(socket.SHUT_RDWR) - except: - pass - ''' - self._closeInternal() - - def _closeInternal(self): - self.connected = False - self.sock.close() - - def _send(self, data): - try: - return self.sock.send(data) - except socket.timeout as e: - raise WebSocketTimeoutException(e.args[0]) - except Exception as e: - if "timed out" in e.args[0]: - raise WebSocketTimeoutException(e.args[0]) - else: - raise e - - def _recv(self, bufsize): - try: - bytes = self.sock.recv(bufsize) - except socket.timeout as e: - raise WebSocketTimeoutException(e.args[0]) - except SSLError as e: - if e.args[0] == "The read operation timed out": - raise WebSocketTimeoutException(e.args[0]) - else: - raise - if not bytes: - raise WebSocketConnectionClosedException() - return bytes - - - def _recv_strict(self, bufsize): - shortage = bufsize - sum(len(x) for x in self._recv_buffer) - while shortage > 0: - bytes = self._recv(shortage) - self._recv_buffer.append(bytes) - shortage -= len(bytes) - unified = "".join(self._recv_buffer) - if shortage == 0: - self._recv_buffer = [] - return unified - else: - self._recv_buffer = [unified[bufsize:]] - return unified[:bufsize] - - - def _recv_line(self): - line = [] - while True: - c = self._recv(1) - line.append(c) - if c == "\n": - break - return "".join(line) - - -class WebSocketApp(object): - """ - Higher level of APIs are provided. - The interface is like JavaScript WebSocket object. - """ - def __init__(self, url, header=[], - on_open=None, on_message=None, on_error=None, - on_close=None, keep_running=True, get_mask_key=None): - """ - url: websocket url. - header: custom header for websocket handshake. - on_open: callable object which is called at opening websocket. - this function has one argument. The arugment is this class object. - on_message: callbale object which is called when recieved data. - on_message has 2 arguments. - The 1st arugment is this class object. - The passing 2nd arugment is utf-8 string which we get from the server. - on_error: callable object which is called when we get error. - on_error has 2 arguments. - The 1st arugment is this class object. - The passing 2nd arugment is exception object. - on_close: callable object which is called when closed the connection. - this function has one argument. The arugment is this class object. - keep_running: a boolean flag indicating whether the app's main loop should - keep running, defaults to True - get_mask_key: a callable to produce new mask keys, see the WebSocket.set_mask_key's - docstring for more information - """ - self.url = url - self.header = header - self.on_open = on_open - self.on_message = on_message - self.on_error = on_error - self.on_close = on_close - self.keep_running = keep_running - self.get_mask_key = get_mask_key - self.sock = None - - def send(self, data, opcode=ABNF.OPCODE_TEXT): - """ - send message. - data: message to send. If you set opcode to OPCODE_TEXT, data must be utf-8 string or unicode. - opcode: operation code of data. default is OPCODE_TEXT. - """ - if self.sock.send(data, opcode) == 0: - raise WebSocketConnectionClosedException() - - def close(self): - """ - close websocket connection. - """ - self.keep_running = False - if(self.sock != None): - self.sock.close() - - def _send_ping(self, interval): - while True: - for i in range(interval): - time.sleep(1) - if not self.keep_running: - return - self.sock.ping() - - def run_forever(self, sockopt=None, sslopt=None, ping_interval=0): - """ - run event loop for WebSocket framework. - This loop is infinite loop and is alive during websocket is available. - sockopt: values for socket.setsockopt. - sockopt must be tuple and each element is argument of sock.setscokopt. - sslopt: ssl socket optional dict. - ping_interval: automatically send "ping" command every specified period(second) - if set to 0, not send automatically. - """ - if sockopt is None: - sockopt = [] - if sslopt is None: - sslopt = {} - if self.sock: - raise WebSocketException("socket is already opened") - thread = None - self.keep_running = True - - try: - self.sock = WebSocket(self.get_mask_key, sockopt=sockopt, sslopt=sslopt) - self.sock.settimeout(default_timeout) - self.sock.connect(self.url, header=self.header) - self._callback(self.on_open) - - if ping_interval: - thread = threading.Thread(target=self._send_ping, args=(ping_interval,)) - thread.setDaemon(True) - thread.start() - - while self.keep_running: - - try: - data = self.sock.recv() - - if data is None or self.keep_running == False: - break - self._callback(self.on_message, data) - - except Exception, e: - #print str(e.args[0]) - if "timed out" not in e.args[0]: - raise e - - except Exception, e: - self._callback(self.on_error, e) - finally: - if thread: - self.keep_running = False - self.sock.close() - self._callback(self.on_close) - self.sock = None - - def _callback(self, callback, *args): - if callback: - try: - callback(self, *args) - except Exception, e: - logger.error(e) - if True:#logger.isEnabledFor(logging.DEBUG): - _, _, tb = sys.exc_info() - traceback.print_tb(tb) - - -if __name__ == "__main__": - enableTrace(True) - ws = create_connection("ws://echo.websocket.org/") - print("Sending 'Hello, World'...") - ws.send("Hello, World") - print("Sent") - print("Receiving...") - result = ws.recv() - print("Received '%s'" % result) - ws.close() +""" +websocket - WebSocket client library for Python + +Copyright (C) 2010 Hiroki Ohtani(liris) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +""" + + +import socket + +try: + import ssl + from ssl import SSLError + HAVE_SSL = True +except ImportError: + # dummy class of SSLError for ssl none-support environment. + class SSLError(Exception): + pass + + HAVE_SSL = False + +from urlparse import urlparse +import os +import array +import struct +import uuid +import hashlib +import base64 +import threading +import time +import logging +import traceback +import sys + +""" +websocket python client. +========================= + +This version support only hybi-13. +Please see http://tools.ietf.org/html/rfc6455 for protocol. +""" + + +# websocket supported version. +VERSION = 13 + +# closing frame status codes. +STATUS_NORMAL = 1000 +STATUS_GOING_AWAY = 1001 +STATUS_PROTOCOL_ERROR = 1002 +STATUS_UNSUPPORTED_DATA_TYPE = 1003 +STATUS_STATUS_NOT_AVAILABLE = 1005 +STATUS_ABNORMAL_CLOSED = 1006 +STATUS_INVALID_PAYLOAD = 1007 +STATUS_POLICY_VIOLATION = 1008 +STATUS_MESSAGE_TOO_BIG = 1009 +STATUS_INVALID_EXTENSION = 1010 +STATUS_UNEXPECTED_CONDITION = 1011 +STATUS_TLS_HANDSHAKE_ERROR = 1015 + +logger = logging.getLogger() + + +class WebSocketException(Exception): + """ + websocket exeception class. + """ + pass + + +class WebSocketConnectionClosedException(WebSocketException): + """ + If remote host closed the connection or some network error happened, + this exception will be raised. + """ + pass + +class WebSocketTimeoutException(WebSocketException): + """ + WebSocketTimeoutException will be raised at socket timeout during read/write data. + """ + pass + +default_timeout = None +traceEnabled = False + + +def enableTrace(tracable): + """ + turn on/off the tracability. + + tracable: boolean value. if set True, tracability is enabled. + """ + global traceEnabled + traceEnabled = tracable + if tracable: + if not logger.handlers: + logger.addHandler(logging.StreamHandler()) + logger.setLevel(logging.DEBUG) + + +def setdefaulttimeout(timeout): + """ + Set the global timeout setting to connect. + + timeout: default socket timeout time. This value is second. + """ + global default_timeout + default_timeout = timeout + + +def getdefaulttimeout(): + """ + Return the global timeout setting(second) to connect. + """ + return default_timeout + + +def _wrap_sni_socket(sock, sslopt, hostname): + context = ssl.SSLContext(sslopt.get('ssl_version', ssl.PROTOCOL_SSLv23)) + + if sslopt.get('cert_reqs', ssl.CERT_NONE) != ssl.CERT_NONE: + capath = ssl.get_default_verify_paths().capath + context.load_verify_locations(cafile=sslopt.get('ca_certs', None), + capath=sslopt.get('ca_cert_path', capath)) + + return context.wrap_socket( + sock, + do_handshake_on_connect=sslopt.get('do_handshake_on_connect', True), + suppress_ragged_eofs=sslopt.get('suppress_ragged_eofs', True), + server_hostname=hostname, + ) + + +def _parse_url(url): + """ + parse url and the result is tuple of + (hostname, port, resource path and the flag of secure mode) + + url: url string. + """ + if ":" not in url: + raise ValueError("url is invalid") + + scheme, url = url.split(":", 1) + + parsed = urlparse(url, scheme="http") + if parsed.hostname: + hostname = parsed.hostname + else: + raise ValueError("hostname is invalid") + port = 0 + if parsed.port: + port = parsed.port + + is_secure = False + if scheme == "ws": + if not port: + port = 80 + elif scheme == "wss": + is_secure = True + if not port: + port = 443 + else: + raise ValueError("scheme %s is invalid" % scheme) + + if parsed.path: + resource = parsed.path + else: + resource = "/" + + if parsed.query: + resource += "?" + parsed.query + + return (hostname, port, resource, is_secure) + + +def create_connection(url, timeout=None, **options): + """ + connect to url and return websocket object. + + Connect to url and return the WebSocket object. + Passing optional timeout parameter will set the timeout on the socket. + If no timeout is supplied, the global default timeout setting returned by getdefauttimeout() is used. + You can customize using 'options'. + If you set "header" list object, you can set your own custom header. + + >>> conn = create_connection("ws://echo.websocket.org/", + ... header=["User-Agent: MyProgram", + ... "x-custom: header"]) + + + timeout: socket timeout time. This value is integer. + if you set None for this value, it means "use default_timeout value" + + options: current support option is only "header". + if you set header as dict value, the custom HTTP headers are added. + """ + sockopt = options.get("sockopt", []) + sslopt = options.get("sslopt", {}) + websock = WebSocket(sockopt=sockopt, sslopt=sslopt) + websock.settimeout(timeout if timeout is not None else default_timeout) + websock.connect(url, **options) + return websock + +_MAX_INTEGER = (1 << 32) -1 +_AVAILABLE_KEY_CHARS = range(0x21, 0x2f + 1) + range(0x3a, 0x7e + 1) +_MAX_CHAR_BYTE = (1<<8) -1 + +# ref. Websocket gets an update, and it breaks stuff. +# http://axod.blogspot.com/2010/06/websocket-gets-update-and-it-breaks.html + + +def _create_sec_websocket_key(): + uid = uuid.uuid4() + return base64.encodestring(uid.bytes).strip() + + +_HEADERS_TO_CHECK = { + "upgrade": "websocket", + "connection": "upgrade", + } + + +class ABNF(object): + """ + ABNF frame class. + see http://tools.ietf.org/html/rfc5234 + and http://tools.ietf.org/html/rfc6455#section-5.2 + """ + + # operation code values. + OPCODE_CONT = 0x0 + OPCODE_TEXT = 0x1 + OPCODE_BINARY = 0x2 + OPCODE_CLOSE = 0x8 + OPCODE_PING = 0x9 + OPCODE_PONG = 0xa + + # available operation code value tuple + OPCODES = (OPCODE_CONT, OPCODE_TEXT, OPCODE_BINARY, OPCODE_CLOSE, + OPCODE_PING, OPCODE_PONG) + + # opcode human readable string + OPCODE_MAP = { + OPCODE_CONT: "cont", + OPCODE_TEXT: "text", + OPCODE_BINARY: "binary", + OPCODE_CLOSE: "close", + OPCODE_PING: "ping", + OPCODE_PONG: "pong" + } + + # data length threashold. + LENGTH_7 = 0x7d + LENGTH_16 = 1 << 16 + LENGTH_63 = 1 << 63 + + def __init__(self, fin=0, rsv1=0, rsv2=0, rsv3=0, + opcode=OPCODE_TEXT, mask=1, data=""): + """ + Constructor for ABNF. + please check RFC for arguments. + """ + self.fin = fin + self.rsv1 = rsv1 + self.rsv2 = rsv2 + self.rsv3 = rsv3 + self.opcode = opcode + self.mask = mask + self.data = data + self.get_mask_key = os.urandom + + def __str__(self): + return "fin=" + str(self.fin) \ + + " opcode=" + str(self.opcode) \ + + " data=" + str(self.data) + + @staticmethod + def create_frame(data, opcode): + """ + create frame to send text, binary and other data. + + data: data to send. This is string value(byte array). + if opcode is OPCODE_TEXT and this value is uniocde, + data value is conveted into unicode string, automatically. + + opcode: operation code. please see OPCODE_XXX. + """ + if opcode == ABNF.OPCODE_TEXT and isinstance(data, unicode): + data = data.encode("utf-8") + # mask must be set if send data from client + return ABNF(1, 0, 0, 0, opcode, 1, data) + + def format(self): + """ + format this object to string(byte array) to send data to server. + """ + if any(x not in (0, 1) for x in [self.fin, self.rsv1, self.rsv2, self.rsv3]): + raise ValueError("not 0 or 1") + if self.opcode not in ABNF.OPCODES: + raise ValueError("Invalid OPCODE") + length = len(self.data) + if length >= ABNF.LENGTH_63: + raise ValueError("data is too long") + + frame_header = chr(self.fin << 7 + | self.rsv1 << 6 | self.rsv2 << 5 | self.rsv3 << 4 + | self.opcode) + if length < ABNF.LENGTH_7: + frame_header += chr(self.mask << 7 | length) + elif length < ABNF.LENGTH_16: + frame_header += chr(self.mask << 7 | 0x7e) + frame_header += struct.pack("!H", length) + else: + frame_header += chr(self.mask << 7 | 0x7f) + frame_header += struct.pack("!Q", length) + + if not self.mask: + return frame_header + self.data + else: + mask_key = self.get_mask_key(4) + return frame_header + self._get_masked(mask_key) + + def _get_masked(self, mask_key): + s = ABNF.mask(mask_key, self.data) + return mask_key + "".join(s) + + @staticmethod + def mask(mask_key, data): + """ + mask or unmask data. Just do xor for each byte + + mask_key: 4 byte string(byte). + + data: data to mask/unmask. + """ + _m = array.array("B", mask_key) + _d = array.array("B", data) + for i in xrange(len(_d)): + _d[i] ^= _m[i % 4] + return _d.tostring() + + +class WebSocket(object): + """ + Low level WebSocket interface. + This class is based on + The WebSocket protocol draft-hixie-thewebsocketprotocol-76 + http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76 + + We can connect to the websocket server and send/recieve data. + The following example is a echo client. + + >>> import websocket + >>> ws = websocket.WebSocket() + >>> ws.connect("ws://echo.websocket.org") + >>> ws.send("Hello, Server") + >>> ws.recv() + 'Hello, Server' + >>> ws.close() + + get_mask_key: a callable to produce new mask keys, see the set_mask_key + function's docstring for more details + sockopt: values for socket.setsockopt. + sockopt must be tuple and each element is argument of sock.setscokopt. + sslopt: dict object for ssl socket option. + """ + + def __init__(self, get_mask_key=None, sockopt=None, sslopt=None): + """ + Initalize WebSocket object. + """ + if sockopt is None: + sockopt = [] + if sslopt is None: + sslopt = {} + self.connected = False + self.sock = socket.socket() + for opts in sockopt: + self.sock.setsockopt(*opts) + self.sslopt = sslopt + self.get_mask_key = get_mask_key + # Buffers over the packets from the layer beneath until desired amount + # bytes of bytes are received. + self._recv_buffer = [] + # These buffer over the build-up of a single frame. + self._frame_header = None + self._frame_length = None + self._frame_mask = None + self._cont_data = None + + def fileno(self): + return self.sock.fileno() + + def set_mask_key(self, func): + """ + set function to create musk key. You can custumize mask key generator. + Mainly, this is for testing purpose. + + func: callable object. the fuct must 1 argument as integer. + The argument means length of mask key. + This func must be return string(byte array), + which length is argument specified. + """ + self.get_mask_key = func + + def gettimeout(self): + """ + Get the websocket timeout(second). + """ + return self.sock.gettimeout() + + def settimeout(self, timeout): + """ + Set the timeout to the websocket. + + timeout: timeout time(second). + """ + self.sock.settimeout(timeout) + + timeout = property(gettimeout, settimeout) + + def connect(self, url, **options): + """ + Connect to url. url is websocket url scheme. ie. ws://host:port/resource + You can customize using 'options'. + If you set "header" dict object, you can set your own custom header. + + >>> ws = WebSocket() + >>> ws.connect("ws://echo.websocket.org/", + ... header={"User-Agent: MyProgram", + ... "x-custom: header"}) + + timeout: socket timeout time. This value is integer. + if you set None for this value, + it means "use default_timeout value" + + options: current support option is only "header". + if you set header as dict value, + the custom HTTP headers are added. + + """ + hostname, port, resource, is_secure = _parse_url(url) + # TODO: we need to support proxy + self.sock.connect((hostname, port)) + if is_secure: + if HAVE_SSL: + if self.sslopt is None: + sslopt = {} + else: + sslopt = self.sslopt + if ssl.HAS_SNI: + self.sock = _wrap_sni_socket(self.sock, sslopt, hostname) + else: + self.sock = ssl.wrap_socket(self.sock, **sslopt) + else: + raise WebSocketException("SSL not available.") + + self._handshake(hostname, port, resource, **options) + + def _handshake(self, host, port, resource, **options): + headers = [] + headers.append("GET %s HTTP/1.1" % resource) + headers.append("Upgrade: websocket") + headers.append("Connection: Upgrade") + if port == 80: + hostport = host + else: + hostport = "%s:%d" % (host, port) + headers.append("Host: %s" % hostport) + + if "origin" in options: + headers.append("Origin: %s" % options["origin"]) + else: + headers.append("Origin: http://%s" % hostport) + + key = _create_sec_websocket_key() + headers.append("Sec-WebSocket-Key: %s" % key) + headers.append("Sec-WebSocket-Version: %s" % VERSION) + if "header" in options: + headers.extend(options["header"]) + + headers.append("") + headers.append("") + + header_str = "\r\n".join(headers) + self._send(header_str) + if traceEnabled: + logger.debug("--- request header ---") + logger.debug(header_str) + logger.debug("-----------------------") + + status, resp_headers = self._read_headers() + if status != 101: + self.close() + raise WebSocketException("Handshake Status %d" % status) + + success = self._validate_header(resp_headers, key) + if not success: + self.close() + raise WebSocketException("Invalid WebSocket Header") + + self.connected = True + + def _validate_header(self, headers, key): + for k, v in _HEADERS_TO_CHECK.iteritems(): + r = headers.get(k, None) + if not r: + return False + r = r.lower() + if v != r: + return False + + result = headers.get("sec-websocket-accept", None) + if not result: + return False + result = result.lower() + + value = key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" + hashed = base64.encodestring(hashlib.sha1(value).digest()).strip().lower() + return hashed == result + + def _read_headers(self): + status = None + headers = {} + if traceEnabled: + logger.debug("--- response header ---") + + while True: + line = self._recv_line() + if line == "\r\n": + break + line = line.strip() + if traceEnabled: + logger.debug(line) + if not status: + status_info = line.split(" ", 2) + status = int(status_info[1]) + else: + kv = line.split(":", 1) + if len(kv) == 2: + key, value = kv + headers[key.lower()] = value.strip().lower() + else: + raise WebSocketException("Invalid header") + + if traceEnabled: + logger.debug("-----------------------") + + return status, headers + + def send(self, payload, opcode=ABNF.OPCODE_TEXT): + """ + Send the data as string. + + payload: Payload must be utf-8 string or unicoce, + if the opcode is OPCODE_TEXT. + Otherwise, it must be string(byte array) + + opcode: operation code to send. Please see OPCODE_XXX. + """ + frame = ABNF.create_frame(payload, opcode) + if self.get_mask_key: + frame.get_mask_key = self.get_mask_key + data = frame.format() + length = len(data) + if traceEnabled: + logger.debug("send: " + repr(data)) + while data: + l = self._send(data) + data = data[l:] + return length + + def send_binary(self, payload): + return self.send(payload, ABNF.OPCODE_BINARY) + + def ping(self, payload=""): + """ + send ping data. + + payload: data payload to send server. + """ + self.send(payload, ABNF.OPCODE_PING) + + def pong(self, payload): + """ + send pong data. + + payload: data payload to send server. + """ + self.send(payload, ABNF.OPCODE_PONG) + + def recv(self): + """ + Receive string data(byte array) from the server. + + return value: string(byte array) value. + """ + opcode, data = self.recv_data() + return data + + def recv_data(self): + """ + Recieve data with operation code. + + return value: tuple of operation code and string(byte array) value. + """ + while True: + frame = self.recv_frame() + if not frame: + # handle error: + # 'NoneType' object has no attribute 'opcode' + raise WebSocketException("Not a valid frame %s" % frame) + elif frame.opcode in (ABNF.OPCODE_TEXT, ABNF.OPCODE_BINARY, ABNF.OPCODE_CONT): + if frame.opcode == ABNF.OPCODE_CONT and not self._cont_data: + raise WebSocketException("Illegal frame") + if self._cont_data: + self._cont_data[1] += frame.data + else: + self._cont_data = [frame.opcode, frame.data] + + if frame.fin: + data = self._cont_data + self._cont_data = None + return data + elif frame.opcode == ABNF.OPCODE_CLOSE: + self.send_close() + return (frame.opcode, None) + elif frame.opcode == ABNF.OPCODE_PING: + self.pong(frame.data) + + def recv_frame(self): + """ + recieve data as frame from server. + + return value: ABNF frame object. + """ + # Header + if self._frame_header is None: + self._frame_header = self._recv_strict(2) + b1 = ord(self._frame_header[0]) + fin = b1 >> 7 & 1 + rsv1 = b1 >> 6 & 1 + rsv2 = b1 >> 5 & 1 + rsv3 = b1 >> 4 & 1 + opcode = b1 & 0xf + b2 = ord(self._frame_header[1]) + has_mask = b2 >> 7 & 1 + # Frame length + if self._frame_length is None: + length_bits = b2 & 0x7f + if length_bits == 0x7e: + length_data = self._recv_strict(2) + self._frame_length = struct.unpack("!H", length_data)[0] + elif length_bits == 0x7f: + length_data = self._recv_strict(8) + self._frame_length = struct.unpack("!Q", length_data)[0] + else: + self._frame_length = length_bits + # Mask + if self._frame_mask is None: + self._frame_mask = self._recv_strict(4) if has_mask else "" + # Payload + payload = self._recv_strict(self._frame_length) + if has_mask: + payload = ABNF.mask(self._frame_mask, payload) + # Reset for next frame + self._frame_header = None + self._frame_length = None + self._frame_mask = None + return ABNF(fin, rsv1, rsv2, rsv3, opcode, has_mask, payload) + + + def send_close(self, status=STATUS_NORMAL, reason=""): + """ + send close data to the server. + + status: status code to send. see STATUS_XXX. + + reason: the reason to close. This must be string. + """ + if status < 0 or status >= ABNF.LENGTH_16: + raise ValueError("code is invalid range") + self.send(struct.pack('!H', status) + reason, ABNF.OPCODE_CLOSE) + + def close(self, status=STATUS_NORMAL, reason=""): + """ + Close Websocket object + + status: status code to send. see STATUS_XXX. + + reason: the reason to close. This must be string. + """ + + try: + self.sock.shutdown(socket.SHUT_RDWR) + except: + pass + + ''' + if self.connected: + if status < 0 or status >= ABNF.LENGTH_16: + raise ValueError("code is invalid range") + + try: + self.send(struct.pack('!H', status) + reason, ABNF.OPCODE_CLOSE) + timeout = self.sock.gettimeout() + self.sock.settimeout(3) + try: + frame = self.recv_frame() + if logger.isEnabledFor(logging.ERROR): + recv_status = struct.unpack("!H", frame.data)[0] + if recv_status != STATUS_NORMAL: + logger.error("close status: " + repr(recv_status)) + except: + pass + self.sock.settimeout(timeout) + self.sock.shutdown(socket.SHUT_RDWR) + except: + pass + ''' + self._closeInternal() + + def _closeInternal(self): + self.connected = False + self.sock.close() + + def _send(self, data): + try: + return self.sock.send(data) + except socket.timeout as e: + raise WebSocketTimeoutException(e.args[0]) + except Exception as e: + if "timed out" in e.args[0]: + raise WebSocketTimeoutException(e.args[0]) + else: + raise e + + def _recv(self, bufsize): + try: + bytes = self.sock.recv(bufsize) + except socket.timeout as e: + raise WebSocketTimeoutException(e.args[0]) + except SSLError as e: + if e.args[0] == "The read operation timed out": + raise WebSocketTimeoutException(e.args[0]) + else: + raise + if not bytes: + raise WebSocketConnectionClosedException() + return bytes + + + def _recv_strict(self, bufsize): + shortage = bufsize - sum(len(x) for x in self._recv_buffer) + while shortage > 0: + bytes = self._recv(shortage) + self._recv_buffer.append(bytes) + shortage -= len(bytes) + unified = "".join(self._recv_buffer) + if shortage == 0: + self._recv_buffer = [] + return unified + else: + self._recv_buffer = [unified[bufsize:]] + return unified[:bufsize] + + + def _recv_line(self): + line = [] + while True: + c = self._recv(1) + line.append(c) + if c == "\n": + break + return "".join(line) + + +class WebSocketApp(object): + """ + Higher level of APIs are provided. + The interface is like JavaScript WebSocket object. + """ + def __init__(self, url, header=[], + on_open=None, on_message=None, on_error=None, + on_close=None, keep_running=True, get_mask_key=None): + """ + url: websocket url. + header: custom header for websocket handshake. + on_open: callable object which is called at opening websocket. + this function has one argument. The arugment is this class object. + on_message: callbale object which is called when recieved data. + on_message has 2 arguments. + The 1st arugment is this class object. + The passing 2nd arugment is utf-8 string which we get from the server. + on_error: callable object which is called when we get error. + on_error has 2 arguments. + The 1st arugment is this class object. + The passing 2nd arugment is exception object. + on_close: callable object which is called when closed the connection. + this function has one argument. The arugment is this class object. + keep_running: a boolean flag indicating whether the app's main loop should + keep running, defaults to True + get_mask_key: a callable to produce new mask keys, see the WebSocket.set_mask_key's + docstring for more information + """ + self.url = url + self.header = header + self.on_open = on_open + self.on_message = on_message + self.on_error = on_error + self.on_close = on_close + self.keep_running = keep_running + self.get_mask_key = get_mask_key + self.sock = None + + def send(self, data, opcode=ABNF.OPCODE_TEXT): + """ + send message. + data: message to send. If you set opcode to OPCODE_TEXT, data must be utf-8 string or unicode. + opcode: operation code of data. default is OPCODE_TEXT. + """ + if self.sock.send(data, opcode) == 0: + raise WebSocketConnectionClosedException() + + def close(self): + """ + close websocket connection. + """ + self.keep_running = False + if(self.sock != None): + self.sock.close() + + def _send_ping(self, interval): + while True: + for i in range(interval): + time.sleep(1) + if not self.keep_running: + return + self.sock.ping() + + def run_forever(self, sockopt=None, sslopt=None, ping_interval=0): + """ + run event loop for WebSocket framework. + This loop is infinite loop and is alive during websocket is available. + sockopt: values for socket.setsockopt. + sockopt must be tuple and each element is argument of sock.setscokopt. + sslopt: ssl socket optional dict. + ping_interval: automatically send "ping" command every specified period(second) + if set to 0, not send automatically. + """ + if sockopt is None: + sockopt = [] + if sslopt is None: + sslopt = {} + if self.sock: + raise WebSocketException("socket is already opened") + thread = None + self.keep_running = True + + try: + self.sock = WebSocket(self.get_mask_key, sockopt=sockopt, sslopt=sslopt) + self.sock.settimeout(default_timeout) + self.sock.connect(self.url, header=self.header) + self._callback(self.on_open) + + if ping_interval: + thread = threading.Thread(target=self._send_ping, args=(ping_interval,)) + thread.setDaemon(True) + thread.start() + + while self.keep_running: + + try: + data = self.sock.recv() + + if data is None or self.keep_running == False: + break + self._callback(self.on_message, data) + + except Exception, e: + #print str(e.args[0]) + if "timed out" not in e.args[0]: + raise e + + except Exception, e: + self._callback(self.on_error, e) + finally: + if thread: + self.keep_running = False + self.sock.close() + self._callback(self.on_close) + self.sock = None + + def _callback(self, callback, *args): + if callback: + try: + callback(self, *args) + except Exception, e: + logger.error(e) + if True:#logger.isEnabledFor(logging.DEBUG): + _, _, tb = sys.exc_info() + traceback.print_tb(tb) + + +if __name__ == "__main__": + enableTrace(True) + ws = create_connection("ws://echo.websocket.org/") + print("Sending 'Hello, World'...") + ws.send("Hello, World") + print("Sent") + print("Receiving...") + result = ws.recv() + print("Received '%s'" % result) + ws.close() diff --git a/resources/lib/entrypoint.py b/resources/lib/entrypoint.py deleted file mode 100644 index cfcbd4c0..00000000 --- a/resources/lib/entrypoint.py +++ /dev/null @@ -1,1384 +0,0 @@ -# -*- coding: utf-8 -*- - -################################################################################################# - -import json -import logging -import os -import ntpath -import shutil -import sys -import urlparse - -import xbmc -import xbmcaddon -import xbmcgui -import xbmcvfs -import xbmcplugin - -import artwork -import utils -import clientinfo -import connectmanager -import database -import downloadutils -import librarysync -import read_embyserver as embyserver -import embydb_functions as embydb -import playlist -import playbackutils as pbutils -import playutils -import api -from views import Playlist, VideoNodes -from utils import window, settings, dialog, language as lang, urllib_path - -################################################################################################# - -log = logging.getLogger("EMBY."+__name__) - -addon = xbmcaddon.Addon(id='plugin.video.emby') -XML_PATH = (addon.getAddonInfo('path'), "default", "1080i") - -################################################################################################# - - -def doPlayback(itemId, dbId): - - if window('emby_online') != "true": - return - - emby = embyserver.Read_EmbyServer() - item = emby.getItem(itemId) - pbutils.PlaybackUtils(item).play(itemId, dbId) - -##### DO RESET AUTH ##### -def resetAuth(): - # User tried login and failed too many times - resp = xbmcgui.Dialog().yesno( - heading=lang(30132), - line1=lang(33050)) - if resp: - log.info("Reset login attempts.") - window('emby_serverStatus', value="Auth") - else: - xbmc.executebuiltin('Addon.OpenSettings(plugin.video.emby)') - -def addDirectoryItem(label, path, folder=True): - li = xbmcgui.ListItem(label, path=path) - li.setThumbnailImage("special://home/addons/plugin.video.emby/icon.png") - li.setArt({"fanart":"special://home/addons/plugin.video.emby/fanart.jpg"}) - li.setArt({"landscape":"special://home/addons/plugin.video.emby/fanart.jpg"}) - xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=path, listitem=li, isFolder=folder) - -def doMainListing(): - - xbmcplugin.setContent(int(sys.argv[1]), 'files') - # Get emby nodes from the window props - embyprops = window('Emby.nodes.total') - if embyprops: - totalnodes = int(embyprops) - for i in range(totalnodes): - path = window('Emby.nodes.%s.index' % i) - if not path: - path = window('Emby.nodes.%s.content' % i) - label = window('Emby.nodes.%s.title' % i) - node = window('Emby.nodes.%s.type' % i) - - ''' because we do not use seperate entrypoints for each content type, - we need to figure out which items to show in each listing. - for now we just only show picture nodes in the picture library - video nodes in the video library and all nodes in any other window - ''' - - if path: - if xbmc.getCondVisibility("Window.IsActive(Pictures)") and node in ("photos", "homevideos"): - addDirectoryItem(label, path) - elif xbmc.getCondVisibility("Window.IsActive(Videos)") and node != "photos": - addDirectoryItem(label, path) - elif not xbmc.getCondVisibility("Window.IsActive(Videos) | Window.IsActive(Pictures) | Window.IsActive(Music)"): - addDirectoryItem(label, path) - - # experimental live tv nodes - ''' - if not xbmc.getCondVisibility("Window.IsActive(Pictures)"): - addDirectoryItem(lang(33051), - "plugin://plugin.video.emby/?mode=browsecontent&type=tvchannels&folderid=root") - addDirectoryItem(lang(33052), - "plugin://plugin.video.emby/?mode=browsecontent&type=recordings&folderid=root") - ''' - ''' - TODO: Create plugin listing for servers - servers = window('emby_servers.json') - if servers: - for server in servers: - log.info(window('emby_server%s.name' % server)) - addDirectoryItem(window('emby_server%s.name' % server), "plugin://plugin.video.emby/?mode=%s" % server) - ''' - - #addDirectoryItem("Manual login dialog", "plugin://plugin.video.emby/?mode=manuallogin", False) - #addDirectoryItem("Connect login dialog", "plugin://plugin.video.emby/?mode=connectlogin") - #addDirectoryItem("Manual server dialog", "plugin://plugin.video.emby/?mode=manualserver") - #addDirectoryItem("Connect servers dialog", "plugin://plugin.video.emby/?mode=connectservers") - #addDirectoryItem("Connect users dialog", "plugin://plugin.video.emby/?mode=connectusers") - - addDirectoryItem(lang(30517), "plugin://plugin.video.emby/?mode=passwords", False) - addDirectoryItem(lang(33053), "plugin://plugin.video.emby/?mode=settings", False) - addDirectoryItem(lang(33054), "plugin://plugin.video.emby/?mode=adduser", False) - addDirectoryItem(lang(33055), "plugin://plugin.video.emby/?mode=refreshplaylist", False) - addDirectoryItem(lang(33098), "plugin://plugin.video.emby/?mode=refreshboxsets", False) - addDirectoryItem(lang(33056), "plugin://plugin.video.emby/?mode=manualsync", False) - addDirectoryItem(lang(33057), "plugin://plugin.video.emby/?mode=repair", False) - addDirectoryItem(lang(33058), "plugin://plugin.video.emby/?mode=reset", False) - addDirectoryItem(lang(33059), "plugin://plugin.video.emby/?mode=texturecache", False) - addDirectoryItem(lang(33060), "plugin://plugin.video.emby/?mode=thememedia", False) - - if settings('backupPath'): - addDirectoryItem(lang(33092), "plugin://plugin.video.emby/?mode=backup", False) - - #xbmcplugin.endOfDirectory(int(sys.argv[1])) - -def test_manual_login(): - """ - from dialogs import LoginManual - dialog = LoginManual("script-emby-connect-login-manual.xml", *XML_PATH) - dialog.set_server("Test server") - dialog.set_user("Test user") - dialog.doModal() - """ - window('emby_test', value="false") - -def test_connect_login(): - from dialogs import LoginConnect - dialog = LoginConnect("script-emby-connect-login.xml", *XML_PATH) - dialog.doModal() - -def test_manual_server(): - from dialogs import ServerManual - dialog = ServerManual("script-emby-connect-server-manual.xml", *XML_PATH) - dialog.doModal() - -def test_connect_servers(): - from dialogs import ServerConnect - dialog = ServerConnect("script-emby-connect-server.xml", *XML_PATH) - test_servers = [ - { - u'LastConnectionMode': 2, - u'Name': u'Server Name', - u'AccessToken': u'Token', - u'RemoteAddress': u'http://remote.address:8096', - u'UserId': u'd4000909883845059aadef13b7110375', - u'ManualAddress': u'http://manual.address:8096', - u'DateLastAccessed': '2018-01-01T02:36:58Z', - u'LocalAddress': u'http://local.address:8096', - u'Id': u'b1ef1940b1964e2188f00b73611d53fd', - u'Users': [ - { - u'IsSignedInOffline': True, - u'Id': u'd4000909883845059aadef13b7110375' - } - ] - } - ] - kwargs = { - 'username': "Test user", - 'user_image': None, - 'servers': test_servers, - 'emby_connect': True - } - dialog.set_args(**kwargs) - dialog.doModal() - -def test_connect_users(): - from dialogs import UsersConnect - test_users = [{ - u'Name': u'Guest', - u'HasConfiguredEasyPassword': False, - u'LastActivityDate': u'2018-01-01T04:10:15.4195197Z', - u'HasPassword': False, - u'LastLoginDate': u'2017-12-28T02:53:01.5770625Z', - u'Policy': { - u'EnabledDevices': [ - - ], - u'EnableMediaPlayback': True, - u'EnableRemoteControlOfOtherUsers': False, - u'RemoteClientBitrateLimit': 0, - u'BlockUnratedItems': [ - - ], - u'EnableAllDevices': True, - u'InvalidLoginAttemptCount': 0, - u'EnableUserPreferenceAccess': True, - u'EnableLiveTvManagement': False, - u'EnableLiveTvAccess': False, - u'IsAdministrator': False, - u'EnableContentDeletion': False, - u'EnabledChannels': [ - - ], - u'IsDisabled': False, - u'EnableSyncTranscoding': False, - u'EnableAudioPlaybackTranscoding': True, - u'EnableSharedDeviceControl': False, - u'AccessSchedules': [ - - ], - u'IsHidden': False, - u'EnableContentDeletionFromFolders': [ - - ], - u'EnableContentDownloading': False, - u'EnableVideoPlaybackTranscoding': True, - u'EnabledFolders': [ - u'0c41907140d802bb58430fed7e2cd79e', - u'a329cda1727467c08a8f1493195d32d3', - u'f137a2dd21bbc1b99aa5c0f6bf02a805', - u'4514ec850e5ad0c47b58444e17b6346c' - ], - u'EnableAllChannels': False, - u'BlockedTags': [ - - ], - u'EnableAllFolders': False, - u'EnablePublicSharing': False, - u'EnablePlaybackRemuxing': True - }, - u'ServerId': u'Test', - u'Configuration': { - u'SubtitleMode': u'Default', - u'HidePlayedInLatest': True, - u'GroupedFolders': [ - - ], - u'DisplayCollectionsView': False, - u'OrderedViews': [ - - ], - u'SubtitleLanguagePreference': u'', - u'AudioLanguagePreference': u'', - u'LatestItemsExcludes': [ - - ], - u'EnableLocalPassword': False, - u'RememberAudioSelections': True, - u'RememberSubtitleSelections': True, - u'DisplayMissingEpisodes': False, - u'PlayDefaultAudioTrack': True, - u'EnableNextEpisodeAutoPlay': True - }, - u'Id': u'a9d56d37cb6b47a3bfd3453c55138ff1', - u'HasConfiguredPassword': False - }] - dialog = UsersConnect("script-emby-connect-users.xml", *XML_PATH) - dialog.set_server("Test server") - dialog.set_users(test_users) - dialog.doModal() - -def emby_connect(): - - # Login user to emby connect - connect = connectmanager.ConnectManager() - try: - connectUser = connect.login_connect() - except RuntimeError: - return - else: - user = connectUser['User'] - token = connectUser['AccessToken'] - username = user['Name'] - dialog(type_="notification", - heading="{emby}", - message="%s %s" % (lang(33000), username.decode('utf-8')), - icon=user.get('ImageUrl') or "{emby}", - time=2000, - sound=False) - - settings('connectUsername', value=username) - -def emby_backup(): - # Create a backup at specified location - path = settings('backupPath') - - # filename - default_value = "Kodi%s.%s" % (xbmc.getInfoLabel('System.BuildVersion')[:2], - xbmc.getInfoLabel('System.Date(dd-mm-yy)')) - folder_name = dialog(type_="input", - heading=lang(33089), - defaultt=default_value) - if not folder_name: - return - - backup = os.path.join(path, folder_name) - log.info("Backup: %s", backup) - - # Create directory - if xbmcvfs.exists(backup+"\\"): - log.info("Existing directory!") - if not dialog(type_="yesno", - heading="{emby}", - line1=lang(33090)): - return emby_backup() - shutil.rmtree(backup) - - # Addon_data - addon_data = xbmc.translatePath("special://profile/addon_data/plugin.video.emby").decode('utf-8') - try: - shutil.copytree(src=addon_data, - dst=os.path.join(backup, "addon_data", "plugin.video.emby")) - except shutil.Error as error: - log.error(error) - - # Database files - database_folder = os.path.join(backup, "Database") - if not xbmcvfs.mkdir(database_folder): - try: - os.makedirs(database_folder) - except OSError as error: - log.error(error) - dialog(type_="ok", - heading="{emby}", - line1="Failed to create backup") - return - - # Emby database - emby_path = database.emby_database() - xbmcvfs.copy(emby_path, os.path.join(database_folder, ntpath.basename(emby_path))) - # Videos database - video_path = database.video_database() - xbmcvfs.copy(video_path, os.path.join(database_folder, ntpath.basename(video_path))) - # Music database - if settings('enableMusic') == "true": - music_path = database.music_database() - xbmcvfs.copy(music_path, os.path.join(database_folder, ntpath.basename(music_path))) - - dialog(type_="ok", - heading="{emby}", - line1="%s: %s" % (lang(33091), backup)) - -##### Generate a new deviceId -def resetDeviceId(): - - dialog = xbmcgui.Dialog() - - deviceId_old = window('emby_deviceId') - try: - window('emby_deviceId', clear=True) - deviceId = clientinfo.ClientInfo().get_device_id(reset=True) - except Exception as e: - log.error("Failed to generate a new device Id: %s" % e) - dialog.ok( - heading=lang(29999), - line1=lang(33032)) - else: - log.info("Successfully removed old deviceId: %s New deviceId: %s" % (deviceId_old, deviceId)) - dialog.ok( - heading=lang(29999), - line1=lang(33033)) - xbmc.executebuiltin('RestartApp') - -##### Delete Item -def deleteItem(): - - # Serves as a keymap action - if xbmc.getInfoLabel('ListItem.Property(embyid)'): # If we already have the embyid - itemId = xbmc.getInfoLabel('ListItem.Property(embyid)') - else: - dbId = xbmc.getInfoLabel('ListItem.DBID') - itemType = xbmc.getInfoLabel('ListItem.DBTYPE') - - if not itemType: - - if xbmc.getCondVisibility('Container.Content(albums)'): - itemType = "album" - elif xbmc.getCondVisibility('Container.Content(artists)'): - itemType = "artist" - elif xbmc.getCondVisibility('Container.Content(songs)'): - itemType = "song" - elif xbmc.getCondVisibility('Container.Content(pictures)'): - itemType = "picture" - else: - log.info("Unknown type, unable to proceed.") - return - - with database.DatabaseConn('emby') as cursor: - emby_db = embydb.Embydb_Functions(cursor) - item = emby_db.getItem_byKodiId(dbId, itemType) - - try: - itemId = item[0] - except TypeError: - log.error("Unknown itemId, unable to proceed.") - return - - if settings('skipContextMenu') != "true": - resp = xbmcgui.Dialog().yesno( - heading=lang(29999), - line1=lang(33041)) - if not resp: - log.info("User skipped deletion for: %s." % itemId) - return - - embyserver.Read_EmbyServer().deleteItem(itemId) - -##### ADD ADDITIONAL USERS ##### -def addUser(): - - if window('emby_online') != "true": - log.info("server is offline") - return - - doUtils = downloadutils.DownloadUtils() - art = artwork.Artwork() - clientInfo = clientinfo.ClientInfo() - deviceId = clientInfo.get_device_id() - deviceName = clientInfo.get_device_name() - userid = window('emby_currUser') - dialog = xbmcgui.Dialog() - - # Get session - url = "{server}/emby/Sessions?DeviceId=%s&format=json" % deviceId - - try: - result = doUtils.downloadUrl(url) - sessionId = result[0]['Id'] - additionalUsers = result[0]['AdditionalUsers'] - # Add user to session - userlist = {} - users = [] - url = "{server}/emby/Users?IsDisabled=false&IsHidden=false&format=json" - result = doUtils.downloadUrl(url) - - # pull the list of users - for user in result: - name = user['Name'] - userId = user['Id'] - if userid != userId: - userlist[name] = userId - users.append(name) - - # Display dialog if there's additional users - if additionalUsers: - - option = dialog.select(lang(33061), [lang(33062), lang(33063)]) - # Users currently in the session - additionalUserlist = {} - additionalUsername = [] - # Users currently in the session - for user in additionalUsers: - name = user['UserName'] - userId = user['UserId'] - additionalUserlist[name] = userId - additionalUsername.append(name) - - if option == 1: - # User selected Remove user - resp = dialog.select(lang(33064), additionalUsername) - if resp > -1: - selected = additionalUsername[resp] - selected_userId = additionalUserlist[selected] - url = "{server}/emby/Sessions/%s/Users/%s" % (sessionId, selected_userId) - doUtils.downloadUrl(url, postBody={}, action_type="DELETE") - dialog.notification( - heading=lang(29999), - message="%s %s" % (lang(33066), selected), - icon="special://home/addons/plugin.video.emby/icon.png", - time=1000) - - # clear picture - position = window('EmbyAdditionalUserPosition.%s' % selected_userId) - window('EmbyAdditionalUserImage.%s' % position, clear=True) - return - else: - return - - elif option == 0: - # User selected Add user - for adduser in additionalUsername: - try: # Remove from selected already added users. It is possible they are hidden. - users.remove(adduser) - except: pass - - elif option < 0: - # User cancelled - return - - # Subtract any additional users - log.info("Displaying list of users: %s" % users) - resp = dialog.select("Add user to the session", users) - # post additional user - if resp > -1: - selected = users[resp] - selected_userId = userlist[selected] - url = "{server}/emby/Sessions/%s/Users/%s" % (sessionId, selected_userId) - doUtils.downloadUrl(url, postBody={}, action_type="POST") - dialog.notification( - heading=lang(29999), - message="%s %s" % (lang(33067), selected), - icon="special://home/addons/plugin.video.emby/icon.png", - time=1000) - - except Exception as error: - log.error("Failed to add user to session: " + str(error)) - dialog.notification( - heading=lang(29999), - message=lang(33068), - icon=xbmcgui.NOTIFICATION_ERROR) - - # Add additional user images - # always clear the individual items first - totalNodes = 10 - for i in range(totalNodes): - if not window('EmbyAdditionalUserImage.%s' % i): - break - window('EmbyAdditionalUserImage.%s' % i, clear=True) - - url = "{server}/emby/Sessions?DeviceId=%s&format=json" % deviceId - - try: - result = doUtils.downloadUrl(url) - additionalUsers = result[0]['AdditionalUsers'] - except Exception as error: - log.error(error) - additionalUsers = [] - - count = 0 - for additionaluser in additionalUsers: - userid = additionaluser['UserId'] - url = "{server}/emby/Users/%s?format=json" % userid - result = doUtils.downloadUrl(url) - window('EmbyAdditionalUserImage.%s' % count, - value=art.get_user_artwork(result['Id'], 'Primary')) - window('EmbyAdditionalUserPosition.%s' % userid, value=str(count)) - count +=1 - -##### THEME MUSIC/VIDEOS ##### -def getThemeMedia(): - - doUtils = downloadutils.DownloadUtils() - dialog = xbmcgui.Dialog() - dummy_listitem = xbmcgui.ListItem() - playback = None - - # Choose playback method - resp = dialog.select(lang(33072), [lang(30165), lang(33071)]) - if resp == 0: - playback = "DirectPlay" - elif resp == 1: - playback = "DirectStream" - else: - return - - library = xbmc.translatePath( - "special://profile/addon_data/plugin.video.emby/library/").decode('utf-8') - # Create library directory - if not xbmcvfs.exists(library): - xbmcvfs.mkdir(library) - - # Set custom path for user - if xbmc.getCondVisibility('System.HasAddon(script.tvtunes)'): - tvtunes = xbmcaddon.Addon(id="script.tvtunes") - tvtunes.setSetting('custom_path_enable', "true") - tvtunes.setSetting('custom_path', library) - log.info("TV Tunes custom path is enabled and set.") - else: - # if it does not exist this will not work so warn user - # often they need to edit the settings first for it to be created. - dialog.ok(heading=lang(29999), line1=lang(33073)) - xbmc.executebuiltin('Addon.OpenSettings(script.tvtunes)') - return - - # Get every user view Id - with database.DatabaseConn('emby') as cursor: - emby_db = embydb.Embydb_Functions(cursor) - viewids = emby_db.getViews() - - # Get Ids with Theme Videos - itemIds = {} - for view in viewids: - url = "{server}/emby/Users/{UserId}/Items?HasThemeVideo=True&ParentId=%s&format=json" % view - result = doUtils.downloadUrl(url) - if result['TotalRecordCount'] != 0: - for item in result['Items']: - itemId = item['Id'] - folderName = item['Name'] - folderName = utils.normalize_string(folderName.encode('utf-8')) - itemIds[itemId] = folderName - - # Get paths for theme videos - for itemId in itemIds: - nfo_path = xbmc.translatePath( - "special://profile/addon_data/plugin.video.emby/library/%s/" % itemIds[itemId]) - # Create folders for each content - if not xbmcvfs.exists(nfo_path): - xbmcvfs.mkdir(nfo_path) - # Where to put the nfos - nfo_path = "%s%s" % (nfo_path, "tvtunes.nfo") - - url = "{server}/emby/Items/%s/ThemeVideos?format=json" % itemId - result = doUtils.downloadUrl(url) - - # Create nfo and write themes to it - nfo_file = xbmcvfs.File(nfo_path, 'w') - pathstowrite = "" - # May be more than one theme - for theme in result['Items']: - putils = playutils.PlayUtils(theme, dummy_listitem) - if playback == "DirectPlay": - playurl = api.API(theme).get_file_path() - else: - playurl = putils.get_direct_url(theme['MediaSources'][0]) - pathstowrite += ('<file>%s</file>' % playurl.encode('utf-8')) - - # Check if the item has theme songs and add them - url = "{server}/emby/Items/%s/ThemeSongs?format=json" % itemId - result = doUtils.downloadUrl(url) - - # May be more than one theme - for theme in result['Items']: - if playback == "DirectPlay": - playurl = api.API(theme).get_file_path() - else: - playurl = playutils.PlayUtils(theme, dummy_listitem).get_direct_url(theme['MediaSources'][0]) - pathstowrite += ('<file>%s</file>' % playurl.encode('utf-8')) - - nfo_file.write( - '<tvtunes>%s</tvtunes>' % pathstowrite - ) - # Close nfo file - nfo_file.close() - - # Get Ids with Theme songs - musicitemIds = {} - for view in viewids: - url = "{server}/emby/Users/{UserId}/Items?HasThemeSong=True&ParentId=%s&format=json" % view - result = doUtils.downloadUrl(url) - if result['TotalRecordCount'] != 0: - for item in result['Items']: - itemId = item['Id'] - folderName = item['Name'] - folderName = utils.normalize_string(folderName.encode('utf-8')) - musicitemIds[itemId] = folderName - - # Get paths - for itemId in musicitemIds: - - # if the item was already processed with video themes back out - if itemId in itemIds: - continue - - nfo_path = xbmc.translatePath( - "special://profile/addon_data/plugin.video.emby/library/%s/" % musicitemIds[itemId]) - # Create folders for each content - if not xbmcvfs.exists(nfo_path): - xbmcvfs.mkdir(nfo_path) - # Where to put the nfos - nfo_path = "%s%s" % (nfo_path, "tvtunes.nfo") - - url = "{server}/emby/Items/%s/ThemeSongs?format=json" % itemId - result = doUtils.downloadUrl(url) - - # Create nfo and write themes to it - nfo_file = xbmcvfs.File(nfo_path, 'w') - pathstowrite = "" - # May be more than one theme - for theme in result['Items']: - if playback == "DirectPlay": - playurl = api.API(theme).get_file_path() - else: - playurl = playutils.PlayUtils(theme, dummy_listitem).get_direct_url(theme['MediaSources'][0]) - pathstowrite += ('<file>%s</file>' % playurl.encode('utf-8')) - - nfo_file.write( - '<tvtunes>%s</tvtunes>' % pathstowrite - ) - # Close nfo file - nfo_file.close() - - dialog.notification( - heading=lang(29999), - message=lang(33069), - icon="special://home/addons/plugin.video.emby/icon.png", - time=1000, - sound=False) - -##### REFRESH EMBY PLAYLISTS ##### -def refreshPlaylist(): - - if window('emby_online') != "true": - log.info("server is offline") - return - - lib = librarysync.LibrarySync() - dialog = xbmcgui.Dialog() - try: - # First remove playlists - Playlist().delete_playlists() - # Remove video nodes - VideoNodes().deleteNodes() - # Refresh views - lib.refreshViews() - dialog.notification( - heading=lang(29999), - message=lang(33069), - icon="special://home/addons/plugin.video.emby/icon.png", - time=1000, - sound=False) - - except Exception as e: - log.exception("Refresh playlists/nodes failed: %s" % e) - dialog.notification( - heading=lang(29999), - message=lang(33070), - icon=xbmcgui.NOTIFICATION_ERROR, - time=1000, - sound=False) - -#### SHOW SUBFOLDERS FOR NODE ##### -def GetSubFolders(nodeindex): - nodetypes = ["",".recent",".recentepisodes",".inprogress",".inprogressepisodes",".unwatched",".nextepisodes",".sets",".genres",".random",".recommended"] - for node in nodetypes: - title = window('Emby.nodes.%s%s.title' %(nodeindex,node)) - if title: - path = window('Emby.nodes.%s%s.content' %(nodeindex,node)) - addDirectoryItem(title, path) - #xbmcplugin.endOfDirectory(int(sys.argv[1])) - -##### BROWSE EMBY NODES DIRECTLY ##### -def BrowseContent(viewname, browse_type="", folderid=""): - - emby = embyserver.Read_EmbyServer() - art = artwork.Artwork() - doUtils = downloadutils.DownloadUtils() - - #folderid used as filter ? - if folderid in ["recent","recentepisodes","inprogress","inprogressepisodes","unwatched","nextepisodes","sets","genres","random","recommended"]: - filter_type = folderid - folderid = "" - else: - filter_type = "" - - xbmcplugin.setPluginCategory(int(sys.argv[1]), viewname) - #get views for root level - if not folderid: - views = emby.getViews(browse_type) - for view in views: - if view.get("name") == viewname.decode('utf-8'): - folderid = view.get("id") - break - - if viewname is not None: - log.info("viewname: %s - type: %s - folderid: %s - filter: %s" %(viewname.decode('utf-8'), browse_type.decode('utf-8'), folderid.decode('utf-8'), filter_type.decode('utf-8'))) - #set the correct params for the content type - #only proceed if we have a folderid - if folderid: - if browse_type == "homevideos": - #xbmcplugin.setContent(int(sys.argv[1]), 'files') - itemtype = "Video,Folder,PhotoAlbum,Photo" - else: - itemtype = "" - - #get the actual listing - if browse_type == "recordings": - listing = emby.getTvRecordings(folderid) - elif browse_type == "tvchannels": - listing = emby.getTvChannels() - elif filter_type == "recent": - listing = emby.getFilteredSection(folderid, itemtype=itemtype.split(",")[0], sortby="DateCreated", recursive=True, limit=25, sortorder="Descending") - elif filter_type == "random": - listing = emby.getFilteredSection(folderid, itemtype=itemtype.split(",")[0], sortby="Random", recursive=True, limit=150, sortorder="Descending") - elif filter_type == "recommended": - listing = emby.getFilteredSection(folderid, itemtype=itemtype.split(",")[0], sortby="SortName", recursive=True, limit=25, sortorder="Ascending", filter_type="IsFavorite") - elif folderid == "favepisodes": - #xbmcplugin.setContent(int(sys.argv[1]), 'episodes') - listing = emby.getFilteredSection(None, itemtype="Episode", sortby="SortName", recursive=True, limit=25, sortorder="Ascending", filter_type="IsFavorite") - elif filter_type == "sets": - listing = emby.getFilteredSection(folderid, itemtype=itemtype.split(",")[1], sortby="SortName", recursive=True, limit=25, sortorder="Ascending", filter_type="IsFavorite") - else: - listing = emby.getFilteredSection(folderid, itemtype=itemtype, recursive=False) - - #process the listing - if listing: - for item in listing.get("Items"): - li = createListItemFromEmbyItem(item,art,doUtils) - if item.get("IsFolder") == True: - #for folders we add an additional browse request, passing the folderId - params = { - - 'id': viewname, - 'mode': "browsecontent", - 'type': browse_type, - 'folderid': item['Id'] - } - path = urllib_path("plugin://plugin.video.emby/", params) - xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=path, listitem=li, isFolder=True) - else: #playable item, set plugin path and mediastreams - xbmcplugin.setContent(int(sys.argv[1]), 'episodes' if folderid else 'files') - xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=li.getProperty("path"), listitem=li) - - - if filter_type == "recent": - xbmcplugin.addSortMethod(int(sys.argv[1]), xbmcplugin.SORT_METHOD_DATE) - else: - xbmcplugin.addSortMethod(int(sys.argv[1]), xbmcplugin.SORT_METHOD_VIDEO_TITLE) - xbmcplugin.addSortMethod(int(sys.argv[1]), xbmcplugin.SORT_METHOD_DATE) - xbmcplugin.addSortMethod(int(sys.argv[1]), xbmcplugin.SORT_METHOD_VIDEO_RATING) - xbmcplugin.addSortMethod(int(sys.argv[1]), xbmcplugin.SORT_METHOD_VIDEO_RUNTIME) - - xbmcplugin.endOfDirectory(handle=int(sys.argv[1])) - -##### CREATE LISTITEM FROM EMBY METADATA ##### -def createListItemFromEmbyItem(item,art=artwork.Artwork(),doUtils=downloadutils.DownloadUtils()): - API = api.API(item) - itemid = item['Id'] - - title = item.get('Name') - li = xbmcgui.ListItem(title) - - premieredate = item.get('PremiereDate',"") - if not premieredate: premieredate = item.get('DateCreated',"") - if premieredate: - premieredatelst = premieredate.split('T')[0].split("-") - premieredate = "%s.%s.%s" %(premieredatelst[2],premieredatelst[1],premieredatelst[0]) - - li.setProperty("embyid",itemid) - - allart = art.get_all_artwork(item) - - if item["Type"] == "Photo": - #listitem setup for pictures... - img_path = allart.get('Primary') - li.setProperty("path",img_path) - try: - picture = doUtils.downloadUrl("{server}/Items/%s/Images" %itemid) - except Exception as error: - lof.info("Error getting images from server: " + str(error)) - picture = None - - if picture is not None: - picture = picture[0] - if picture.get("Width") > picture.get("Height"): - li.setArt( {"fanart": img_path}) #add image as fanart for use with skinhelper auto thumb/backgrund creation - li.setInfo('pictures', infoLabels={ "picturepath": img_path, "date": premieredate, "size": picture.get("Size"), "exif:width": str(picture.get("Width")), "exif:height": str(picture.get("Height")), "title": title}) - li.setThumbnailImage(img_path) - li.setProperty("plot",API.get_overview()) - li.setIconImage('DefaultPicture.png') - - elif item['Type'] == "PhotoAlbum": - img_path = allart.get('Primary') - li.setProperty("path", img_path) - li.setThumbnailImage(img_path) - li.setIconImage('DefaultPicture.png') - backdrop = allart.get('Backdrop') - if backdrop: - li.setArt({ 'fanart': backdrop[0] }) - else: - #normal video items - li.setProperty('IsPlayable', 'true') - path = "%s?id=%s&mode=play" % (sys.argv[0], item.get("Id")) - li.setProperty("path",path) - genre = API.get_genres() - overlay = 0 - userdata = API.get_userdata() - runtime = item.get("RunTimeTicks",0)/ 10000000.0 - seektime = userdata['Resume'] - if seektime: - li.setProperty("resumetime", str(seektime)) - li.setProperty("totaltime", str(runtime)) - - played = userdata['Played'] - if played: overlay = 7 - else: overlay = 6 - playcount = userdata['PlayCount'] - if playcount is None: - playcount = 0 - - rating = item.get('CommunityRating') - if not rating: rating = 0 - - # Populate the extradata list and artwork - extradata = { - 'id': itemid, - 'rating': rating, - 'year': item.get('ProductionYear'), - 'genre': genre, - 'playcount': str(playcount), - 'title': title, - 'plot': API.get_overview(), - 'Overlay': str(overlay), - 'duration': runtime - } - if premieredate: - extradata["premieredate"] = premieredate - extradata["date"] = premieredate - li.setInfo('video', infoLabels=extradata) - if allart.get('Primary'): - li.setThumbnailImage(allart.get('Primary')) - else: li.setThumbnailImage('DefaultTVShows.png') - li.setIconImage('DefaultTVShows.png') - if not allart.get('Background'): #add image as fanart for use with skinhelper auto thumb/backgrund creation - li.setArt( {"fanart": allart.get('Primary') } ) - else: - pbutils.PlaybackUtils(item).setArtwork(li) - - mediastreams = API.get_media_streams() - videostreamFound = False - if mediastreams: - for key, value in mediastreams.iteritems(): - if key == "video" and value: videostreamFound = True - if value: li.addStreamInfo(key, value[0]) - if not videostreamFound: - #just set empty streamdetails to prevent errors in the logs - li.addStreamInfo("video", {'duration': runtime}) - - return li - -##### BROWSE EMBY CHANNELS ##### -def BrowseChannels(itemid, folderid=None): - - _addon_id = int(sys.argv[1]) - _addon_url = sys.argv[0] - doUtils = downloadutils.DownloadUtils() - art = artwork.Artwork() - - xbmcplugin.setContent(int(sys.argv[1]), 'files') - if folderid: - url = ( - "{server}/emby/Channels/%s/Items?userid={UserId}&folderid=%s&format=json" - % (itemid, folderid)) - elif itemid == "0": - # id 0 is the root channels folder - url = "{server}/emby/Channels?{UserId}&format=json" - else: - url = "{server}/emby/Channels/%s/Items?UserId={UserId}&format=json" % itemid - - try: - result = doUtils.downloadUrl(url) - except Exception as error: - log.info("Error getting channel: " + str(error)) - result = None - - if result is not None and result.get("Items"): - for item in result.get("Items"): - itemid = item['Id'] - itemtype = item['Type'] - li = createListItemFromEmbyItem(item,art,doUtils) - - isFolder = item.get('IsFolder', False) - - channelId = item.get('ChannelId', "") - channelName = item.get('ChannelName', "") - if itemtype == "Channel": - path = "%s?id=%s&mode=channels" % (_addon_url, itemid) - xbmcplugin.addDirectoryItem(handle=_addon_id, url=path, listitem=li, isFolder=True) - elif isFolder: - path = "%s?id=%s&mode=channelsfolder&folderid=%s" % (_addon_url, channelId, itemid) - xbmcplugin.addDirectoryItem(handle=_addon_id, url=path, listitem=li, isFolder=True) - else: - path = "%s?id=%s&mode=play" % (_addon_url, itemid) - li.setProperty('IsPlayable', 'true') - xbmcplugin.addDirectoryItem(handle=_addon_id, url=path, listitem=li) - - xbmcplugin.endOfDirectory(handle=int(sys.argv[1])) - -##### LISTITEM SETUP FOR VIDEONODES ##### -def createListItem(item): - - title = item['title'] - label2 = "" - li = xbmcgui.ListItem(title) - li.setProperty('IsPlayable', "true") - - metadata = { - - 'Title': title, - 'duration': str(item['runtime']/60), - 'Plot': item['plot'], - 'Playcount': item['playcount'] - } - - if "showtitle" in item: - metadata['TVshowTitle'] = item['showtitle'] - label2 = item['showtitle'] - - if "episodeid" in item: - # Listitem of episode - metadata['mediatype'] = "episode" - metadata['dbid'] = item['episodeid'] - - # TODO: Review once Krypton is RC - probably no longer needed if there's dbid - if "episode" in item: - episode = item['episode'] - metadata['Episode'] = episode - - if "season" in item: - season = item['season'] - metadata['Season'] = season - - if season and episode: - episodeno = "s%.2de%.2d" % (season, episode) - li.setProperty('episodeno', episodeno) - label2 = "%s - %s" % (label2, episodeno) if label2 else episodeno - - if "firstaired" in item: - metadata['Premiered'] = item['firstaired'] - - if "rating" in item: - metadata['Rating'] = str(round(float(item['rating']),1)) - - if "director" in item: - metadata['Director'] = " / ".join(item['director']) - - if "writer" in item: - metadata['Writer'] = " / ".join(item['writer']) - - if "cast" in item: - cast = [] - castandrole = [] - for person in item['cast']: - name = person['name'] - cast.append(name) - castandrole.append((name, person['role'])) - metadata['Cast'] = cast - metadata['CastAndRole'] = castandrole - - li.setLabel2(label2) - li.setInfo(type="Video", infoLabels=metadata) - li.setProperty('resumetime', str(item['resume']['position'])) - li.setProperty('totaltime', str(item['resume']['total'])) - li.setArt(item['art']) - li.setThumbnailImage(item['art'].get('thumb','')) - li.setIconImage('DefaultTVShows.png') - li.setProperty('dbid', str(item['episodeid'])) - li.setProperty('fanart_image', item['art'].get('tvshow.fanart','')) - for key, value in item['streamdetails'].iteritems(): - for stream in value: - li.addStreamInfo(key, stream) - - return li - -##### GET NEXTUP EPISODES FOR TAGNAME ##### -def getNextUpEpisodes(tagname, limit): - - count = 0 - # if the addon is called with nextup parameter, - # we return the nextepisodes list of the given tagname - xbmcplugin.setContent(int(sys.argv[1]), 'episodes') - # First we get a list of all the TV shows - filtered by tag - query = { - - 'jsonrpc': "2.0", - 'id': "libTvShows", - 'method': "VideoLibrary.GetTVShows", - 'params': { - - 'sort': {'order': "descending", 'method': "lastplayed"}, - 'filter': { - 'and': [ - {'operator': "true", 'field': "inprogress", 'value': ""}, - {'operator': "is", 'field': "tag", 'value': "%s" % tagname} - ]}, - 'properties': ['title', 'studio', 'mpaa', 'file', 'art'] - } - } - result = xbmc.executeJSONRPC(json.dumps(query)) - result = json.loads(result) - # If we found any, find the oldest unwatched show for each one. - try: - items = result['result']['tvshows'] - except (KeyError, TypeError): - pass - else: - for item in items: - if settings('ignoreSpecialsNextEpisodes') == "true": - query = { - - 'jsonrpc': "2.0", - 'id': 1, - 'method': "VideoLibrary.GetEpisodes", - 'params': { - - 'tvshowid': item['tvshowid'], - 'sort': {'method': "episode"}, - 'filter': { - 'and': [ - {'operator': "lessthan", 'field': "playcount", 'value': "1"}, - {'operator': "greaterthan", 'field': "season", 'value': "0"} - ]}, - 'properties': [ - "title", "playcount", "season", "episode", "showtitle", - "plot", "file", "rating", "resume", "tvshowid", "art", - "streamdetails", "firstaired", "runtime", "writer", - "dateadded", "lastplayed" - ], - 'limits': {"end": 1} - } - } - else: - query = { - - 'jsonrpc': "2.0", - 'id': 1, - 'method': "VideoLibrary.GetEpisodes", - 'params': { - - 'tvshowid': item['tvshowid'], - 'sort': {'method': "episode"}, - 'filter': {'operator': "lessthan", 'field': "playcount", 'value': "1"}, - 'properties': [ - "title", "playcount", "season", "episode", "showtitle", - "plot", "file", "rating", "resume", "tvshowid", "art", - "streamdetails", "firstaired", "runtime", "writer", - "dateadded", "lastplayed" - ], - 'limits': {"end": 1} - } - } - - result = xbmc.executeJSONRPC(json.dumps(query)) - result = json.loads(result) - try: - episodes = result['result']['episodes'] - except (KeyError, TypeError): - pass - else: - for episode in episodes: - li = createListItem(episode) - xbmcplugin.addDirectoryItem( - handle=int(sys.argv[1]), - url=episode['file'], - listitem=li) - count += 1 - - if count == limit: - break - - xbmcplugin.endOfDirectory(handle=int(sys.argv[1])) - -##### GET INPROGRESS EPISODES FOR TAGNAME ##### -def getInProgressEpisodes(tagname, limit): - - count = 0 - # if the addon is called with inprogressepisodes parameter, - # we return the inprogressepisodes list of the given tagname - xbmcplugin.setContent(int(sys.argv[1]), 'episodes') - # First we get a list of all the in-progress TV shows - filtered by tag - query = { - - 'jsonrpc': "2.0", - 'id': "libTvShows", - 'method': "VideoLibrary.GetTVShows", - 'params': { - - 'sort': {'order': "descending", 'method': "lastplayed"}, - 'filter': { - 'and': [ - {'operator': "true", 'field': "inprogress", 'value': ""}, - {'operator': "is", 'field': "tag", 'value': "%s" % tagname} - ]}, - 'properties': ['title', 'studio', 'mpaa', 'file', 'art'] - } - } - result = xbmc.executeJSONRPC(json.dumps(query)) - result = json.loads(result) - # If we found any, find the oldest unwatched show for each one. - try: - items = result['result']['tvshows'] - except (KeyError, TypeError): - pass - else: - for item in items: - query = { - - 'jsonrpc': "2.0", - 'id': 1, - 'method': "VideoLibrary.GetEpisodes", - 'params': { - - 'tvshowid': item['tvshowid'], - 'sort': {'method': "episode"}, - 'filter': {'operator': "true", 'field': "inprogress", 'value': ""}, - 'properties': [ - "title", "playcount", "season", "episode", "showtitle", "plot", - "file", "rating", "resume", "tvshowid", "art", "cast", - "streamdetails", "firstaired", "runtime", "writer", - "dateadded", "lastplayed" - ] - } - } - result = xbmc.executeJSONRPC(json.dumps(query)) - result = json.loads(result) - try: - episodes = result['result']['episodes'] - except (KeyError, TypeError): - pass - else: - for episode in episodes: - li = createListItem(episode) - xbmcplugin.addDirectoryItem( - handle=int(sys.argv[1]), - url=episode['file'], - listitem=li) - count += 1 - - if count == limit: - break - - xbmcplugin.endOfDirectory(handle=int(sys.argv[1])) - -##### GET RECENT EPISODES FOR TAGNAME ##### -def getRecentEpisodes(tagname, limit, filters=""): - - count = 0 - filters = filters.split(',') if filters else [] - # if the addon is called with recentepisodes parameter, - # we return the recentepisodes list of the given tagname - xbmcplugin.setContent(int(sys.argv[1]), 'episodes') - # First we get a list of all the TV shows - filtered by tag - query = { - - 'jsonrpc': "2.0", - 'id': "libTvShows", - 'method': "VideoLibrary.GetTVShows", - 'params': { - - 'sort': {'order': "descending", 'method': "dateadded"}, - 'filter': {'operator': "is", 'field': "tag", 'value': "%s" % tagname}, - 'properties': ["title", "sorttitle"] - } - } - result = xbmc.executeJSONRPC(json.dumps(query)) - result = json.loads(result) - # If we found any, find the oldest unwatched show for each one. - try: - items = result['result']['tvshows'] - except (KeyError, TypeError): - pass - else: - allshowsIds = set(item['tvshowid'] for item in items) - - query = { - - 'jsonrpc': "2.0", - 'id': 1, - 'method': "VideoLibrary.GetEpisodes", - 'params': { - - 'sort': {'order': "descending", 'method': "dateadded"}, - 'properties': [ - "title", "playcount", "season", "episode", "showtitle", "plot", - "file", "rating", "resume", "tvshowid", "art", "streamdetails", - "firstaired", "runtime", "cast", "writer", "dateadded", "lastplayed" - ], - "limits": {"end": limit*5} - } - } - if 'playcount' not in filters: - query['params']['filter'] = {'operator': "lessthan", 'field': "playcount", 'value': "1"} - - result = xbmc.executeJSONRPC(json.dumps(query)) - result = json.loads(result) - try: - episodes = result['result']['episodes'] - except (KeyError, TypeError): - pass - else: - for episode in episodes: - if episode['tvshowid'] in allshowsIds: - li = createListItem(episode) - xbmcplugin.addDirectoryItem( - handle=int(sys.argv[1]), - url=episode['file'], - listitem=li) - count += 1 - - if count == limit: - break - - xbmcplugin.endOfDirectory(handle=int(sys.argv[1])) - -##### GET VIDEO EXTRAS FOR LISTITEM ##### -def getVideoFiles(embyId,embyPath): - #returns the video files for the item as plugin listing, can be used for browsing the actual files or videoextras etc. - emby = embyserver.Read_EmbyServer() - if not embyId: - if "plugin.video.emby" in embyPath: - embyId = embyPath.split("/")[-2] - if embyId: - item = emby.getItem(embyId) - putils = playutils.PlayUtils(item) - if putils.isDirectPlay(): - #only proceed if we can access the files directly. TODO: copy local on the fly if accessed outside - filelocation = putils.directPlay() - if not filelocation.endswith("/"): - filelocation = filelocation.rpartition("/")[0] - dirs, files = xbmcvfs.listdir(filelocation) - for file in files: - file = filelocation + file - li = xbmcgui.ListItem(file, path=file) - xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=file, listitem=li) - for dir in dirs: - dir = filelocation + dir - li = xbmcgui.ListItem(dir, path=dir) - xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=dir, listitem=li, isFolder=True) - #xbmcplugin.endOfDirectory(int(sys.argv[1])) - -##### GET EXTRAFANART FOR LISTITEM ##### -def getExtraFanArt(embyId,embyPath): - - emby = embyserver.Read_EmbyServer() - art = artwork.Artwork() - - # Get extrafanart for listitem - # will be called by skinhelper script to get the extrafanart - try: - # for tvshows we get the embyid just from the path - if not embyId: - if "plugin.video.emby" in embyPath: - embyId = embyPath.split("/")[-2] - - if embyId: - #only proceed if we actually have a emby id - log.info("Requesting extrafanart for Id: %s" % embyId) - - # We need to store the images locally for this to work - # because of the caching system in xbmc - fanartDir = xbmc.translatePath("special://thumbnails/emby/%s/" % embyId).decode('utf-8') - - if not xbmcvfs.exists(fanartDir): - # Download the images to the cache directory - xbmcvfs.mkdirs(fanartDir) - item = emby.getItem(embyId) - if item: - backdrops = art.get_all_artwork(item)['Backdrop'] - tags = item['BackdropImageTags'] - count = 0 - for backdrop in backdrops: - # Same ordering as in artwork - tag = tags[count] - if os.path.supports_unicode_filenames: - fanartFile = os.path.join(fanartDir, "fanart%s.jpg" % tag) - else: - fanartFile = os.path.join(fanartDir.encode("utf-8"), "fanart%s.jpg" % tag.encode("utf-8")) - li = xbmcgui.ListItem(tag, path=fanartFile) - xbmcplugin.addDirectoryItem( - handle=int(sys.argv[1]), - url=fanartFile, - listitem=li) - xbmcvfs.copy(backdrop, fanartFile) - count += 1 - else: - log.debug("Found cached backdrop.") - # Use existing cached images - dirs, files = xbmcvfs.listdir(fanartDir) - for file in files: - fanartFile = os.path.join(fanartDir, file.decode('utf-8')) - li = xbmcgui.ListItem(file, path=fanartFile) - xbmcplugin.addDirectoryItem( - handle=int(sys.argv[1]), - url=fanartFile, - listitem=li) - except Exception as e: - log.error("Error getting extrafanart: %s" % e) - - # Always do endofdirectory to prevent errors in the logs - #xbmcplugin.endOfDirectory(int(sys.argv[1])) diff --git a/resources/lib/entrypoint/__init__.py b/resources/lib/entrypoint/__init__.py new file mode 100644 index 00000000..690ff83b --- /dev/null +++ b/resources/lib/entrypoint/__init__.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- + +################################################################################################# + +import logging +import sys + +from helper import loghandler +from emby import Emby + +################################################################################################# + +Emby.set_loghandler(loghandler.LogHandler, logging.DEBUG) +loghandler.config() +LOG = logging.getLogger('EMBY.entrypoint') + +################################################################################################# + +try: + sys.path.insert(0, xbmc.translatePath('special://temp/emby/').decode('utf-8')) +except Exception as error: + LOG.debug('No objects not found, using default.') + +from default import Events +from service import Service +from context import Context diff --git a/resources/lib/entrypoint/context.py b/resources/lib/entrypoint/context.py new file mode 100644 index 00000000..bd8076f6 --- /dev/null +++ b/resources/lib/entrypoint/context.py @@ -0,0 +1,159 @@ +# -*- coding: utf-8 -*- + +################################################################################################# + +import json +import logging +import sys + +import xbmc +import xbmcaddon + +import database +from dialogs import context +from helper import _, settings, dialog +from downloader import TheVoid +from objects import Actions + +################################################################################################# + +LOG = logging.getLogger("EMBY."+__name__) +XML_PATH = (xbmcaddon.Addon('plugin.video.emby').getAddonInfo('path'), "default", "1080i") +OPTIONS = { + 'Refresh': _(30410), + 'Delete': _(30409), + 'Addon': _(30408), + 'AddFav': _(30405), + 'RemoveFav': _(30406), + 'Transcode': _(30412) +} + +################################################################################################# + + +class Context(object): + + _selected_option = None + + def __init__(self, transcode=False): + + self.kodi_id = sys.listitem.getVideoInfoTag().getDbId() or None + self.media = self.get_media_type() + self.server = sys.listitem.getProperty('embyserver') or None + item_id = sys.listitem.getProperty('embyid') + + if self.server or item_id: + self.item = TheVoid('GetItem', {'ServerId': self.server, 'Id': item_id}).get() + else: + self.item = self.get_item_id() + + if self.item: + + if transcode: + self.transcode() + + elif self.select_menu(): + self.action_menu() + + if self._selected_option.decode('utf-8') in (OPTIONS['Delete'], OPTIONS['AddFav'], OPTIONS['RemoveFav']): + + xbmc.sleep(500) + xbmc.executebuiltin('Container.Refresh') + + def get_media_type(self): + + ''' Get media type based on sys.listitem. If unfilled, base on visible window. + ''' + media = sys.listitem.getVideoInfoTag().getMediaType() + + if not media: + + if xbmc.getCondVisibility('Container.Content(albums)'): + media = "album" + elif xbmc.getCondVisibility('Container.Content(artists)'): + media = "artist" + elif xbmc.getCondVisibility('Container.Content(songs)'): + media = "song" + elif xbmc.getCondVisibility('Container.Content(pictures)'): + media = "picture" + else: + LOG.info("media is unknown") + + return media.decode('utf-8') + + def get_item_id(self): + + ''' Get synced item from embydb. + ''' + item = database.get_item(self.kodi_id, self.media) + + if not item: + return + + return { + 'Id': item[0], + 'UserData': json.loads(item[4]) if item[4] else {}, + 'Type': item[3] + } + + def select_menu(self): + + ''' Display the select dialog. + Favorites, Refresh, Delete (opt), Settings. + ''' + options = [] + + if self.item['Type'] not in ('Season'): + + if self.item['UserData'].get('IsFavorite'): + options.append(OPTIONS['RemoveFav']) + else: + options.append(OPTIONS['AddFav']) + + options.append(OPTIONS['Refresh']) + + if settings('enableContextDelete.bool'): + options.append(OPTIONS['Delete']) + + options.append(OPTIONS['Addon']) + + context_menu = context.ContextMenu("script-emby-context.xml", *XML_PATH) + context_menu.set_options(options) + context_menu.doModal() + + if context_menu.is_selected(): + self._selected_option = context_menu.get_selected() + + return self._selected_option + + def action_menu(self): + + selected = self._selected_option.decode('utf-8') + + if selected == OPTIONS['Refresh']: + TheVoid('RefreshItem', {'ServerId': self.server, 'Id': self.item['Id']}) + + elif selected == OPTIONS['AddFav']: + TheVoid('FavoriteItem', {'ServerId': self.server, 'Id': self.item['Id'], 'Favorite': True}) + + elif selected == OPTIONS['RemoveFav']: + TheVoid('FavoriteItem', {'ServerId': self.server, 'Id': self.item['Id'], 'Favorite': False}) + + elif selected == OPTIONS['Addon']: + xbmc.executebuiltin('Addon.OpenSettings(plugin.video.emby)') + + elif selected == OPTIONS['Delete']: + delete = True + + if not settings('skipContextMenu.bool'): + + if not dialog("yesno", heading="{emby}", line1=_(33015)): + delete = False + + if delete: + TheVoid('DeleteItem', {'ServerId': self.server, 'Id': self.item['Id']}) + + def transcode(self): + + item = TheVoid('GetItem', {'Id': self.item['Id'], 'ServerId': self.server}).get() + Actions(self.server).play(item, self.kodi_id, True) diff --git a/resources/lib/entrypoint/default.py b/resources/lib/entrypoint/default.py new file mode 100644 index 00000000..e182fb18 --- /dev/null +++ b/resources/lib/entrypoint/default.py @@ -0,0 +1,667 @@ +# -*- coding: utf-8 -*- + +################################################################################################# + +import json +import logging +import sys +import urlparse +import urllib +import os +import sys + +import xbmc +import xbmcvfs +import xbmcgui +import xbmcplugin +import xbmcaddon + +import client +from database import reset, get_sync, Database, emby_db, get_credentials +from objects import Objects, Actions +from downloader import TheVoid +from helper import _, event, settings, window, dialog, api, JSONRPC +from emby import Emby + +################################################################################################# + +LOG = logging.getLogger("EMBY."+__name__) + +################################################################################################# + + +class Events(object): + + + def __init__(self): + + ''' Parse the parameters. Reroute to our service.py + where user is fully identified already. + ''' + base_url = sys.argv[0] + path = sys.argv[2] + + try: + params = dict(urlparse.parse_qsl(path[1:])) + except Exception: + params = {} + + mode = params.get('mode') + server = params.get('server') + + if server == 'None': + server = None + + LOG.warn("path: %s params: %s", path, json.dumps(params, indent=4)) + + if '/extrafanart' in base_url: + + emby_path = path[1:] + emby_id = params.get('id') + get_fanart(emby_id, emby_path, server) + + elif '/Extras' in base_url or '/VideoFiles' in base_url: + + emby_path = path[1:] + emby_id = params.get('id') + get_video_extras(emby_id, emby_path, server) + + elif mode =='play': + + item = TheVoid('GetItem', {'Id': params['id'], 'ServerId': server}).get() + Actions(params.get('server')).play(item, params.get('dbid')) + + elif mode == 'playlist': + event('PlayPlaylist', {'Id': params['id'], 'ServerId': server}) + elif mode == 'deviceid': + client.reset_device_id() + elif mode == 'reset': + reset() + elif mode == 'refreshboxsets': + event('SyncLibrary', {'Id': "Boxsets:Refresh"}) + elif mode == 'nextepisodes': + get_next_episodes(params['id'], params['limit']) + elif mode == 'browse': + browse(params.get('type'), params.get('id'), params.get('folder'), server) + elif mode == 'synclib': + event('SyncLibrary', {'Id': params.get('id')}) + elif mode == 'repairlib': + event('RepairLibrary', {'Id': params.get('id')}) + elif mode == 'removelib': + event('RemoveLibrary', {'Id': params.get('id')}) + elif mode == 'repairlibs': + event('RepairLibrarySelection') + elif mode == 'updatelibs': + event('SyncLibrarySelection') + elif mode == 'connect': + event('EmbyConnect') + elif mode == 'addserver': + event('AddServer') + elif mode == 'login': + event('ServerConnect', {'Id': server}) + elif mode == 'removeserver': + event('RemoveServer', {'Id': server}) + elif mode == 'settings': + xbmc.executebuiltin('Addon.OpenSettings(plugin.video.emby)') + elif mode == 'adduser': + add_user() + elif mode == 'updateserver': + event('UpdateServer') + elif mode == 'thememedia': + get_themes() + else: + listing() + + +def listing(): + + ''' Display all emby nodes and dynamic entries when appropriate. + ''' + total = int(window('Emby.nodes.total') or 0) + sync = get_sync() + servers = get_credentials()['Servers'][1:] + + for i in range(total): + + window_prop = "Emby.nodes.%s" % i + path = window('%s.index' % window_prop) + + if not path: + path = window('%s.content' % window_prop) or window('%s.path' % window_prop) + + label = window('%s.title' % window_prop) + node = window('%s.type' % window_prop) + artwork = window('%s.artwork' % window_prop) + view_id = window('%s.id' % window_prop) + context = [] + + if view_id and node in ('movies', 'tvshows', 'musicvideos', 'music') and view_id not in sync['Whitelist']: + + context.append((_(33123), "RunPlugin(plugin://plugin.video.emby?mode=synclib&id=%s)" % view_id)) + + if view_id and node in ('movies', 'tvshows', 'musicvideos', 'music') and view_id in sync['Whitelist']: + + context.append((_(33136), "RunPlugin(plugin://plugin.video.emby?mode=synclib&id=%s)" % view_id)) + context.append((_(33132), "RunPlugin(plugin://plugin.video.emby?mode=repairlib&id=%s)" % view_id)) + context.append((_(33133), "RunPlugin(plugin://plugin.video.emby?mode=removelib&id=%s)" % view_id)) + + LOG.debug("--[ listing/%s/%s ] %s", node, label, path) + + if path: + if xbmc.getCondVisibility('Window.IsActive(Pictures)') and node in ('photos', 'homevideos'): + directory(label, path, artwork=artwork) + elif xbmc.getCondVisibility('Window.IsActive(Videos)') and node not in ('photos', 'homevideos', 'music'): + directory(label, path, artwork=artwork, context=context) + elif xbmc.getCondVisibility('Window.IsActive(Music)') and node == 'music': + directory(label, path, artwork=artwork, context=context) + elif not xbmc.getCondVisibility('Window.IsActive(Videos) | Window.IsActive(Pictures) | Window.IsActive(Music)'): + directory(label, path, artwork=artwork) + + for server in servers: + context = [] + + if server.get('ManualAddress'): + context.append((_(33141), "RunPlugin(plugin://plugin.video.emby/?mode=removeserver&server=%s)")) + + if 'AccessToken' not in server: + directory("%s (%s)" % (server['Name'], _(30539)), "plugin://plugin.video.emby/?mode=login&server=%s" % server['Id'], False, context=context) + else: + directory(server['Name'], "plugin://plugin.video.emby/?mode=browse&server=%s" % server['Id'], context=context) + + + directory(_(33134), "plugin://plugin.video.emby/?mode=addserver", False) + directory(_(5), "plugin://plugin.video.emby/?mode=settings", False) + directory(_(33054), "plugin://plugin.video.emby/?mode=adduser", False) + directory(_(33098), "plugin://plugin.video.emby/?mode=refreshboxsets", False) + directory(_(33139), "plugin://plugin.video.emby/?mode=updatelibs", False) + directory(_(33140), "plugin://plugin.video.emby/?mode=repairlibs", False) + directory(_(33060), "plugin://plugin.video.emby/?mode=thememedia", False) + directory(_(33058), "plugin://plugin.video.emby/?mode=reset", False) + + xbmcplugin.setContent(int(sys.argv[1]), 'files') + xbmcplugin.endOfDirectory(int(sys.argv[1])) + +def directory(label, path, folder=True, artwork=None, fanart=None, context=None): + + ''' Add directory listitem. context should be a list of tuples [(label, action)*] + ''' + li = dir_listitem(label, path, artwork, fanart) + + if context: + li.addContextMenuItems(context) + + xbmcplugin.addDirectoryItem(int(sys.argv[1]), path, li, folder) + + return li + +def dir_listitem(label, path, artwork=None, fanart=None): + + li = xbmcgui.ListItem(label, path=path) + li.setThumbnailImage(artwork or "special://home/addons/plugin.video.emby/icon.png") + li.setArt({"fanart": fanart or "special://home/addons/plugin.video.emby/fanart.jpg"}) + li.setArt({"landscape": artwork or fanart or "special://home/addons/plugin.video.emby/fanart.jpg"}) + + return li + +def browse(media, view_id=None, folder=None, server_id=None): + + ''' Browse content dynamically. + ''' + LOG.info("--[ v:%s/%s ] %s", view_id, media, folder) + + if view_id: + + view = TheVoid('GetItem', {'ServerId': server_id, 'Id': view_id}).get() + xbmcplugin.setPluginCategory(int(sys.argv[1]), view['Name']) + + content_type = "files" + + if media in ('tvshows', 'seasons', 'episodes', 'movies', 'musicvideos'): + content_type = media + elif media in ('homevideos', 'photos'): + content_type = "images" + + if folder == 'FavEpisodes': + listing = TheVoid('Browse', {'Media': "Episode", 'ServerId': server_id, 'Limit': 25, 'Filters': "IsFavorite"}).get() + elif media == 'homevideos': + listing = TheVoid('Browse', {'Id': folder or view_id, 'Media': "Video,Folder,PhotoAlbum,Photo", 'ServerId': server_id, 'Recursive': False}).get() + elif media == 'movies': + listing = TheVoid('Browse', {'Id': folder or view_id, 'Media': "Movie,BoxSet", 'ServerId': server_id, 'Recursive': True}).get() + elif media == 'episodes': + listing = TheVoid('Browse', {'Id': folder or view_id, 'Media': "Episode", 'ServerId': server_id, 'Recursive': True}).get() + elif media == 'library': + listing = TheVoid('Browse', {'Id': folder or view_id, 'ServerId': server_id, 'Recursive': True}).get() + else: + listing = TheVoid('Browse', {'Id': folder or view_id, 'ServerId': server_id, 'Recursive': False}).get() + + + if listing and listing.get('Items'): + + actions = Actions(server_id) + list_li = [] + + for item in listing['Items']: + + li = xbmcgui.ListItem() + li.setProperty('embyid', item['Id']) + li.setProperty('embyserver', server_id) + actions.set_listitem(item, li) + + if item.get('IsFolder'): + + params = { + 'id': view_id or item['Id'], + 'mode': "browse", + 'type': get_folder_type(item) or media, + 'folder': item['Id'], + 'server': server_id + } + path = "%s?%s" % ("plugin://plugin.video.emby", urllib.urlencode(params)) + context = [] + + if item['Type'] in ('Series', 'Season', 'Playlist'): + context.append(("Play", "RunPlugin(plugin://plugin.video.emby?mode=playlist&id=%s&server=%s)" % (item['Id'], server_id))) + + if item['UserData']['Played']: + context.append((_(16104), "RunPlugin(plugin://plugin.video.emby?mode=unwatched&id=%s&server=%s)" % (item['Id'], server_id))) + else: + context.append((_(16103), "RunPlugin(plugin://plugin.video.emby?mode=watched&id=%s&server=%s)" % (item['Id'], server_id))) + + li.addContextMenuItems(context) + list_li.append((path, li, True)) + else: + if item['Type'] not in ('Photo', 'PhotoAlbum'): + params = { + 'id': item['Id'], + 'mode': "play", + 'server': server_id + } + path = "%s?%s" % ("plugin://plugin.video.emby", urllib.urlencode(params)) + li.setProperty('path', path) + context = [(_(13412), "RunPlugin(plugin://plugin.video.emby?mode=playlist&id=%s&server=%s)" % (item['Id'], server_id))] + + if item['UserData']['Played']: + context.append((_(16104), "RunPlugin(plugin://plugin.video.emby?mode=unwatched&id=%s&server=%s)" % (item['Id'], server_id))) + else: + context.append((_(16103), "RunPlugin(plugin://plugin.video.emby?mode=watched&id=%s&server=%s)" % (item['Id'], server_id))) + + li.addContextMenuItems(context) + + list_li.append((li.getProperty('path'), li, False)) + + xbmcplugin.addDirectoryItems(int(sys.argv[1]), list_li, len(list_li)) + + if content_type == 'images': + xbmcplugin.addSortMethod(int(sys.argv[1]), xbmcplugin.SORT_METHOD_VIDEO_TITLE) + xbmcplugin.addSortMethod(int(sys.argv[1]), xbmcplugin.SORT_METHOD_DATE) + xbmcplugin.addSortMethod(int(sys.argv[1]), xbmcplugin.SORT_METHOD_VIDEO_RATING) + xbmcplugin.addSortMethod(int(sys.argv[1]), xbmcplugin.SORT_METHOD_VIDEO_RUNTIME) + + xbmcplugin.setContent(int(sys.argv[1]), content_type) + xbmcplugin.endOfDirectory(int(sys.argv[1])) + +def get_folder_type(item): + + media = item['Type'] + + if media == 'Series': + return "seasons" + elif media == 'Season': + return "episodes" + elif media == 'BoxSet': + return "movies" + elif media == 'MusicArtist': + return "albums" + elif media == 'MusicAlbum': + return "songs" + elif media == 'CollectionFolder': + return item.get('CollectionType', 'library') + +def get_fanart(item_id, path, server_id=None): + + ''' Get extra fanart for listitems. This is called by skinhelper. + Images are stored locally, due to the Kodi caching system. + ''' + if not item_id and 'plugin.video.emby' in path: + item_id = path.split('/')[-2] + + if not item_id: + return + + LOG.info("[ extra fanart ] %s", item_id) + objects = Objects() + list_li = [] + API = api.API(item, TheVoid('GetServerAddress', {'ServerId': server_id}).get()) + directory = xbmc.translatePath("special://thumbnails/emby/%s/" % item_id).decode('utf-8') + + if not xbmcvfs.exists(directory): + + xbmcvfs.mkdirs(directory) + item = TheVoid('GetItem', {'ServerId': server_id, 'Id': item_id}).get() + obj = objects.map(item, 'Artwork') + backdrops = API.get_all_artwork(obj) + tags = obj['BackdropTags'] + + for index, backdrop in enumerate(backdrops): + + tag = tags[index] + fanart = os.path.join(directory, "fanart%s.jpg" % tag) + li = xbmcgui.ListItem(tag, path=fanart) + xbmcvfs.copy(backdrop, fanart) + list_li.append((fanart, li, False)) + else: + LOG.debug("cached backdrop found") + dirs, files = xbmcvfs.listdir(directory) + + for file in files: + fanart = os.path.join(directory, file.decode('utf-8')) + li = xbmcgui.ListItem(file, path=fanart) + list_li.append((fanart, li, False)) + + xbmcplugin.addDirectoryItems(int(sys.argv[1]), list_li, len(list_li)) + xbmcplugin.endOfDirectory(int(sys.argv[1])) + +def get_video_extras(item_id, path, server_id=None): + + ''' Returns the video files for the item as plugin listing, can be used + to browse actual files or video extras, etc. + ''' + if not item_id and 'plugin.video.emby' in path: + item_id = path.split('/')[-2] + + if not item_id: + return + + item = TheVoid('GetItem', {'ServerId': server_id, 'Id': item_id}).get() + # TODO + + """ + def getVideoFiles(embyId,embyPath): + #returns the video files for the item as plugin listing, can be used for browsing the actual files or videoextras etc. + emby = embyserver.Read_EmbyServer() + if not embyId: + if "plugin.video.emby" in embyPath: + embyId = embyPath.split("/")[-2] + if embyId: + item = emby.getItem(embyId) + putils = playutils.PlayUtils(item) + if putils.isDirectPlay(): + #only proceed if we can access the files directly. TODO: copy local on the fly if accessed outside + filelocation = putils.directPlay() + if not filelocation.endswith("/"): + filelocation = filelocation.rpartition("/")[0] + dirs, files = xbmcvfs.listdir(filelocation) + for file in files: + file = filelocation + file + li = xbmcgui.ListItem(file, path=file) + xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=file, listitem=li) + for dir in dirs: + dir = filelocation + dir + li = xbmcgui.ListItem(dir, path=dir) + xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=dir, listitem=li, isFolder=True) + #xbmcplugin.endOfDirectory(int(sys.argv[1])) + """ + +def get_next_episodes(item_id, limit): + + ''' Only for synced content. + ''' + with Database('emby') as embydb: + + db = emby_db.EmbyDatabase(embydb.cursor) + library = db.get_view_name(item_id) + + if not library: + return + + result = JSONRPC('VideoLibrary.GetTVShows').execute({ + 'sort': {'order': "descending", 'method': "lastplayed"}, + 'filter': { + 'and': [ + {'operator': "true", 'field': "inprogress", 'value': ""}, + {'operator': "is", 'field': "tag", 'value': "%s" % library} + ]}, + 'properties': ['title', 'studio', 'mpaa', 'file', 'art'] + }) + + try: + items = result['result']['tvshows'] + except (KeyError, TypeError): + return + + list_li = [] + + for item in items: + if settings('ignoreSpecialsNextEpisodes.bool'): + params = { + 'tvshowid': item['tvshowid'], + 'sort': {'method': "episode"}, + 'filter': { + 'and': [ + {'operator': "lessthan", 'field': "playcount", 'value': "1"}, + {'operator': "greaterthan", 'field': "season", 'value': "0"} + ]}, + 'properties': [ + "title", "playcount", "season", "episode", "showtitle", + "plot", "file", "rating", "resume", "tvshowid", "art", + "streamdetails", "firstaired", "runtime", "writer", + "dateadded", "lastplayed" + ], + 'limits': {"end": 1} + } + else: + params = { + 'tvshowid': item['tvshowid'], + 'sort': {'method': "episode"}, + 'filter': {'operator': "lessthan", 'field': "playcount", 'value': "1"}, + 'properties': [ + "title", "playcount", "season", "episode", "showtitle", + "plot", "file", "rating", "resume", "tvshowid", "art", + "streamdetails", "firstaired", "runtime", "writer", + "dateadded", "lastplayed" + ], + 'limits': {"end": 1} + } + + result = JSONRPC('VideoLibrary.GetEpisodes').execute(params) + + try: + episodes = result['result']['episodes'] + except (KeyError, TypeError): + pass + else: + for episode in episodes: + + li = create_listitem(episode) + list_li.append((episode['file'], li)) + + if len(list_li) == limit: + break + + xbmcplugin.addDirectoryItems(int(sys.argv[1]), list_li, len(list_li)) + xbmcplugin.setContent(int(sys.argv[1]), 'episodes') + xbmcplugin.endOfDirectory(int(sys.argv[1])) + +def create_listitem(item): + + ''' Listitem based on jsonrpc items. + ''' + title = item['title'] + label2 = "" + li = xbmcgui.ListItem(title) + li.setProperty('IsPlayable', "true") + + metadata = { + 'Title': title, + 'duration': str(item['runtime']/60), + 'Plot': item['plot'], + 'Playcount': item['playcount'] + } + + if "showtitle" in item: + metadata['TVshowTitle'] = item['showtitle'] + label2 = item['showtitle'] + + if "episodeid" in item: + # Listitem of episode + metadata['mediatype'] = "episode" + metadata['dbid'] = item['episodeid'] + + # TODO: Review once Krypton is RC - probably no longer needed if there's dbid + if "episode" in item: + episode = item['episode'] + metadata['Episode'] = episode + + if "season" in item: + season = item['season'] + metadata['Season'] = season + + if season and episode: + episodeno = "s%.2de%.2d" % (season, episode) + li.setProperty('episodeno', episodeno) + label2 = "%s - %s" % (label2, episodeno) if label2 else episodeno + + if "firstaired" in item: + metadata['Premiered'] = item['firstaired'] + + if "rating" in item: + metadata['Rating'] = str(round(float(item['rating']),1)) + + if "director" in item: + metadata['Director'] = " / ".join(item['director']) + + if "writer" in item: + metadata['Writer'] = " / ".join(item['writer']) + + if "cast" in item: + cast = [] + castandrole = [] + for person in item['cast']: + name = person['name'] + cast.append(name) + castandrole.append((name, person['role'])) + metadata['Cast'] = cast + metadata['CastAndRole'] = castandrole + + li.setLabel2(label2) + li.setInfo(type="Video", infoLabels=metadata) + li.setProperty('resumetime', str(item['resume']['position'])) + li.setProperty('totaltime', str(item['resume']['total'])) + li.setArt(item['art']) + li.setThumbnailImage(item['art'].get('thumb','')) + li.setIconImage('DefaultTVShows.png') + li.setProperty('dbid', str(item['episodeid'])) + li.setProperty('fanart_image', item['art'].get('tvshow.fanart','')) + + for key, value in item['streamdetails'].iteritems(): + for stream in value: + li.addStreamInfo(key, stream) + + return li + +def add_user(): + + ''' Add or remove users from the default server session. + ''' + if not window('emby_online.bool'): + return + + session = TheVoid('GetSession', {}).get() + users = TheVoid('GetUsers', {'IsDisabled': False, 'IsHidden': False}).get() + current = session[0]['AdditionalUsers'] + + result = dialog("select", _(33061), [_(33062), _(33063)] if current else [_(33062)]) + + if result < 0: + return + + if not result: # Add user + eligible = [x for x in users if x['Id'] not in [current_user['UserId'] for current_user in current]] + resp = dialog("select", _(33064), [x['Name'] for x in eligible]) + + if resp < 0: + return + + user = eligible[resp] + event('AddUser', {'Id': user['Id'], 'Add': True}) + else: # Remove user + resp = dialog("select", _(33064), [x['UserName'] for x in current]) + + if resp < 0: + return + + user = current[resp] + event('AddUser', {'Id': user['UserId'], 'Add': False}) + +def get_themes(): + + ''' Add theme media locally, via strm. This is only for tv tunes. + If another script is used, adjust this code. + ''' + from helper.utils import normalize_string + from helper.playutils import PlayUtils + from helper.xmls import tvtunes_nfo + + library = xbmc.translatePath("special://profile/addon_data/plugin.video.emby/library/").decode('utf-8') + play = settings('useDirectPaths') == "1" + + if not xbmcvfs.exists(library): + xbmcvfs.mkdir(library) + + if xbmc.getCondVisibility('System.HasAddon(script.tvtunes)'): + + tvtunes = xbmcaddon.Addon(id="script.tvtunes") + tvtunes.setSetting('custom_path_enable', "true") + tvtunes.setSetting('custom_path', library) + LOG.info("TV Tunes custom path is enabled and set.") + else: + dialog("ok", heading="{emby}", line1=_(33152)) + + return + + with Database('emby') as embydb: + all_views = emby_db.EmbyDatabase(embydb.cursor).get_views() + views = [x[0] for x in all_views if x[2] in ('movies', 'tvshows', 'mixed')] + + + items = {} + server = TheVoid('GetServerAddress', {'ServerId': None}).get() + token = TheVoid('GetToken', {'ServerId': None}).get() + + for view in views: + result = TheVoid('GetThemes', {'Type': "Video", 'Id': view}).get() + + for item in result['Items']: + + folder = normalize_string(item['Name'].encode('utf-8')) + items[item['Id']] = folder + + result = TheVoid('GetThemes', {'Type': "Song", 'Id': view}).get() + + for item in result['Items']: + + folder = normalize_string(item['Name'].encode('utf-8')) + items[item['Id']] = folder + + for item in items: + + nfo_path = os.path.join(library, items[item]) + nfo_file = os.path.join(nfo_path, "tvtunes.nfo") + + if not xbmcvfs.exists(nfo_path): + xbmcvfs.mkdir(nfo_path) + + themes = TheVoid('GetTheme', {'Id': item}).get() + paths = [] + + for theme in themes['ThemeVideosResult']['Items'] + themes['ThemeSongsResult']['Items']: + putils = PlayUtils(theme, False, None, server, token) + + if play: + paths.append(putils.direct_play(theme['MediaSources'][0]).encode('utf-8')) + else: + paths.append(putils.direct_url(theme['MediaSources'][0]).encode('utf-8')) + + tvtunes_nfo(nfo_file, paths) + + dialog("notification", heading="{emby}", message=_(33153), icon="{emby}", time=1000, sound=False) diff --git a/resources/lib/entrypoint/service.py b/resources/lib/entrypoint/service.py new file mode 100644 index 00000000..46d2aa18 --- /dev/null +++ b/resources/lib/entrypoint/service.py @@ -0,0 +1,461 @@ +# -*- coding: utf-8 -*- + +################################################################################################# + +import _strptime # Workaround for threads using datetime: _striptime is locked +import json +import logging +import sys +from datetime import datetime + +import xbmc +import xbmcgui + +import connect +import client +import library +import setup +import monitor +import objects.utils +from libraries import requests +from views import Views, verify_kodi_defaults +from helper import _, window, settings, event, dialog, find +from objects import version +from downloader import get_objects +from emby import Emby + +################################################################################################# + +LOG = logging.getLogger("EMBY."+__name__) + +################################################################################################# + + +class Service(xbmc.Monitor): + + running = True + library_thread = None + monitor = None + play_event = None + warn = True + settings = {'last_progress': datetime.today()} + + + def __init__(self): + + self.settings['profile'] = xbmc.translatePath('special://profile') + self.settings['mode'] = settings('useDirectPaths') + self.settings['log_level'] = settings('logLevel') or "1" + self.settings['enable_context'] = settings('enableContext.bool') + self.settings['enable_context_transcode'] = settings('enableContextTranscode.bool') + self.settings['kodi_companion'] = settings('kodiCompanion.bool') + window('emby_logLevel', value=str(self.settings['log_level'])) + window('emby_kodiProfile', value=self.settings['profile']) + + if self.settings['enable_context']: + window('emby_context', value="true") + if self.settings['enable_context_transcode']: + window('emby_context_transcode', value="true") + + LOG.warn("--->>>[ %s ]", client.get_addon_name()) + LOG.warn("Version: %s", client.get_version()) + LOG.warn("KODI Version: %s", xbmc.getInfoLabel('System.BuildVersion')) + LOG.warn("Platform: %s", client.get_platform()) + LOG.warn("Python Version: %s", sys.version) + LOG.warn("Using dynamic paths: %s", settings('useDirectPaths') == "0") + LOG.warn("Log Level: %s", self.settings['log_level']) + + verify_kodi_defaults() + Views().get_nodes() + window('emby.connected.bool', True) + + xbmc.Monitor.__init__(self) + self.check_update() + settings('groupedSets.bool', objects.utils.get_grouped_set()) + + def service(self): + + ''' Keeps the service monitor going. + Exit on Kodi shutdown or profile switch. + + if profile switch happens more than once, + Threads depending on abortRequest will not trigger. + ''' + self.monitor = monitor.Monitor() + self.connect = connect.Connect() + self.start_default() + + self.settings['mode'] = settings('useDirectPaths') + + while self.running: + if window('emby_online.bool'): + + if self.settings['profile'] != window('emby_kodiProfile'): + LOG.info("[ profile switch ] %s", self.settings['profile']) + + break + + if self.monitor.player.isPlaying(): + difference = datetime.today() - self.settings['last_progress'] + + if difference.seconds > 270: + + event("ReportProgressRequested", None) + self.settings['last_progress'] = datetime.today() + + if self.waitForAbort(1): + break + + self.shutdown() + + def start_default(self): + + try: + self.connect.register() + setup.Setup() + except Exception as error: + LOG.error(error) + + def stop_default(self): + + window('emby_online', clear=True) + Emby().close() + + if self.library_thread is not None: + + self.library_thread.stop_client() + self.library_thread = None + + + def check_update(self): + + ''' Check for objects build version and compare. + This pulls a dict that contains all the information for the build needed. + ''' + LOG.info("--[ check updates ]") + kodi = xbmc.getInfoLabel('System.BuildVersion') + url = "https://sheets.googleapis.com/v4/spreadsheets/1cKWQCVL0lVONulO2KyGzBilzhGvsyuSjFvrqe8g6nJw/values/A2:B?key=AIzaSyAP-1mcBglk9zIofJlqGpvKXkff3GRMhdI" + + try: + self.versions = {k.lower(): v for k, v in dict(requests.get(url).json()['values']).items()} + build = find(self.versions, kodi) + + if not build: + raise Exception("build %s incompatible?!", kodi) + + label, zipfile = build.split('-', 1) + + if label == version: + LOG.info("--[ objects/%s ]", version) + + return + + get_objects(zipfile, label + '.zip') + except Exception as error: + + LOG.info(error) + self.shutdown() + + return + + dialog("ok", heading="{emby}", line1=_(33135)) + xbmc.executebuiltin('RestartApp') + + def onNotification(self, sender, method, data): + + ''' All notifications are sent via NotifyAll built-in or Kodi. + Central hub. + ''' + if sender.lower() not in ('plugin.video.emby', 'xbmc'): + return + + if sender == 'plugin.video.emby': + method = method.split('.')[1] + + if method not in ('ServerUnreachable', 'ServerShuttingDown', 'UserDataChanged', 'ServerConnect', + 'LibraryChanged', 'ServerOnline', 'SyncLibrary', 'RepairLibrary', 'RemoveLibrary', + 'EmbyConnect', 'SyncLibrarySelection', 'RepairLibrarySelection', 'AddServer', + 'Unauthorized', 'UpdateServer', 'UserConfigurationUpdated', 'ServerRestarting'): + return + + data = json.loads(data)[0] + else: + if method not in ('System.OnQuit', 'System.OnSleep', 'System.OnWake'): + return + + data = json.loads(data) + + LOG.debug("[ %s: %s ] %s", sender, method, json.dumps(data, indent=4)) + + if method == 'ServerOnline': + if data['ServerId'] is None: + + window('emby_online.bool', True) + self.warn = True + + if self.library_thread is None: + + self.library_thread = library.Library(self) + self.library_thread.start() + + elif method in ('ServerUnreachable', 'ServerShuttingDown'): + + if self.warn or data.get('ServerId'): + + self.warn = data.get('ServerId') is not None + dialog("notification", heading="{emby}", message=_(33146) if data.get('ServerId') is None else _(33149), icon=xbmcgui.NOTIFICATION_ERROR) + + if data.get('ServerId') is None: + self.stop_default() + + if self.waitForAbort(20): + return + + self.start_default() + + elif method == 'Unauthorized': + dialog("notification", heading="{emby}", message=_(33147) if data['ServerId'] is None else _(33148), icon=xbmcgui.NOTIFICATION_ERROR) + + elif method == 'ServerRestarting': + if data.get('ServerId'): + return + + if settings('restartMsg.bool'): + dialog("notification", heading="{emby}", message=_(33006), icon="{emby}") + + self.stop_default() + + if self.waitForAbort(10): + return + + self.start_default() + + elif method == 'ServerConnect': + self.connect.register(data['Id']) + xbmc.executebuiltin("Container.Refresh") + + elif method == 'EmbyConnect': + self.connect.setup_login_connect() + + elif method == 'AddServer': + + self.connect.setup_manual_server() + xbmc.executebuiltin("Container.Refresh") + + elif method == 'RemoveServer': + + self.connect.remove_server(data['Id']) + xbmc.executebuiltin("Container.Refresh") + + elif method == 'UpdateServer': + + dialog("ok", heading="{emby}", line1=_(33151)) + self.connect.setup_manual_server() + + elif method == 'UserDataChanged' and self.library_thread: + if data.get('ServerId'): + return + + self.library_thread.userdata(data['UserDataList']) + + elif method == 'LibraryChanged' and self.library_thread: + if data.get('ServerId'): + return + + self.library_thread.updated(data['ItemsUpdated'] + data['ItemsAdded']) + self.library_thread.removed(data['ItemsRemoved']) + + elif method == 'System.OnQuit': + window('emby_should_stop.bool', True) + self.running = False + + elif method in ('SyncLibrarySelection', 'RepairLibrarySelection'): + self.library_thread.select_libraries('SyncLibrary' if method == 'SyncLibrarySelection' else 'RepairLibrary') + + elif method == 'SyncLibrary': + libraries = data['Id'].split(',') + + for lib in libraries: + self.library_thread.add_library(lib) + + xbmc.executebuiltin("Container.Refresh") + + elif method == 'RepairLibrary': + libraries = data['Id'].split(',') + + for lib in libraries: + self.library_thread.remove_library(lib) + self.library_thread.add_library(lib) + + xbmc.executebuiltin("Container.Refresh") + + elif method == 'RemoveLibrary': + libraries = data['Id'].split(',') + + for lib in libraries: + self.library_thread.remove_library(lib) + + xbmc.executebuiltin("Container.Refresh") + + elif method == 'System.OnSleep': + LOG.info("-->[ sleep ]") + + if self.library_thread is not None: + + self.library_thread.stop_client() + self.library_thread = None + + Emby.close_all() + + elif method == 'System.OnWake': + + LOG.info("--<[ sleep ]") + xbmc.sleep(10000)# Allow network to wake up + + try: + self.connect.register() + except Exception as error: + LOG.error(error) + + elif method == 'GUI.OnScreensaverDeactivated': + + LOG.info("--<[ screensaver ]") + xbmc.sleep(5000) + + if self.library_thread is not None: + self.library_thread.fast_sync() + + elif method == 'UserConfigurationUpdated': + + if data.get('ServerId') is None: + Views().get_views() + + def onSettingsChanged(self): + + ''' React to setting changes that impact window values. + ''' + if window('emby_should_stop.bool'): + return + + if settings('logLevel') != self.settings['log_level']: + + log_level = settings('logLevel') + window('emby_logLevel', str(log_level)) + self.settings['logLevel'] = log_level + LOG.warn("New log level: %s", log_level) + + if settings('enableContext.bool') != self.settings['enable_context']: + + window('emby_context', settings('enableContext')) + self.settings['enable_context'] = settings('enableContext.bool') + LOG.warn("New context setting: %s", self.settings['enable_context']) + + if settings('enableContextTranscode.bool') != self.settings['enable_context_transcode']: + + window('emby_context_transcode', settings('enableContextTranscode')) + self.settings['enable_context_transcode'] = settings('enableContextTranscode.bool') + LOG.warn("New context transcode setting: %s", self.settings['enable_context_transcode']) + + if settings('useDirectPaths') != self.settings['mode'] and self.library_thread.started: + + self.settings['mode'] = settings('useDirectPaths') + LOG.warn("New playback mode setting: %s", self.settings['mode']) + + if not self.settings.get('mode_warn'): + + self.settings['mode_warn'] = True + dialog("yesno", heading="{emby}", line1=_(33118)) + + if settings('kodiCompanion.bool') != self.settings['kodi_companion']: + self.settings['kodi_companion'] = settings('kodiCompanion.bool') + + if not self.settings['kodi_companion']: + dialog("ok", heading="{emby}", line1=_(33138)) + + def shutdown(self): + + LOG.warn("---<[ EXITING ]") + + properties = [ # TODO: review + "emby_state", "emby_serverStatus", + "emby_syncRunning", "emby_dbCheck", + "emby_currUser", "emby_dbScan", + "emby_initialScan", "emby_playbackProps", + + "emby_play", "emby_online", "emby.connected", "emby_should_stop", "emby.resume", + "emby.external", "emby.external_check" + ] + for prop in properties: + window(prop, clear=True) + + Emby.close_all() + + if self.library_thread is not None: + self.library_thread.stop_client() + + if self.monitor is not None: + self.monitor.listener.stop() + + LOG.warn("---<<<[ %s ]", client.get_addon_name()) + + + + + + + + + + + + + + +""" + if window('emby_online') == "true": + + # Emby server is online + # Verify if user is set and has access to the server + if user_client.get_user() is not None and user_client.get_access(): + + if self.kodi_player.isPlaying(): + self._report_progress() + + # If an item is playing + if not self.startup: + self.startup = self._startup() + + if not self.websocket_running: + # Start the Websocket Client + self.websocket_running = True + self.websocket_thread.start() + if not self.library_running: + # Start the syncing thread + self.library_running = True + self.library_thread.start() + if not self.capabitilities and user_client.post_capabilities(): + self.capabitilities = True + + if self.monitor.waitForAbort(15): + # Abort was requested while waiting. We should exit + break + else: + + if (user_client.get_user() is None) and self.warn_auth: + # Alert user is not authenticated and suppress future warning + self.warn_auth = False + log.info("Not authenticated yet.") + + # User access is restricted. + # Keep verifying until access is granted + # unless server goes offline or Kodi is shut down. + self._access_check() + else: + # Wait until Emby server is online + # or Kodi is shut down. + self._server_online_check() + + if self.monitor.waitForAbort(1): + # Abort was requested while waiting. We should exit + break +""" \ No newline at end of file diff --git a/resources/lib/librarysync.py b/resources/lib/full_sync.py similarity index 54% rename from resources/lib/librarysync.py rename to resources/lib/full_sync.py index ce19584c..6fc85ce2 100644 --- a/resources/lib/librarysync.py +++ b/resources/lib/full_sync.py @@ -1,875 +1,802 @@ -# -*- coding: utf-8 -*- - -################################################################################################## - -import logging -import sqlite3 -import threading -from datetime import datetime, timedelta, time - -import xbmc -import xbmcgui -import xbmcvfs - -import api -import utils -import clientinfo -import database -import downloadutils -import itemtypes -import emby as mb -import embydb_functions as embydb -import read_embyserver as embyserver -import userclient -import views -from objects import Movies, MusicVideos, TVShows, Music -from utils import window, settings, language as lang, should_stop -#from ga_client import GoogleAnalytics - -################################################################################################## - -log = logging.getLogger("EMBY."+__name__) - -################################################################################################## - -class LibrarySync(threading.Thread): - - _shared_state = {} - - isFastSync = False - - stop_thread = False - suspend_thread = False - - # Track websocketclient updates - addedItems = [] - updateItems = [] - userdataItems = [] - removeItems = [] - forceLibraryUpdate = False - incremental_count = 0 - refresh_views = False - - - def __init__(self): - - self.__dict__ = self._shared_state - self.monitor = xbmc.Monitor() - - self.clientInfo = clientinfo.ClientInfo() - self.doUtils = downloadutils.DownloadUtils().downloadUrl - self.user = userclient.UserClient() - self.emby = embyserver.Read_EmbyServer() - self.kodi_version = int(xbmc.getInfoLabel('System.BuildVersion')[:2]) - - threading.Thread.__init__(self) - - - def progressDialog(self, title): - - dialog = None - - dialog = xbmcgui.DialogProgressBG() - dialog.create("Emby for Kodi", title) - log.debug("Show progress dialog: %s" % title) - - return dialog - - def startSync(self): - - #ga = GoogleAnalytics() - - # Run at start up - optional to use the server plugin - if settings('SyncInstallRunDone') == "true": - # Validate views - self.refreshViews() - completed = False - # Verify if server plugin is installed. - if settings('serverSync') == "true": - # Try to use fast start up - url = "{server}/emby/Plugins?format=json" - - try: - result = self.doUtils(url) - except Exception as error: - log.info("Error getting plugin list form server: " + str(error)) - result = [] - - for plugin in result: - if plugin['Name'] in ("Emby.Kodi Sync Queue", "Kodi companion"): - log.debug("Found server plugin.") - self.isFastSync = True - #ga.sendEventData("SyncAction", "FastSync") - completed = self.fastSync() - break - - if not completed: - # Fast sync failed or server plugin is not found - #ga.sendEventData("SyncAction", "Sync") - completed = ManualSync().sync() - else: - # Install sync is not completed - #ga.sendEventData("SyncAction", "FullSync") - completed = self.fullSync() - - return completed - - def fastSync(self): - - lastSync = settings('LastIncrementalSync') - if not lastSync: - lastSync = "2010-01-01T00:00:00Z" - - lastSyncTime = utils.convertDate(lastSync) - log.info("Last sync run: %s" % lastSyncTime) - - # get server RetentionDateTime - try: - result = self.doUtils("{server}/emby/Emby.Kodi.SyncQueue/GetServerDateTime?format=json") - retention_time = result['RetentionDateTime'] - except Exception as error: - log.error(error) - retention_time = "2010-01-01T00:00:00Z" - - retention_time = utils.convertDate(retention_time) - log.info("RetentionDateTime: %s" % retention_time) - - # if last sync before retention time do a full sync - if retention_time > lastSyncTime: - log.info("Fast sync server retention insufficient, fall back to full sync") - return False - - params = {'LastUpdateDT': lastSync} - if settings('enableMusic') != "true": - params['filter'] = "music" - url = "{server}/emby/Emby.Kodi.SyncQueue/{UserId}/GetItems?format=json" - - try: - result = self.doUtils(url, parameters=params) - processlist = { - - 'added': result['ItemsAdded'], - 'update': result['ItemsUpdated'], - 'userdata': result['UserDataChanged'], - 'remove': result['ItemsRemoved'] - } - - except Exception as error: # To be reviewed to only catch specific errors. - log.error(error) - log.error("Failed to retrieve latest updates using fast sync.") - xbmcgui.Dialog().ok(lang(29999), lang(33095)) - return False - - else: - log.info("Fast sync changes: %s" % result) - for action in processlist: - self.triage_items(action, processlist[action]) - return True - - def saveLastSync(self): - - # Save last sync time - overlap = 2 - - try: # datetime fails when used more than once, TypeError - if self.isFastSync: - result = self.doUtils("{server}/emby/Emby.Kodi.SyncQueue/GetServerDateTime?format=json") - server_time = result['ServerDateTime'] - server_time = utils.convertDate(server_time) - else: - raise Exception("Fast sync server plugin is not enabled.") - - except Exception as e: - # If the server plugin is not installed or an error happened. - log.debug("An exception occurred: %s" % e) - time_now = datetime.utcnow()-timedelta(minutes=overlap) - lastSync = time_now.strftime('%Y-%m-%dT%H:%M:%SZ') - log.info("New sync time: client time -%s min: %s" % (overlap, lastSync)) - - else: - lastSync = (server_time - timedelta(minutes=overlap)).strftime('%Y-%m-%dT%H:%M:%SZ') - log.info("New sync time: server time -%s min: %s" % (overlap, lastSync)) - - finally: - settings('LastIncrementalSync', value=lastSync) - - def dbCommit(self, connection): - # Central commit, verifies if Kodi database update is running - kodidb_scan = window('emby_kodiScan') == "true" - count = 0 - - while kodidb_scan: - - log.info("Kodi scan is running. Waiting...") - kodidb_scan = window('emby_kodiScan') == "true" - - if count == 10: - log.info("Flag still active, but will try to commit") - window('emby_kodiScan', clear=True) - - if should_stop(): - log.info("Commit unsuccessful. Sync terminated.") - break - - if self.monitor.waitForAbort(1): - # Abort was requested while waiting. We should exit - log.info("Commit unsuccessful.") - break - - count += 1 - - try: - connection.commit() - log.info("Commit successful.") - except sqlite3.OperationalError as error: - log.error(error) - if "database is locked" in error: - log.info("retrying...") - window('emby_kodiScan', value="true") - self.dbCommit(connection) - - def fullSync(self, manualrun=False, repair=False): - # Only run once when first setting up. Can be run manually. - music_enabled = settings('enableMusic') == "true" - - if settings('dbSyncScreensaver') == "true": - - xbmc.executebuiltin('InhibitIdleShutdown(true)') - screensaver = utils.getScreensaver() - utils.setScreensaver(value="") - - window('emby_dbScan', value="true") - # Add sources - utils.sourcesXML() - - # use emby and video DBs - with database.DatabaseConn('emby') as cursor_emby: - with database.DatabaseConn('video') as cursor_video: - # content sync: movies, tvshows, musicvideos, music - - if manualrun: - message = "Manual sync" - elif repair: - message = "Repair sync" - repair_list = [] - choices = ['all', 'movies', 'musicvideos', 'tvshows'] - if music_enabled: - choices.append('music') - - if self.kodi_version > 15: - # Jarvis or higher - types = xbmcgui.Dialog().multiselect(lang(33094), choices) - if types is None: - pass - elif 0 in types: # all - choices.pop(0) - repair_list.extend(choices) - else: - for index in types: - repair_list.append(choices[index]) - else: - resp = xbmcgui.Dialog().select(lang(33094), choices) - if resp == 0: # all - choices.pop(resp) - repair_list.extend(choices) - else: - repair_list.append(choices[resp]) - - log.info("Repair queued for: %s", repair_list) - else: - message = "Initial sync" - window('emby_initialScan', value="true") - - pDialog = self.progressDialog("%s" % message) - starttotal = datetime.now() - - # Set views - views.Views(cursor_emby, cursor_video).maintain() - cursor_emby.connection.commit() - #self.maintainViews(cursor_emby, cursor_video) - - # Sync video library - process = { - - 'movies': self.movies, - 'boxsets': self.boxsets, - 'musicvideos': self.musicvideos, - 'tvshows': self.tvshows - } - for itemtype in process: - - if repair and itemtype not in repair_list: - continue - - startTime = datetime.now() - completed = process[itemtype](cursor_emby, cursor_video, pDialog) - if not completed: - xbmc.executebuiltin('InhibitIdleShutdown(false)') - utils.setScreensaver(value=screensaver) - window('emby_dbScan', clear=True) - if pDialog: - pDialog.close() - - return False - else: - elapsedTime = datetime.now() - startTime - log.info("SyncDatabase (finished %s in: %s)" - % (itemtype, str(elapsedTime).split('.')[0])) - - - # sync music - # use emby and music - if music_enabled: - if repair and 'music' not in repair_list: - pass - else: - with database.DatabaseConn('emby') as cursor_emby: - with database.DatabaseConn('music') as cursor_music: - startTime = datetime.now() - completed = self.music(cursor_emby, cursor_music, pDialog) - if not completed: - xbmc.executebuiltin('InhibitIdleShutdown(false)') - utils.setScreensaver(value=screensaver) - window('emby_dbScan', clear=True) - if pDialog: - pDialog.close() - - return False - else: - elapsedTime = datetime.now() - startTime - log.info("SyncDatabase (finished music in: %s)" - % (str(elapsedTime).split('.')[0])) - - if pDialog: - pDialog.close() - - with database.DatabaseConn('emby') as cursor_emby: - emby_db = embydb.Embydb_Functions(cursor_emby) - current_version = emby_db.get_version(self.clientInfo.get_version()) - - window('emby_version', current_version) - - settings('SyncInstallRunDone', value="true") - - self.saveLastSync() - xbmc.executebuiltin('UpdateLibrary(video)') - elapsedtotal = datetime.now() - starttotal - - if settings('dbSyncScreensaver') == "true": - - xbmc.executebuiltin('InhibitIdleShutdown(false)') - utils.setScreensaver(value=screensaver) - - window('emby_dbScan', clear=True) - window('emby_initialScan', clear=True) - - xbmcgui.Dialog().notification( - heading=lang(29999), - message="%s %s %s" % - (message, lang(33025), str(elapsedtotal).split('.')[0]), - icon="special://home/addons/plugin.video.emby/icon.png", - sound=False) - - return True - - - def refreshViews(self): - - with database.DatabaseConn('emby') as cursor_emby: - with database.DatabaseConn() as cursor_video: - # Compare views, assign correct tags to items - views.Views(cursor_emby, cursor_video).maintain() - - def offline_mode_views(self): - - with database.DatabaseConn('emby') as cursor_emby: - with database.DatabaseConn() as cursor_video: - views.Views(cursor_emby, cursor_video).offline_mode() - - def movies(self, embycursor, kodicursor, pdialog): - - # Get movies from emby - emby_db = embydb.Embydb_Functions(embycursor) - movies = Movies(embycursor, kodicursor, pdialog) - - views = emby_db.getView_byType('movies') - views += emby_db.getView_byType('mixed') - log.info("Media folders: %s" % views) - - ##### PROCESS MOVIES ##### - for view in views: - - log.info("Processing: %s", view) - view_name = view['name'] - - # Get items per view - if pdialog: - pdialog.update( - heading=lang(29999), - message="%s %s..." % (lang(33017), view_name)) - - movies.count = 0 - for all_movies in mb.get_items(view['id'], "Movie"): - - if should_stop(): - return False - - movies.add_all("Movie", all_movies, view) - - log.debug("Movies finished.") - return True - - def boxsets(self, embycursor, kodicursor, pdialog): - - movies = Movies(embycursor, kodicursor, pdialog) - - if pdialog: - pdialog.update(heading=lang(29999), message=lang(33018)) - - movies.count = 0 - for boxsets in mb.get_items(None, "BoxSet"): - - if should_stop(): - return False - - movies.add_all("BoxSet", boxsets) - - log.debug("Boxsets finished.") - return True - - def musicvideos(self, embycursor, kodicursor, pdialog): - - # Get musicvideos from emby - emby_db = embydb.Embydb_Functions(embycursor) - mvideos = MusicVideos(embycursor, kodicursor, pdialog) - - views = emby_db.getView_byType('musicvideos') - log.info("Media folders: %s" % views) - - for view in views: - log.info("Processing: %s", view) - - # Get items per view - viewName = view['name'] - - if pdialog: - pdialog.update( - heading=lang(29999), - message="%s %s..." % (lang(33019), viewName)) - - # Initial or repair sync - mvideos.count = 0 - for all_mvideos in mb.get_items(view['id'], "MusicVideo"): - - if should_stop(): - return False - - mvideos.add_all("MusicVideo", all_mvideos, view) - - else: - log.debug("MusicVideos finished.") - - return True - - def tvshows(self, embycursor, kodicursor, pdialog): - - # Get shows from emby - emby_db = embydb.Embydb_Functions(embycursor) - tvshows = TVShows(embycursor, kodicursor, pdialog) - - views = emby_db.getView_byType('tvshows') - views += emby_db.getView_byType('mixed') - log.info("Media folders: %s" % views) - - for view in views: - - # Get items per view - if pdialog: - pdialog.update( - heading=lang(29999), - message="%s %s..." % (lang(33020), view['name'])) - - tvshows.count = 0 - for all_tvshows in mb.get_items(view['id'], "Series"): - - if should_stop(): - return False - - tvshows.add_all("Series", all_tvshows, view) - - else: - log.debug("TVShows finished.") - - return True - - def music(self, embycursor, kodicursor, pdialog): - # Get music from emby - emby_db = embydb.Embydb_Functions(embycursor) - music = Music(embycursor, kodicursor, pdialog) - - views = emby_db.getView_byType('music') - log.info("Media folders: %s", views) - - # Add music artists and everything will fall into place - if pdialog: - pdialog.update(heading=lang(29999), - message="%s Music..." % lang(33021)) - - for view in views: - - music.count = 0 - for all_artists in mb.get_artists(view['id']): - - if should_stop(): - return False - - music.add_all("MusicArtist", all_artists) - - log.debug("Finished syncing music") - - return True - - # Reserved for websocket_client.py and fast start - def triage_items(self, process, items): - - processlist = { - - 'added': self.addedItems, - 'update': self.updateItems, - 'userdata': self.userdataItems, - 'remove': self.removeItems - } - if items: - if process == "userdata": - itemids = [] - for item in items: - itemids.append(item['ItemId']) - items = itemids - - log.info("Queue %s: %s" % (process, items)) - processlist[process].extend(items) - - def incrementalSync(self): - - self.incremental_count += 1 - update_embydb = False - pDialog = None - - # do a view update if needed - if self.refresh_views: - self.refreshViews() - self.refresh_views = False - self.forceLibraryUpdate = True - - # do a lib update if any items in list - totalUpdates = len(self.addedItems) + len(self.updateItems) + len(self.userdataItems) + len(self.removeItems) - if totalUpdates > 0 and window('emby_kodiScan') != "true": - with database.DatabaseConn('emby') as cursor_emby: - with database.DatabaseConn('video') as cursor_video: - - if settings('dbSyncScreensaver') == "true": - - xbmc.executebuiltin('InhibitIdleShutdown(true)') - screensaver = utils.getScreensaver() - utils.setScreensaver(value="") - - emby_db = embydb.Embydb_Functions(cursor_emby) - - incSyncIndicator = int(settings('incSyncIndicator') or 10) - if incSyncIndicator != -1 and totalUpdates > incSyncIndicator: - # Only present dialog if we are going to process items - pDialog = self.progressDialog('Incremental sync') - log.info("incSyncIndicator=" + str(incSyncIndicator) + " totalUpdates=" + str(totalUpdates)) - - process = { - - 'added': self.addedItems, - 'update': self.updateItems, - 'userdata': self.userdataItems, - 'remove': self.removeItems - } - for process_type in ['added', 'update', 'userdata', 'remove']: - - if process[process_type]: - - listItems = list(process[process_type]) - del process[process_type][:] # Reset class list - - items_process = itemtypes.Items(cursor_emby, cursor_video) - update = False - - # Prepare items according to process process_type - if process_type == "added": - items = mb.sortby_mediatype(listItems) - - elif process_type in ("userdata", "remove"): - items = emby_db.sortby_mediaType(listItems, unsorted=False) - - else: - items = emby_db.sortby_mediaType(listItems) - if items.get('Unsorted'): - sorted_items = mb.sortby_mediatype(items['Unsorted']) - doupdate = items_process.itemsbyId(sorted_items, "added", pDialog) - if doupdate: - embyupdate, kodiupdate_video = doupdate - if embyupdate: - update_embydb = True - if kodiupdate_video: - self.forceLibraryUpdate = True - del items['Unsorted'] - - doupdate = items_process.itemsbyId(items, process_type, pDialog) - if doupdate: - embyupdate, kodiupdate_video = doupdate - if embyupdate: - update_embydb = True - if kodiupdate_video: - self.forceLibraryUpdate = True - - if settings('dbSyncScreensaver') == "true": - - xbmc.executebuiltin('InhibitIdleShutdown(false)') - utils.setScreensaver(value=screensaver) - - # if stuff happened then do some stuff - if update_embydb: - update_embydb = False - log.info("Updating emby database.") - self.saveLastSync() - - if self.forceLibraryUpdate: - # Force update the Kodi library - self.forceLibraryUpdate = False - - log.info("Updating video library.") - self.incremental_count = 0 - window('emby_kodiScan', value="true") - xbmc.executebuiltin('UpdateLibrary(video)') - - if pDialog: - pDialog.close() - - def compareDBVersion(self, current, minimum): - # It returns True is database is up to date. False otherwise. - log.info("current: %s minimum: %s" % (current, minimum)) - - try: - currMajor, currMinor, currPatch = current.split(".") - minMajor, minMinor, minPatch = minimum.split(".") - except ValueError as error: - raise ValueError("Unable to compare versions: %s, %s" % (current, minimum)) - - if currMajor > minMajor: - return True - elif currMajor == minMajor and (currMinor > minMinor or - (currMinor == minMinor and currPatch >= minPatch)): - return True - else: - # Database out of date. - return False - - def run(self): - - try: - self.run_internal() - except Warning as e: - if "restricted" in e: - pass - elif "401" in e: - pass - except Exception as e: - """ - ga = GoogleAnalytics() - errStrings = ga.formatException() - if not (hasattr(e, 'quiet') and e.quiet): - ga.sendEventData("Exception", errStrings[0], errStrings[1]) - """ - window('emby_dbScan', clear=True) - log.exception(e) - xbmcgui.Dialog().ok( - heading=lang(29999), - line1=( - "Library sync thread has exited! " - "You should restart Kodi now. " - "Please report this on the forum.")) - #line2=(errStrings[0] + " (" + errStrings[1] + ")")) - - def run_internal(self): - - dialog = xbmcgui.Dialog() - - startupComplete = False - - log.warn("---===### Starting LibrarySync ###===---") - if utils.verify_advancedsettings(): - # Advancedsettings was modified, Kodi needs to restart - log.warn("###===--- LibrarySync Aborted ---===###") - return - - while not self.monitor.abortRequested(): - - # In the event the server goes offline - while self.suspend_thread: - # Set in service.py - if self.monitor.waitForAbort(5): - # Abort was requested while waiting. We should exit - break - - if (window('emby_dbCheck') != "true" and settings('SyncInstallRunDone') == "true"): - # Verify the validity of the database - log.info("Doing DB Version Check") - with database.DatabaseConn('emby') as cursor: - emby_db = embydb.Embydb_Functions(cursor) - currentVersion = emby_db.get_version() - ###$ Begin migration $### - if not currentVersion: - currentVersion = emby_db.get_version(settings('dbCreatedWithVersion') or self.clientInfo.get_version()) - log.info("Migration of database version completed") - ###$ End migration $### - - window('emby_version', value=currentVersion) - - minVersion = window('emby_minDBVersion') - uptoDate = self.compareDBVersion(currentVersion, minVersion) - - if not uptoDate: - log.warn("Database version out of date: %s minimum version required: %s" - % (currentVersion, minVersion)) - - resp = dialog.yesno(lang(29999), lang(33022)) - if not resp: - log.warn("Database version is out of date! USER IGNORED!") - dialog.ok(lang(29999), lang(33023)) - else: - database.db_reset() - - break - - window('emby_dbCheck', value="true") - - - if not startupComplete: - # Verify the video database can be found - videoDb = database.video_database() - if not xbmcvfs.exists(videoDb): - # Database does not exists - log.error( - "The current Kodi version is incompatible " - "with the Emby for Kodi add-on. Please visit " - "https://github.com/MediaBrowser/Emby.Kodi/wiki " - "to know which Kodi versions are supported.") - - dialog.ok( - heading=lang(29999), - line1=lang(33024)) - break - - # Run start up sync - log.warn("Database version: %s", window('emby_version')) - log.info("SyncDatabase (started)") - startTime = datetime.now() - librarySync = self.startSync() - elapsedTime = datetime.now() - startTime - log.info("SyncDatabase (finished in: %s) %s" - % (str(elapsedTime).split('.')[0], librarySync)) - - # Add other servers at this point - # TODO: re-add once plugin listing is created - # self.user.load_connect_servers() - - # Only try the initial sync once per kodi session regardless - # This will prevent an infinite loop in case something goes wrong. - startupComplete = True - window('emby.connected', value="true") - - # Process updates - if self.incremental_count > 5: - self.incremental_count = 0 - window('emby_kodiScan', clear=True) - - if ((not xbmc.Player().isPlayingVideo() or xbmc.getCondVisibility('VideoPlayer.Content(livetv)')) and - window('emby_dbScan') != "true" and window('emby_shouldStop') != "true"): - - self.incrementalSync() - - if window('emby_onWake') == "true" and window('emby_online') == "true": - # Kodi is waking up - # Set in kodimonitor.py - window('emby_onWake', clear=True) - if window('emby_syncRunning') != "true": - log.info("SyncDatabase onWake (started)") - librarySync = self.startSync() - log.info("SyncDatabase onWake (finished) %s" % librarySync) - - if self.stop_thread: - # Set in service.py - log.debug("Service terminated thread.") - break - - if self.monitor.waitForAbort(1): - # Abort was requested while waiting. We should exit - break - - log.warn("###===--- LibrarySync Stopped ---===###") - - def stopThread(self): - self.stop_thread = True - log.debug("Ending thread...") - - def suspendThread(self): - self.suspend_thread = True - log.debug("Pausing thread...") - - def resumeThread(self): - self.suspend_thread = False - log.debug("Resuming thread...") - - -class ManualSync(LibrarySync): - - - def __init__(self): - LibrarySync.__init__(self) - - def sync(self, mediatype=None): - - if mediatype in ('movies', 'boxsets', 'musicvideos', 'tvshows'): - with database.DatabaseConn('emby') as cursor_emby: - with database.DatabaseConn('video') as cursor_video: - pDialog = self.progressDialog("Manual Sync: %s" % mediatype) - if mediatype == 'movies': - self.movies(cursor_emby, cursor_video, pDialog) - elif mediatype == "boxsets": - self.boxsets(cursor_emby, cursor_video, pDialog) - elif mediatype =='musicvideos': - self.musicvideos(cursor_emby, cursor_video, pDialog) - elif mediatype == 'tvshows': - self.tvshows(cursor_emby, cursor_video, pDialog) - - pDialog.close() - return - - elif mediatype == 'music': - with database.DatabaseConn('emby') as cursor_emby: - with database.DatabaseConn('music') as cursor_music: - pDialog = self.progressDialog("Manual Sync: %s" % mediatype) - self.music(cursor_emby, cursor_music, pDialog) - pDialog.close() - return - else: - return self.fullSync(manualrun=True) - - def movies(self, embycursor, kodicursor, pdialog): - return Movies(embycursor, kodicursor, pdialog).compare_all() - - def boxsets(self, embycursor, kodicursor, pdialog): - return Movies(embycursor, kodicursor, pdialog).force_refresh_boxsets() - - def musicvideos(self, embycursor, kodicursor, pdialog): - return MusicVideos(embycursor, kodicursor, pdialog).compare_all() - - def tvshows(self, embycursor, kodicursor, pdialog): - return TVShows(embycursor, kodicursor, pdialog).compare_all() - - def music(self, embycursor, kodicursor, pdialog): - return Music(embycursor, kodicursor).compare_all() +# -*- coding: utf-8 -*- + +################################################################################################## + +import datetime +import json +import logging +import os + +import xbmc +import xbmcvfs + +import downloader as server +import helper.xmls as xmls +from database import Database, get_sync, save_sync +from objects import Movies, TVShows, MusicVideos, Music +from helper import _, settings, progress, dialog, LibraryException +from emby import Emby + +################################################################################################## + +LOG = logging.getLogger("EMBY."+__name__) + +################################################################################################## + + +class FullSync(object): + + sync = None + + def __init__(self, library, library_id=None): + + self.library = library + self.direct_path = settings('useDirectPaths') == "1" + + self.server = Emby() + self.sync = get_sync() + + if library_id: + self.sync['Libraries'].append(library_id) + else: + self.mapping() + + self.start() + + def mapping(self): + + ''' Load the mapping of the full sync. + This allows us to restore a previous sync. + ''' + if self.sync['Libraries']: + + if not dialog("yesno", heading="{emby}", line1=_(33102)): + dialog("ok", heading="{emby}", line1=_(33122)) + + raise LibraryException("ProgressStopped") + + else: + LOG.info("generate full sync") + libraries = [] + + for library in self.server['api'].get_media_folders()['Items']: + library['Media'] = library.get('OriginalCollectionType', library.get('CollectionType', "mixed")) + + if library['Type'] in ('Channel', 'PlaylistsFolder') or library['Media'] not in ('movies', 'tvshows', 'musicvideos', 'music', 'mixed'): + continue + + libraries.append(library) + + libraries = self.select_libraries(libraries) + + if [x['Media'] for x in libraries if x['Media'] in ('movies', 'mixed')]: + self.sync['Libraries'].append("Boxsets:") + + save_sync(self.sync) + + def select_libraries(self, libraries): + + ''' Select all or whitelist libraries. Provides a new list. + ''' + if not dialog("yesno", heading="{emby}", line1=_(33125), nolabel=_(33126), yeslabel=_(33127)): + raise LibraryException('SyncLibraryLater') + + choices = [x['Name'] for x in libraries] + choices.insert(0, _(33121)) + selection = dialog("multi", _(33120), choices) + + if selection is None: + raise LibraryException('LibrarySelection') + elif not selection: + raise LibraryException('SyncLibraryLater') + + if 0 in selection: + selection = list(range(1, len(libraries) + 1)) + + selected_libraries = [] + + for x in selection: + library = libraries[x - 1] + + if library['Media'] != 'mixed': + selected_libraries.append(library['Id']) + else: + selected_libraries.append("Mixed:%s" % library['Id']) + + self.sync['Libraries'] = selected_libraries + + return [libraries[x - 1] for x in selection] + + def start(self): + + ''' Main sync process. + ''' + xmls.sources() + start_time = datetime.datetime.now() + + for library in list(self.sync['Libraries']): + + self.process_library(library) + + if not library.startswith('Boxsets:'): + self.sync['Whitelist'].append(library) + + self.sync['Libraries'].pop(self.sync['Libraries'].index(library)) + self.sync['RestorePoint'] = {} + + elapsed = datetime.datetime.now() - start_time + settings('SyncInstallRunDone.bool', True) + self.library.save_last_sync() + save_sync(self.sync) + xbmc.executebuiltin('UpdateLibrary(video)') + dialog("notification", heading="{emby}", message="%s %s" % (_(33025), str(elapsed).split('.')[0]), + icon="{emby}", sound=False) + LOG.info("Full sync completed in: %s", str(elapsed).split('.')[0]) + + def process_library(self, library_id): + + ''' Add a library by it's id. Create a node and a playlist whenever appropriate. + ''' + media = { + 'movies': self.movies, + 'musicvideos': self.musicvideos, + 'tvshows': self.tvshows, + 'music': self.music + } + try: + if library_id.startswith('Boxsets:'): + + if library_id.endswith('Refresh'): + self.refresh_boxsets() + else: + self.boxsets() + + return + + library = self.server['api'].get_item(library_id.replace('Mixed:', "")) + + if library_id.startswith('Mixed:'): + for mixed in ('movies', 'tvshows'): + + media[mixed](library) + self.sync['RestorePoint'] = {} + else: + if library['CollectionType']: + settings('enableMusic.bool', True) + + media[library['CollectionType']](library) + except LibraryException as error: + + if error.status == 'StopCalled': + save_sync(self.sync) + + raise + + except Exception as error: + + dialog("ok", heading="{emby}", line1=_(33119)) + save_sync(self.sync) + LOG.error("full sync exited unexpectedly") + + raise + + @progress() + def movies(self, library, dialog): + + ''' Process movies from a single library. + ''' + with Database() as videodb: + with Database('emby') as embydb: + obj = Movies(self.server, embydb, videodb, self.direct_path) + + for items in server.get_items(library['Id'], "Movie", False, self.sync['RestorePoint'].get('params')): + + self.sync['RestorePoint'] = items['RestorePoint'] + start_index = items['RestorePoint']['params']['StartIndex'] + + for index, movie in enumerate(items['Items']): + + dialog.update(int((float(start_index + index) / float(items['TotalRecordCount']))*100), + heading="%s: %s" % (_('addon_name'), library['Name']), + message=movie['Name']) + obj.movie(movie, library=library) + + @progress() + def tvshows(self, library, dialog): + + ''' Process tvshows and episodes from a single library. + ''' + with Database() as videodb: + with Database('emby') as embydb: + obj = TVShows(self.server, embydb, videodb, self.direct_path) + + for items in server.get_items(library['Id'], "Series", False, self.sync['RestorePoint'].get('params')): + + self.sync['RestorePoint'] = items['RestorePoint'] + start_index = items['RestorePoint']['params']['StartIndex'] + + for index, show in enumerate(items['Items']): + + percent = int((float(start_index + index) / float(items['TotalRecordCount']))*100) + message = show['Name'] + dialog.update(percent, heading="%s: %s" % (_('addon_name'), library['Name']), message=message) + + if obj.tvshow(show, library=library) != False: + + for episodes in server.get_items(show['Id'], "Episode"): + for episode in episodes['Items']: + + dialog.update(percent, message="%s/%s" % (message, episode['Name'][:10])) + obj.episode(episode) + + @progress() + def musicvideos(self, library, dialog): + + ''' Process musicvideos from a single library. + ''' + with Database() as videodb: + with Database('emby') as embydb: + obj = MusicVideos(self.server, embydb, videodb, self.direct_path) + + for items in server.get_items(library['Id'], "MusicVideo", False, self.sync['RestorePoint'].get('params')): + + self.sync['RestorePoint'] = items['RestorePoint'] + start_index = items['RestorePoint']['params']['StartIndex'] + + for index, mvideo in enumerate(items['Items']): + + dialog.update(int((float(start_index + index) / float(items['TotalRecordCount']))*100), + heading="%s: %s" % (_('addon_name'), library['Name']), + message=mvideo['Name']) + obj.musicvideo(mvideo, library=library) + + @progress() + def music(self, library, dialog): + + ''' Process artists, album, songs from a single library. + ''' + with Database('music') as musicdb: + with Database('emby') as embydb: + obj = Music(self.server, embydb, musicdb, self.direct_path) + + for items in server.get_artists(library['Id'], False, self.sync['RestorePoint'].get('params')): + + self.sync['RestorePoint'] = items['RestorePoint'] + start_index = items['RestorePoint']['params']['StartIndex'] + + for index, artist in enumerate(items['Items']): + + percent = int((float(start_index + index) / float(items['TotalRecordCount']))*100) + message = artist['Name'] + dialog.update(percent, heading="%s: %s" % (_('addon_name'), library['Name']), message=message) + obj.artist(artist, library=library) + + for albums in server.get_albums_by_artist(artist['Id']): + + for album in albums['Items']: + obj.album(album) + + for songs in server.get_items(album['Id'], "Audio"): + for song in songs['Items']: + + dialog.update(percent, + message="%s/%s/%s" % (message, album['Name'][:7], song['Name'][:7])) + obj.song(song) + + @progress(_(33018)) + def boxsets(self, dialog): + + ''' Process all boxsets. + ''' + with Database() as videodb: + with Database('emby') as embydb: + obj = Movies(self.server, embydb, videodb, self.direct_path) + + for items in server.get_items(None, "BoxSet", False, self.sync['RestorePoint'].get('params')): + + self.sync['RestorePoint'] = items['RestorePoint'] + start_index = items['RestorePoint']['params']['StartIndex'] + + for index, boxset in enumerate(items['Items']): + + dialog.update(int((float(start_index + index) / float(items['TotalRecordCount']))*100), + heading="%s: %s" % (_('addon_name'), _('boxsets')), + message=boxset['Name']) + obj.boxset(boxset) + + def refresh_boxsets(self): + + ''' Delete all exisitng boxsets and re-add. + ''' + with Database() as videodb: + with Database('emby') as embydb: + + obj = Movies(self.server, embydb, videodb, self.direct_path) + obj.boxsets_reset() + + self.boxsets() + + + + + +""" +# -*- coding: utf-8 -*- + +################################################################################################## + +import logging +import sqlite3 +import threading +from datetime import datetime, timedelta, time + +import xbmc +import xbmcgui +import xbmcvfs + +import api +import utils +import clientinfo +import database +import downloadutils +import itemtypes +import emby_api as mb +import embydb_functions as embydb +import read_embyserver as embyserver +import userclient +import views +from objects import Movies, MusicVideos, TVShows, Music +from utils import window, settings, language as lang, should_stop +from ga_client import GoogleAnalytics + +################################################################################################## + +log = logging.getLogger("EMBY."+__name__) + +################################################################################################## + +class LibrarySync(threading.Thread): + + _shared_state = {} + + isFastSync = False + + stop_thread = False + suspend_thread = False + + # Track websocketclient updates + addedItems = [] + updateItems = [] + userdataItems = [] + removeItems = [] + forceLibraryUpdate = False + incremental_count = 0 + refresh_views = False + + + def __init__(self): + + self.__dict__ = self._shared_state + self.monitor = xbmc.Monitor() + + self.clientInfo = clientinfo.ClientInfo() + self.doUtils = downloadutils.DownloadUtils().downloadUrl + self.user = userclient.UserClient() + self.emby = embyserver.Read_EmbyServer() + self.kodi_version = int(xbmc.getInfoLabel('System.BuildVersion')[:2]) + + threading.Thread.__init__(self) + + + def progressDialog(self, title): + + dialog = None + + dialog = xbmcgui.DialogProgressBG() + dialog.create("Emby for Kodi", title) + log.debug("Show progress dialog: %s" % title) + + return dialog + + def saveLastSync(self): + + # Save last sync time + overlap = 2 + + try: # datetime fails when used more than once, TypeError + if self.isFastSync: + result = self.doUtils("{server}/emby/Emby.Kodi.SyncQueue/GetServerDateTime?format=json") + server_time = result['ServerDateTime'] + server_time = utils.convertDate(server_time) + else: + raise Exception("Fast sync server plugin is not enabled.") + + except Exception as e: + # If the server plugin is not installed or an error happened. + log.debug("An exception occurred: %s" % e) + time_now = datetime.utcnow()-timedelta(minutes=overlap) + lastSync = time_now.strftime('%Y-%m-%dT%H:%M:%SZ') + log.info("New sync time: client time -%s min: %s" % (overlap, lastSync)) + + else: + lastSync = (server_time - timedelta(minutes=overlap)).strftime('%Y-%m-%dT%H:%M:%SZ') + log.info("New sync time: server time -%s min: %s" % (overlap, lastSync)) + + finally: + settings('LastIncrementalSync', value=lastSync) + + + def fullSync(self, manualrun=False, repair=False): + # Only run once when first setting up. Can be run manually. + music_enabled = settings('enableMusic') == "true" + + xbmc.executebuiltin('InhibitIdleShutdown(true)') + screensaver = utils.getScreensaver() + utils.setScreensaver(value="") + window('emby_dbScan', value="true") + # Add sources + utils.sourcesXML() + + # use emby and video DBs + with database.DatabaseConn('emby') as cursor_emby: + with database.DatabaseConn('video') as cursor_video: + # content sync: movies, tvshows, musicvideos, music + + if manualrun: + message = "Manual sync" + elif repair: + message = "Repair sync" + repair_list = [] + choices = ['all', 'movies', 'musicvideos', 'tvshows'] + if music_enabled: + choices.append('music') + + if self.kodi_version > 15: + # Jarvis or higher + types = xbmcgui.Dialog().multiselect(lang(33094), choices) + if types is None: + pass + elif 0 in types: # all + choices.pop(0) + repair_list.extend(choices) + else: + for index in types: + repair_list.append(choices[index]) + else: + resp = xbmcgui.Dialog().select(lang(33094), choices) + if resp == 0: # all + choices.pop(resp) + repair_list.extend(choices) + else: + repair_list.append(choices[resp]) + + log.info("Repair queued for: %s", repair_list) + else: + message = "Initial sync" + window('emby_initialScan', value="true") + + pDialog = self.progressDialog("%s" % message) + starttotal = datetime.now() + + # Set views + views.Views(cursor_emby, cursor_video).maintain() + cursor_emby.connection.commit() + #self.maintainViews(cursor_emby, cursor_video) + + # Sync video library + process = { + + 'movies': self.movies, + 'boxsets': self.boxsets, + 'musicvideos': self.musicvideos, + 'tvshows': self.tvshows + } + for itemtype in process: + + if repair and itemtype not in repair_list: + continue + + startTime = datetime.now() + completed = process[itemtype](cursor_emby, cursor_video, pDialog) + if not completed: + xbmc.executebuiltin('InhibitIdleShutdown(false)') + utils.setScreensaver(value=screensaver) + window('emby_dbScan', clear=True) + if pDialog: + pDialog.close() + + return False + else: + elapsedTime = datetime.now() - startTime + log.info("SyncDatabase (finished %s in: %s)" + % (itemtype, str(elapsedTime).split('.')[0])) + + + # sync music + # use emby and music + if music_enabled: + if repair and 'music' not in repair_list: + pass + else: + with database.DatabaseConn('emby') as cursor_emby: + with database.DatabaseConn('music') as cursor_music: + startTime = datetime.now() + completed = self.music(cursor_emby, cursor_music, pDialog) + if not completed: + xbmc.executebuiltin('InhibitIdleShutdown(false)') + utils.setScreensaver(value=screensaver) + window('emby_dbScan', clear=True) + if pDialog: + pDialog.close() + + return False + else: + elapsedTime = datetime.now() - startTime + log.info("SyncDatabase (finished music in: %s)" + % (str(elapsedTime).split('.')[0])) + + if pDialog: + pDialog.close() + + with database.DatabaseConn('emby') as cursor_emby: + emby_db = embydb.Embydb_Functions(cursor_emby) + current_version = emby_db.get_version(self.clientInfo.get_version()) + + window('emby_version', current_version) + + settings('SyncInstallRunDone', value="true") + + self.saveLastSync() + xbmc.executebuiltin('UpdateLibrary(video)') + elapsedtotal = datetime.now() - starttotal + + xbmc.executebuiltin('InhibitIdleShutdown(false)') + utils.setScreensaver(value=screensaver) + window('emby_dbScan', clear=True) + window('emby_initialScan', clear=True) + + xbmcgui.Dialog().notification( + heading=lang(29999), + message="%s %s %s" % + (message, lang(33025), str(elapsedtotal).split('.')[0]), + icon="special://home/addons/plugin.video.emby/icon.png", + sound=False) + + return True + + + def refreshViews(self): + + with database.DatabaseConn('emby') as cursor_emby: + with database.DatabaseConn() as cursor_video: + # Compare views, assign correct tags to items + views.Views(cursor_emby, cursor_video).maintain() + + def offline_mode_views(self): + + with database.DatabaseConn('emby') as cursor_emby: + with database.DatabaseConn() as cursor_video: + views.Views(cursor_emby, cursor_video).offline_mode() + + + def compareDBVersion(self, current, minimum): + # It returns True is database is up to date. False otherwise. + log.info("current: %s minimum: %s" % (current, minimum)) + + try: + currMajor, currMinor, currPatch = current.split(".") + minMajor, minMinor, minPatch = minimum.split(".") + except ValueError as error: + raise ValueError("Unable to compare versions: %s, %s" % (current, minimum)) + + if currMajor > minMajor: + return True + elif currMajor == minMajor and (currMinor > minMinor or + (currMinor == minMinor and currPatch >= minPatch)): + return True + else: + # Database out of date. + return False + + def run(self): + + try: + self.run_internal() + except Warning as e: + if "restricted" in e: + pass + elif "401" in e: + pass + except Exception as e: + ga = GoogleAnalytics() + errStrings = ga.formatException() + if not (hasattr(e, 'quiet') and e.quiet): + ga.sendEventData("Exception", errStrings[0], errStrings[1]) + window('emby_dbScan', clear=True) + log.exception(e) + xbmcgui.Dialog().ok( + heading=lang(29999), + line1=( + "Library sync thread has exited! " + "You should restart Kodi now. " + "Please report this on the forum."), + line2=(errStrings[0] + " (" + errStrings[1] + ")")) + + def run_internal(self): + + dialog = xbmcgui.Dialog() + + startupComplete = False + + log.warn("---===### Starting LibrarySync ###===---") + if utils.verify_advancedsettings(): + # Advancedsettings was modified, Kodi needs to restart + log.warn("###===--- LibrarySync Aborted ---===###") + return + + while not self.monitor.abortRequested(): + + # In the event the server goes offline + while self.suspend_thread: + # Set in service.py + if self.monitor.waitForAbort(5): + # Abort was requested while waiting. We should exit + break + + if (window('emby_dbCheck') != "true" and settings('SyncInstallRunDone') == "true"): + # Verify the validity of the database + log.info("Doing DB Version Check") + with database.DatabaseConn('emby') as cursor: + emby_db = embydb.Embydb_Functions(cursor) + currentVersion = emby_db.get_version() + ###$ Begin migration $### + if not currentVersion: + currentVersion = emby_db.get_version(settings('dbCreatedWithVersion') or self.clientInfo.get_version()) + log.info("Migration of database version completed") + ###$ End migration $### + + window('emby_version', value=currentVersion) + + minVersion = window('emby_minDBVersion') + uptoDate = self.compareDBVersion(currentVersion, minVersion) + + if not uptoDate: + log.warn("Database version out of date: %s minimum version required: %s" + % (currentVersion, minVersion)) + + resp = dialog.yesno(lang(29999), lang(33022)) + if not resp: + log.warn("Database version is out of date! USER IGNORED!") + dialog.ok(lang(29999), lang(33023)) + else: + database.db_reset() + + break + + window('emby_dbCheck', value="true") + + + if not startupComplete: + # Verify the video database can be found + videoDb = database.video_database() + if not xbmcvfs.exists(videoDb): + # Database does not exists + log.error( + "The current Kodi version is incompatible " + "with the Emby for Kodi add-on. Please visit " + "https://github.com/MediaBrowser/Emby.Kodi/wiki " + "to know which Kodi versions are supported.") + + dialog.ok( + heading=lang(29999), + line1=lang(33024)) + break + + # Run start up sync + log.warn("Database version: %s", window('emby_version')) + log.info("SyncDatabase (started)") + startTime = datetime.now() + librarySync = self.startSync() + elapsedTime = datetime.now() - startTime + log.info("SyncDatabase (finished in: %s) %s" + % (str(elapsedTime).split('.')[0], librarySync)) + + # Add other servers at this point + # TODO: re-add once plugin listing is created + # self.user.load_connect_servers() + + # Only try the initial sync once per kodi session regardless + # This will prevent an infinite loop in case something goes wrong. + startupComplete = True + + # Process updates + if self.incremental_count > 5: + self.incremental_count = 0 + window('emby_kodiScan', clear=True) + + if ((not xbmc.Player().isPlayingVideo() or xbmc.getCondVisibility('VideoPlayer.Content(livetv)')) and + window('emby_dbScan') != "true" and window('emby_shouldStop') != "true"): + + self.incrementalSync() + + if window('emby_onWake') == "true" and window('emby_online') == "true": + # Kodi is waking up + # Set in kodimonitor.py + window('emby_onWake', clear=True) + if window('emby_syncRunning') != "true": + log.info("SyncDatabase onWake (started)") + librarySync = self.startSync() + log.info("SyncDatabase onWake (finished) %s" % librarySync) + + if self.stop_thread: + # Set in service.py + log.debug("Service terminated thread.") + break + + if self.monitor.waitForAbort(1): + # Abort was requested while waiting. We should exit + break + + log.warn("###===--- LibrarySync Stopped ---===###") + + def stopThread(self): + self.stop_thread = True + log.debug("Ending thread...") + + def suspendThread(self): + self.suspend_thread = True + log.debug("Pausing thread...") + + def resumeThread(self): + self.suspend_thread = False + log.debug("Resuming thread...") + +class ManualSync(LibrarySync): + + + def __init__(self): + LibrarySync.__init__(self) + + def sync(self, mediatype=None): + + if mediatype in ('movies', 'boxsets', 'musicvideos', 'tvshows'): + with database.DatabaseConn('emby') as cursor_emby: + with database.DatabaseConn('video') as cursor_video: + pDialog = self.progressDialog("Manual Sync: %s" % mediatype) + if mediatype == 'movies': + self.movies(cursor_emby, cursor_video, pDialog) + elif mediatype == "boxsets": + self.boxsets(cursor_emby, cursor_video, pDialog) + elif mediatype =='musicvideos': + self.musicvideos(cursor_emby, cursor_video, pDialog) + elif mediatype == 'tvshows': + self.tvshows(cursor_emby, cursor_video, pDialog) + + pDialog.close() + return + + elif mediatype == 'music': + with database.DatabaseConn('emby') as cursor_emby: + with database.DatabaseConn('music') as cursor_music: + pDialog = self.progressDialog("Manual Sync: %s" % mediatype) + self.music(cursor_emby, cursor_music, pDialog) + pDialog.close() + return + else: + return self.fullSync(manualrun=True) + + def movies(self, embycursor, kodicursor, pdialog): + return Movies(embycursor, kodicursor, pdialog).compare_all() + + def boxsets(self, embycursor, kodicursor, pdialog): + return Movies(embycursor, kodicursor, pdialog).force_refresh_boxsets() + + def musicvideos(self, embycursor, kodicursor, pdialog): + return MusicVideos(embycursor, kodicursor, pdialog).compare_all() + + def tvshows(self, embycursor, kodicursor, pdialog): + return TVShows(embycursor, kodicursor, pdialog).compare_all() + + def music(self, embycursor, kodicursor, pdialog): + return Music(embycursor, kodicursor).compare_all() +""" diff --git a/resources/lib/helper/__init__.py b/resources/lib/helper/__init__.py new file mode 100644 index 00000000..fc0a9909 --- /dev/null +++ b/resources/lib/helper/__init__.py @@ -0,0 +1,20 @@ +from translate import _ +from exceptions import LibraryException + +from utils import addon_id +from utils import window +from utils import settings +from utils import dialog +from utils import find +from utils import event +from utils import validate +from utils import values +from utils import JSONRPC +from utils import indent +from utils import write_xml + +from wrapper import progress +from wrapper import catch +from wrapper import stop +from wrapper import emby_item +from wrapper import library_check diff --git a/resources/lib/helper/api.py b/resources/lib/helper/api.py new file mode 100644 index 00000000..570ffa97 --- /dev/null +++ b/resources/lib/helper/api.py @@ -0,0 +1,494 @@ +# -*- coding: utf-8 -*- + +################################################################################################## + +import json +import datetime +import logging + +from . import settings + +################################################################################################## + +LOG = logging.getLogger("EMBY."+__name__) + +################################################################################################## + + +class API(object): + + + def __init__(self, item, server): + + ''' Get item information in special cases. + server is the server address + ''' + self.item = item + self.server = server + + def get_playcount(self, played, playcount): + + ''' Convert Emby played/playcount into + the Kodi equivalent. The playcount is tied to the watch status. + ''' + return (playcount or 1) if played else None + + + + + def get_userdata(self): + # Default + favorite = False + likes = None + playcount = None + played = False + last_played = None + resume = 0 + + try: + userdata = self.item['UserData'] + except KeyError: # No userdata found. + pass + else: + favorite = userdata['IsFavorite'] + likes = userdata.get('Likes') + + last_played = userdata.get('LastPlayedDate') + if last_played: + last_played = last_played.split('.')[0].replace('T', " ") + + if userdata['Played']: + # Playcount is tied to the watch status + played = True + playcount = userdata['PlayCount'] + if playcount == 0: + playcount = 1 + + if last_played is None: + last_played = self.get_date_created() + + playback_position = userdata.get('PlaybackPositionTicks') + if playback_position: + resume = playback_position / 10000000.0 + + return { + + 'Favorite': favorite, + 'Likes': likes, + 'PlayCount': playcount, + 'Played': played, + 'LastPlayedDate': last_played, + 'Resume': resume + } + + def get_people(self): + # Process People + director = [] + writer = [] + cast = [] + + if 'People' in self.item: + for person in self.item['People']: + + type_ = person['Type'] + name = person['Name'] + + if type_ == 'Director': + director.append(name) + elif type_ == 'Actor': + cast.append(name) + elif type_ in ('Writing', 'Writer'): + writer.append(name) + + return { + + 'Director': director, + 'Writer': writer, + 'Cast': cast + } + + def get_actors(self): + cast = [] + + if 'People' in self.item: + self.get_people_artwork(self.item['People']) + + for person in self.item['People']: + + if person['Type'] == "Actor": + cast.append({ + 'name': person['Name'], + 'role': person.get('Role', "Unknown"), + 'order': len(cast) + 1, + 'thumbnail': person['imageurl'] + }) + + return cast + + def get_media_streams(self): + + video_tracks = [] + audio_tracks = [] + subtitle_languages = [] + + try: + media_streams = self.item['MediaSources'][0]['MediaStreams'] + + except KeyError: + if not self.item.get("MediaStreams"): + return None + media_streams = self.item['MediaStreams'] + + for media_stream in media_streams: + # Sort through Video, Audio, Subtitle + stream_type = media_stream['Type'] + + if stream_type == "Video": + self._video_stream(video_tracks, media_stream) + + elif stream_type == "Audio": + self._audio_stream(audio_tracks, media_stream) + + elif stream_type == "Subtitle": + subtitle_languages.append(media_stream.get('Language', "Unknown")) + + return { + + 'video': video_tracks, + 'audio': audio_tracks, + 'subtitle': subtitle_languages + } + + def media_streams(self, video, audio, subtitles): + return { + 'video': video or [], + 'audio': audio or [], + 'subtitle': subtitles or [] + } + + def video_streams(self, tracks, container=None): + + if container: + container = container.split(',')[0] + + for track in tracks: + + track.update({ + 'codec': track.get('Codec', "").lower(), + 'profile': track.get('Profile', "").lower(), + 'height': track.get('Height'), + 'width': track.get('Width'), + '3d': self.item.get('Video3DFormat'), + 'aspect': 1.85 + }) + + if "msmpeg4" in track['codec']: + track['codec'] = "divx" + + elif "mpeg4" in track['codec']: + if "simple profile" in track['profile'] or not track['profile']: + track['codec'] = "xvid" + + elif "h264" in track['codec']: + if container in ('mp4', 'mov', 'm4v'): + track['codec'] = "avc1" + + try: + width, height = self.item.get('AspectRatio', track.get('AspectRatio', "0")).split(':') + track['aspect'] = round(float(width) / float(height), 6) + except (ValueError, ZeroDivisionError): + + if track['width'] and track['height']: + track['aspect'] = round(float(track['width'] / track['height']), 6) + + track['duration'] = self.get_runtime() + + return tracks + + def audio_streams(self, tracks): + + for track in tracks: + + track.update({ + 'codec': track.get('Codec', "").lower(), + 'profile': track.get('Profile', "").lower(), + 'channels': track.get('Channels'), + 'language': track.get('Language') + }) + + if "dts-hd ma" in track['profile']: + track['codec'] = "dtshd_ma" + + elif "dts-hd hra" in track['profile']: + track['codec'] = "dtshd_hra" + + return tracks + + def get_runtime(self): + + try: + runtime = self.item['RunTimeTicks'] / 10000000.0 + + except KeyError: + runtime = self.item.get('CumulativeRunTimeTicks', 0) / 10000000.0 + + return runtime + + @classmethod + def adjust_resume(cls, resume_seconds): + + resume = 0 + if resume_seconds: + resume = round(float(resume_seconds), 6) + jumpback = int(settings('resumeJumpBack')) + if resume > jumpback: + # To avoid negative bookmark + resume = resume - jumpback + + return resume + + def get_studios(self): + # Process Studios + studios = [] + try: + studio = self.item['SeriesStudio'] + studios.append(self.validate_studio(studio)) + + except KeyError: + for studio in self.item['Studios']: + + name = studio['Name'] + studios.append(self.validate_studio(name)) + + return studios + + def validate_studio(self, studio_name): + # Convert studio for Kodi to properly detect them + studios = { + + 'abc (us)': "ABC", + 'fox (us)': "FOX", + 'mtv (us)': "MTV", + 'showcase (ca)': "Showcase", + 'wgn america': "WGN", + 'bravo (us)': "Bravo", + 'tnt (us)': "TNT", + 'comedy central': "Comedy Central (US)" + } + return studios.get(studio_name.lower(), studio_name) + + def get_genres(self): + + all_genres = "" + genres = self.item.get('Genres', self.item.get('SeriesGenres')) + + if genres: + all_genres = " / ".join(genres) + + return all_genres + + def get_date_created(self): + + try: + date_added = self.item['DateCreated'] + date_added = date_added.split('.')[0].replace('T', " ") + except KeyError: + date_added = None + + return date_added + + def get_premiere_date(self): + + try: + premiere = self.item['PremiereDate'] + premiere = premiere.split('.')[0].replace('T', " ") + except KeyError: + premiere = None + + return premiere + + def get_overview(self, overview=None): + + overview = overview or self.item.get('Overview') + + if not overview: + return + + overview = overview.replace("\"", "\'") + overview = overview.replace("\n", "[CR]") + overview = overview.replace("\r", " ") + overview = overview.replace("<br>", "[CR]") + + return overview + + def get_tagline(self): + + try: + tagline = self.item['Taglines'][0] + except IndexError: + tagline = None + + return tagline + + def get_provider(self, name): + + try: + provider = self.item['ProviderIds'][name] + except KeyError: + provider = None + + return provider + + def get_mpaa(self): + # Convert more complex cases + mpaa = self.item.get('OfficialRating', "") + + if mpaa in ("NR", "UR"): + # Kodi seems to not like NR, but will accept Not Rated + mpaa = "Not Rated" + + if "FSK-" in mpaa: + mpaa = mpaa.replace("-", " ") + + return mpaa + + def get_country(self): + + try: + country = self.item['ProductionLocations'][0] + except (IndexError, KeyError): + country = None + + return country + + def get_file_path(self, path=None): + + if path is None: + path = self.item.get('Path') + + if not path: + return "" + + if path.startswith('\\\\'): + path = path.replace('\\\\', "smb://", 1).replace('\\\\', "\\").replace('\\', "/") + + if 'Container' in self.item: + + if self.item['Container'] == 'dvd': + path = "%s/VIDEO_TS/VIDEO_TS.IFO" % path + elif self.item['Container'] == 'bluray': + path = "%s/BDMV/index.bdmv" % path + + path = path.replace('\\\\', "\\") + + if '\\' in path: + path = path.replace('/', "\\") + + if '://' in path: + protocol = path.split('://')[0] + path = path.replace(protocol, protocol.lower()) + + return path + + def get_user_artwork(self, user_id): + + ''' Get emby user profile picture. + ''' + return "%s/emby/Users/%s/Images/Primary?Format=original" % (self.server, user_id) + + def get_people_artwork(self, people): + + ''' Get people (actor, director, etc) artwork. + ''' + for person in people: + + if 'PrimaryImageTag' in person: + + query = "&MaxWidth=400&MaxHeight=400&Index=0" + person['imageurl'] = self.get_artwork(person['Id'], "Primary", person['PrimaryImageTag'], query) + else: + person['imageurl'] = None + + return people + + def get_all_artwork(self, obj, parent_info=False): + + ''' Get all artwork possible. If parent_info is True, + it will fill missing artwork with parent artwork. + + obj is from objects.Objects().map(item, 'Artwork') + ''' + query = "" + all_artwork = { + 'Primary': "", + 'BoxRear': "", + 'Art': "", + 'Banner': "", + 'Logo': "", + 'Thumb': "", + 'Disc': "", + 'Backdrop': [] + } + + if settings('compressArt.bool'): + query = "&Quality=90" + + if not settings('enableCoverArt.bool'): + query += "&EnableImageEnhancers=false" + + all_artwork['Backdrop'] = self.get_backdrops(obj['Id'], obj['BackdropTags'] or [], query) + + for artwork in obj['Tags'] or []: + all_artwork[artwork] = self.get_artwork(obj['Id'], artwork, obj['Tags'][artwork]) + + if parent_info: + if not all_artwork['Backdrop'] and obj['ParentBackdropId']: + all_artwork['Backdrop'] = self.get_backdrops(obj['ParentBackdropId'], obj['ParentBackdropTags'], query) + + for art in ('Logo', 'Art', 'Thumb'): + if not all_artwork[art] and obj['Parent%sId' % art]: + all_artwork[art] = self.get_artwork(obj['Parent%sId' % art], art, obj['Parent%sTag' % art], query) + + if obj.get('SeriesTag'): + all_artwork['Series.Primary'] = self.get_artwork(obj['SeriesId'], "Primary", obj['SeriesTag'], query) + + if not all_artwork['Primary']: + all_artwork['Primary'] = all_artwork['Series.Primary'] + + elif not all_artwork['Primary'] and obj.get('AlbumId'): + all_artwork['Primary'] = self.get_artwork(obj['AlbumId'], "Primary", obj['AlbumTag'], query) + + return all_artwork + + def get_backdrops(self, item_id, tags, query=None): + + ''' Get backdrops based of "BackdropImageTags" in the emby object. + ''' + backdrops = [] + + if item_id is None: + return backdrops + + for index, tag in enumerate(tags): + + artwork = "%s/emby/Items/%s/Images/Backdrop/%s?Format=original&Tag=%s%s" % (self.server, item_id, index, tag, query or "") + backdrops.append(artwork) + + return backdrops + + def get_artwork(self, item_id, image, tag=None, query=None): + + ''' Get any type of artwork: Primary, Art, Banner, Logo, Thumb, Disc + ''' + if item_id is None: + return "" + + url = "%s/emby/Items/%s/Images/%s/0?Format=original" % (self.server, item_id, image) + + if tag is not None: + url += "&Tag=%s" % tag + + if query is not None: + url += query or "" + + return url diff --git a/resources/lib/helper/exceptions.py b/resources/lib/helper/exceptions.py new file mode 100644 index 00000000..b3bacc3f --- /dev/null +++ b/resources/lib/helper/exceptions.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- + +################################################################################################# + +class LibraryException(Exception): + # Emby library sync exception + def __init__(self, status): + self.status = status + + diff --git a/resources/lib/loghandler.py b/resources/lib/helper/loghandler.py similarity index 70% rename from resources/lib/loghandler.py rename to resources/lib/helper/loghandler.py index 6a891540..ef437f33 100644 --- a/resources/lib/loghandler.py +++ b/resources/lib/helper/loghandler.py @@ -5,7 +5,8 @@ import logging import xbmc -from utils import window, settings +import database +from . import window ################################################################################################## @@ -24,12 +25,30 @@ class LogHandler(logging.StreamHandler): logging.StreamHandler.__init__(self) self.setFormatter(MyFormatter()) + self.sensitive = {'Token': [], 'Server': []} + + for server in database.get_credentials()['Servers']: + + if server.get('AccessToken'): + self.sensitive['Token'].append(server['AccessToken']) + + self.sensitive['Server'].append(server['LocalAddress'].split('://')[1]) + self.sensitive['Server'].append(server['RemoteAddress'].split('://')[1]) + + if server.get('ManualAddress'): + self.sensitive['Server'].append(server['ManualAddress'].split('://')[1]) + + def emit(self, record): if self._get_log_level(record.levelno): string = self.format(record) - string = string.replace(settings('server') or "{server}", "{emby-server}") - string = string.replace(settings('token') or "{token}", "{emby-token}") + + for server in self.sensitive['Server']: + string = string.replace(server or "{server}", "{emby-server}") + + for token in self.sensitive['Token']: + string = string.replace(token or "{token}", "{emby-token}") try: xbmc.log(string, level=xbmc.LOGNOTICE) except UnicodeEncodeError: diff --git a/resources/lib/helper/playutils.py b/resources/lib/helper/playutils.py new file mode 100644 index 00000000..2e81d7a0 --- /dev/null +++ b/resources/lib/helper/playutils.py @@ -0,0 +1,475 @@ +# -*- coding: utf-8 -*- + +################################################################################################# + +import json +import logging +import os +from uuid import uuid4 + +import xbmc +import xbmcvfs +import xbmcgui + +import api +import database +import client +from . import _, settings, window +from libraries import requests +from downloader import TheVoid +from emby import Emby + +################################################################################################# + +LOG = logging.getLogger("EMBY."+__name__) + +################################################################################################# + + +def set_properties(item, method, server_id=None): + + ''' Set all properties for playback detection. + ''' + info = item.get('PlaybackInfo') or {} + + current = window('emby_play.json') or [] + current.append({ + 'Type': item['Type'], + 'Id': item['Id'], + 'Path': info['Path'], + 'PlayMethod': method, + 'MediaSourceId': info.get('MediaSourceId', item['Id']), + 'Runtime': item.get('RunTimeTicks'), + 'PlaySessionId': info.get('PlaySessionId', str(uuid4()).replace("-", "")), + 'ServerId': server_id, + 'DeviceId': client.get_device_id(), + 'SubsMapping': info.get('Subtitles'), + 'AudioStreamIndex': info.get('AudioStreamIndex'), + 'SubtitleStreamIndex': info.get('SubtitleStreamIndex') + }) + + window('emby_play.json', current) + +class PlayUtils(object): + + + def __init__(self, item, force_transcode=False, server_id=None, server=None, token=None): + + ''' Item will be updated with the property PlaybackInfo, which + holds all the playback information. + ''' + self.item = item + self.item['PlaybackInfo'] = {} + self.info = { + 'ServerId': server_id, + 'ServerAddress': server, + 'ForceTranscode': force_transcode, + 'Token': token or TheVoid('GetToken', {'ServerId': server_id}).get() + } + + def get_sources(self, source_id=None): + + ''' Return sources based on the optional source_id or the device profile. + ''' + params = { + 'ServerId': self.info['ServerId'], + 'Id': self.item['Id'], + 'Profile': self.get_device_profile() + } + info = TheVoid('GetPlaybackInfo', params).get() + LOG.info(info) + self.info['PlaySessionId'] = info['PlaySessionId'] + sources = [] + + if not info.get('MediaSources'): + LOG.info("No MediaSources found.") + + elif source_id: + for source in info: + + if source['Id'] == source_id: + sources.append(source) + + break + + elif not self.is_selection(info) or len(info['MediaSources']) == 1: + + LOG.info("Skip source selection.") + sources.append(info['MediaSources'][0]) + + else: + sources.extend([x for x in info['MediaSources']]) + + return sources + + def select_source(self, sources): + + if len(sources) > 1: + selection = [] + + for source in sources: + selection.append(source.get('Name', "na")) + + resp = xbmcgui.Dialog().select(_(33130), selection) + if resp > -1: + source = sources[resp] + else: + log.info("No media source selected.") + return False + else: + source = sources[0] + + self.get(source) + + return source + + def is_selection(self, sources): + + ''' Do not allow source selection for. + ''' + if self.item['MediaType'] != 'Video': + LOG.debug("MediaType is not a video.") + + return False + + elif self.item['Type'] == 'TvChannel': + LOG.debug("TvChannel detected.") + + return False + + elif len(sources) == 1 and sources[0]['Type'] == 'Placeholder': + LOG.debug("Placeholder detected.") + + return False + + elif 'SourceType' in self.item and self.item['SourceType'] != 'Library': + LOG.debug("SourceType not from library.") + + return False + + return True + + def is_file_exists(self, source): + + path = self.direct_play(source) + + if xbmcvfs.exists(self.info['Path']): + LOG.info("Path exists.") + + return True + + LOG.info("Failed to find file.") + + return False + + def is_strm(self, source): + + if source['Container'] == 'strm' or self.item['Path'].endswith('.strm'): + LOG.info("strm detected") + + return True + + return False + + def get(self, source): + + ''' The server returns sources based on the MaxStreamingBitrate value and other filters. + ''' + self.info['MediaSourceId'] = source['Id'] + + if source['RequiresOpening']: + source = self.live_stream(source) + + if source['SupportsDirectPlay'] and (self.is_strm(source) or not settings('playFromStream.bool') and self.is_file_exists(source)): + + LOG.info("--[ direct play ]") + self.direct_play(source) + + elif source['SupportsDirectStream']: + + LOG.info("--[ direct stream ]") + self.direct_url(source) + + else: + LOG.info("--[ transcode ]") + self.transcode(source) + + self.info['AudioStreamIndex'] = source.get('DefaultAudioStreamIndex') + self.info['SubtitleStreamIndex'] = source.get('DefaultSubtitleStreamIndex') + self.item['PlaybackInfo'].update(self.info) + + def live_stream(self, source): + + ''' Get live stream media info. + ''' + params = { + 'ServerId': self.info['ServerId'], + 'Id': self.item['Id'], + 'Profile': self.get_device_profile(), + 'PlaySessionId': self.info['PlaySessionId'], + 'Token': source['OpenToken'] + } + info = TheVoid('GetLiveStream', params).get() + LOG.info(info) + + if info['RequiresClosing']: + self.info['LiveStreamId'] = info['LiveStreamId'] + + return info['MediaSource'] + + def transcode(self, source): + + if not 'TranscodingUrl' in source: + raise Exception("use get_sources to get transcoding url") + + self.info['Method'] = "Transcode" + base, params = source['TranscodingUrl'].split('?') + self.info['Path'] = "%s/emby%s?%s" % (self.info['ServerAddress'], base.replace('stream', "master"), params) + self.info['Path'] += "&maxWidth=%s&maxHeight=%s" % (self.get_resolution()) + + return self.info['Path'] + + def direct_play(self, source): + + API = api.API(self.item, self.info['ServerAddress']) + self.info['Method'] = "DirectPlay" + self.info['Path'] = API.get_file_path() + + return self.info['Path'] + + def direct_url(self, source): + + self.info['Method'] = "DirectStream" + + if self.item['Type'] == "Audio": + self.info['Path'] = ("%s/emby/Audio/%s/stream.%s?static=true&api_key=%s" % + (self.info['ServerAddress'], self.item['Id'], + source['Container'].split(',')[0], + self.info['Token'])) + else: + self.info['Path'] = ("%s/emby/Videos/%s/stream?static=true&api_key=%s" % + (self.info['ServerAddress'], self.item['Id'], self.info['Token'])) + + return self.info['Path'] + + def get_bitrate(self): + + ''' Get the video quality based on add-on settings. + Max bit rate supported by server: 2147483 (max signed 32bit integer) + ''' + bitrate = [664, 996, 1320, 2000, 3200, + 4700, 6200, 7700, 9200, 10700, + 12200, 13700, 15200, 16700, 18200, + 20000, 25000, 30000, 35000, 40000, + 100000, 1000000, 2147483] + return bitrate[int(settings('videoBitrate') or 22)] + + def get_resolution(self): + return int(xbmc.getInfoLabel('System.ScreenWidth')), int(xbmc.getInfoLabel('System.ScreenHeight')) + + def get_device_profile(self): + + ''' Get device profile based on the add-on settings. + ''' + profile = { + "Name": "Kodi", + "MaxStreamingBitrate": self.get_bitrate() * 1000, + "MusicStreamingTranscodingBitrate": 1280000, + "TimelineOffsetSeconds": 5, + "TranscodingProfiles": [ + { + "Type": "Audio" + }, + { + "Container": "m3u8", + "Type": "Video" + }, + { + "Container": "jpeg", + "Type": "Photo" + } + ], + "DirectPlayProfiles": [ + { + "Type": "Video" + }, + { + "Type": "Video", + "Container": "strm" + }, + { + "Type": "Audio" + }, + { + "Type": "Photo" + } + ], + "ResponseProfiles": [], + "ContainerProfiles": [], + "CodecProfiles": [], + "SubtitleProfiles": [ + { + "Format": "srt", + "Method": "External" + }, + { + "Format": "srt", + "Method": "Embed" + }, + { + "Format": "ass", + "Method": "External" + }, + { + "Format": "ass", + "Method": "Embed" + }, + { + "Format": "sub", + "Method": "Embed" + }, + { + "Format": "sub", + "Method": "External" + }, + { + "Format": "ssa", + "Method": "Embed" + }, + { + "Format": "ssa", + "Method": "External" + }, + { + "Format": "smi", + "Method": "Embed" + }, + { + "Format": "smi", + "Method": "External" + }, + { + "Format": "pgssub", + "Method": "Embed" + }, + { + "Format": "pgssub", + "Method": "External" + }, + { + "Format": "dvdsub", + "Method": "Embed" + }, + { + "Format": "dvdsub", + "Method": "External" + }, + { + "Format": "pgs", + "Method": "Embed" + }, + { + "Format": "pgs", + "Method": "External" + } + ] + } + if settings('transcode_h265.bool'): + profile['DirectPlayProfiles'][0]['VideoCodec'] = "h264,mpeg4,mpeg2video" + + if settings('transcodeHi10P.bool'): + profile['CodecProfiles'].append( + { + 'Type': 'Video', + 'Conditions': [ + { + 'Condition': "LessThanEqual", + 'Property': "VideoBitDepth", + 'Value': "8" + } + ] + } + ) + + if self.info['ForceTranscode'] or self.item['LocationType'] == 'Remote': + profile['DirectPlayProfiles'] = [ + { + "Type": "Video", + "Container": "strm" + } + ] + + return profile + + def set_external_subs(self, source, listitem): + + ''' Try to download external subs locally so we can label them. + Since Emby returns all possible tracks together, sort them. + IsTextSubtitleStream if true, is available to download from server. + ''' + if not source['MediaStreams']: + return + + subs = [] + mapping = {} + kodi = 0 + + for stream in source['MediaStreams']: + + if stream['Type'] == 'Subtitle' and stream['IsExternal'] and stream['IsTextSubtitleStream']: + index = stream['Index'] + + if 'DeliveryUrl' in stream: + url = "%s/emby%s" % (self.info['ServerAddress'], stream['DeliveryUrl']) + else: + url = ("%s/emby/Videos/%s/%s/Subtitles/%s/Stream.%s?api_key=%s" % + (self.info['ServerAddress'], self.item['Id'], source['Id'], index, stream['Codec'], self.info['Token'])) + + if url is None: + continue + + LOG.info("[ subtitles/%s ] %s", index, url) + + if 'Language' in stream: + filename = "Stream.%s.%s" % (stream['Language'].encode('utf-8'), stream['Codec']) + + try: + subs.append(self.download_external_subs(url, filename)) + except Exception as error: + LOG.error(error) + subs.append(url) + else: + subs.append(url) + + mapping[kodi] = index + kodi += 1 + + listitem.setSubtitles(subs) + self.item['PlaybackInfo']['Subtitles'] = mapping + + + @classmethod + def download_external_subs(cls, src, filename): + + ''' Download external subtitles to temp folder + to be able to have proper names to streams. + ''' + temp = xbmc.translatePath("special://profile/addon_data/plugin.video.emby/temp/").decode('utf-8') + + if not xbmcvfs.exists(temp): + xbmcvfs.mkdir(temp) + + path = os.path.join(temp, filename) + + try: + response = requests.get(src, stream=True) + response.raise_for_status() + except Exception as e: + raise + else: + response.encoding = 'utf-8' + with open(path, 'wb') as f: + f.write(response.content) + del response + + return path diff --git a/resources/lib/helper/translate.py b/resources/lib/helper/translate.py new file mode 100644 index 00000000..a0731c2d --- /dev/null +++ b/resources/lib/helper/translate.py @@ -0,0 +1,53 @@ +# -*- coding: utf-8 -*- + +################################################################################################## + +import json +import logging +import os + +import xbmc +import xbmcaddon + +################################################################################################## + +LOG = logging.getLogger('EMBY.'+__name__) + +################################################################################################## + +def _(string): + + ''' Get add-on string. Returns in unicode. + ''' + if type(string) != int: + string = STRINGS[string] + + result = xbmcaddon.Addon('plugin.video.emby').getLocalizedString(string) + + if not result: + result = xbmc.getLocalizedString(string) + + return result + + +STRINGS = { + 'addon_name': 29999, + 'playback_mode': 30511, + 'empty_user': 30613, + 'empty_user_pass': 30608, + 'empty_server': 30617, + 'network_credentials': 30517, + 'invalid_auth': 33009, + 'addon_mode': 33036, + 'native_mode': 33037, + 'cancel': 30606, + 'username': 30024, + 'password': 30602, + 'gathering': 33021, + 'boxsets': 30185, + 'movies': 30302, + 'tvshows': 30305, + 'fav_movies': 30180, + 'fav_tvshows': 30181, + 'fav_episodes': 30182 +} diff --git a/resources/lib/helper/utils.py b/resources/lib/helper/utils.py new file mode 100644 index 00000000..14f5feac --- /dev/null +++ b/resources/lib/helper/utils.py @@ -0,0 +1,389 @@ +# -*- coding: utf-8 -*- + +################################################################################################# + +import json +import logging +import os +import re +import unicodedata +from uuid import uuid4 + +import xbmc +import xbmcaddon +import xbmcgui +import xbmcvfs + +from . import _ + +################################################################################################# + +LOG = logging.getLogger("EMBY."+__name__) + +################################################################################################# + +def addon_id(): + return "plugin.video.emby" + +def kodi_version(): + return xbmc.getInfoLabel('System.BuildVersion')[:2] + +def window(key, value=None, clear=False, window_id=10000): + + ''' Get or set window properties. + ''' + window = xbmcgui.Window(window_id) + + if clear: + + LOG.debug("--[ window clear: %s ]", key) + window.clearProperty(key) + elif value is not None: + if key.endswith('.json'): + + key = key.replace('.json', "") + value = json.dumps(value) + + elif key.endswith('.bool'): + + key = key.replace('.bool', "") + value = "true" if value else "false" + + window.setProperty(key, value) + else: + result = window.getProperty(key.replace('.json', "").replace('.bool', "")) + + if result: + if key.endswith('.json'): + result = json.loads(result) + elif key.endswith('.bool'): + result = result in ("true", "1") + + return result + +def settings(setting, value=None): + + ''' Get or add add-on settings. + getSetting returns unicode object. + ''' + addon = xbmcaddon.Addon(addon_id()) + + if value is not None: + if setting.endswith('.bool'): + + setting = setting.replace('.bool', "") + value = "true" if value else "false" + + addon.setSetting(setting, value) + else: + result = addon.getSetting(setting.replace('.bool', "")) + + if result and setting.endswith('.bool'): + result = result in ("true", "1") + + return result + +def create_id(): + return uuid4() + +def find(dict, item): + + ''' Find value in dictionary. + ''' + if item in dict: + return dict[item] + + for key,value in sorted(dict.iteritems(), key=lambda (k,v): (v,k)): + if re.match(key, item): + return dict[key] + +def event(method, data=None): + + ''' Data is a dictionary. + ''' + data = data or {} + xbmc.executebuiltin('NotifyAll(plugin.video.emby, %s, "[%s]")' % (method, json.dumps(data).replace('"', '\\"'))) + LOG.debug("---[ event: %s ] %s", method, data) + +def dialog(dialog_type, *args, **kwargs): + + d = xbmcgui.Dialog() + + if "icon" in kwargs: + kwargs['icon'] = kwargs['icon'].replace("{emby}", + "special://home/addons/plugin.video.emby/icon.png") + if "heading" in kwargs: + kwargs['heading'] = kwargs['heading'].replace("{emby}", _('addon_name')) + + types = { + 'yesno': d.yesno, + 'ok': d.ok, + 'notification': d.notification, + 'input': d.input, + 'select': d.select, + 'numeric': d.numeric, + 'multi': d.multiselect + } + return types[dialog_type](*args, **kwargs) + +def should_stop(): + + ''' Checkpoint during the sync process. + ''' + if xbmc.Monitor().abortRequested(): + return True + + if window('emby_should_stop.bool'): + LOG.info("exiiiiitttinggg") + return True + + if not window('emby_online.bool'): + return True + + return False + +class JSONRPC(object): + + version = 1 + jsonrpc = "2.0" + + def __init__(self, method, **kwargs): + + self.method = method + + for arg in kwargs: + self.arg = arg + + def _query(self): + + query = { + 'jsonrpc': self.jsonrpc, + 'id': self.version, + 'method': self.method, + } + if self.params is not None: + query['params'] = self.params + + return json.dumps(query) + + def execute(self, params=None): + + self.params = params + return json.loads(xbmc.executeJSONRPC(self._query())) + +def validate(path): + + ''' Verify if path is accessible. + ''' + if window('emby_pathverified.bool'): + return True + + path = path if os.path.supports_unicode_filenames else path.encode('utf-8') + + if not xbmcvfs.exists(path): + if dialog(type_="yesno", + heading="{emby}", + line1="%s %s. %s" % (_(33047), path, _(33048))): + + return False + + window('emby_pathverified', "true") + + return True + +def values(item, keys): + + ''' Grab the values in the item for a list of keys {key},{key1}.... + If the key has no brackets, the key will be passed as is. + ''' + return (item[key.replace('{', "").replace('}', "")] if type(key) == str and key.startswith('{') else key for key in keys) + +def indent(elem, level=0): + + ''' Prettify xml docs. + ''' + try: + i = "\n" + level * " " + if len(elem): + if not elem.text or not elem.text.strip(): + elem.text = i + " " + if not elem.tail or not elem.tail.strip(): + elem.tail = i + for elem in elem: + indent(elem, level + 1) + if not elem.tail or not elem.tail.strip(): + elem.tail = i + else: + if level and (not elem.tail or not elem.tail.strip()): + elem.tail = i + except Exception: + return + +def write_xml(content, file): + with open(file, 'w') as infile: + + content = content.replace("'", '"') + content = content.replace('?>', ' standalone="yes" ?>', 1) + infile.write(content) + +def delete_build(): + + ''' Delete objects from kodi cache + ''' + LOG.debug("--[ delete objects ]") + path = xbmc.translatePath('special://temp/emby/').decode('utf-8') + dirs, files = xbmcvfs.listdir(path) + + delete_recursive(path, dirs) + + for file in files: + xbmcvfs.delete(os.path.join(path, file.decode('utf-8'))) + +def delete_recursive(path, dirs): + + ''' Delete files and dirs recursively. + ''' + for directory in dirs: + + dirs2, files = xbmcvfs.listdir(os.path.join(path, directory.decode('utf-8'))) + + for file in files: + xbmcvfs.delete(os.path.join(path, directory.decode('utf-8'), file.decode('utf-8'))) + + delete_recursive(os.path.join(path, directory.decode('utf-8')), dirs2) + xbmcvfs.rmdir(os.path.join(path, directory.decode('utf-8'))) + +def normalize_string(text): + + ''' For theme media, do not modify unless + modified in TV Tunes. + ''' + text = text.replace(":", "") + text = text.replace("/", "-") + text = text.replace("\\", "-") + text = text.replace("<", "") + text = text.replace(">", "") + text = text.replace("*", "") + text = text.replace("?", "") + text = text.replace('|', "") + text = text.strip() + # Remove dots from the last character as windows can not have directories + # with dots at the end + text = text.rstrip('.') + text = unicodedata.normalize('NFKD', unicode(text, 'utf-8')).encode('ascii', 'ignore') + + return text + + +""" + + + + +################################################################################################# +# Utility methods + +def getScreensaver(): + # Get the current screensaver value + result = JSONRPC('Settings.getSettingValue').execute({'setting': "screensaver.mode"}) + try: + return result['result']['value'] + except KeyError: + return "" + +def setScreensaver(value): + # Toggle the screensaver + params = { + 'setting': "screensaver.mode", + 'value': value + } + result = JSONRPC('Settings.setSettingValue').execute(params) + log.info("Toggling screensaver: %s %s" % (value, result)) + +def convertDate(date): + try: + date = datetime.strptime(date, "%Y-%m-%dT%H:%M:%SZ") + except (ImportError, TypeError): + # TypeError: attribute of type 'NoneType' is not callable + # Known Kodi/python error + date = datetime(*(time.strptime(date, "%Y-%m-%dT%H:%M:%SZ")[0:6])) + + return date + + +def indent(elem, level=0): + # Prettify xml trees + i = "\n" + level*" " + if len(elem): + if not elem.text or not elem.text.strip(): + elem.text = i + " " + if not elem.tail or not elem.tail.strip(): + elem.tail = i + for elem in elem: + indent(elem, level+1) + if not elem.tail or not elem.tail.strip(): + elem.tail = i + else: + if level and (not elem.tail or not elem.tail.strip()): + elem.tail = i + +def profiling(sortby="cumulative"): + # Will print results to Kodi log + def decorator(func): + def wrapper(*args, **kwargs): + import cProfile + import pstats + + pr = cProfile.Profile() + + pr.enable() + result = func(*args, **kwargs) + pr.disable() + + s = StringIO.StringIO() + ps = pstats.Stats(pr, stream=s).sort_stats(sortby) + ps.print_stats() + log.info(s.getvalue()) + + return result + + return wrapper + return decorator + +################################################################################################# +# Addon utilities + + +def verify_advancedsettings(): + # Track the existance of <cleanonupdate>true</cleanonupdate> + # incompatible with plugin paths + log.info("verifying advanced settings") + if settings('useDirectPaths') != "0": return + + path = xbmc.translatePath("special://userdata/").decode('utf-8') + xmlpath = "%sadvancedsettings.xml" % path + + try: + xmlparse = etree.parse(xmlpath) + except: # Document is blank or missing + return + else: + root = xmlparse.getroot() + + video = root.find('videolibrary') + if video is not None: + cleanonupdate = video.find('cleanonupdate') + if cleanonupdate is not None and cleanonupdate.text == "true": + log.warn("cleanonupdate disabled") + video.remove(cleanonupdate) + + try: + indent(root) + except: pass + etree.ElementTree(root).write(xmlpath) + + xbmcgui.Dialog().ok(heading=language(29999), line1=language(33097)) + xbmc.executebuiltin('RestartApp') + return True + return +""" diff --git a/resources/lib/helper/wrapper.py b/resources/lib/helper/wrapper.py new file mode 100644 index 00000000..ff0acbff --- /dev/null +++ b/resources/lib/helper/wrapper.py @@ -0,0 +1,146 @@ +# -*- coding: utf-8 -*- + +################################################################################################# + +import logging + +import xbmcgui + +from . import _, LibraryException +from utils import should_stop + +################################################################################################# + +LOG = logging.getLogger("EMBY."+__name__) + +################################################################################################# + +def progress(message=None): + + ''' Will start and close the progress dialog. + ''' + def decorator(func): + def wrapper(self, item=None, *args, **kwargs): + + dialog = xbmcgui.DialogProgressBG() + + if item and type(item) == dict: + + dialog.create(_('addon_name'), "%s %s" % (_('gathering'), item['Name'])) + LOG.info("Processing %s: %s", item['Name'], item['Id']) + else: + dialog.create(_('addon_name'), message) + LOG.info("Processing %s", message) + + if item: + args = (item,) + args + + kwargs['dialog'] = dialog + result = func(self, *args, **kwargs) + dialog.close() + + return result + + return wrapper + return decorator + + +def catch(errors=(Exception,)): + + ''' Wrapper to catch exceptions and return using catch + ''' + def decorator(func): + def wrapper(*args, **kwargs): + + try: + return func(*args, **kwargs) + except errors as error: + LOG.exception(error) + + raise Exception("Caught exception") + + return wrapper + return decorator + +def stop(default=None): + + ''' Wrapper to catch exceptions and return using catch + ''' + def decorator(func): + def wrapper(*args, **kwargs): + + try: + if should_stop(): + raise Exception + + except Exception as error: + + if default is not None: + return default + + raise LibraryException("StopCalled") + + return func(*args, **kwargs) + + return wrapper + return decorator + +def emby_item(): + + ''' Wrapper to retrieve the emby_db item. + ''' + def decorator(func): + def wrapper(self, item, *args, **kwargs): + e_item = self.emby_db.get_item_by_id(item['Id'] if type(item) == dict else item) + + return func(self, item, e_item=e_item, *args, **kwargs) + + return wrapper + return decorator + +def library_check(): + + ''' Wrapper to retrieve the library + ''' + def decorator(func): + def wrapper(self, item, *args, **kwargs): + from database import get_sync + + sync = get_sync() + + if kwargs.get('library') is None: + + if 'e_item' in kwargs: + try: + view_id = kwargs['e_item'][7] + view_name = self.emby_db.get_view_name(view_id) + view = {'Name': view_name, 'Id': view_id} + except Exception: + view = None + + if view is None: + ancestors = self.server['api'].get_ancestors(item['Id']) + + if not ancestors: + + return + + for ancestor in ancestors: + if ancestor['Type'] == 'CollectionFolder': + + view = self.emby_db.get_view_name(ancestor['Id']) + view = {'Id': None, 'Name': None} if view is None else {'Name': ancestor['Name'], 'Id': ancestor['Id']} + + break + + if view['Id'] not in sync['Whitelist']: + LOG.info("Library %s is not synced. Skip update.", view['Id']) + + return + + kwargs['library'] = view + + return func(self, item, *args, **kwargs) + + return wrapper + return decorator diff --git a/resources/lib/utils.py b/resources/lib/helper/xmls.py similarity index 76% rename from resources/lib/utils.py rename to resources/lib/helper/xmls.py index 5b400450..0029dc59 100644 --- a/resources/lib/utils.py +++ b/resources/lib/helper/xmls.py @@ -1,389 +1,381 @@ -# -*- coding: utf-8 -*- - -################################################################################################# - -import inspect -import json -import logging -import sqlite3 -import StringIO -import os -import sys -import time -import urllib -import unicodedata -import xml.etree.ElementTree as etree -from datetime import datetime -from uuid import uuid4 - - -import xbmc -import xbmcaddon -import xbmcgui -import xbmcplugin -import xbmcvfs - -################################################################################################# - -log = logging.getLogger("EMBY."+__name__) - -################################################################################################# -# Main methods - -def window(property_, value=None, clear=False, window_id=10000): - # Get or set window property - WINDOW = xbmcgui.Window(window_id) - - if clear: - WINDOW.clearProperty(property_) - elif value is not None: - if ".json" in property_: - value = json.dumps(value) - WINDOW.setProperty(property_, value) - else: - result = WINDOW.getProperty(property_) - if result and ".json" in property_: - result = json.loads(result) - return result - -def settings(setting, value=None): - # Get or add addon setting - addon = xbmcaddon.Addon(id='plugin.video.emby') - - if value is not None: - addon.setSetting(setting, value) - else: # returns unicode object - return addon.getSetting(setting) - -def language(string_id): - # Central string retrieval - unicode - return xbmcaddon.Addon(id='plugin.video.emby').getLocalizedString(string_id) - -def dialog(type_, *args, **kwargs): - - d = xbmcgui.Dialog() - - if "icon" in kwargs: - kwargs['icon'] = kwargs['icon'].replace("{emby}", - "special://home/addons/plugin.video.emby/icon.png") - if "heading" in kwargs: - kwargs['heading'] = kwargs['heading'].replace("{emby}", language(29999)) - - types = { - 'yesno': d.yesno, - 'ok': d.ok, - 'notification': d.notification, - 'input': d.input, - 'select': d.select, - 'numeric': d.numeric - } - return types[type_](*args, **kwargs) - -def urllib_path(plugin, params): - return "%s?%s" % (plugin, urllib.urlencode(params)) - -def create_id(): - return uuid4() - -class JSONRPC(object): - - id_ = 1 - jsonrpc = "2.0" - - def __init__(self, method, **kwargs): - - self.method = method - - for arg in kwargs: # id_(int), jsonrpc(str) - self.arg = arg - - def _query(self): - - query = { - - 'jsonrpc': self.jsonrpc, - 'id': self.id_, - 'method': self.method, - } - if self.params is not None: - query['params'] = self.params - - return json.dumps(query) - - def execute(self, params=None): - - self.params = params - return json.loads(xbmc.executeJSONRPC(self._query())) - -################################################################################################# -# Database related methods - -def should_stop(): - # Checkpoint during the syncing process - if xbmc.Monitor().abortRequested(): - return True - elif window('emby_shouldStop') == "true": - return True - else: # Keep going - return False - -################################################################################################# -# Utility methods - -def getScreensaver(): - # Get the current screensaver value - result = JSONRPC('Settings.getSettingValue').execute({'setting': "screensaver.mode"}) - try: - return result['result']['value'] - except KeyError: - return "" - -def setScreensaver(value): - # Toggle the screensaver - params = { - 'setting': "screensaver.mode", - 'value': value - } - result = JSONRPC('Settings.setSettingValue').execute(params) - log.info("Toggling screensaver: %s %s" % (value, result)) - -def convertDate(date): - try: - date = datetime.strptime(date, "%Y-%m-%dT%H:%M:%SZ") - except (ImportError, TypeError): - # TypeError: attribute of type 'NoneType' is not callable - # Known Kodi/python error - date = datetime(*(time.strptime(date, "%Y-%m-%dT%H:%M:%SZ")[0:6])) - - return date - -def normalize_string(text): - # For theme media, do not modify unless - # modified in TV Tunes - text = text.replace(":", "") - text = text.replace("/", "-") - text = text.replace("\\", "-") - text = text.replace("<", "") - text = text.replace(">", "") - text = text.replace("*", "") - text = text.replace("?", "") - text = text.replace('|', "") - text = text.strip() - # Remove dots from the last character as windows can not have directories - # with dots at the end - text = text.rstrip('.') - text = unicodedata.normalize('NFKD', unicode(text, 'utf-8')).encode('ascii', 'ignore') - - return text - -def indent(elem, level=0): - # Prettify xml trees - i = "\n" + level*" " - if len(elem): - if not elem.text or not elem.text.strip(): - elem.text = i + " " - if not elem.tail or not elem.tail.strip(): - elem.tail = i - for elem in elem: - indent(elem, level+1) - if not elem.tail or not elem.tail.strip(): - elem.tail = i - else: - if level and (not elem.tail or not elem.tail.strip()): - elem.tail = i - -def profiling(sortby="cumulative"): - # Will print results to Kodi log - def decorator(func): - def wrapper(*args, **kwargs): - import cProfile - import pstats - - pr = cProfile.Profile() - - pr.enable() - result = func(*args, **kwargs) - pr.disable() - - s = StringIO.StringIO() - ps = pstats.Stats(pr, stream=s).sort_stats(sortby) - ps.print_stats() - log.info(s.getvalue()) - - return result - - return wrapper - return decorator - -################################################################################################# -# Addon utilities - -def sourcesXML(): - # To make Master lock compatible - path = xbmc.translatePath("special://profile/").decode('utf-8') - xmlpath = "%ssources.xml" % path - - try: - xmlparse = etree.parse(xmlpath) - except: # Document is blank or missing - root = etree.Element('sources') - else: - root = xmlparse.getroot() - - - video = root.find('video') - if video is None: - video = etree.SubElement(root, 'video') - etree.SubElement(video, 'default', attrib={'pathversion': "1"}) - - # Add elements - count = 2 - for source in root.findall('.//path'): - if source.text == "smb://": - count -= 1 - - if count == 0: - # sources already set - break - else: - # Missing smb:// occurences, re-add. - for i in range(0, count): - source = etree.SubElement(video, 'source') - etree.SubElement(source, 'name').text = "Emby" - etree.SubElement(source, 'path', attrib={'pathversion': "1"}).text = "smb://" - etree.SubElement(source, 'allowsharing').text = "true" - # Prettify and write to file - try: - indent(root) - except: pass - etree.ElementTree(root).write(xmlpath) - -def passwordsXML(): - - # To add network credentials - path = xbmc.translatePath("special://userdata/").decode('utf-8') - xmlpath = "%spasswords.xml" % path - - try: - xmlparse = etree.parse(xmlpath) - except: # Document is blank or missing - root = etree.Element('passwords') - else: - root = xmlparse.getroot() - - dialog = xbmcgui.Dialog() - credentials = settings('networkCreds') - if credentials: - # Present user with options - option = dialog.select(language(33075), [language(33076), language(33077)]) - - if option < 0: - # User cancelled dialog - return - - elif option == 1: - # User selected remove - for paths in root.getiterator('passwords'): - for path in paths: - if path.find('.//from').text == "smb://%s/" % credentials: - paths.remove(path) - log.info("Successfully removed credentials for: %s" % credentials) - etree.ElementTree(root).write(xmlpath) - break - else: - log.info("Failed to find saved server: %s in passwords.xml" % credentials) - - settings('networkCreds', value="") - xbmcgui.Dialog().notification( - heading=language(29999), - message="%s %s" % (language(33078), credentials), - icon="special://home/addons/plugin.video.emby/icon.png", - time=1000, - sound=False) - return - - elif option == 0: - # User selected to modify - server = dialog.input(language(33083), credentials) - if not server: - return - else: - # No credentials added - dialog.ok(heading=language(29999), line1=language(33082)) - server = dialog.input(language(33084)) - if not server: - return - - # Network username - user = dialog.input(language(33079)) - if not user: - return - # Network password - password = dialog.input(heading=language(33080), option=xbmcgui.ALPHANUM_HIDE_INPUT) - if not password: - return - - # Add elements - for path in root.findall('.//path'): - if path.find('.//from').text.lower() == "smb://%s/" % server.lower(): - # Found the server, rewrite credentials - topath = "smb://%s:%s@%s/" % (user, password, server) - path.find('.//to').text = topath.replace("\\", ";") - break - else: - # Server not found, add it. - path = etree.SubElement(root, 'path') - etree.SubElement(path, 'from', attrib={'pathversion': "1"}).text = "smb://%s/" % server - topath = "smb://%s:%s@%s/" % (user, password, server) - etree.SubElement(path, 'to', attrib={'pathversion': "1"}).text = topath.replace("\\", ";") - # Force Kodi to see the credentials without restarting - xbmcvfs.exists(topath) - - # Add credentials - settings('networkCreds', value=server) - log.info("Added server: %s to passwords.xml" % server) - # Prettify and write to file - try: - indent(root) - except: pass - etree.ElementTree(root).write(xmlpath) - - dialog.notification( - heading=language(29999), - message="%s %s" % (language(33081), server), - icon="special://home/addons/plugin.video.emby/icon.png", - time=1000, - sound=False) - -def verify_advancedsettings(): - # Track the existance of <cleanonupdate>true</cleanonupdate> - # incompatible with plugin paths - log.info("verifying advanced settings") - if settings('useDirectPaths') != "0": return - - path = xbmc.translatePath("special://userdata/").decode('utf-8') - xmlpath = "%sadvancedsettings.xml" % path - - try: - xmlparse = etree.parse(xmlpath) - except: # Document is blank or missing - return - else: - root = xmlparse.getroot() - - video = root.find('videolibrary') - if video is not None: - cleanonupdate = video.find('cleanonupdate') - if cleanonupdate is not None and cleanonupdate.text == "true": - log.warn("cleanonupdate disabled") - video.remove(cleanonupdate) - - try: - indent(root) - except: pass - etree.ElementTree(root).write(xmlpath) - - xbmcgui.Dialog().ok(heading=language(29999), line1=language(33097)) - xbmc.executebuiltin('RestartApp') - return True - return +# -*- coding: utf-8 -*- + +################################################################################################# + +import json +import logging +import os +import xml.etree.ElementTree as etree + +import xbmc + +from . import _, indent, write_xml + +################################################################################################# + +LOG = logging.getLogger("EMBY."+__name__) + +################################################################################################# + +def sources(): + + ''' Create master lock compatible sources. + Also add the kodi.emby.media source. + ''' + path = xbmc.translatePath("special://profile/").decode('utf-8') + file = os.path.join(path, 'sources.xml') + + try: + xml = etree.parse(file).getroot() + except Exception: + + xml = etree.Element('sources') + video = etree.SubElement(xml, 'video') + etree.SubElement(video, 'default', attrib={'pathversion': "1"}) + + video = xml.find('video') + count = 2 + + for source in xml.findall('.//path'): + if source.text == 'smb://': + count -= 1 + + if count == 0: + break + else: + for i in range(0, count): + source = etree.SubElement(video, 'source') + etree.SubElement(source, 'name').text = "Emby" + etree.SubElement(source, 'path', attrib={'pathversion': "1"}).text = "smb://" + etree.SubElement(source, 'allowsharing').text = "true" + + for source in xml.findall('.//path'): + if source.text == 'http://kodi.emby.media': + break + else: + source = etree.SubElement(video, 'source') + etree.SubElement(source, 'name').text = "kodi.emby.media" + etree.SubElement(source, 'path', attrib={'pathversion': "1"}).text = "http://kodi.emby.media" + etree.SubElement(source, 'allowsharing').text = "true" + + indent(xml) + write_xml(etree.tostring(xml, 'UTF-8'), file) + +def tvtunes_nfo(path, urls): + + ''' Create tvtunes.nfo + ''' + try: + xml = etree.parse(path).getroot() + except Exception: + xml = etree.Element('tvtunes') + + for elem in xml.getiterator('tvtunes'): + for file in list(elem): + elem.remove(file) + + for url in urls: + etree.SubElement(xml, 'file').text = url + + indent(xml) + write_xml(etree.tostring(xml, 'UTF-8'), path) + + + + +""" +# -*- coding: utf-8 -*- + +################################################################################################# + +import inspect +import json +import logging +import sqlite3 +import StringIO +import os +import sys +import time +import urllib +import unicodedata +import xml.etree.ElementTree as etree +from datetime import datetime +from uuid import uuid4 + + +import xbmc +import xbmcaddon +import xbmcgui +import xbmcplugin +import xbmcvfs + +################################################################################################# + +log = logging.getLogger("EMBY."+__name__) + +################################################################################################# + + + + +################################################################################################# +# Utility methods + +def getScreensaver(): + # Get the current screensaver value + result = JSONRPC('Settings.getSettingValue').execute({'setting': "screensaver.mode"}) + try: + return result['result']['value'] + except KeyError: + return "" + +def setScreensaver(value): + # Toggle the screensaver + params = { + 'setting': "screensaver.mode", + 'value': value + } + result = JSONRPC('Settings.setSettingValue').execute(params) + log.info("Toggling screensaver: %s %s" % (value, result)) + +def convertDate(date): + try: + date = datetime.strptime(date, "%Y-%m-%dT%H:%M:%SZ") + except (ImportError, TypeError): + # TypeError: attribute of type 'NoneType' is not callable + # Known Kodi/python error + date = datetime(*(time.strptime(date, "%Y-%m-%dT%H:%M:%SZ")[0:6])) + + return date + +def normalize_string(text): + # For theme media, do not modify unless + # modified in TV Tunes + text = text.replace(":", "") + text = text.replace("/", "-") + text = text.replace("\\", "-") + text = text.replace("<", "") + text = text.replace(">", "") + text = text.replace("*", "") + text = text.replace("?", "") + text = text.replace('|', "") + text = text.strip() + # Remove dots from the last character as windows can not have directories + # with dots at the end + text = text.rstrip('.') + text = unicodedata.normalize('NFKD', unicode(text, 'utf-8')).encode('ascii', 'ignore') + + return text + +def indent(elem, level=0): + # Prettify xml trees + i = "\n" + level*" " + if len(elem): + if not elem.text or not elem.text.strip(): + elem.text = i + " " + if not elem.tail or not elem.tail.strip(): + elem.tail = i + for elem in elem: + indent(elem, level+1) + if not elem.tail or not elem.tail.strip(): + elem.tail = i + else: + if level and (not elem.tail or not elem.tail.strip()): + elem.tail = i + +def profiling(sortby="cumulative"): + # Will print results to Kodi log + def decorator(func): + def wrapper(*args, **kwargs): + import cProfile + import pstats + + pr = cProfile.Profile() + + pr.enable() + result = func(*args, **kwargs) + pr.disable() + + s = StringIO.StringIO() + ps = pstats.Stats(pr, stream=s).sort_stats(sortby) + ps.print_stats() + log.info(s.getvalue()) + + return result + + return wrapper + return decorator + +################################################################################################# +# Addon utilities + +def sourcesXML(): + # To make Master lock compatible + path = xbmc.translatePath("special://profile/").decode('utf-8') + xmlpath = "%ssources.xml" % path + + try: + xmlparse = etree.parse(xmlpath) + except: # Document is blank or missing + root = etree.Element('sources') + else: + root = xmlparse.getroot() + + + video = root.find('video') + if video is None: + video = etree.SubElement(root, 'video') + etree.SubElement(video, 'default', attrib={'pathversion': "1"}) + + # Add elements + count = 2 + for source in root.findall('.//path'): + if source.text == "smb://": + count -= 1 + + if count == 0: + # sources already set + break + else: + # Missing smb:// occurences, re-add. + for i in range(0, count): + source = etree.SubElement(video, 'source') + etree.SubElement(source, 'name').text = "Emby" + etree.SubElement(source, 'path', attrib={'pathversion': "1"}).text = "smb://" + etree.SubElement(source, 'allowsharing').text = "true" + # Prettify and write to file + try: + indent(root) + except: pass + etree.ElementTree(root).write(xmlpath) + +def passwordsXML(): + + # To add network credentials + path = xbmc.translatePath("special://userdata/").decode('utf-8') + xmlpath = "%spasswords.xml" % path + + try: + xmlparse = etree.parse(xmlpath) + except: # Document is blank or missing + root = etree.Element('passwords') + else: + root = xmlparse.getroot() + + dialog = xbmcgui.Dialog() + credentials = settings('networkCreds') + if credentials: + # Present user with options + option = dialog.select(language(33075), [language(33076), language(33077)]) + + if option < 0: + # User cancelled dialog + return + + elif option == 1: + # User selected remove + for paths in root.getiterator('passwords'): + for path in paths: + if path.find('.//from').text == "smb://%s/" % credentials: + paths.remove(path) + log.info("Successfully removed credentials for: %s" % credentials) + etree.ElementTree(root).write(xmlpath) + break + else: + log.info("Failed to find saved server: %s in passwords.xml" % credentials) + + settings('networkCreds', value="") + xbmcgui.Dialog().notification( + heading=language(29999), + message="%s %s" % (language(33078), credentials), + icon="special://home/addons/plugin.video.emby/icon.png", + time=1000, + sound=False) + return + + elif option == 0: + # User selected to modify + server = dialog.input(language(33083), credentials) + if not server: + return + else: + # No credentials added + dialog.ok(heading=language(29999), line1=language(33082)) + server = dialog.input(language(33084)) + if not server: + return + + # Network username + user = dialog.input(language(33079)) + if not user: + return + # Network password + password = dialog.input(heading=language(33080), option=xbmcgui.ALPHANUM_HIDE_INPUT) + if not password: + return + + # Add elements + for path in root.findall('.//path'): + if path.find('.//from').text.lower() == "smb://%s/" % server.lower(): + # Found the server, rewrite credentials + topath = "smb://%s:%s@%s/" % (user, password, server) + path.find('.//to').text = topath.replace("\\", ";") + break + else: + # Server not found, add it. + path = etree.SubElement(root, 'path') + etree.SubElement(path, 'from', attrib={'pathversion': "1"}).text = "smb://%s/" % server + topath = "smb://%s:%s@%s/" % (user, password, server) + etree.SubElement(path, 'to', attrib={'pathversion': "1"}).text = topath.replace("\\", ";") + # Force Kodi to see the credentials without restarting + xbmcvfs.exists(topath) + + # Add credentials + settings('networkCreds', value=server) + log.info("Added server: %s to passwords.xml" % server) + # Prettify and write to file + try: + indent(root) + except: pass + etree.ElementTree(root).write(xmlpath) + + dialog.notification( + heading=language(29999), + message="%s %s" % (language(33081), server), + icon="special://home/addons/plugin.video.emby/icon.png", + time=1000, + sound=False) + +def verify_advancedsettings(): + # Track the existance of <cleanonupdate>true</cleanonupdate> + # incompatible with plugin paths + log.info("verifying advanced settings") + if settings('useDirectPaths') != "0": return + + path = xbmc.translatePath("special://userdata/").decode('utf-8') + xmlpath = "%sadvancedsettings.xml" % path + + try: + xmlparse = etree.parse(xmlpath) + except: # Document is blank or missing + return + else: + root = xmlparse.getroot() + + video = root.find('videolibrary') + if video is not None: + cleanonupdate = video.find('cleanonupdate') + if cleanonupdate is not None and cleanonupdate.text == "true": + log.warn("cleanonupdate disabled") + video.remove(cleanonupdate) + + try: + indent(root) + except: pass + etree.ElementTree(root).write(xmlpath) + + xbmcgui.Dialog().ok(heading=language(29999), line1=language(33097)) + xbmc.executebuiltin('RestartApp') + return True + return +""" diff --git a/resources/lib/image_cache_thread.py b/resources/lib/image_cache_thread.py deleted file mode 100644 index dbaa06e0..00000000 --- a/resources/lib/image_cache_thread.py +++ /dev/null @@ -1,60 +0,0 @@ -# -*- coding: utf-8 -*- - -################################################################################################# - -import logging -import threading -import requests - -################################################################################################# - -log = logging.getLogger("EMBY."+__name__) - -################################################################################################# - -class ImageCacheThread(threading.Thread): - - url_to_process = None - is_finished = False - - xbmc_host = "" - xbmc_port = "" - xbmc_username = "" - xbmc_password = "" - - - def __init__(self): - - threading.Thread.__init__(self) - - - def set_url(self, url): - - self.url_to_process = url - - def set_host(self, host, port): - - self.xbmc_host = host - self.xbmc_port = port - - def set_auth(self, username, password): - - self.xbmc_username = username - self.xbmc_password = password - - def run(self): - - log.debug("Image Caching Thread Processing: %s", self.url_to_process) - - try: - requests.head( - url=("http://%s:%s/image/image://%s" - % (self.xbmc_host, self.xbmc_port, self.url_to_process)), - auth=(self.xbmc_username, self.xbmc_password), - timeout=(35.1, 35.1)) - # We don't need the result - except Exception: - pass - - log.debug("Image Caching Thread Exited") - self.is_finished = True diff --git a/resources/lib/initialsetup.py b/resources/lib/initialsetup.py deleted file mode 100644 index fa082138..00000000 --- a/resources/lib/initialsetup.py +++ /dev/null @@ -1,144 +0,0 @@ -# -*- coding: utf-8 -*- - -################################################################################################# - -import logging - -import xbmc -import xbmcgui - -import clientinfo -import connectmanager -import connect.connectionmanager as connectionmanager -import userclient -from utils import settings, language as lang, passwordsXML - -################################################################################################# - -log = logging.getLogger("EMBY."+__name__) -STATE = connectionmanager.ConnectionState - -################################################################################################# - - -class InitialSetup(object): - - - def __init__(self): - - self.addon_id = clientinfo.ClientInfo().get_addon_id() - self.user_client = userclient.UserClient() - self.connectmanager = connectmanager.ConnectManager() - - - def setup(self): - # Check server, user, direct paths, music, direct stream if not direct path. - dialog = xbmcgui.Dialog() - - log.debug("Initial setup called") - - if self._server_verification() and settings('userId'): - # Setup is already completed - return - - if not self._user_identification(): - # User failed to identify - return - - ##### ADDITIONAL PROMPTS ##### - - direct_paths = dialog.yesno(heading=lang(30511), - line1=lang(33035), - nolabel=lang(33036), - yeslabel=lang(33037)) - if direct_paths: - log.info("User opted to use direct paths") - settings('useDirectPaths', value="1") - - # ask for credentials - credentials = dialog.yesno(heading=lang(30517), line1=lang(33038)) - if credentials: - log.info("Presenting network credentials dialog") - passwordsXML() - - music_disabled = dialog.yesno(heading=lang(29999), line1=lang(33039)) - if music_disabled: - log.info("User opted to disable Emby music library") - settings('enableMusic', value="false") - else: - # Only prompt if the user didn't select direct paths for videos - if not direct_paths: - music_access = dialog.yesno(heading=lang(29999), line1=lang(33040)) - if music_access: - log.info("User opted to direct stream music") - settings('streamMusic', value="true") - - def _server_verification(self): - - current_server = self.user_client.get_server() - if current_server and not settings('serverId'): - server = self.connectmanager.get_server(current_server, {'ssl': self.user_client.get_ssl()}) - log.info("Detected: %s", server) - try: - server_id = server['Servers'][0]['Id'] - settings('serverId', value=server_id) - except Exception as error: - log.error(error) - - if current_server: - current_state = self.connectmanager.get_state() - try: - for server in current_state['Servers']: - if server['Id'] == settings('serverId'): - # Update token - server['UserId'] = settings('userId') or None - server['AccessToken'] = settings('token') or None - self.connectmanager.update_token(server) - - server_address = self.connectmanager.get_address(server) - self._set_server(server_address, server) - log.info("Found server!") - except Exception as error: - log.error(error) - - return True - - return False - - def _user_identification(self): - - try: - server = self.connectmanager.select_servers() - log.info("Server: %s", server) - server_address = self.connectmanager.get_address(server) - self._set_server(server_address, server) - - if not server.get('AccessToken') and not server.get('UserId'): - user = self.connectmanager.login(server) - log.info("User authenticated: %s", user) - settings('username', value=user['User']['Name']) - self._set_user(user['User']['Id'], user['AccessToken']) - else: # Logged with Emby Connect - user = self.connectmanager.get_state() - settings('connectUsername', value=user['ConnectUser']['Name']) - self._set_user(server['UserId'], server['AccessToken']) - - return True - - except RuntimeError as error: - log.exception(error) - xbmc.executebuiltin('Addon.OpenSettings(%s)' % self.addon_id) - return False - - @classmethod - def _set_server(cls, server_address, server): - - settings('serverName', value=server['Name']) - settings('serverId', value=server['Id']) - settings('server', value=server_address) - - @classmethod - def _set_user(cls, user_id, token): - - settings('userId', value=user_id) - settings('token', value=token) diff --git a/resources/lib/itemtypes.py b/resources/lib/itemtypes.py deleted file mode 100644 index 21c0298a..00000000 --- a/resources/lib/itemtypes.py +++ /dev/null @@ -1,98 +0,0 @@ -# -*- coding: utf-8 -*- - -################################################################################################## - -import logging - -import emby as mb -import read_embyserver as embyserver -from objects import Movies, MusicVideos, TVShows, Music -from utils import settings -from database import DatabaseConn - -################################################################################################# - -log = logging.getLogger("EMBY."+__name__) - -################################################################################################# - - -class Items(object): - - - def __init__(self, embycursor, kodicursor): - - self.embycursor = embycursor - self.kodicursor = kodicursor - - self.emby = embyserver.Read_EmbyServer() - self.music_enabled = settings('enableMusic') == "true" - - - def itemsbyId(self, items, process, pdialog=None): - # Process items by itemid. Process can be added, update, userdata, remove - embycursor = self.embycursor - kodicursor = self.kodicursor - - itemtypes = { - - 'Movie': Movies, - 'BoxSet': Movies, - 'MusicVideo': MusicVideos, - 'Series': TVShows, - 'Season': TVShows, - 'Episode': TVShows, - 'MusicAlbum': Music, - 'MusicArtist': Music, - 'AlbumArtist': Music, - 'Audio': Music - } - - update_videolibrary = False - total = 0 - for item in items: - total += len(items[item]) - - if total == 0: - return False - - #log.info("Processing %s: %s", process, items) - if pdialog: - pdialog.update(heading="Processing %s: %s items" % (process, total)) - - # this is going to open a music connection even if it is not needed but - # I feel that is better than trying to sort out the login yourself - with DatabaseConn('music') as cursor_music: - - for itemtype in items: - - # Safety check - if not itemtypes.get(itemtype): - # We don't process this type of item - continue - - itemlist = items[itemtype] - if not itemlist: - # The list to process is empty - continue - - if itemtype in ('MusicAlbum', 'MusicArtist', 'AlbumArtist', 'Audio'): - if self.music_enabled: - items_process = itemtypes[itemtype](embycursor, cursor_music, pdialog) # see note above - else: - # Music is not enabled, do not proceed with itemtype - continue - else: - update_videolibrary = True - items_process = itemtypes[itemtype](embycursor, kodicursor, pdialog) - - if process == "added": - items_process.add_all(itemtype, itemlist) - elif process == "remove": - items_process.remove_all(itemtype, itemlist) - else: - process_items = mb.get_all(mb.get_item_list(itemlist)) - items_process.process_all(itemtype, process, process_items, total) - - - return (True, update_videolibrary) diff --git a/resources/lib/kodimonitor.py b/resources/lib/kodimonitor.py deleted file mode 100644 index d9e763d2..00000000 --- a/resources/lib/kodimonitor.py +++ /dev/null @@ -1,269 +0,0 @@ -# -*- coding: utf-8 -*- - -################################################################################################# - -import json -import logging -import threading - -import xbmc -import xbmcgui - -import downloadutils -import embydb_functions as embydb -import playbackutils as pbutils -from utils import window, settings, create_id -#from ga_client import log_error -from database import DatabaseConn - -################################################################################################# - -log = logging.getLogger("EMBY."+__name__) -KODI = int(xbmc.getInfoLabel('System.BuildVersion')[:2]) - -################################################################################################# - - -class KodiMonitor(xbmc.Monitor): - - retry = True - special_monitor = None - - def __init__(self): - - xbmc.Monitor.__init__(self) - self.special_monitor = SpecialMonitor().start() - - self.download = downloadutils.DownloadUtils().downloadUrl - log.info("Kodi monitor started") - - - def onScanStarted(self, library): - - log.debug("Kodi library scan %s running", library) - if library == "video": - window('emby_kodiScan', value="true") - - def onScanFinished(self, library): - - log.info("Kodi library scan %s finished", library) - if library == "video": - window('emby_kodiScan', clear=True) - - def onSettingsChanged(self): - # Monitor emby settings - current_log_level = settings('logLevel') - if window('emby_logLevel') != current_log_level: - # The log level changed, set new prop - log.info("New log level: %s", current_log_level) - window('emby_logLevel', value=current_log_level) - - current_context = "true" if settings('enableContext') == "true" else "" - if window('emby_context') != current_context: - log.info("New context setting: %s", current_context) - window('emby_context', value=current_context) - - current_context = "true" if settings('enableContextTranscode') == "true" else "" - if window('emby_context_transcode') != current_context: - log.info("New context transcode setting: %s", current_context) - window('emby_context_transcode', value=current_context) - - #@log_error() - def onNotification(self, sender, method, data): - - if method not in ('Playlist.OnAdd', 'Player.OnStop', 'Player.OnClear'): - log.info("Method: %s Data: %s", method, data) - - try: - if data: - data = json.loads(data, 'utf-8') - except: - log.info("Error parsing message data: %s", data) - return - - if method == 'Player.OnPlay': - self.retry = True - self._on_play_(data) - - elif method == 'Player.OnSeek': - window('emby_command', value="true") - - elif method == 'VideoLibrary.OnUpdate': - self._video_update(data) - - elif method == 'System.OnSleep': - # Connection is going to sleep - log.info("Marking the server as offline. System.OnSleep activated.") - window('emby_online', value="sleep") - - elif method == 'System.OnWake': - self._system_wake() - - elif method == 'GUI.OnScreensaverDeactivated': - self._screensaver_deactivated() - - def _on_play_(self, data): - # Set up report progress for emby playback - try: - kodi_id = None - - if KODI >= 17 and xbmc.Player().isPlayingVideo(): - ''' Seems to misbehave when playback is not terminated prior to playing new content. - The kodi id remains that of the previous title. Maybe onPlay happens before - this information is updated. Added a failsafe further below. - ''' - item = xbmc.Player().getVideoInfoTag() - kodi_id = item.getDbId() - item_type = item.getMediaType() - - if kodi_id is None or int(kodi_id) == -1 or 'item' in data and 'id' in data['item'] and data['item']['id'] != kodi_id: - item = data['item'] - kodi_id = item['id'] - item_type = item['type'] - - log.info("kodi_id: %s item_type: %s", kodi_id, item_type) - - except (KeyError, TypeError): - log.info("Item is invalid for playstate update") - # Retry once, sometimes xbmc.Player().isPlayingVideo() will return false when played from widget. - if self.retry: - self.retry = False - xbmc.sleep(200) - return self._on_play_(data) - else: - if ((settings('useDirectPaths') == "1" and not item_type == "song") or - (item_type == "song" and settings('enableMusic') == "true")): - # Set up properties for player - item_id = self._get_item_id(kodi_id, item_type) - if item_id: - url = "{server}/emby/Users/{UserId}/Items/%s?format=json" % item_id - result = self.download(url) - log.debug("Item: %s", result) - - playurl = None - count = 0 - while not playurl and count < 2: - try: - playurl = xbmc.Player().getPlayingFile() - except RuntimeError: - count += 1 - xbmc.sleep(200) - else: - window('emby_%s.play.json' % playurl, { - - 'playmethod': "DirectStream" if item_type == "song" and settings('streamMusic') == "true" else "DirectPlay", - 'playsession_id': str(create_id()).replace("-", "") - }) - listitem = xbmcgui.ListItem() - pbutils.PlaybackUtils(result).set_properties(playurl, listitem) - - def _video_update(self, data): - # Manually marking as watched/unwatched - try: - item = data['item'] - kodi_id = item['id'] - item_type = item['type'] - except (KeyError, TypeError): - log.info("Item is invalid for playstate update") - else: - # Send notification to the server. - item_id = self._get_item_id(kodi_id, item_type) - if item_id: - # Stop from manually marking as watched unwatched, with actual playback. - if window('emby.skip.%s' % item_id): - # property is set in player.py - window('emby.skip.%s' % item_id, clear=True) - else: - # notify the server - url = "{server}/emby/Users/{UserId}/PlayedItems/%s?format=json" % item_id - if data.get('playcount'): - self.download(url, action_type="POST") - log.info("Mark as watched for itemid: %s", item_id) - else: - self.download(url, action_type="DELETE") - log.info("Mark as unwatched for itemid: %s", item_id) - - @classmethod - def _system_wake(cls): - # Allow network to wake up - xbmc.sleep(10000) - window('emby_online', value="false") - window('emby_onWake', value="true") - - @classmethod - def _screensaver_deactivated(cls): - - if settings('dbSyncScreensaver') == "true": - xbmc.sleep(5000) - window('emby_onWake', value="true") - - @classmethod - def _get_item_id(cls, kodi_id, item_type): - - item_id = None - - with DatabaseConn('emby') as cursor: - emby_db = embydb.Embydb_Functions(cursor) - db_item = emby_db.getItem_byKodiId(kodi_id, item_type) - - try: - item_id = db_item[0] - except TypeError: - log.info("Could not retrieve item Id") - - return item_id - - -class SpecialMonitor(threading.Thread): - - _stop_thread = False - external_count = 0 - - def run(self): - - ''' Detect the resume dialog for widgets. - Detect external players. - ''' - - monitor = xbmc.Monitor() - log.warn("----====# Starting Special Monitor #====----") - - while not self._stop_thread: - - player = xbmc.Player() - isPlaying = player.isPlaying() - - if (not isPlaying and xbmc.getCondVisibility('Window.IsVisible(DialogContextMenu.xml)') and - xbmc.getInfoLabel('Control.GetLabel(1002)').decode('utf-8') == xbmc.getLocalizedString(12021)): - - control = int(xbmcgui.Window(10106).getFocusId()) - if control == 1002: # Start from beginning - log.info("Resume dialog: Start from beginning selected.") - window('emby.resume', clear=True) - else: - log.info("Resume dialog: Resume selected.") - window('emby.resume', value="true") - - elif isPlaying and not window('emby.external_check'): - time = player.getTime() - - if time > 1: # Not external player. - window('emby.external_check', value="true") - self.external_count = 0 - elif self.external_count == 120: - log.info("External player detected.") - window('emby.external', value="true") - window('emby.external_check', value="true") - self.external_count = 0 - elif time == 0: - self.external_count += 1 - - - if monitor.waitForAbort(0.5): - # Abort was requested while waiting. We should exit - break - - log.warn("#====---- Special Monitor Stopped ----====#") - - def stop_monitor(self): - self._stop_thread = True diff --git a/resources/lib/libraries/__init__.py b/resources/lib/libraries/__init__.py new file mode 100644 index 00000000..20b15530 --- /dev/null +++ b/resources/lib/libraries/__init__.py @@ -0,0 +1 @@ +import requests diff --git a/resources/lib/mutagen/__init__.py b/resources/lib/libraries/mutagen/__init__.py similarity index 100% rename from resources/lib/mutagen/__init__.py rename to resources/lib/libraries/mutagen/__init__.py diff --git a/resources/lib/mutagen/__pycache__/__init__.cpython-35.pyc b/resources/lib/libraries/mutagen/__pycache__/__init__.cpython-35.pyc similarity index 100% rename from resources/lib/mutagen/__pycache__/__init__.cpython-35.pyc rename to resources/lib/libraries/mutagen/__pycache__/__init__.cpython-35.pyc diff --git a/resources/lib/mutagen/__pycache__/_compat.cpython-35.pyc b/resources/lib/libraries/mutagen/__pycache__/_compat.cpython-35.pyc similarity index 100% rename from resources/lib/mutagen/__pycache__/_compat.cpython-35.pyc rename to resources/lib/libraries/mutagen/__pycache__/_compat.cpython-35.pyc diff --git a/resources/lib/mutagen/__pycache__/_constants.cpython-35.pyc b/resources/lib/libraries/mutagen/__pycache__/_constants.cpython-35.pyc similarity index 100% rename from resources/lib/mutagen/__pycache__/_constants.cpython-35.pyc rename to resources/lib/libraries/mutagen/__pycache__/_constants.cpython-35.pyc diff --git a/resources/lib/mutagen/__pycache__/_file.cpython-35.pyc b/resources/lib/libraries/mutagen/__pycache__/_file.cpython-35.pyc similarity index 100% rename from resources/lib/mutagen/__pycache__/_file.cpython-35.pyc rename to resources/lib/libraries/mutagen/__pycache__/_file.cpython-35.pyc diff --git a/resources/lib/mutagen/__pycache__/_mp3util.cpython-35.pyc b/resources/lib/libraries/mutagen/__pycache__/_mp3util.cpython-35.pyc similarity index 100% rename from resources/lib/mutagen/__pycache__/_mp3util.cpython-35.pyc rename to resources/lib/libraries/mutagen/__pycache__/_mp3util.cpython-35.pyc diff --git a/resources/lib/mutagen/__pycache__/_tags.cpython-35.pyc b/resources/lib/libraries/mutagen/__pycache__/_tags.cpython-35.pyc similarity index 100% rename from resources/lib/mutagen/__pycache__/_tags.cpython-35.pyc rename to resources/lib/libraries/mutagen/__pycache__/_tags.cpython-35.pyc diff --git a/resources/lib/mutagen/__pycache__/_toolsutil.cpython-35.pyc b/resources/lib/libraries/mutagen/__pycache__/_toolsutil.cpython-35.pyc similarity index 100% rename from resources/lib/mutagen/__pycache__/_toolsutil.cpython-35.pyc rename to resources/lib/libraries/mutagen/__pycache__/_toolsutil.cpython-35.pyc diff --git a/resources/lib/mutagen/__pycache__/_util.cpython-35.pyc b/resources/lib/libraries/mutagen/__pycache__/_util.cpython-35.pyc similarity index 100% rename from resources/lib/mutagen/__pycache__/_util.cpython-35.pyc rename to resources/lib/libraries/mutagen/__pycache__/_util.cpython-35.pyc diff --git a/resources/lib/mutagen/__pycache__/_vorbis.cpython-35.pyc b/resources/lib/libraries/mutagen/__pycache__/_vorbis.cpython-35.pyc similarity index 100% rename from resources/lib/mutagen/__pycache__/_vorbis.cpython-35.pyc rename to resources/lib/libraries/mutagen/__pycache__/_vorbis.cpython-35.pyc diff --git a/resources/lib/mutagen/__pycache__/aac.cpython-35.pyc b/resources/lib/libraries/mutagen/__pycache__/aac.cpython-35.pyc similarity index 100% rename from resources/lib/mutagen/__pycache__/aac.cpython-35.pyc rename to resources/lib/libraries/mutagen/__pycache__/aac.cpython-35.pyc diff --git a/resources/lib/mutagen/__pycache__/aiff.cpython-35.pyc b/resources/lib/libraries/mutagen/__pycache__/aiff.cpython-35.pyc similarity index 100% rename from resources/lib/mutagen/__pycache__/aiff.cpython-35.pyc rename to resources/lib/libraries/mutagen/__pycache__/aiff.cpython-35.pyc diff --git a/resources/lib/mutagen/__pycache__/apev2.cpython-35.pyc b/resources/lib/libraries/mutagen/__pycache__/apev2.cpython-35.pyc similarity index 100% rename from resources/lib/mutagen/__pycache__/apev2.cpython-35.pyc rename to resources/lib/libraries/mutagen/__pycache__/apev2.cpython-35.pyc diff --git a/resources/lib/mutagen/__pycache__/easyid3.cpython-35.pyc b/resources/lib/libraries/mutagen/__pycache__/easyid3.cpython-35.pyc similarity index 100% rename from resources/lib/mutagen/__pycache__/easyid3.cpython-35.pyc rename to resources/lib/libraries/mutagen/__pycache__/easyid3.cpython-35.pyc diff --git a/resources/lib/mutagen/__pycache__/easymp4.cpython-35.pyc b/resources/lib/libraries/mutagen/__pycache__/easymp4.cpython-35.pyc similarity index 100% rename from resources/lib/mutagen/__pycache__/easymp4.cpython-35.pyc rename to resources/lib/libraries/mutagen/__pycache__/easymp4.cpython-35.pyc diff --git a/resources/lib/mutagen/__pycache__/flac.cpython-35.pyc b/resources/lib/libraries/mutagen/__pycache__/flac.cpython-35.pyc similarity index 100% rename from resources/lib/mutagen/__pycache__/flac.cpython-35.pyc rename to resources/lib/libraries/mutagen/__pycache__/flac.cpython-35.pyc diff --git a/resources/lib/mutagen/__pycache__/m4a.cpython-35.pyc b/resources/lib/libraries/mutagen/__pycache__/m4a.cpython-35.pyc similarity index 100% rename from resources/lib/mutagen/__pycache__/m4a.cpython-35.pyc rename to resources/lib/libraries/mutagen/__pycache__/m4a.cpython-35.pyc diff --git a/resources/lib/mutagen/__pycache__/monkeysaudio.cpython-35.pyc b/resources/lib/libraries/mutagen/__pycache__/monkeysaudio.cpython-35.pyc similarity index 100% rename from resources/lib/mutagen/__pycache__/monkeysaudio.cpython-35.pyc rename to resources/lib/libraries/mutagen/__pycache__/monkeysaudio.cpython-35.pyc diff --git a/resources/lib/mutagen/__pycache__/mp3.cpython-35.pyc b/resources/lib/libraries/mutagen/__pycache__/mp3.cpython-35.pyc similarity index 100% rename from resources/lib/mutagen/__pycache__/mp3.cpython-35.pyc rename to resources/lib/libraries/mutagen/__pycache__/mp3.cpython-35.pyc diff --git a/resources/lib/mutagen/__pycache__/musepack.cpython-35.pyc b/resources/lib/libraries/mutagen/__pycache__/musepack.cpython-35.pyc similarity index 100% rename from resources/lib/mutagen/__pycache__/musepack.cpython-35.pyc rename to resources/lib/libraries/mutagen/__pycache__/musepack.cpython-35.pyc diff --git a/resources/lib/mutagen/__pycache__/ogg.cpython-35.pyc b/resources/lib/libraries/mutagen/__pycache__/ogg.cpython-35.pyc similarity index 100% rename from resources/lib/mutagen/__pycache__/ogg.cpython-35.pyc rename to resources/lib/libraries/mutagen/__pycache__/ogg.cpython-35.pyc diff --git a/resources/lib/mutagen/__pycache__/oggflac.cpython-35.pyc b/resources/lib/libraries/mutagen/__pycache__/oggflac.cpython-35.pyc similarity index 100% rename from resources/lib/mutagen/__pycache__/oggflac.cpython-35.pyc rename to resources/lib/libraries/mutagen/__pycache__/oggflac.cpython-35.pyc diff --git a/resources/lib/mutagen/__pycache__/oggopus.cpython-35.pyc b/resources/lib/libraries/mutagen/__pycache__/oggopus.cpython-35.pyc similarity index 100% rename from resources/lib/mutagen/__pycache__/oggopus.cpython-35.pyc rename to resources/lib/libraries/mutagen/__pycache__/oggopus.cpython-35.pyc diff --git a/resources/lib/mutagen/__pycache__/oggspeex.cpython-35.pyc b/resources/lib/libraries/mutagen/__pycache__/oggspeex.cpython-35.pyc similarity index 100% rename from resources/lib/mutagen/__pycache__/oggspeex.cpython-35.pyc rename to resources/lib/libraries/mutagen/__pycache__/oggspeex.cpython-35.pyc diff --git a/resources/lib/mutagen/__pycache__/oggtheora.cpython-35.pyc b/resources/lib/libraries/mutagen/__pycache__/oggtheora.cpython-35.pyc similarity index 100% rename from resources/lib/mutagen/__pycache__/oggtheora.cpython-35.pyc rename to resources/lib/libraries/mutagen/__pycache__/oggtheora.cpython-35.pyc diff --git a/resources/lib/mutagen/__pycache__/oggvorbis.cpython-35.pyc b/resources/lib/libraries/mutagen/__pycache__/oggvorbis.cpython-35.pyc similarity index 100% rename from resources/lib/mutagen/__pycache__/oggvorbis.cpython-35.pyc rename to resources/lib/libraries/mutagen/__pycache__/oggvorbis.cpython-35.pyc diff --git a/resources/lib/mutagen/__pycache__/optimfrog.cpython-35.pyc b/resources/lib/libraries/mutagen/__pycache__/optimfrog.cpython-35.pyc similarity index 100% rename from resources/lib/mutagen/__pycache__/optimfrog.cpython-35.pyc rename to resources/lib/libraries/mutagen/__pycache__/optimfrog.cpython-35.pyc diff --git a/resources/lib/mutagen/__pycache__/trueaudio.cpython-35.pyc b/resources/lib/libraries/mutagen/__pycache__/trueaudio.cpython-35.pyc similarity index 100% rename from resources/lib/mutagen/__pycache__/trueaudio.cpython-35.pyc rename to resources/lib/libraries/mutagen/__pycache__/trueaudio.cpython-35.pyc diff --git a/resources/lib/mutagen/__pycache__/wavpack.cpython-35.pyc b/resources/lib/libraries/mutagen/__pycache__/wavpack.cpython-35.pyc similarity index 100% rename from resources/lib/mutagen/__pycache__/wavpack.cpython-35.pyc rename to resources/lib/libraries/mutagen/__pycache__/wavpack.cpython-35.pyc diff --git a/resources/lib/mutagen/_compat.py b/resources/lib/libraries/mutagen/_compat.py similarity index 100% rename from resources/lib/mutagen/_compat.py rename to resources/lib/libraries/mutagen/_compat.py diff --git a/resources/lib/mutagen/_constants.py b/resources/lib/libraries/mutagen/_constants.py similarity index 100% rename from resources/lib/mutagen/_constants.py rename to resources/lib/libraries/mutagen/_constants.py diff --git a/resources/lib/mutagen/_file.py b/resources/lib/libraries/mutagen/_file.py similarity index 100% rename from resources/lib/mutagen/_file.py rename to resources/lib/libraries/mutagen/_file.py diff --git a/resources/lib/mutagen/_mp3util.py b/resources/lib/libraries/mutagen/_mp3util.py similarity index 100% rename from resources/lib/mutagen/_mp3util.py rename to resources/lib/libraries/mutagen/_mp3util.py diff --git a/resources/lib/mutagen/_tags.py b/resources/lib/libraries/mutagen/_tags.py similarity index 100% rename from resources/lib/mutagen/_tags.py rename to resources/lib/libraries/mutagen/_tags.py diff --git a/resources/lib/mutagen/_toolsutil.py b/resources/lib/libraries/mutagen/_toolsutil.py similarity index 100% rename from resources/lib/mutagen/_toolsutil.py rename to resources/lib/libraries/mutagen/_toolsutil.py diff --git a/resources/lib/mutagen/_util.py b/resources/lib/libraries/mutagen/_util.py similarity index 100% rename from resources/lib/mutagen/_util.py rename to resources/lib/libraries/mutagen/_util.py diff --git a/resources/lib/mutagen/_vorbis.py b/resources/lib/libraries/mutagen/_vorbis.py similarity index 100% rename from resources/lib/mutagen/_vorbis.py rename to resources/lib/libraries/mutagen/_vorbis.py diff --git a/resources/lib/mutagen/aac.py b/resources/lib/libraries/mutagen/aac.py similarity index 100% rename from resources/lib/mutagen/aac.py rename to resources/lib/libraries/mutagen/aac.py diff --git a/resources/lib/mutagen/aiff.py b/resources/lib/libraries/mutagen/aiff.py similarity index 100% rename from resources/lib/mutagen/aiff.py rename to resources/lib/libraries/mutagen/aiff.py diff --git a/resources/lib/mutagen/apev2.py b/resources/lib/libraries/mutagen/apev2.py similarity index 100% rename from resources/lib/mutagen/apev2.py rename to resources/lib/libraries/mutagen/apev2.py diff --git a/resources/lib/mutagen/asf/__init__.py b/resources/lib/libraries/mutagen/asf/__init__.py similarity index 100% rename from resources/lib/mutagen/asf/__init__.py rename to resources/lib/libraries/mutagen/asf/__init__.py diff --git a/resources/lib/mutagen/asf/__pycache__/__init__.cpython-35.pyc b/resources/lib/libraries/mutagen/asf/__pycache__/__init__.cpython-35.pyc similarity index 100% rename from resources/lib/mutagen/asf/__pycache__/__init__.cpython-35.pyc rename to resources/lib/libraries/mutagen/asf/__pycache__/__init__.cpython-35.pyc diff --git a/resources/lib/mutagen/asf/__pycache__/_attrs.cpython-35.pyc b/resources/lib/libraries/mutagen/asf/__pycache__/_attrs.cpython-35.pyc similarity index 100% rename from resources/lib/mutagen/asf/__pycache__/_attrs.cpython-35.pyc rename to resources/lib/libraries/mutagen/asf/__pycache__/_attrs.cpython-35.pyc diff --git a/resources/lib/mutagen/asf/__pycache__/_objects.cpython-35.pyc b/resources/lib/libraries/mutagen/asf/__pycache__/_objects.cpython-35.pyc similarity index 100% rename from resources/lib/mutagen/asf/__pycache__/_objects.cpython-35.pyc rename to resources/lib/libraries/mutagen/asf/__pycache__/_objects.cpython-35.pyc diff --git a/resources/lib/mutagen/asf/__pycache__/_util.cpython-35.pyc b/resources/lib/libraries/mutagen/asf/__pycache__/_util.cpython-35.pyc similarity index 100% rename from resources/lib/mutagen/asf/__pycache__/_util.cpython-35.pyc rename to resources/lib/libraries/mutagen/asf/__pycache__/_util.cpython-35.pyc diff --git a/resources/lib/mutagen/asf/_attrs.py b/resources/lib/libraries/mutagen/asf/_attrs.py similarity index 100% rename from resources/lib/mutagen/asf/_attrs.py rename to resources/lib/libraries/mutagen/asf/_attrs.py diff --git a/resources/lib/mutagen/asf/_objects.py b/resources/lib/libraries/mutagen/asf/_objects.py similarity index 100% rename from resources/lib/mutagen/asf/_objects.py rename to resources/lib/libraries/mutagen/asf/_objects.py diff --git a/resources/lib/mutagen/asf/_util.py b/resources/lib/libraries/mutagen/asf/_util.py similarity index 100% rename from resources/lib/mutagen/asf/_util.py rename to resources/lib/libraries/mutagen/asf/_util.py diff --git a/resources/lib/mutagen/easyid3.py b/resources/lib/libraries/mutagen/easyid3.py similarity index 100% rename from resources/lib/mutagen/easyid3.py rename to resources/lib/libraries/mutagen/easyid3.py diff --git a/resources/lib/mutagen/easymp4.py b/resources/lib/libraries/mutagen/easymp4.py similarity index 100% rename from resources/lib/mutagen/easymp4.py rename to resources/lib/libraries/mutagen/easymp4.py diff --git a/resources/lib/mutagen/flac.py b/resources/lib/libraries/mutagen/flac.py similarity index 100% rename from resources/lib/mutagen/flac.py rename to resources/lib/libraries/mutagen/flac.py diff --git a/resources/lib/mutagen/id3/__init__.py b/resources/lib/libraries/mutagen/id3/__init__.py similarity index 100% rename from resources/lib/mutagen/id3/__init__.py rename to resources/lib/libraries/mutagen/id3/__init__.py diff --git a/resources/lib/mutagen/id3/__pycache__/__init__.cpython-35.pyc b/resources/lib/libraries/mutagen/id3/__pycache__/__init__.cpython-35.pyc similarity index 100% rename from resources/lib/mutagen/id3/__pycache__/__init__.cpython-35.pyc rename to resources/lib/libraries/mutagen/id3/__pycache__/__init__.cpython-35.pyc diff --git a/resources/lib/mutagen/id3/__pycache__/_frames.cpython-35.pyc b/resources/lib/libraries/mutagen/id3/__pycache__/_frames.cpython-35.pyc similarity index 100% rename from resources/lib/mutagen/id3/__pycache__/_frames.cpython-35.pyc rename to resources/lib/libraries/mutagen/id3/__pycache__/_frames.cpython-35.pyc diff --git a/resources/lib/mutagen/id3/__pycache__/_specs.cpython-35.pyc b/resources/lib/libraries/mutagen/id3/__pycache__/_specs.cpython-35.pyc similarity index 100% rename from resources/lib/mutagen/id3/__pycache__/_specs.cpython-35.pyc rename to resources/lib/libraries/mutagen/id3/__pycache__/_specs.cpython-35.pyc diff --git a/resources/lib/mutagen/id3/__pycache__/_util.cpython-35.pyc b/resources/lib/libraries/mutagen/id3/__pycache__/_util.cpython-35.pyc similarity index 100% rename from resources/lib/mutagen/id3/__pycache__/_util.cpython-35.pyc rename to resources/lib/libraries/mutagen/id3/__pycache__/_util.cpython-35.pyc diff --git a/resources/lib/mutagen/id3/_frames.py b/resources/lib/libraries/mutagen/id3/_frames.py similarity index 100% rename from resources/lib/mutagen/id3/_frames.py rename to resources/lib/libraries/mutagen/id3/_frames.py diff --git a/resources/lib/mutagen/id3/_specs.py b/resources/lib/libraries/mutagen/id3/_specs.py similarity index 100% rename from resources/lib/mutagen/id3/_specs.py rename to resources/lib/libraries/mutagen/id3/_specs.py diff --git a/resources/lib/mutagen/id3/_util.py b/resources/lib/libraries/mutagen/id3/_util.py similarity index 100% rename from resources/lib/mutagen/id3/_util.py rename to resources/lib/libraries/mutagen/id3/_util.py diff --git a/resources/lib/mutagen/m4a.py b/resources/lib/libraries/mutagen/m4a.py similarity index 100% rename from resources/lib/mutagen/m4a.py rename to resources/lib/libraries/mutagen/m4a.py diff --git a/resources/lib/mutagen/monkeysaudio.py b/resources/lib/libraries/mutagen/monkeysaudio.py similarity index 100% rename from resources/lib/mutagen/monkeysaudio.py rename to resources/lib/libraries/mutagen/monkeysaudio.py diff --git a/resources/lib/mutagen/mp3.py b/resources/lib/libraries/mutagen/mp3.py similarity index 100% rename from resources/lib/mutagen/mp3.py rename to resources/lib/libraries/mutagen/mp3.py diff --git a/resources/lib/mutagen/mp4/__init__.py b/resources/lib/libraries/mutagen/mp4/__init__.py similarity index 100% rename from resources/lib/mutagen/mp4/__init__.py rename to resources/lib/libraries/mutagen/mp4/__init__.py diff --git a/resources/lib/mutagen/mp4/__pycache__/__init__.cpython-35.pyc b/resources/lib/libraries/mutagen/mp4/__pycache__/__init__.cpython-35.pyc similarity index 100% rename from resources/lib/mutagen/mp4/__pycache__/__init__.cpython-35.pyc rename to resources/lib/libraries/mutagen/mp4/__pycache__/__init__.cpython-35.pyc diff --git a/resources/lib/mutagen/mp4/__pycache__/_as_entry.cpython-35.pyc b/resources/lib/libraries/mutagen/mp4/__pycache__/_as_entry.cpython-35.pyc similarity index 100% rename from resources/lib/mutagen/mp4/__pycache__/_as_entry.cpython-35.pyc rename to resources/lib/libraries/mutagen/mp4/__pycache__/_as_entry.cpython-35.pyc diff --git a/resources/lib/mutagen/mp4/__pycache__/_atom.cpython-35.pyc b/resources/lib/libraries/mutagen/mp4/__pycache__/_atom.cpython-35.pyc similarity index 100% rename from resources/lib/mutagen/mp4/__pycache__/_atom.cpython-35.pyc rename to resources/lib/libraries/mutagen/mp4/__pycache__/_atom.cpython-35.pyc diff --git a/resources/lib/mutagen/mp4/__pycache__/_util.cpython-35.pyc b/resources/lib/libraries/mutagen/mp4/__pycache__/_util.cpython-35.pyc similarity index 100% rename from resources/lib/mutagen/mp4/__pycache__/_util.cpython-35.pyc rename to resources/lib/libraries/mutagen/mp4/__pycache__/_util.cpython-35.pyc diff --git a/resources/lib/mutagen/mp4/_as_entry.py b/resources/lib/libraries/mutagen/mp4/_as_entry.py similarity index 100% rename from resources/lib/mutagen/mp4/_as_entry.py rename to resources/lib/libraries/mutagen/mp4/_as_entry.py diff --git a/resources/lib/mutagen/mp4/_atom.py b/resources/lib/libraries/mutagen/mp4/_atom.py similarity index 100% rename from resources/lib/mutagen/mp4/_atom.py rename to resources/lib/libraries/mutagen/mp4/_atom.py diff --git a/resources/lib/mutagen/mp4/_util.py b/resources/lib/libraries/mutagen/mp4/_util.py similarity index 100% rename from resources/lib/mutagen/mp4/_util.py rename to resources/lib/libraries/mutagen/mp4/_util.py diff --git a/resources/lib/mutagen/musepack.py b/resources/lib/libraries/mutagen/musepack.py similarity index 100% rename from resources/lib/mutagen/musepack.py rename to resources/lib/libraries/mutagen/musepack.py diff --git a/resources/lib/mutagen/ogg.py b/resources/lib/libraries/mutagen/ogg.py similarity index 100% rename from resources/lib/mutagen/ogg.py rename to resources/lib/libraries/mutagen/ogg.py diff --git a/resources/lib/mutagen/oggflac.py b/resources/lib/libraries/mutagen/oggflac.py similarity index 100% rename from resources/lib/mutagen/oggflac.py rename to resources/lib/libraries/mutagen/oggflac.py diff --git a/resources/lib/mutagen/oggopus.py b/resources/lib/libraries/mutagen/oggopus.py similarity index 100% rename from resources/lib/mutagen/oggopus.py rename to resources/lib/libraries/mutagen/oggopus.py diff --git a/resources/lib/mutagen/oggspeex.py b/resources/lib/libraries/mutagen/oggspeex.py similarity index 100% rename from resources/lib/mutagen/oggspeex.py rename to resources/lib/libraries/mutagen/oggspeex.py diff --git a/resources/lib/mutagen/oggtheora.py b/resources/lib/libraries/mutagen/oggtheora.py similarity index 100% rename from resources/lib/mutagen/oggtheora.py rename to resources/lib/libraries/mutagen/oggtheora.py diff --git a/resources/lib/mutagen/oggvorbis.py b/resources/lib/libraries/mutagen/oggvorbis.py similarity index 100% rename from resources/lib/mutagen/oggvorbis.py rename to resources/lib/libraries/mutagen/oggvorbis.py diff --git a/resources/lib/mutagen/optimfrog.py b/resources/lib/libraries/mutagen/optimfrog.py similarity index 100% rename from resources/lib/mutagen/optimfrog.py rename to resources/lib/libraries/mutagen/optimfrog.py diff --git a/resources/lib/mutagen/trueaudio.py b/resources/lib/libraries/mutagen/trueaudio.py similarity index 100% rename from resources/lib/mutagen/trueaudio.py rename to resources/lib/libraries/mutagen/trueaudio.py diff --git a/resources/lib/mutagen/wavpack.py b/resources/lib/libraries/mutagen/wavpack.py similarity index 100% rename from resources/lib/mutagen/wavpack.py rename to resources/lib/libraries/mutagen/wavpack.py diff --git a/resources/lib/libraries/requests/__init__.py b/resources/lib/libraries/requests/__init__.py new file mode 100644 index 00000000..bd5b5b97 --- /dev/null +++ b/resources/lib/libraries/requests/__init__.py @@ -0,0 +1,83 @@ +# -*- coding: utf-8 -*- + +# __ +# /__) _ _ _ _ _/ _ +# / ( (- (/ (/ (- _) / _) +# / + +""" +Requests HTTP library +~~~~~~~~~~~~~~~~~~~~~ + +Requests is an HTTP library, written in Python, for human beings. Basic GET +usage: + + >>> import requests + >>> r = requests.get('https://www.python.org') + >>> r.status_code + 200 + >>> 'Python is a programming language' in r.content + True + +... or POST: + + >>> payload = dict(key1='value1', key2='value2') + >>> r = requests.post('http://httpbin.org/post', data=payload) + >>> print(r.text) + { + ... + "form": { + "key2": "value2", + "key1": "value1" + }, + ... + } + +The other HTTP methods are supported - see `requests.api`. Full documentation +is at <http://python-requests.org>. + +:copyright: (c) 2015 by Kenneth Reitz. +:license: Apache 2.0, see LICENSE for more details. + +""" + +__title__ = 'requests' +__version__ = '2.9.1' +__build__ = 0x020901 +__author__ = 'Kenneth Reitz' +__license__ = 'Apache 2.0' +__copyright__ = 'Copyright 2015 Kenneth Reitz' + +# Attempt to enable urllib3's SNI support, if possible +try: + from .packages.urllib3.contrib import pyopenssl + pyopenssl.inject_into_urllib3() +except ImportError: + pass + +from . import utils +from .models import Request, Response, PreparedRequest +from .api import request, get, head, post, patch, put, delete, options +from .sessions import session, Session +from .status_codes import codes +from .exceptions import ( + RequestException, Timeout, URLRequired, + TooManyRedirects, HTTPError, ConnectionError, + FileModeWarning, +) + +# Set default logging handler to avoid "No handler found" warnings. +import logging +try: # Python 2.7+ + from logging import NullHandler +except ImportError: + class NullHandler(logging.Handler): + def emit(self, record): + pass + +logging.getLogger(__name__).addHandler(NullHandler()) + +import warnings + +# FileModeWarnings go off per the default. +warnings.simplefilter('default', FileModeWarning, append=True) diff --git a/resources/lib/libraries/requests/adapters.py b/resources/lib/libraries/requests/adapters.py new file mode 100644 index 00000000..6266d5be --- /dev/null +++ b/resources/lib/libraries/requests/adapters.py @@ -0,0 +1,453 @@ +# -*- coding: utf-8 -*- + +""" +requests.adapters +~~~~~~~~~~~~~~~~~ + +This module contains the transport adapters that Requests uses to define +and maintain connections. +""" + +import os.path +import socket + +from .models import Response +from .packages.urllib3.poolmanager import PoolManager, proxy_from_url +from .packages.urllib3.response import HTTPResponse +from .packages.urllib3.util import Timeout as TimeoutSauce +from .packages.urllib3.util.retry import Retry +from .compat import urlparse, basestring +from .utils import (DEFAULT_CA_BUNDLE_PATH, get_encoding_from_headers, + prepend_scheme_if_needed, get_auth_from_url, urldefragauth, + select_proxy) +from .structures import CaseInsensitiveDict +from .packages.urllib3.exceptions import ClosedPoolError +from .packages.urllib3.exceptions import ConnectTimeoutError +from .packages.urllib3.exceptions import HTTPError as _HTTPError +from .packages.urllib3.exceptions import MaxRetryError +from .packages.urllib3.exceptions import NewConnectionError +from .packages.urllib3.exceptions import ProxyError as _ProxyError +from .packages.urllib3.exceptions import ProtocolError +from .packages.urllib3.exceptions import ReadTimeoutError +from .packages.urllib3.exceptions import SSLError as _SSLError +from .packages.urllib3.exceptions import ResponseError +from .cookies import extract_cookies_to_jar +from .exceptions import (ConnectionError, ConnectTimeout, ReadTimeout, SSLError, + ProxyError, RetryError) +from .auth import _basic_auth_str + +DEFAULT_POOLBLOCK = False +DEFAULT_POOLSIZE = 10 +DEFAULT_RETRIES = 0 +DEFAULT_POOL_TIMEOUT = None + + +class BaseAdapter(object): + """The Base Transport Adapter""" + + def __init__(self): + super(BaseAdapter, self).__init__() + + def send(self): + raise NotImplementedError + + def close(self): + raise NotImplementedError + + +class HTTPAdapter(BaseAdapter): + """The built-in HTTP Adapter for urllib3. + + Provides a general-case interface for Requests sessions to contact HTTP and + HTTPS urls by implementing the Transport Adapter interface. This class will + usually be created by the :class:`Session <Session>` class under the + covers. + + :param pool_connections: The number of urllib3 connection pools to cache. + :param pool_maxsize: The maximum number of connections to save in the pool. + :param int max_retries: The maximum number of retries each connection + should attempt. Note, this applies only to failed DNS lookups, socket + connections and connection timeouts, never to requests where data has + made it to the server. By default, Requests does not retry failed + connections. If you need granular control over the conditions under + which we retry a request, import urllib3's ``Retry`` class and pass + that instead. + :param pool_block: Whether the connection pool should block for connections. + + Usage:: + + >>> import requests + >>> s = requests.Session() + >>> a = requests.adapters.HTTPAdapter(max_retries=3) + >>> s.mount('http://', a) + """ + __attrs__ = ['max_retries', 'config', '_pool_connections', '_pool_maxsize', + '_pool_block'] + + def __init__(self, pool_connections=DEFAULT_POOLSIZE, + pool_maxsize=DEFAULT_POOLSIZE, max_retries=DEFAULT_RETRIES, + pool_block=DEFAULT_POOLBLOCK): + if max_retries == DEFAULT_RETRIES: + self.max_retries = Retry(0, read=False) + else: + self.max_retries = Retry.from_int(max_retries) + self.config = {} + self.proxy_manager = {} + + super(HTTPAdapter, self).__init__() + + self._pool_connections = pool_connections + self._pool_maxsize = pool_maxsize + self._pool_block = pool_block + + self.init_poolmanager(pool_connections, pool_maxsize, block=pool_block) + + def __getstate__(self): + return dict((attr, getattr(self, attr, None)) for attr in + self.__attrs__) + + def __setstate__(self, state): + # Can't handle by adding 'proxy_manager' to self.__attrs__ because + # self.poolmanager uses a lambda function, which isn't pickleable. + self.proxy_manager = {} + self.config = {} + + for attr, value in state.items(): + setattr(self, attr, value) + + self.init_poolmanager(self._pool_connections, self._pool_maxsize, + block=self._pool_block) + + def init_poolmanager(self, connections, maxsize, block=DEFAULT_POOLBLOCK, **pool_kwargs): + """Initializes a urllib3 PoolManager. + + This method should not be called from user code, and is only + exposed for use when subclassing the + :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`. + + :param connections: The number of urllib3 connection pools to cache. + :param maxsize: The maximum number of connections to save in the pool. + :param block: Block when no free connections are available. + :param pool_kwargs: Extra keyword arguments used to initialize the Pool Manager. + """ + # save these values for pickling + self._pool_connections = connections + self._pool_maxsize = maxsize + self._pool_block = block + + self.poolmanager = PoolManager(num_pools=connections, maxsize=maxsize, + block=block, strict=True, **pool_kwargs) + + def proxy_manager_for(self, proxy, **proxy_kwargs): + """Return urllib3 ProxyManager for the given proxy. + + This method should not be called from user code, and is only + exposed for use when subclassing the + :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`. + + :param proxy: The proxy to return a urllib3 ProxyManager for. + :param proxy_kwargs: Extra keyword arguments used to configure the Proxy Manager. + :returns: ProxyManager + """ + if not proxy in self.proxy_manager: + proxy_headers = self.proxy_headers(proxy) + self.proxy_manager[proxy] = proxy_from_url( + proxy, + proxy_headers=proxy_headers, + num_pools=self._pool_connections, + maxsize=self._pool_maxsize, + block=self._pool_block, + **proxy_kwargs) + + return self.proxy_manager[proxy] + + def cert_verify(self, conn, url, verify, cert): + """Verify a SSL certificate. This method should not be called from user + code, and is only exposed for use when subclassing the + :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`. + + :param conn: The urllib3 connection object associated with the cert. + :param url: The requested URL. + :param verify: Whether we should actually verify the certificate. + :param cert: The SSL certificate to verify. + """ + if url.lower().startswith('https') and verify: + + cert_loc = None + + # Allow self-specified cert location. + if verify is not True: + cert_loc = verify + + if not cert_loc: + cert_loc = DEFAULT_CA_BUNDLE_PATH + + if not cert_loc: + raise Exception("Could not find a suitable SSL CA certificate bundle.") + + conn.cert_reqs = 'CERT_REQUIRED' + + if not os.path.isdir(cert_loc): + conn.ca_certs = cert_loc + else: + conn.ca_cert_dir = cert_loc + else: + conn.cert_reqs = 'CERT_NONE' + conn.ca_certs = None + conn.ca_cert_dir = None + + if cert: + if not isinstance(cert, basestring): + conn.cert_file = cert[0] + conn.key_file = cert[1] + else: + conn.cert_file = cert + + def build_response(self, req, resp): + """Builds a :class:`Response <requests.Response>` object from a urllib3 + response. This should not be called from user code, and is only exposed + for use when subclassing the + :class:`HTTPAdapter <requests.adapters.HTTPAdapter>` + + :param req: The :class:`PreparedRequest <PreparedRequest>` used to generate the response. + :param resp: The urllib3 response object. + """ + response = Response() + + # Fallback to None if there's no status_code, for whatever reason. + response.status_code = getattr(resp, 'status', None) + + # Make headers case-insensitive. + response.headers = CaseInsensitiveDict(getattr(resp, 'headers', {})) + + # Set encoding. + response.encoding = get_encoding_from_headers(response.headers) + response.raw = resp + response.reason = response.raw.reason + + if isinstance(req.url, bytes): + response.url = req.url.decode('utf-8') + else: + response.url = req.url + + # Add new cookies from the server. + extract_cookies_to_jar(response.cookies, req, resp) + + # Give the Response some context. + response.request = req + response.connection = self + + return response + + def get_connection(self, url, proxies=None): + """Returns a urllib3 connection for the given URL. This should not be + called from user code, and is only exposed for use when subclassing the + :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`. + + :param url: The URL to connect to. + :param proxies: (optional) A Requests-style dictionary of proxies used on this request. + """ + proxy = select_proxy(url, proxies) + + if proxy: + proxy = prepend_scheme_if_needed(proxy, 'http') + proxy_manager = self.proxy_manager_for(proxy) + conn = proxy_manager.connection_from_url(url) + else: + # Only scheme should be lower case + parsed = urlparse(url) + url = parsed.geturl() + conn = self.poolmanager.connection_from_url(url) + + return conn + + def close(self): + """Disposes of any internal state. + + Currently, this just closes the PoolManager, which closes pooled + connections. + """ + self.poolmanager.clear() + + def request_url(self, request, proxies): + """Obtain the url to use when making the final request. + + If the message is being sent through a HTTP proxy, the full URL has to + be used. Otherwise, we should only use the path portion of the URL. + + This should not be called from user code, and is only exposed for use + when subclassing the + :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`. + + :param request: The :class:`PreparedRequest <PreparedRequest>` being sent. + :param proxies: A dictionary of schemes or schemes and hosts to proxy URLs. + """ + proxy = select_proxy(request.url, proxies) + scheme = urlparse(request.url).scheme + if proxy and scheme != 'https': + url = urldefragauth(request.url) + else: + url = request.path_url + + return url + + def add_headers(self, request, **kwargs): + """Add any headers needed by the connection. As of v2.0 this does + nothing by default, but is left for overriding by users that subclass + the :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`. + + This should not be called from user code, and is only exposed for use + when subclassing the + :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`. + + :param request: The :class:`PreparedRequest <PreparedRequest>` to add headers to. + :param kwargs: The keyword arguments from the call to send(). + """ + pass + + def proxy_headers(self, proxy): + """Returns a dictionary of the headers to add to any request sent + through a proxy. This works with urllib3 magic to ensure that they are + correctly sent to the proxy, rather than in a tunnelled request if + CONNECT is being used. + + This should not be called from user code, and is only exposed for use + when subclassing the + :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`. + + :param proxies: The url of the proxy being used for this request. + """ + headers = {} + username, password = get_auth_from_url(proxy) + + if username and password: + headers['Proxy-Authorization'] = _basic_auth_str(username, + password) + + return headers + + def send(self, request, stream=False, timeout=None, verify=True, cert=None, proxies=None): + """Sends PreparedRequest object. Returns Response object. + + :param request: The :class:`PreparedRequest <PreparedRequest>` being sent. + :param stream: (optional) Whether to stream the request content. + :param timeout: (optional) How long to wait for the server to send + data before giving up, as a float, or a :ref:`(connect timeout, + read timeout) <timeouts>` tuple. + :type timeout: float or tuple + :param verify: (optional) Whether to verify SSL certificates. + :param cert: (optional) Any user-provided SSL certificate to be trusted. + :param proxies: (optional) The proxies dictionary to apply to the request. + """ + + conn = self.get_connection(request.url, proxies) + + self.cert_verify(conn, request.url, verify, cert) + url = self.request_url(request, proxies) + self.add_headers(request) + + chunked = not (request.body is None or 'Content-Length' in request.headers) + + if isinstance(timeout, tuple): + try: + connect, read = timeout + timeout = TimeoutSauce(connect=connect, read=read) + except ValueError as e: + # this may raise a string formatting error. + err = ("Invalid timeout {0}. Pass a (connect, read) " + "timeout tuple, or a single float to set " + "both timeouts to the same value".format(timeout)) + raise ValueError(err) + else: + timeout = TimeoutSauce(connect=timeout, read=timeout) + + try: + if not chunked: + resp = conn.urlopen( + method=request.method, + url=url, + body=request.body, + headers=request.headers, + redirect=False, + assert_same_host=False, + preload_content=False, + decode_content=False, + retries=self.max_retries, + timeout=timeout + ) + + # Send the request. + else: + if hasattr(conn, 'proxy_pool'): + conn = conn.proxy_pool + + low_conn = conn._get_conn(timeout=DEFAULT_POOL_TIMEOUT) + + try: + low_conn.putrequest(request.method, + url, + skip_accept_encoding=True) + + for header, value in request.headers.items(): + low_conn.putheader(header, value) + + low_conn.endheaders() + + for i in request.body: + low_conn.send(hex(len(i))[2:].encode('utf-8')) + low_conn.send(b'\r\n') + low_conn.send(i) + low_conn.send(b'\r\n') + low_conn.send(b'0\r\n\r\n') + + # Receive the response from the server + try: + # For Python 2.7+ versions, use buffering of HTTP + # responses + r = low_conn.getresponse(buffering=True) + except TypeError: + # For compatibility with Python 2.6 versions and back + r = low_conn.getresponse() + + resp = HTTPResponse.from_httplib( + r, + pool=conn, + connection=low_conn, + preload_content=False, + decode_content=False + ) + except: + # If we hit any problems here, clean up the connection. + # Then, reraise so that we can handle the actual exception. + low_conn.close() + raise + + except (ProtocolError, socket.error) as err: + raise ConnectionError(err, request=request) + + except MaxRetryError as e: + if isinstance(e.reason, ConnectTimeoutError): + # TODO: Remove this in 3.0.0: see #2811 + if not isinstance(e.reason, NewConnectionError): + raise ConnectTimeout(e, request=request) + + if isinstance(e.reason, ResponseError): + raise RetryError(e, request=request) + + raise ConnectionError(e, request=request) + + except ClosedPoolError as e: + raise ConnectionError(e, request=request) + + except _ProxyError as e: + raise ProxyError(e) + + except (_SSLError, _HTTPError) as e: + if isinstance(e, _SSLError): + raise SSLError(e, request=request) + elif isinstance(e, ReadTimeoutError): + raise ReadTimeout(e, request=request) + else: + raise + + return self.build_response(request, resp) diff --git a/resources/lib/libraries/requests/api.py b/resources/lib/libraries/requests/api.py new file mode 100644 index 00000000..b21a1a4f --- /dev/null +++ b/resources/lib/libraries/requests/api.py @@ -0,0 +1,145 @@ +# -*- coding: utf-8 -*- + +""" +requests.api +~~~~~~~~~~~~ + +This module implements the Requests API. + +:copyright: (c) 2012 by Kenneth Reitz. +:license: Apache2, see LICENSE for more details. + +""" + +from . import sessions + + +def request(method, url, **kwargs): + """Constructs and sends a :class:`Request <Request>`. + + :param method: method for the new :class:`Request` object. + :param url: URL for the new :class:`Request` object. + :param params: (optional) Dictionary or bytes to be sent in the query string for the :class:`Request`. + :param data: (optional) Dictionary, bytes, or file-like object to send in the body of the :class:`Request`. + :param json: (optional) json data to send in the body of the :class:`Request`. + :param headers: (optional) Dictionary of HTTP Headers to send with the :class:`Request`. + :param cookies: (optional) Dict or CookieJar object to send with the :class:`Request`. + :param files: (optional) Dictionary of ``'name': file-like-objects`` (or ``{'name': ('filename', fileobj)}``) for multipart encoding upload. + :param auth: (optional) Auth tuple to enable Basic/Digest/Custom HTTP Auth. + :param timeout: (optional) How long to wait for the server to send data + before giving up, as a float, or a :ref:`(connect timeout, read + timeout) <timeouts>` tuple. + :type timeout: float or tuple + :param allow_redirects: (optional) Boolean. Set to True if POST/PUT/DELETE redirect following is allowed. + :type allow_redirects: bool + :param proxies: (optional) Dictionary mapping protocol to the URL of the proxy. + :param verify: (optional) whether the SSL cert will be verified. A CA_BUNDLE path can also be provided. Defaults to ``True``. + :param stream: (optional) if ``False``, the response content will be immediately downloaded. + :param cert: (optional) if String, path to ssl client cert file (.pem). If Tuple, ('cert', 'key') pair. + :return: :class:`Response <Response>` object + :rtype: requests.Response + + Usage:: + + >>> import requests + >>> req = requests.request('GET', 'http://httpbin.org/get') + <Response [200]> + """ + + # By using the 'with' statement we are sure the session is closed, thus we + # avoid leaving sockets open which can trigger a ResourceWarning in some + # cases, and look like a memory leak in others. + with sessions.Session() as session: + return session.request(method=method, url=url, **kwargs) + + +def get(url, params=None, **kwargs): + """Sends a GET request. + + :param url: URL for the new :class:`Request` object. + :param params: (optional) Dictionary or bytes to be sent in the query string for the :class:`Request`. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :return: :class:`Response <Response>` object + :rtype: requests.Response + """ + + kwargs.setdefault('allow_redirects', True) + return request('get', url, params=params, **kwargs) + + +def options(url, **kwargs): + """Sends a OPTIONS request. + + :param url: URL for the new :class:`Request` object. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :return: :class:`Response <Response>` object + :rtype: requests.Response + """ + + kwargs.setdefault('allow_redirects', True) + return request('options', url, **kwargs) + + +def head(url, **kwargs): + """Sends a HEAD request. + + :param url: URL for the new :class:`Request` object. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :return: :class:`Response <Response>` object + :rtype: requests.Response + """ + + kwargs.setdefault('allow_redirects', False) + return request('head', url, **kwargs) + + +def post(url, data=None, json=None, **kwargs): + """Sends a POST request. + + :param url: URL for the new :class:`Request` object. + :param data: (optional) Dictionary, bytes, or file-like object to send in the body of the :class:`Request`. + :param json: (optional) json data to send in the body of the :class:`Request`. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :return: :class:`Response <Response>` object + :rtype: requests.Response + """ + + return request('post', url, data=data, json=json, **kwargs) + + +def put(url, data=None, **kwargs): + """Sends a PUT request. + + :param url: URL for the new :class:`Request` object. + :param data: (optional) Dictionary, bytes, or file-like object to send in the body of the :class:`Request`. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :return: :class:`Response <Response>` object + :rtype: requests.Response + """ + + return request('put', url, data=data, **kwargs) + + +def patch(url, data=None, **kwargs): + """Sends a PATCH request. + + :param url: URL for the new :class:`Request` object. + :param data: (optional) Dictionary, bytes, or file-like object to send in the body of the :class:`Request`. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :return: :class:`Response <Response>` object + :rtype: requests.Response + """ + + return request('patch', url, data=data, **kwargs) + + +def delete(url, **kwargs): + """Sends a DELETE request. + + :param url: URL for the new :class:`Request` object. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :return: :class:`Response <Response>` object + :rtype: requests.Response + """ + + return request('delete', url, **kwargs) diff --git a/resources/lib/libraries/requests/auth.py b/resources/lib/libraries/requests/auth.py new file mode 100644 index 00000000..2af55fb5 --- /dev/null +++ b/resources/lib/libraries/requests/auth.py @@ -0,0 +1,223 @@ +# -*- coding: utf-8 -*- + +""" +requests.auth +~~~~~~~~~~~~~ + +This module contains the authentication handlers for Requests. +""" + +import os +import re +import time +import hashlib +import threading + +from base64 import b64encode + +from .compat import urlparse, str +from .cookies import extract_cookies_to_jar +from .utils import parse_dict_header, to_native_string +from .status_codes import codes + +CONTENT_TYPE_FORM_URLENCODED = 'application/x-www-form-urlencoded' +CONTENT_TYPE_MULTI_PART = 'multipart/form-data' + + +def _basic_auth_str(username, password): + """Returns a Basic Auth string.""" + + authstr = 'Basic ' + to_native_string( + b64encode(('%s:%s' % (username, password)).encode('latin1')).strip() + ) + + return authstr + + +class AuthBase(object): + """Base class that all auth implementations derive from""" + + def __call__(self, r): + raise NotImplementedError('Auth hooks must be callable.') + + +class HTTPBasicAuth(AuthBase): + """Attaches HTTP Basic Authentication to the given Request object.""" + def __init__(self, username, password): + self.username = username + self.password = password + + def __call__(self, r): + r.headers['Authorization'] = _basic_auth_str(self.username, self.password) + return r + + +class HTTPProxyAuth(HTTPBasicAuth): + """Attaches HTTP Proxy Authentication to a given Request object.""" + def __call__(self, r): + r.headers['Proxy-Authorization'] = _basic_auth_str(self.username, self.password) + return r + + +class HTTPDigestAuth(AuthBase): + """Attaches HTTP Digest Authentication to the given Request object.""" + def __init__(self, username, password): + self.username = username + self.password = password + # Keep state in per-thread local storage + self._thread_local = threading.local() + + def init_per_thread_state(self): + # Ensure state is initialized just once per-thread + if not hasattr(self._thread_local, 'init'): + self._thread_local.init = True + self._thread_local.last_nonce = '' + self._thread_local.nonce_count = 0 + self._thread_local.chal = {} + self._thread_local.pos = None + self._thread_local.num_401_calls = None + + def build_digest_header(self, method, url): + + realm = self._thread_local.chal['realm'] + nonce = self._thread_local.chal['nonce'] + qop = self._thread_local.chal.get('qop') + algorithm = self._thread_local.chal.get('algorithm') + opaque = self._thread_local.chal.get('opaque') + + if algorithm is None: + _algorithm = 'MD5' + else: + _algorithm = algorithm.upper() + # lambdas assume digest modules are imported at the top level + if _algorithm == 'MD5' or _algorithm == 'MD5-SESS': + def md5_utf8(x): + if isinstance(x, str): + x = x.encode('utf-8') + return hashlib.md5(x).hexdigest() + hash_utf8 = md5_utf8 + elif _algorithm == 'SHA': + def sha_utf8(x): + if isinstance(x, str): + x = x.encode('utf-8') + return hashlib.sha1(x).hexdigest() + hash_utf8 = sha_utf8 + + KD = lambda s, d: hash_utf8("%s:%s" % (s, d)) + + if hash_utf8 is None: + return None + + # XXX not implemented yet + entdig = None + p_parsed = urlparse(url) + #: path is request-uri defined in RFC 2616 which should not be empty + path = p_parsed.path or "/" + if p_parsed.query: + path += '?' + p_parsed.query + + A1 = '%s:%s:%s' % (self.username, realm, self.password) + A2 = '%s:%s' % (method, path) + + HA1 = hash_utf8(A1) + HA2 = hash_utf8(A2) + + if nonce == self._thread_local.last_nonce: + self._thread_local.nonce_count += 1 + else: + self._thread_local.nonce_count = 1 + ncvalue = '%08x' % self._thread_local.nonce_count + s = str(self._thread_local.nonce_count).encode('utf-8') + s += nonce.encode('utf-8') + s += time.ctime().encode('utf-8') + s += os.urandom(8) + + cnonce = (hashlib.sha1(s).hexdigest()[:16]) + if _algorithm == 'MD5-SESS': + HA1 = hash_utf8('%s:%s:%s' % (HA1, nonce, cnonce)) + + if not qop: + respdig = KD(HA1, "%s:%s" % (nonce, HA2)) + elif qop == 'auth' or 'auth' in qop.split(','): + noncebit = "%s:%s:%s:%s:%s" % ( + nonce, ncvalue, cnonce, 'auth', HA2 + ) + respdig = KD(HA1, noncebit) + else: + # XXX handle auth-int. + return None + + self._thread_local.last_nonce = nonce + + # XXX should the partial digests be encoded too? + base = 'username="%s", realm="%s", nonce="%s", uri="%s", ' \ + 'response="%s"' % (self.username, realm, nonce, path, respdig) + if opaque: + base += ', opaque="%s"' % opaque + if algorithm: + base += ', algorithm="%s"' % algorithm + if entdig: + base += ', digest="%s"' % entdig + if qop: + base += ', qop="auth", nc=%s, cnonce="%s"' % (ncvalue, cnonce) + + return 'Digest %s' % (base) + + def handle_redirect(self, r, **kwargs): + """Reset num_401_calls counter on redirects.""" + if r.is_redirect: + self._thread_local.num_401_calls = 1 + + def handle_401(self, r, **kwargs): + """Takes the given response and tries digest-auth, if needed.""" + + if self._thread_local.pos is not None: + # Rewind the file position indicator of the body to where + # it was to resend the request. + r.request.body.seek(self._thread_local.pos) + s_auth = r.headers.get('www-authenticate', '') + + if 'digest' in s_auth.lower() and self._thread_local.num_401_calls < 2: + + self._thread_local.num_401_calls += 1 + pat = re.compile(r'digest ', flags=re.IGNORECASE) + self._thread_local.chal = parse_dict_header(pat.sub('', s_auth, count=1)) + + # Consume content and release the original connection + # to allow our new request to reuse the same one. + r.content + r.close() + prep = r.request.copy() + extract_cookies_to_jar(prep._cookies, r.request, r.raw) + prep.prepare_cookies(prep._cookies) + + prep.headers['Authorization'] = self.build_digest_header( + prep.method, prep.url) + _r = r.connection.send(prep, **kwargs) + _r.history.append(r) + _r.request = prep + + return _r + + self._thread_local.num_401_calls = 1 + return r + + def __call__(self, r): + # Initialize per-thread state, if needed + self.init_per_thread_state() + # If we have a saved nonce, skip the 401 + if self._thread_local.last_nonce: + r.headers['Authorization'] = self.build_digest_header(r.method, r.url) + try: + self._thread_local.pos = r.body.tell() + except AttributeError: + # In the case of HTTPDigestAuth being reused and the body of + # the previous request was a file-like object, pos has the + # file position of the previous body. Ensure it's set to + # None. + self._thread_local.pos = None + r.register_hook('response', self.handle_401) + r.register_hook('response', self.handle_redirect) + self._thread_local.num_401_calls = 1 + + return r diff --git a/resources/lib/libraries/requests/cacert.pem b/resources/lib/libraries/requests/cacert.pem new file mode 100644 index 00000000..6a66daa9 --- /dev/null +++ b/resources/lib/libraries/requests/cacert.pem @@ -0,0 +1,5616 @@ + +# Issuer: O=Equifax OU=Equifax Secure Certificate Authority +# Subject: O=Equifax OU=Equifax Secure Certificate Authority +# Label: "Equifax Secure CA" +# Serial: 903804111 +# MD5 Fingerprint: 67:cb:9d:c0:13:24:8a:82:9b:b2:17:1e:d1:1b:ec:d4 +# SHA1 Fingerprint: d2:32:09:ad:23:d3:14:23:21:74:e4:0d:7f:9d:62:13:97:86:63:3a +# SHA256 Fingerprint: 08:29:7a:40:47:db:a2:36:80:c7:31:db:6e:31:76:53:ca:78:48:e1:be:bd:3a:0b:01:79:a7:07:f9:2c:f1:78 +-----BEGIN CERTIFICATE----- +MIIDIDCCAomgAwIBAgIENd70zzANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQGEwJV +UzEQMA4GA1UEChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2Vy +dGlmaWNhdGUgQXV0aG9yaXR5MB4XDTk4MDgyMjE2NDE1MVoXDTE4MDgyMjE2NDE1 +MVowTjELMAkGA1UEBhMCVVMxEDAOBgNVBAoTB0VxdWlmYXgxLTArBgNVBAsTJEVx +dWlmYXggU2VjdXJlIENlcnRpZmljYXRlIEF1dGhvcml0eTCBnzANBgkqhkiG9w0B +AQEFAAOBjQAwgYkCgYEAwV2xWGcIYu6gmi0fCG2RFGiYCh7+2gRvE4RiIcPRfM6f +BeC4AfBONOziipUEZKzxa1NfBbPLZ4C/QgKO/t0BCezhABRP/PvwDN1Dulsr4R+A +cJkVV5MW8Q+XarfCaCMczE1ZMKxRHjuvK9buY0V7xdlfUNLjUA86iOe/FP3gx7kC +AwEAAaOCAQkwggEFMHAGA1UdHwRpMGcwZaBjoGGkXzBdMQswCQYDVQQGEwJVUzEQ +MA4GA1UEChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2VydGlm +aWNhdGUgQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMBoGA1UdEAQTMBGBDzIwMTgw +ODIyMTY0MTUxWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAUSOZo+SvSspXXR9gj +IBBPM5iQn9QwHQYDVR0OBBYEFEjmaPkr0rKV10fYIyAQTzOYkJ/UMAwGA1UdEwQF +MAMBAf8wGgYJKoZIhvZ9B0EABA0wCxsFVjMuMGMDAgbAMA0GCSqGSIb3DQEBBQUA +A4GBAFjOKer89961zgK5F7WF0bnj4JXMJTENAKaSbn+2kmOeUJXRmm/kEd5jhW6Y +7qj/WsjTVbJmcVfewCHrPSqnI0kBBIZCe/zuf6IWUrVnZ9NA2zsmWLIodz2uFHdh +1voqZiegDfqnc1zqcPGUIWVEX/r87yloqaKHee9570+sB3c4 +-----END CERTIFICATE----- + +# Issuer: CN=GlobalSign Root CA O=GlobalSign nv-sa OU=Root CA +# Subject: CN=GlobalSign Root CA O=GlobalSign nv-sa OU=Root CA +# Label: "GlobalSign Root CA" +# Serial: 4835703278459707669005204 +# MD5 Fingerprint: 3e:45:52:15:09:51:92:e1:b7:5d:37:9f:b1:87:29:8a +# SHA1 Fingerprint: b1:bc:96:8b:d4:f4:9d:62:2a:a8:9a:81:f2:15:01:52:a4:1d:82:9c +# SHA256 Fingerprint: eb:d4:10:40:e4:bb:3e:c7:42:c9:e3:81:d3:1e:f2:a4:1a:48:b6:68:5c:96:e7:ce:f3:c1:df:6c:d4:33:1c:99 +-----BEGIN CERTIFICATE----- +MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkG +A1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv +b3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAw +MDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9i +YWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxT +aWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDaDuaZ +jc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavp +xy0Sy6scTHAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp +1Wrjsok6Vjk4bwY8iGlbKk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdG +snUOhugZitVtbNV4FpWi6cgKOOvyJBNPc1STE4U6G7weNLWLBYy5d4ux2x8gkasJ +U26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrXgzT/LCrBbBlDSgeF59N8 +9iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8E +BTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0B +AQUFAAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOz +yj1hTdNGCbM+w6DjY1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE +38NflNUVyRRBnMRddWQVDf9VMOyGj/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymP +AbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhHhm4qxFYxldBniYUr+WymXUad +DKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveCX4XSQRjbgbME +HMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A== +-----END CERTIFICATE----- + +# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R2 +# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R2 +# Label: "GlobalSign Root CA - R2" +# Serial: 4835703278459682885658125 +# MD5 Fingerprint: 94:14:77:7e:3e:5e:fd:8f:30:bd:41:b0:cf:e7:d0:30 +# SHA1 Fingerprint: 75:e0:ab:b6:13:85:12:27:1c:04:f8:5f:dd:de:38:e4:b7:24:2e:fe +# SHA256 Fingerprint: ca:42:dd:41:74:5f:d0:b8:1e:b9:02:36:2c:f9:d8:bf:71:9d:a1:bd:1b:1e:fc:94:6f:5b:4c:99:f4:2c:1b:9e +-----BEGIN CERTIFICATE----- +MIIDujCCAqKgAwIBAgILBAAAAAABD4Ym5g0wDQYJKoZIhvcNAQEFBQAwTDEgMB4G +A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjIxEzARBgNVBAoTCkdsb2JhbFNp +Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDYxMjE1MDgwMDAwWhcNMjExMjE1 +MDgwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMjETMBEG +A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAKbPJA6+Lm8omUVCxKs+IVSbC9N/hHD6ErPL +v4dfxn+G07IwXNb9rfF73OX4YJYJkhD10FPe+3t+c4isUoh7SqbKSaZeqKeMWhG8 +eoLrvozps6yWJQeXSpkqBy+0Hne/ig+1AnwblrjFuTosvNYSuetZfeLQBoZfXklq +tTleiDTsvHgMCJiEbKjNS7SgfQx5TfC4LcshytVsW33hoCmEofnTlEnLJGKRILzd +C9XZzPnqJworc5HGnRusyMvo4KD0L5CLTfuwNhv2GXqF4G3yYROIXJ/gkwpRl4pa +zq+r1feqCapgvdzZX99yqWATXgAByUr6P6TqBwMhAo6CygPCm48CAwEAAaOBnDCB +mTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUm+IH +V2ccHsBqBt5ZtJot39wZhi4wNgYDVR0fBC8wLTAroCmgJ4YlaHR0cDovL2NybC5n +bG9iYWxzaWduLm5ldC9yb290LXIyLmNybDAfBgNVHSMEGDAWgBSb4gdXZxwewGoG +3lm0mi3f3BmGLjANBgkqhkiG9w0BAQUFAAOCAQEAmYFThxxol4aR7OBKuEQLq4Gs +J0/WwbgcQ3izDJr86iw8bmEbTUsp9Z8FHSbBuOmDAGJFtqkIk7mpM0sYmsL4h4hO +291xNBrBVNpGP+DTKqttVCL1OmLNIG+6KYnX3ZHu01yiPqFbQfXf5WRDLenVOavS +ot+3i9DAgBkcRcAtjOj4LaR0VknFBbVPFd5uRHg5h6h+u/N5GJG79G+dwfCMNYxd +AfvDbbnvRG15RjF+Cv6pgsH/76tuIMRQyV+dTZsXjAzlAcmgQWpzU/qlULRuJQ/7 +TBj0/VLZjmmx6BEP3ojY+x1J96relc8geMJgEtslQIxq/H5COEBkEveegeGTLg== +-----END CERTIFICATE----- + +# Issuer: CN=VeriSign Class 3 Public Primary Certification Authority - G3 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 1999 VeriSign, Inc. - For authorized use only +# Subject: CN=VeriSign Class 3 Public Primary Certification Authority - G3 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 1999 VeriSign, Inc. - For authorized use only +# Label: "Verisign Class 3 Public Primary Certification Authority - G3" +# Serial: 206684696279472310254277870180966723415 +# MD5 Fingerprint: cd:68:b6:a7:c7:c4:ce:75:e0:1d:4f:57:44:61:92:09 +# SHA1 Fingerprint: 13:2d:0d:45:53:4b:69:97:cd:b2:d5:c3:39:e2:55:76:60:9b:5c:c6 +# SHA256 Fingerprint: eb:04:cf:5e:b1:f3:9a:fa:76:2f:2b:b1:20:f2:96:cb:a5:20:c1:b9:7d:b1:58:95:65:b8:1c:b9:a1:7b:72:44 +-----BEGIN CERTIFICATE----- +MIIEGjCCAwICEQCbfgZJoz5iudXukEhxKe9XMA0GCSqGSIb3DQEBBQUAMIHKMQsw +CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZl +cmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWdu +LCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlT +aWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3Jp +dHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQswCQYD +VQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlT +aWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJ +bmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWdu +IENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg +LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMu6nFL8eB8aHm8b +N3O9+MlrlBIwT/A2R/XQkQr1F8ilYcEWQE37imGQ5XYgwREGfassbqb1EUGO+i2t +KmFZpGcmTNDovFJbcCAEWNF6yaRpvIMXZK0Fi7zQWM6NjPXr8EJJC52XJ2cybuGu +kxUccLwgTS8Y3pKI6GyFVxEa6X7jJhFUokWWVYPKMIno3Nij7SqAP395ZVc+FSBm +CC+Vk7+qRy+oRpfwEuL+wgorUeZ25rdGt+INpsyow0xZVYnm6FNcHOqd8GIWC6fJ +Xwzw3sJ2zq/3avL6QaaiMxTJ5Xpj055iN9WFZZ4O5lMkdBteHRJTW8cs54NJOxWu +imi5V5cCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAERSWwauSCPc/L8my/uRan2Te +2yFPhpk0djZX3dAVL8WtfxUfN2JzPtTnX84XA9s1+ivbrmAJXx5fj267Cz3qWhMe +DGBvtcC1IyIuBwvLqXTLR7sdwdela8wv0kL9Sd2nic9TutoAWii/gt/4uhMdUIaC +/Y4wjylGsB49Ndo4YhYYSq3mtlFs3q9i6wHQHiT+eo8SGhJouPtmmRQURVyu565p +F4ErWjfJXir0xuKhXFSbplQAz/DxwceYMBo7Nhbbo27q/a2ywtrvAkcTisDxszGt +TxzhT5yvDwyd93gN2PQ1VoDat20Xj50egWTh/sVFuq1ruQp6Tk9LhO5L8X3dEQ== +-----END CERTIFICATE----- + +# Issuer: CN=VeriSign Class 4 Public Primary Certification Authority - G3 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 1999 VeriSign, Inc. - For authorized use only +# Subject: CN=VeriSign Class 4 Public Primary Certification Authority - G3 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 1999 VeriSign, Inc. - For authorized use only +# Label: "Verisign Class 4 Public Primary Certification Authority - G3" +# Serial: 314531972711909413743075096039378935511 +# MD5 Fingerprint: db:c8:f2:27:2e:b1:ea:6a:29:23:5d:fe:56:3e:33:df +# SHA1 Fingerprint: c8:ec:8c:87:92:69:cb:4b:ab:39:e9:8d:7e:57:67:f3:14:95:73:9d +# SHA256 Fingerprint: e3:89:36:0d:0f:db:ae:b3:d2:50:58:4b:47:30:31:4e:22:2f:39:c1:56:a0:20:14:4e:8d:96:05:61:79:15:06 +-----BEGIN CERTIFICATE----- +MIIEGjCCAwICEQDsoKeLbnVqAc/EfMwvlF7XMA0GCSqGSIb3DQEBBQUAMIHKMQsw +CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZl +cmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWdu +LCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlT +aWduIENsYXNzIDQgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3Jp +dHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQswCQYD +VQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlT +aWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJ +bmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWdu +IENsYXNzIDQgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg +LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK3LpRFpxlmr8Y+1 +GQ9Wzsy1HyDkniYlS+BzZYlZ3tCD5PUPtbut8XzoIfzk6AzufEUiGXaStBO3IFsJ ++mGuqPKljYXCKtbeZjbSmwL0qJJgfJxptI8kHtCGUvYynEFYHiK9zUVilQhu0Gbd +U6LM8BDcVHOLBKFGMzNcF0C5nk3T875Vg+ixiY5afJqWIpA7iCXy0lOIAgwLePLm +NxdLMEYH5IBtptiWLugs+BGzOA1mppvqySNb247i8xOOGlktqgLw7KSHZtzBP/XY +ufTsgsbSPZUd5cBPhMnZo0QoBmrXRazwa2rvTl/4EYIeOGM0ZlDUPpNz+jDDZq3/ +ky2X7wMCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAj/ola09b5KROJ1WrIhVZPMq1 +CtRK26vdoV9TxaBXOcLORyu+OshWv8LZJxA6sQU8wHcxuzrTBXttmhwwjIDLk5Mq +g6sFUYICABFna/OIYUdfA5PVWw3g8dShMjWFsjrbsIKr0csKvE+MW8VLADsfKoKm +fjaF3H48ZwC15DtS4KjrXRX5xm3wrR0OhbepmnMUWluPQSjA1egtTaRezarZ7c7c +2NU8Qh0XwRJdRTjDOPP8hS6DRkiy1yBfkjaP53kPmF6Z6PDQpLv1U70qzlmwr25/ +bLvSHgCwIe34QWKCudiyxLtGUPMxxY8BqHTr9Xgn2uf3ZkPznoM+IKrDNWCRzg== +-----END CERTIFICATE----- + +# Issuer: CN=Entrust.net Certification Authority (2048) O=Entrust.net OU=www.entrust.net/CPS_2048 incorp. by ref. (limits liab.)/(c) 1999 Entrust.net Limited +# Subject: CN=Entrust.net Certification Authority (2048) O=Entrust.net OU=www.entrust.net/CPS_2048 incorp. by ref. (limits liab.)/(c) 1999 Entrust.net Limited +# Label: "Entrust.net Premium 2048 Secure Server CA" +# Serial: 946069240 +# MD5 Fingerprint: ee:29:31:bc:32:7e:9a:e6:e8:b5:f7:51:b4:34:71:90 +# SHA1 Fingerprint: 50:30:06:09:1d:97:d4:f5:ae:39:f7:cb:e7:92:7d:7d:65:2d:34:31 +# SHA256 Fingerprint: 6d:c4:71:72:e0:1c:bc:b0:bf:62:58:0d:89:5f:e2:b8:ac:9a:d4:f8:73:80:1e:0c:10:b9:c8:37:d2:1e:b1:77 +-----BEGIN CERTIFICATE----- +MIIEKjCCAxKgAwIBAgIEOGPe+DANBgkqhkiG9w0BAQUFADCBtDEUMBIGA1UEChML +RW50cnVzdC5uZXQxQDA+BgNVBAsUN3d3dy5lbnRydXN0Lm5ldC9DUFNfMjA0OCBp +bmNvcnAuIGJ5IHJlZi4gKGxpbWl0cyBsaWFiLikxJTAjBgNVBAsTHChjKSAxOTk5 +IEVudHJ1c3QubmV0IExpbWl0ZWQxMzAxBgNVBAMTKkVudHJ1c3QubmV0IENlcnRp +ZmljYXRpb24gQXV0aG9yaXR5ICgyMDQ4KTAeFw05OTEyMjQxNzUwNTFaFw0yOTA3 +MjQxNDE1MTJaMIG0MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDFAMD4GA1UECxQ3d3d3 +LmVudHJ1c3QubmV0L0NQU18yMDQ4IGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxp +YWIuKTElMCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEG +A1UEAxMqRW50cnVzdC5uZXQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgKDIwNDgp +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArU1LqRKGsuqjIAcVFmQq +K0vRvwtKTY7tgHalZ7d4QMBzQshowNtTK91euHaYNZOLGp18EzoOH1u3Hs/lJBQe +sYGpjX24zGtLA/ECDNyrpUAkAH90lKGdCCmziAv1h3edVc3kw37XamSrhRSGlVuX +MlBvPci6Zgzj/L24ScF2iUkZ/cCovYmjZy/Gn7xxGWC4LeksyZB2ZnuU4q941mVT +XTzWnLLPKQP5L6RQstRIzgUyVYr9smRMDuSYB3Xbf9+5CFVghTAp+XtIpGmG4zU/ +HoZdenoVve8AjhUiVBcAkCaTvA5JaJG/+EfTnZVCwQ5N328mz8MYIWJmQ3DW1cAH +4QIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV +HQ4EFgQUVeSB0RGAvtiJuQijMfmhJAkWuXAwDQYJKoZIhvcNAQEFBQADggEBADub +j1abMOdTmXx6eadNl9cZlZD7Bh/KM3xGY4+WZiT6QBshJ8rmcnPyT/4xmf3IDExo +U8aAghOY+rat2l098c5u9hURlIIM7j+VrxGrD9cv3h8Dj1csHsm7mhpElesYT6Yf +zX1XEC+bBAlahLVu2B064dae0Wx5XnkcFMXj0EyTO2U87d89vqbllRrDtRnDvV5b +u/8j72gZyxKTJ1wDLW8w0B62GqzeWvfRqqgnpv55gcR5mTNXuhKwqeBCbJPKVt7+ +bYQLCIt+jerXmCHG8+c8eS9enNFMFY3h7CI3zJpDC5fcgJCNs2ebb0gIFVbPv/Er +fF6adulZkMV8gzURZVE= +-----END CERTIFICATE----- + +# Issuer: CN=Baltimore CyberTrust Root O=Baltimore OU=CyberTrust +# Subject: CN=Baltimore CyberTrust Root O=Baltimore OU=CyberTrust +# Label: "Baltimore CyberTrust Root" +# Serial: 33554617 +# MD5 Fingerprint: ac:b6:94:a5:9c:17:e0:d7:91:52:9b:b1:97:06:a6:e4 +# SHA1 Fingerprint: d4:de:20:d0:5e:66:fc:53:fe:1a:50:88:2c:78:db:28:52:ca:e4:74 +# SHA256 Fingerprint: 16:af:57:a9:f6:76:b0:ab:12:60:95:aa:5e:ba:de:f2:2a:b3:11:19:d6:44:ac:95:cd:4b:93:db:f3:f2:6a:eb +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJ +RTESMBAGA1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYD +VQQDExlCYWx0aW1vcmUgQ3liZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoX +DTI1MDUxMjIzNTkwMFowWjELMAkGA1UEBhMCSUUxEjAQBgNVBAoTCUJhbHRpbW9y +ZTETMBEGA1UECxMKQ3liZXJUcnVzdDEiMCAGA1UEAxMZQmFsdGltb3JlIEN5YmVy +VHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKMEuyKr +mD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjr +IZ3AQSsBUnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeK +mpYcqWe4PwzV9/lSEy/CG9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSu +XmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9XbIGevOF6uvUA65ehD5f/xXtabz5OTZy +dc93Uk3zyZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjprl3RjM71oGDHweI12v/ye +jl0qhqdNkNwnGjkCAwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoIVDaGezq1 +BE3wMBIGA1UdEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3 +DQEBBQUAA4IBAQCFDF2O5G9RaEIFoN27TyclhAO992T9Ldcw46QQF+vaKSm2eT92 +9hkTI7gQCvlYpNRhcL0EYWoSihfVCr3FvDB81ukMJY2GQE/szKN+OMY3EU/t3Wgx +jkzSswF07r51XgdIGn9w/xZchMB5hbgF/X++ZRGjD8ACtPhSNzkE1akxehi/oCr0 +Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsaY71k5h+3zvDyny67G7fyUIhz +ksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9HRCwBXbsdtTLS +R9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp +-----END CERTIFICATE----- + +# Issuer: CN=AddTrust Class 1 CA Root O=AddTrust AB OU=AddTrust TTP Network +# Subject: CN=AddTrust Class 1 CA Root O=AddTrust AB OU=AddTrust TTP Network +# Label: "AddTrust Low-Value Services Root" +# Serial: 1 +# MD5 Fingerprint: 1e:42:95:02:33:92:6b:b9:5f:c0:7f:da:d6:b2:4b:fc +# SHA1 Fingerprint: cc:ab:0e:a0:4c:23:01:d6:69:7b:dd:37:9f:cd:12:eb:24:e3:94:9d +# SHA256 Fingerprint: 8c:72:09:27:9a:c0:4e:27:5e:16:d0:7f:d3:b7:75:e8:01:54:b5:96:80:46:e3:1f:52:dd:25:76:63:24:e9:a7 +-----BEGIN CERTIFICATE----- +MIIEGDCCAwCgAwIBAgIBATANBgkqhkiG9w0BAQUFADBlMQswCQYDVQQGEwJTRTEU +MBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3 +b3JrMSEwHwYDVQQDExhBZGRUcnVzdCBDbGFzcyAxIENBIFJvb3QwHhcNMDAwNTMw +MTAzODMxWhcNMjAwNTMwMTAzODMxWjBlMQswCQYDVQQGEwJTRTEUMBIGA1UEChML +QWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3b3JrMSEwHwYD +VQQDExhBZGRUcnVzdCBDbGFzcyAxIENBIFJvb3QwggEiMA0GCSqGSIb3DQEBAQUA +A4IBDwAwggEKAoIBAQCWltQhSWDia+hBBwzexODcEyPNwTXH+9ZOEQpnXvUGW2ul +CDtbKRY654eyNAbFvAWlA3yCyykQruGIgb3WntP+LVbBFc7jJp0VLhD7Bo8wBN6n +tGO0/7Gcrjyvd7ZWxbWroulpOj0OM3kyP3CCkplhbY0wCI9xP6ZIVxn4JdxLZlyl +dI+Yrsj5wAYi56xz36Uu+1LcsRVlIPo1Zmne3yzxbrww2ywkEtvrNTVokMsAsJch +PXQhI2U0K7t4WaPW4XY5mqRJjox0r26kmqPZm9I4XJuiGMx1I4S+6+JNM3GOGvDC ++Mcdoq0Dlyz4zyXG9rgkMbFjXZJ/Y/AlyVMuH79NAgMBAAGjgdIwgc8wHQYDVR0O +BBYEFJWxtPCUtr3H2tERCSG+wa9J/RB7MAsGA1UdDwQEAwIBBjAPBgNVHRMBAf8E +BTADAQH/MIGPBgNVHSMEgYcwgYSAFJWxtPCUtr3H2tERCSG+wa9J/RB7oWmkZzBl +MQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFk +ZFRydXN0IFRUUCBOZXR3b3JrMSEwHwYDVQQDExhBZGRUcnVzdCBDbGFzcyAxIENB +IFJvb3SCAQEwDQYJKoZIhvcNAQEFBQADggEBACxtZBsfzQ3duQH6lmM0MkhHma6X +7f1yFqZzR1r0693p9db7RcwpiURdv0Y5PejuvE1Uhh4dbOMXJ0PhiVYrqW9yTkkz +43J8KiOavD7/KCrto/8cI7pDVwlnTUtiBi34/2ydYB7YHEt9tTEv2dB8Xfjea4MY +eDdXL+gzB2ffHsdrKpV2ro9Xo/D0UrSpUwjP4E/TelOL/bscVjby/rK25Xa71SJl +pz/+0WatC7xrmYbvP33zGDLKe8bjq2RGlfgmadlVg3sslgf/WSxEo8bl6ancoWOA +WiFeIc9TVPC6b4nbqKqVz4vjccweGyBECMB6tkD9xOQ14R0WHNC8K47Wcdk= +-----END CERTIFICATE----- + +# Issuer: CN=AddTrust External CA Root O=AddTrust AB OU=AddTrust External TTP Network +# Subject: CN=AddTrust External CA Root O=AddTrust AB OU=AddTrust External TTP Network +# Label: "AddTrust External Root" +# Serial: 1 +# MD5 Fingerprint: 1d:35:54:04:85:78:b0:3f:42:42:4d:bf:20:73:0a:3f +# SHA1 Fingerprint: 02:fa:f3:e2:91:43:54:68:60:78:57:69:4d:f5:e4:5b:68:85:18:68 +# SHA256 Fingerprint: 68:7f:a4:51:38:22:78:ff:f0:c8:b1:1f:8d:43:d5:76:67:1c:6e:b2:bc:ea:b4:13:fb:83:d9:65:d0:6d:2f:f2 +-----BEGIN CERTIFICATE----- +MIIENjCCAx6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBvMQswCQYDVQQGEwJTRTEU +MBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFkZFRydXN0IEV4dGVybmFs +IFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBFeHRlcm5hbCBDQSBSb290 +MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFowbzELMAkGA1UEBhMCU0Ux +FDASBgNVBAoTC0FkZFRydXN0IEFCMSYwJAYDVQQLEx1BZGRUcnVzdCBFeHRlcm5h +bCBUVFAgTmV0d29yazEiMCAGA1UEAxMZQWRkVHJ1c3QgRXh0ZXJuYWwgQ0EgUm9v +dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALf3GjPm8gAELTngTlvt +H7xsD821+iO2zt6bETOXpClMfZOfvUq8k+0DGuOPz+VtUFrWlymUWoCwSXrbLpX9 +uMq/NzgtHj6RQa1wVsfwTz/oMp50ysiQVOnGXw94nZpAPA6sYapeFI+eh6FqUNzX +mk6vBbOmcZSccbNQYArHE504B4YCqOmoaSYYkKtMsE8jqzpPhNjfzp/haW+710LX +a0Tkx63ubUFfclpxCDezeWWkWaCUN/cALw3CknLa0Dhy2xSoRcRdKn23tNbE7qzN +E0S3ySvdQwAl+mG5aWpYIxG3pzOPVnVZ9c0p10a3CitlttNCbxWyuHv77+ldU9U0 +WicCAwEAAaOB3DCB2TAdBgNVHQ4EFgQUrb2YejS0Jvf6xCZU7wO94CTLVBowCwYD +VR0PBAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wgZkGA1UdIwSBkTCBjoAUrb2YejS0 +Jvf6xCZU7wO94CTLVBqhc6RxMG8xCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRU +cnVzdCBBQjEmMCQGA1UECxMdQWRkVHJ1c3QgRXh0ZXJuYWwgVFRQIE5ldHdvcmsx +IjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENBIFJvb3SCAQEwDQYJKoZIhvcN +AQEFBQADggEBALCb4IUlwtYj4g+WBpKdQZic2YR5gdkeWxQHIzZlj7DYd7usQWxH +YINRsPkyPef89iYTx4AWpb9a/IfPeHmJIZriTAcKhjW88t5RxNKWt9x+Tu5w/Rw5 +6wwCURQtjr0W4MHfRnXnJK3s9EK0hZNwEGe6nQY1ShjTK3rMUUKhemPR5ruhxSvC +Nr4TDea9Y355e6cJDUCrat2PisP29owaQgVR1EX1n6diIWgVIEM8med8vSTYqZEX +c4g/VhsxOBi0cQ+azcgOno4uG+GMmIPLHzHxREzGBHNJdmAPx/i9F4BrLunMTA5a +mnkPIAou1Z5jJh5VkpTYghdae9C8x49OhgQ= +-----END CERTIFICATE----- + +# Issuer: CN=AddTrust Public CA Root O=AddTrust AB OU=AddTrust TTP Network +# Subject: CN=AddTrust Public CA Root O=AddTrust AB OU=AddTrust TTP Network +# Label: "AddTrust Public Services Root" +# Serial: 1 +# MD5 Fingerprint: c1:62:3e:23:c5:82:73:9c:03:59:4b:2b:e9:77:49:7f +# SHA1 Fingerprint: 2a:b6:28:48:5e:78:fb:f3:ad:9e:79:10:dd:6b:df:99:72:2c:96:e5 +# SHA256 Fingerprint: 07:91:ca:07:49:b2:07:82:aa:d3:c7:d7:bd:0c:df:c9:48:58:35:84:3e:b2:d7:99:60:09:ce:43:ab:6c:69:27 +-----BEGIN CERTIFICATE----- +MIIEFTCCAv2gAwIBAgIBATANBgkqhkiG9w0BAQUFADBkMQswCQYDVQQGEwJTRTEU +MBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3 +b3JrMSAwHgYDVQQDExdBZGRUcnVzdCBQdWJsaWMgQ0EgUm9vdDAeFw0wMDA1MzAx +MDQxNTBaFw0yMDA1MzAxMDQxNTBaMGQxCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtB +ZGRUcnVzdCBBQjEdMBsGA1UECxMUQWRkVHJ1c3QgVFRQIE5ldHdvcmsxIDAeBgNV +BAMTF0FkZFRydXN0IFB1YmxpYyBDQSBSb290MIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEA6Rowj4OIFMEg2Dybjxt+A3S72mnTRqX4jsIMEZBRpS9mVEBV +6tsfSlbunyNu9DnLoblv8n75XYcmYZ4c+OLspoH4IcUkzBEMP9smcnrHAZcHF/nX +GCwwfQ56HmIexkvA/X1id9NEHif2P0tEs7c42TkfYNVRknMDtABp4/MUTu7R3AnP +dzRGULD4EfL+OHn3Bzn+UZKXC1sIXzSGAa2Il+tmzV7R/9x98oTaunet3IAIx6eH +1lWfl2royBFkuucZKT8Rs3iQhCBSWxHveNCD9tVIkNAwHM+A+WD+eeSI8t0A65RF +62WUaUC6wNW0uLp9BBGo6zEFlpROWCGOn9Bg/QIDAQABo4HRMIHOMB0GA1UdDgQW +BBSBPjfYkrAfd59ctKtzquf2NGAv+jALBgNVHQ8EBAMCAQYwDwYDVR0TAQH/BAUw +AwEB/zCBjgYDVR0jBIGGMIGDgBSBPjfYkrAfd59ctKtzquf2NGAv+qFopGYwZDEL +MAkGA1UEBhMCU0UxFDASBgNVBAoTC0FkZFRydXN0IEFCMR0wGwYDVQQLExRBZGRU +cnVzdCBUVFAgTmV0d29yazEgMB4GA1UEAxMXQWRkVHJ1c3QgUHVibGljIENBIFJv +b3SCAQEwDQYJKoZIhvcNAQEFBQADggEBAAP3FUr4JNojVhaTdt02KLmuG7jD8WS6 +IBh4lSknVwW8fCr0uVFV2ocC3g8WFzH4qnkuCRO7r7IgGRLlk/lL+YPoRNWyQSW/ +iHVv/xD8SlTQX/D67zZzfRs2RcYhbbQVuE7PnFylPVoAjgbjPGsye/Kf8Lb93/Ao +GEjwxrzQvzSAlsJKsW2Ox5BF3i9nrEUEo3rcVZLJR2bYGozH7ZxOmuASu7VqTITh +4SINhwBk/ox9Yjllpu9CtoAlEmEBqCQTcAARJl/6NVDFSMwGR+gn2HCNX2TmoUQm +XiLsks3/QppEIW1cxeMiHV9HEufOX1362KqxMy3ZdvJOOjMMK7MtkAY= +-----END CERTIFICATE----- + +# Issuer: CN=AddTrust Qualified CA Root O=AddTrust AB OU=AddTrust TTP Network +# Subject: CN=AddTrust Qualified CA Root O=AddTrust AB OU=AddTrust TTP Network +# Label: "AddTrust Qualified Certificates Root" +# Serial: 1 +# MD5 Fingerprint: 27:ec:39:47:cd:da:5a:af:e2:9a:01:65:21:a9:4c:bb +# SHA1 Fingerprint: 4d:23:78:ec:91:95:39:b5:00:7f:75:8f:03:3b:21:1e:c5:4d:8b:cf +# SHA256 Fingerprint: 80:95:21:08:05:db:4b:bc:35:5e:44:28:d8:fd:6e:c2:cd:e3:ab:5f:b9:7a:99:42:98:8e:b8:f4:dc:d0:60:16 +-----BEGIN CERTIFICATE----- +MIIEHjCCAwagAwIBAgIBATANBgkqhkiG9w0BAQUFADBnMQswCQYDVQQGEwJTRTEU +MBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3 +b3JrMSMwIQYDVQQDExpBZGRUcnVzdCBRdWFsaWZpZWQgQ0EgUm9vdDAeFw0wMDA1 +MzAxMDQ0NTBaFw0yMDA1MzAxMDQ0NTBaMGcxCzAJBgNVBAYTAlNFMRQwEgYDVQQK +EwtBZGRUcnVzdCBBQjEdMBsGA1UECxMUQWRkVHJ1c3QgVFRQIE5ldHdvcmsxIzAh +BgNVBAMTGkFkZFRydXN0IFF1YWxpZmllZCBDQSBSb290MIIBIjANBgkqhkiG9w0B +AQEFAAOCAQ8AMIIBCgKCAQEA5B6a/twJWoekn0e+EV+vhDTbYjx5eLfpMLXsDBwq +xBb/4Oxx64r1EW7tTw2R0hIYLUkVAcKkIhPHEWT/IhKauY5cLwjPcWqzZwFZ8V1G +87B4pfYOQnrjfxvM0PC3KP0q6p6zsLkEqv32x7SxuCqg+1jxGaBvcCV+PmlKfw8i +2O+tCBGaKZnhqkRFmhJePp1tUvznoD1oL/BLcHwTOK28FSXx1s6rosAx1i+f4P8U +WfyEk9mHfExUE+uf0S0R+Bg6Ot4l2ffTQO2kBhLEO+GRwVY18BTcZTYJbqukB8c1 +0cIDMzZbdSZtQvESa0NvS3GU+jQd7RNuyoB/mC9suWXY6QIDAQABo4HUMIHRMB0G +A1UdDgQWBBQ5lYtii1zJ1IC6WA+XPxUIQ8yYpzALBgNVHQ8EBAMCAQYwDwYDVR0T +AQH/BAUwAwEB/zCBkQYDVR0jBIGJMIGGgBQ5lYtii1zJ1IC6WA+XPxUIQ8yYp6Fr +pGkwZzELMAkGA1UEBhMCU0UxFDASBgNVBAoTC0FkZFRydXN0IEFCMR0wGwYDVQQL +ExRBZGRUcnVzdCBUVFAgTmV0d29yazEjMCEGA1UEAxMaQWRkVHJ1c3QgUXVhbGlm +aWVkIENBIFJvb3SCAQEwDQYJKoZIhvcNAQEFBQADggEBABmrder4i2VhlRO6aQTv +hsoToMeqT2QbPxj2qC0sVY8FtzDqQmodwCVRLae/DLPt7wh/bDxGGuoYQ992zPlm +hpwsaPXpF/gxsxjE1kh9I0xowX67ARRvxdlu3rsEQmr49lx95dr6h+sNNVJn0J6X +dgWTP5XHAeZpVTh/EGGZyeNfpso+gmNIquIISD6q8rKFYqa0p9m9N5xotS1WfbC3 +P6CxB9bpT9zeRXEwMn8bLgn5v1Kh7sKAPgZcLlVAwRv1cEWw3F369nJad9Jjzc9Y +iQBCYz95OdBEsIJuQRno3eDBiFrRHnGTHyQwdOUeqN48Jzd/g66ed8/wMLH/S5no +xqE= +-----END CERTIFICATE----- + +# Issuer: CN=Entrust Root Certification Authority O=Entrust, Inc. OU=www.entrust.net/CPS is incorporated by reference/(c) 2006 Entrust, Inc. +# Subject: CN=Entrust Root Certification Authority O=Entrust, Inc. OU=www.entrust.net/CPS is incorporated by reference/(c) 2006 Entrust, Inc. +# Label: "Entrust Root Certification Authority" +# Serial: 1164660820 +# MD5 Fingerprint: d6:a5:c3:ed:5d:dd:3e:00:c1:3d:87:92:1f:1d:3f:e4 +# SHA1 Fingerprint: b3:1e:b1:b7:40:e3:6c:84:02:da:dc:37:d4:4d:f5:d4:67:49:52:f9 +# SHA256 Fingerprint: 73:c1:76:43:4f:1b:c6:d5:ad:f4:5b:0e:76:e7:27:28:7c:8d:e5:76:16:c1:e6:e6:14:1a:2b:2c:bc:7d:8e:4c +-----BEGIN CERTIFICATE----- +MIIEkTCCA3mgAwIBAgIERWtQVDANBgkqhkiG9w0BAQUFADCBsDELMAkGA1UEBhMC +VVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xOTA3BgNVBAsTMHd3dy5lbnRydXN0 +Lm5ldC9DUFMgaXMgaW5jb3Jwb3JhdGVkIGJ5IHJlZmVyZW5jZTEfMB0GA1UECxMW +KGMpIDIwMDYgRW50cnVzdCwgSW5jLjEtMCsGA1UEAxMkRW50cnVzdCBSb290IENl +cnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA2MTEyNzIwMjM0MloXDTI2MTEyNzIw +NTM0MlowgbAxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMTkw +NwYDVQQLEzB3d3cuZW50cnVzdC5uZXQvQ1BTIGlzIGluY29ycG9yYXRlZCBieSBy +ZWZlcmVuY2UxHzAdBgNVBAsTFihjKSAyMDA2IEVudHJ1c3QsIEluYy4xLTArBgNV +BAMTJEVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASIwDQYJ +KoZIhvcNAQEBBQADggEPADCCAQoCggEBALaVtkNC+sZtKm9I35RMOVcF7sN5EUFo +Nu3s/poBj6E4KPz3EEZmLk0eGrEaTsbRwJWIsMn/MYszA9u3g3s+IIRe7bJWKKf4 +4LlAcTfFy0cOlypowCKVYhXbR9n10Cv/gkvJrT7eTNuQgFA/CYqEAOwwCj0Yzfv9 +KlmaI5UXLEWeH25DeW0MXJj+SKfFI0dcXv1u5x609mhF0YaDW6KKjbHjKYD+JXGI +rb68j6xSlkuqUY3kEzEZ6E5Nn9uss2rVvDlUccp6en+Q3X0dgNmBu1kmwhH+5pPi +94DkZfs0Nw4pgHBNrziGLp5/V6+eF67rHMsoIV+2HNjnogQi+dPa2MsCAwEAAaOB +sDCBrTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zArBgNVHRAEJDAi +gA8yMDA2MTEyNzIwMjM0MlqBDzIwMjYxMTI3MjA1MzQyWjAfBgNVHSMEGDAWgBRo +kORnpKZTgMeGZqTx90tD+4S9bTAdBgNVHQ4EFgQUaJDkZ6SmU4DHhmak8fdLQ/uE +vW0wHQYJKoZIhvZ9B0EABBAwDhsIVjcuMTo0LjADAgSQMA0GCSqGSIb3DQEBBQUA +A4IBAQCT1DCw1wMgKtD5Y+iRDAUgqV8ZyntyTtSx29CW+1RaGSwMCPeyvIWonX9t +O1KzKtvn1ISMY/YPyyYBkVBs9F8U4pN0wBOeMDpQ47RgxRzwIkSNcUesyBrJ6Zua +AGAT/3B+XxFNSRuzFVJ7yVTav52Vr2ua2J7p8eRDjeIRRDq/r72DQnNSi6q7pynP +9WQcCk3RvKqsnyrQ/39/2n3qse0wJcGE2jTSW3iDVuycNsMm4hH2Z0kdkquM++v/ +eu6FSqdQgPCnXEqULl8FmTxSQeDNtGPPAUO6nIPcj2A781q0tHuu2guQOHXvgR1m +0vdXcDazv/wor3ElhVsT/h5/WrQ8 +-----END CERTIFICATE----- + +# Issuer: O=RSA Security Inc OU=RSA Security 2048 V3 +# Subject: O=RSA Security Inc OU=RSA Security 2048 V3 +# Label: "RSA Security 2048 v3" +# Serial: 13297492616345471454730593562152402946 +# MD5 Fingerprint: 77:0d:19:b1:21:fd:00:42:9c:3e:0c:a5:dd:0b:02:8e +# SHA1 Fingerprint: 25:01:90:19:cf:fb:d9:99:1c:b7:68:25:74:8d:94:5f:30:93:95:42 +# SHA256 Fingerprint: af:8b:67:62:a1:e5:28:22:81:61:a9:5d:5c:55:9e:e2:66:27:8f:75:d7:9e:83:01:89:a5:03:50:6a:bd:6b:4c +-----BEGIN CERTIFICATE----- +MIIDYTCCAkmgAwIBAgIQCgEBAQAAAnwAAAAKAAAAAjANBgkqhkiG9w0BAQUFADA6 +MRkwFwYDVQQKExBSU0EgU2VjdXJpdHkgSW5jMR0wGwYDVQQLExRSU0EgU2VjdXJp +dHkgMjA0OCBWMzAeFw0wMTAyMjIyMDM5MjNaFw0yNjAyMjIyMDM5MjNaMDoxGTAX +BgNVBAoTEFJTQSBTZWN1cml0eSBJbmMxHTAbBgNVBAsTFFJTQSBTZWN1cml0eSAy +MDQ4IFYzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAt49VcdKA3Xtp +eafwGFAyPGJn9gqVB93mG/Oe2dJBVGutn3y+Gc37RqtBaB4Y6lXIL5F4iSj7Jylg +/9+PjDvJSZu1pJTOAeo+tWN7fyb9Gd3AIb2E0S1PRsNO3Ng3OTsor8udGuorryGl +wSMiuLgbWhOHV4PR8CDn6E8jQrAApX2J6elhc5SYcSa8LWrg903w8bYqODGBDSnh +AMFRD0xS+ARaqn1y07iHKrtjEAMqs6FPDVpeRrc9DvV07Jmf+T0kgYim3WBU6JU2 +PcYJk5qjEoAAVZkZR73QpXzDuvsf9/UP+Ky5tfQ3mBMY3oVbtwyCO4dvlTlYMNpu +AWgXIszACwIDAQABo2MwYTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +BjAfBgNVHSMEGDAWgBQHw1EwpKrpRa41JPr/JCwz0LGdjDAdBgNVHQ4EFgQUB8NR +MKSq6UWuNST6/yQsM9CxnYwwDQYJKoZIhvcNAQEFBQADggEBAF8+hnZuuDU8TjYc +HnmYv/3VEhF5Ug7uMYm83X/50cYVIeiKAVQNOvtUudZj1LGqlk2iQk3UUx+LEN5/ +Zb5gEydxiKRz44Rj0aRV4VCT5hsOedBnvEbIvz8XDZXmxpBp3ue0L96VfdASPz0+ +f00/FGj1EVDVwfSQpQgdMWD/YIwjVAqv/qFuxdF6Kmh4zx6CCiC0H63lhbJqaHVO +rSU3lIW+vaHU6rcMSzyd6BIA8F+sDeGscGNz9395nzIlQnQFgCi/vcEkllgVsRch +6YlL2weIZ/QVrXA+L02FO8K32/6YaCOJ4XQP3vTFhGMpG8zLB8kApKnXwiJPZ9d3 +7CAFYd4= +-----END CERTIFICATE----- + +# Issuer: CN=GeoTrust Global CA O=GeoTrust Inc. +# Subject: CN=GeoTrust Global CA O=GeoTrust Inc. +# Label: "GeoTrust Global CA" +# Serial: 144470 +# MD5 Fingerprint: f7:75:ab:29:fb:51:4e:b7:77:5e:ff:05:3c:99:8e:f5 +# SHA1 Fingerprint: de:28:f4:a4:ff:e5:b9:2f:a3:c5:03:d1:a3:49:a7:f9:96:2a:82:12 +# SHA256 Fingerprint: ff:85:6a:2d:25:1d:cd:88:d3:66:56:f4:50:12:67:98:cf:ab:aa:de:40:79:9c:72:2d:e4:d2:b5:db:36:a7:3a +-----BEGIN CERTIFICATE----- +MIIDVDCCAjygAwIBAgIDAjRWMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVT +MRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9i +YWwgQ0EwHhcNMDIwNTIxMDQwMDAwWhcNMjIwNTIxMDQwMDAwWjBCMQswCQYDVQQG +EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEbMBkGA1UEAxMSR2VvVHJ1c3Qg +R2xvYmFsIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2swYYzD9 +9BcjGlZ+W988bDjkcbd4kdS8odhM+KhDtgPpTSEHCIjaWC9mOSm9BXiLnTjoBbdq +fnGk5sRgprDvgOSJKA+eJdbtg/OtppHHmMlCGDUUna2YRpIuT8rxh0PBFpVXLVDv +iS2Aelet8u5fa9IAjbkU+BQVNdnARqN7csiRv8lVK83Qlz6cJmTM386DGXHKTubU +1XupGc1V3sjs0l44U+VcT4wt/lAjNvxm5suOpDkZALeVAjmRCw7+OC7RHQWa9k0+ +bw8HHa8sHo9gOeL6NlMTOdReJivbPagUvTLrGAMoUgRx5aszPeE4uwc2hGKceeoW +MPRfwCvocWvk+QIDAQABo1MwUTAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTA +ephojYn7qwVkDBF9qn1luMrMTjAfBgNVHSMEGDAWgBTAephojYn7qwVkDBF9qn1l +uMrMTjANBgkqhkiG9w0BAQUFAAOCAQEANeMpauUvXVSOKVCUn5kaFOSPeCpilKIn +Z57QzxpeR+nBsqTP3UEaBU6bS+5Kb1VSsyShNwrrZHYqLizz/Tt1kL/6cdjHPTfS +tQWVYrmm3ok9Nns4d0iXrKYgjy6myQzCsplFAMfOEVEiIuCl6rYVSAlk6l5PdPcF +PseKUgzbFbS9bZvlxrFUaKnjaZC2mqUPuLk/IH2uSrW4nOQdtqvmlKXBx4Ot2/Un +hw4EbNX/3aBd7YdStysVAq45pmp06drE57xNNB6pXE0zX5IJL4hmXXeXxx12E6nV +5fEWCRE11azbJHFwLJhWC9kXtNHjUStedejV0NxPNO3CBWaAocvmMw== +-----END CERTIFICATE----- + +# Issuer: CN=GeoTrust Global CA 2 O=GeoTrust Inc. +# Subject: CN=GeoTrust Global CA 2 O=GeoTrust Inc. +# Label: "GeoTrust Global CA 2" +# Serial: 1 +# MD5 Fingerprint: 0e:40:a7:6c:de:03:5d:8f:d1:0f:e4:d1:8d:f9:6c:a9 +# SHA1 Fingerprint: a9:e9:78:08:14:37:58:88:f2:05:19:b0:6d:2b:0d:2b:60:16:90:7d +# SHA256 Fingerprint: ca:2d:82:a0:86:77:07:2f:8a:b6:76:4f:f0:35:67:6c:fe:3e:5e:32:5e:01:21:72:df:3f:92:09:6d:b7:9b:85 +-----BEGIN CERTIFICATE----- +MIIDZjCCAk6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBEMQswCQYDVQQGEwJVUzEW +MBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEdMBsGA1UEAxMUR2VvVHJ1c3QgR2xvYmFs +IENBIDIwHhcNMDQwMzA0MDUwMDAwWhcNMTkwMzA0MDUwMDAwWjBEMQswCQYDVQQG +EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEdMBsGA1UEAxMUR2VvVHJ1c3Qg +R2xvYmFsIENBIDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDvPE1A +PRDfO1MA4Wf+lGAVPoWI8YkNkMgoI5kF6CsgncbzYEbYwbLVjDHZ3CB5JIG/NTL8 +Y2nbsSpr7iFY8gjpeMtvy/wWUsiRxP89c96xPqfCfWbB9X5SJBri1WeR0IIQ13hL +TytCOb1kLUCgsBDTOEhGiKEMuzozKmKY+wCdE1l/bztyqu6mD4b5BWHqZ38MN5aL +5mkWRxHCJ1kDs6ZgwiFAVvqgx306E+PsV8ez1q6diYD3Aecs9pYrEw15LNnA5IZ7 +S4wMcoKK+xfNAGw6EzywhIdLFnopsk/bHdQL82Y3vdj2V7teJHq4PIu5+pIaGoSe +2HSPqht/XvT+RSIhAgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE +FHE4NvICMVNHK266ZUapEBVYIAUJMB8GA1UdIwQYMBaAFHE4NvICMVNHK266ZUap +EBVYIAUJMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQUFAAOCAQEAA/e1K6td +EPx7srJerJsOflN4WT5CBP51o62sgU7XAotexC3IUnbHLB/8gTKY0UvGkpMzNTEv +/NgdRN3ggX+d6YvhZJFiCzkIjKx0nVnZellSlxG5FntvRdOW2TF9AjYPnDtuzywN +A0ZF66D0f0hExghAzN4bcLUprbqLOzRldRtxIR0sFAqwlpW41uryZfspuk/qkZN0 +abby/+Ea0AzRdoXLiiW9l14sbxWZJue2Kf8i7MkCx1YAzUm5s2x7UwQa4qjJqhIF +I8LO57sEAszAR6LkxCkvW0VXiVHuPOtSCP8HNR6fNWpHSlaY0VqFH4z1Ir+rzoPz +4iIprn2DQKi6bA== +-----END CERTIFICATE----- + +# Issuer: CN=GeoTrust Universal CA O=GeoTrust Inc. +# Subject: CN=GeoTrust Universal CA O=GeoTrust Inc. +# Label: "GeoTrust Universal CA" +# Serial: 1 +# MD5 Fingerprint: 92:65:58:8b:a2:1a:31:72:73:68:5c:b4:a5:7a:07:48 +# SHA1 Fingerprint: e6:21:f3:35:43:79:05:9a:4b:68:30:9d:8a:2f:74:22:15:87:ec:79 +# SHA256 Fingerprint: a0:45:9b:9f:63:b2:25:59:f5:fa:5d:4c:6d:b3:f9:f7:2f:f1:93:42:03:35:78:f0:73:bf:1d:1b:46:cb:b9:12 +-----BEGIN CERTIFICATE----- +MIIFaDCCA1CgAwIBAgIBATANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJVUzEW +MBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEeMBwGA1UEAxMVR2VvVHJ1c3QgVW5pdmVy +c2FsIENBMB4XDTA0MDMwNDA1MDAwMFoXDTI5MDMwNDA1MDAwMFowRTELMAkGA1UE +BhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xHjAcBgNVBAMTFUdlb1RydXN0 +IFVuaXZlcnNhbCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKYV +VaCjxuAfjJ0hUNfBvitbtaSeodlyWL0AG0y/YckUHUWCq8YdgNY96xCcOq9tJPi8 +cQGeBvV8Xx7BDlXKg5pZMK4ZyzBIle0iN430SppyZj6tlcDgFgDgEB8rMQ7XlFTT +QjOgNB0eRXbdT8oYN+yFFXoZCPzVx5zw8qkuEKmS5j1YPakWaDwvdSEYfyh3peFh +F7em6fgemdtzbvQKoiFs7tqqhZJmr/Z6a4LauiIINQ/PQvE1+mrufislzDoR5G2v +c7J2Ha3QsnhnGqQ5HFELZ1aD/ThdDc7d8Lsrlh/eezJS/R27tQahsiFepdaVaH/w +mZ7cRQg+59IJDTWU3YBOU5fXtQlEIGQWFwMCTFMNaN7VqnJNk22CDtucvc+081xd +VHppCZbW2xHBjXWotM85yM48vCR85mLK4b19p71XZQvk/iXttmkQ3CgaRr0BHdCX +teGYO8A3ZNY9lO4L4fUorgtWv3GLIylBjobFS1J72HGrH4oVpjuDWtdYAVHGTEHZ +f9hBZ3KiKN9gg6meyHv8U3NyWfWTehd2Ds735VzZC1U0oqpbtWpU5xPKV+yXbfRe +Bi9Fi1jUIxaS5BZuKGNZMN9QAZxjiRqf2xeUgnA3wySemkfWWspOqGmJch+RbNt+ +nhutxx9z3SxPGWX9f5NAEC7S8O08ni4oPmkmM8V7AgMBAAGjYzBhMA8GA1UdEwEB +/wQFMAMBAf8wHQYDVR0OBBYEFNq7LqqwDLiIJlF0XG0D08DYj3rWMB8GA1UdIwQY +MBaAFNq7LqqwDLiIJlF0XG0D08DYj3rWMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG +9w0BAQUFAAOCAgEAMXjmx7XfuJRAyXHEqDXsRh3ChfMoWIawC/yOsjmPRFWrZIRc +aanQmjg8+uUfNeVE44B5lGiku8SfPeE0zTBGi1QrlaXv9z+ZhP015s8xxtxqv6fX +IwjhmF7DWgh2qaavdy+3YL1ERmrvl/9zlcGO6JP7/TG37FcREUWbMPEaiDnBTzyn +ANXH/KttgCJwpQzgXQQpAvvLoJHRfNbDflDVnVi+QTjruXU8FdmbyUqDWcDaU/0z +uzYYm4UPFd3uLax2k7nZAY1IEKj79TiG8dsKxr2EoyNB3tZ3b4XUhRxQ4K5RirqN +Pnbiucon8l+f725ZDQbYKxek0nxru18UGkiPGkzns0ccjkxFKyDuSN/n3QmOGKja +QI2SJhFTYXNd673nxE0pN2HrrDktZy4W1vUAg4WhzH92xH3kt0tm7wNFYGm2DFKW +koRepqO1pD4r2czYG0eq8kTaT/kD6PAUyz/zg97QwVTjt+gKN02LIFkDMBmhLMi9 +ER/frslKxfMnZmaGrGiR/9nmUxwPi1xpZQomyB40w11Re9epnAahNt3ViZS82eQt +DF4JbAiXfKM9fJP/P6EUp8+1Xevb2xzEdt+Iub1FBZUbrvxGakyvSOPOrg/Sfuvm +bJxPgWp6ZKy7PtXny3YuxadIwVyQD8vIP/rmMuGNG2+k5o7Y+SlIis5z/iw= +-----END CERTIFICATE----- + +# Issuer: CN=GeoTrust Universal CA 2 O=GeoTrust Inc. +# Subject: CN=GeoTrust Universal CA 2 O=GeoTrust Inc. +# Label: "GeoTrust Universal CA 2" +# Serial: 1 +# MD5 Fingerprint: 34:fc:b8:d0:36:db:9e:14:b3:c2:f2:db:8f:e4:94:c7 +# SHA1 Fingerprint: 37:9a:19:7b:41:85:45:35:0c:a6:03:69:f3:3c:2e:af:47:4f:20:79 +# SHA256 Fingerprint: a0:23:4f:3b:c8:52:7c:a5:62:8e:ec:81:ad:5d:69:89:5d:a5:68:0d:c9:1d:1c:b8:47:7f:33:f8:78:b9:5b:0b +-----BEGIN CERTIFICATE----- +MIIFbDCCA1SgAwIBAgIBATANBgkqhkiG9w0BAQUFADBHMQswCQYDVQQGEwJVUzEW +MBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEgMB4GA1UEAxMXR2VvVHJ1c3QgVW5pdmVy +c2FsIENBIDIwHhcNMDQwMzA0MDUwMDAwWhcNMjkwMzA0MDUwMDAwWjBHMQswCQYD +VQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEgMB4GA1UEAxMXR2VvVHJ1 +c3QgVW5pdmVyc2FsIENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC +AQCzVFLByT7y2dyxUxpZKeexw0Uo5dfR7cXFS6GqdHtXr0om/Nj1XqduGdt0DE81 +WzILAePb63p3NeqqWuDW6KFXlPCQo3RWlEQwAx5cTiuFJnSCegx2oG9NzkEtoBUG +FF+3Qs17j1hhNNwqCPkuwwGmIkQcTAeC5lvO0Ep8BNMZcyfwqph/Lq9O64ceJHdq +XbboW0W63MOhBW9Wjo8QJqVJwy7XQYci4E+GymC16qFjwAGXEHm9ADwSbSsVsaxL +se4YuU6W3Nx2/zu+z18DwPw76L5GG//aQMJS9/7jOvdqdzXQ2o3rXhhqMcceujwb +KNZrVMaqW9eiLBsZzKIC9ptZvTdrhrVtgrrY6slWvKk2WP0+GfPtDCapkzj4T8Fd +IgbQl+rhrcZV4IErKIM6+vR7IVEAvlI4zs1meaj0gVbi0IMJR1FbUGrP20gaXT73 +y/Zl92zxlfgCOzJWgjl6W70viRu/obTo/3+NjN8D8WBOWBFM66M/ECuDmgFz2ZRt +hAAnZqzwcEAJQpKtT5MNYQlRJNiS1QuUYbKHsu3/mjX/hVTK7URDrBs8FmtISgoc +QIgfksILAAX/8sgCSqSqqcyZlpwvWOB94b67B9xfBHJcMTTD7F8t4D1kkCLm0ey4 +Lt1ZrtmhN79UNdxzMk+MBB4zsslG8dhcyFVQyWi9qLo2CQIDAQABo2MwYTAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR281Xh+qQ2+/CfXGJx7Tz0RzgQKzAfBgNV +HSMEGDAWgBR281Xh+qQ2+/CfXGJx7Tz0RzgQKzAOBgNVHQ8BAf8EBAMCAYYwDQYJ +KoZIhvcNAQEFBQADggIBAGbBxiPz2eAubl/oz66wsCVNK/g7WJtAJDday6sWSf+z +dXkzoS9tcBc0kf5nfo/sm+VegqlVHy/c1FEHEv6sFj4sNcZj/NwQ6w2jqtB8zNHQ +L1EuxBRa3ugZ4T7GzKQp5y6EqgYweHZUcyiYWTjgAA1i00J9IZ+uPTqM1fp3DRgr +Fg5fNuH8KrUwJM/gYwx7WBr+mbpCErGR9Hxo4sjoryzqyX6uuyo9DRXcNJW2GHSo +ag/HtPQTxORb7QrSpJdMKu0vbBKJPfEncKpqA1Ihn0CoZ1Dy81of398j9tx4TuaY +T1U6U+Pv8vSfx3zYWK8pIpe44L2RLrB27FcRz+8pRPPphXpgY+RdM4kX2TGq2tbz +GDVyz4crL2MjhF2EjD9XoIj8mZEoJmmZ1I+XRL6O1UixpCgp8RW04eWe3fiPpm8m +1wk8OhwRDqZsN/etRIcsKMfYdIKz0G9KV7s1KSegi+ghp4dkNl3M2Basx7InQJJV +OCiNUW7dFGdTbHFcJoRNdVq2fmBWqU2t+5sel/MN2dKXVHfaPRK34B7vCAas+YWH +6aLcr34YEoP9VhdBLtUpgn2Z9DH2canPLAEnpQW5qrJITirvn5NSUZU8UnOOVkwX +QMAJKOSLakhT2+zNVVXxxvjpoixMptEmX36vWkzaH6byHCx+rgIW0lbQL1dTR+iS +-----END CERTIFICATE----- + +# Issuer: CN=Visa eCommerce Root O=VISA OU=Visa International Service Association +# Subject: CN=Visa eCommerce Root O=VISA OU=Visa International Service Association +# Label: "Visa eCommerce Root" +# Serial: 25952180776285836048024890241505565794 +# MD5 Fingerprint: fc:11:b8:d8:08:93:30:00:6d:23:f9:7e:eb:52:1e:02 +# SHA1 Fingerprint: 70:17:9b:86:8c:00:a4:fa:60:91:52:22:3f:9f:3e:32:bd:e0:05:62 +# SHA256 Fingerprint: 69:fa:c9:bd:55:fb:0a:c7:8d:53:bb:ee:5c:f1:d5:97:98:9f:d0:aa:ab:20:a2:51:51:bd:f1:73:3e:e7:d1:22 +-----BEGIN CERTIFICATE----- +MIIDojCCAoqgAwIBAgIQE4Y1TR0/BvLB+WUF1ZAcYjANBgkqhkiG9w0BAQUFADBr +MQswCQYDVQQGEwJVUzENMAsGA1UEChMEVklTQTEvMC0GA1UECxMmVmlzYSBJbnRl +cm5hdGlvbmFsIFNlcnZpY2UgQXNzb2NpYXRpb24xHDAaBgNVBAMTE1Zpc2EgZUNv +bW1lcmNlIFJvb3QwHhcNMDIwNjI2MDIxODM2WhcNMjIwNjI0MDAxNjEyWjBrMQsw +CQYDVQQGEwJVUzENMAsGA1UEChMEVklTQTEvMC0GA1UECxMmVmlzYSBJbnRlcm5h +dGlvbmFsIFNlcnZpY2UgQXNzb2NpYXRpb24xHDAaBgNVBAMTE1Zpc2EgZUNvbW1l +cmNlIFJvb3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvV95WHm6h +2mCxlCfLF9sHP4CFT8icttD0b0/Pmdjh28JIXDqsOTPHH2qLJj0rNfVIsZHBAk4E +lpF7sDPwsRROEW+1QK8bRaVK7362rPKgH1g/EkZgPI2h4H3PVz4zHvtH8aoVlwdV +ZqW1LS7YgFmypw23RuwhY/81q6UCzyr0TP579ZRdhE2o8mCP2w4lPJ9zcc+U30rq +299yOIzzlr3xF7zSujtFWsan9sYXiwGd/BmoKoMWuDpI/k4+oKsGGelT84ATB+0t +vz8KPFUgOSwsAGl0lUq8ILKpeeUYiZGo3BxN77t+Nwtd/jmliFKMAGzsGHxBvfaL +dXe6YJ2E5/4tAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQD +AgEGMB0GA1UdDgQWBBQVOIMPPyw/cDMezUb+B4wg4NfDtzANBgkqhkiG9w0BAQUF +AAOCAQEAX/FBfXxcCLkr4NWSR/pnXKUTwwMhmytMiUbPWU3J/qVAtmPN3XEolWcR +zCSs00Rsca4BIGsDoo8Ytyk6feUWYFN4PMCvFYP3j1IzJL1kk5fui/fbGKhtcbP3 +LBfQdCVp9/5rPJS+TUtBjE7ic9DjkCJzQ83z7+pzzkWKsKZJ/0x9nXGIxHYdkFsd +7v3M9+79YKWxehZx0RbQfBI8bGmX265fOZpwLwU8GUYEmSA20GBuYQa7FkKMcPcw +++DbZqMAAb3mLNqRX6BGi01qnD093QVG/na/oAo85ADmJ7f/hC3euiInlhBx6yLt +398znM/jra6O1I7mT1GvFpLgXPYHDw== +-----END CERTIFICATE----- + +# Issuer: CN=Certum CA O=Unizeto Sp. z o.o. +# Subject: CN=Certum CA O=Unizeto Sp. z o.o. +# Label: "Certum Root CA" +# Serial: 65568 +# MD5 Fingerprint: 2c:8f:9f:66:1d:18:90:b1:47:26:9d:8e:86:82:8c:a9 +# SHA1 Fingerprint: 62:52:dc:40:f7:11:43:a2:2f:de:9e:f7:34:8e:06:42:51:b1:81:18 +# SHA256 Fingerprint: d8:e0:fe:bc:1d:b2:e3:8d:00:94:0f:37:d2:7d:41:34:4d:99:3e:73:4b:99:d5:65:6d:97:78:d4:d8:14:36:24 +-----BEGIN CERTIFICATE----- +MIIDDDCCAfSgAwIBAgIDAQAgMA0GCSqGSIb3DQEBBQUAMD4xCzAJBgNVBAYTAlBM +MRswGQYDVQQKExJVbml6ZXRvIFNwLiB6IG8uby4xEjAQBgNVBAMTCUNlcnR1bSBD +QTAeFw0wMjA2MTExMDQ2MzlaFw0yNzA2MTExMDQ2MzlaMD4xCzAJBgNVBAYTAlBM +MRswGQYDVQQKExJVbml6ZXRvIFNwLiB6IG8uby4xEjAQBgNVBAMTCUNlcnR1bSBD +QTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM6xwS7TT3zNJc4YPk/E +jG+AanPIW1H4m9LcuwBcsaD8dQPugfCI7iNS6eYVM42sLQnFdvkrOYCJ5JdLkKWo +ePhzQ3ukYbDYWMzhbGZ+nPMJXlVjhNWo7/OxLjBos8Q82KxujZlakE403Daaj4GI +ULdtlkIJ89eVgw1BS7Bqa/j8D35in2fE7SZfECYPCE/wpFcozo+47UX2bu4lXapu +Ob7kky/ZR6By6/qmW6/KUz/iDsaWVhFu9+lmqSbYf5VT7QqFiLpPKaVCjF62/IUg +AKpoC6EahQGcxEZjgoi2IrHu/qpGWX7PNSzVttpd90gzFFS269lvzs2I1qsb2pY7 +HVkCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEA +uI3O7+cUus/usESSbLQ5PqKEbq24IXfS1HeCh+YgQYHu4vgRt2PRFze+GXYkHAQa +TOs9qmdvLdTN/mUxcMUbpgIKumB7bVjCmkn+YzILa+M6wKyrO7Do0wlRjBCDxjTg +xSvgGrZgFCdsMneMvLJymM/NzD+5yCRCFNZX/OYmQ6kd5YCQzgNUKD73P9P4Te1q +CjqTE5s7FCMTY5w/0YcneeVMUeMBrYVdGjux1XMQpNPyvG5k9VpWkKjHDkx0Dy5x +O/fIR/RpbxXyEV6DHpx8Uq79AtoSqFlnGNu8cN2bsWntgM6JQEhqDjXKKWYVIZQs +6GAqm4VKQPNriiTsBhYscw== +-----END CERTIFICATE----- + +# Issuer: CN=AAA Certificate Services O=Comodo CA Limited +# Subject: CN=AAA Certificate Services O=Comodo CA Limited +# Label: "Comodo AAA Services root" +# Serial: 1 +# MD5 Fingerprint: 49:79:04:b0:eb:87:19:ac:47:b0:bc:11:51:9b:74:d0 +# SHA1 Fingerprint: d1:eb:23:a4:6d:17:d6:8f:d9:25:64:c2:f1:f1:60:17:64:d8:e3:49 +# SHA256 Fingerprint: d7:a7:a0:fb:5d:7e:27:31:d7:71:e9:48:4e:bc:de:f7:1d:5f:0c:3e:0a:29:48:78:2b:c8:3e:e0:ea:69:9e:f4 +-----BEGIN CERTIFICATE----- +MIIEMjCCAxqgAwIBAgIBATANBgkqhkiG9w0BAQUFADB7MQswCQYDVQQGEwJHQjEb +MBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow +GAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDEhMB8GA1UEAwwYQUFBIENlcnRpZmlj +YXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAwMFoXDTI4MTIzMTIzNTk1OVowezEL +MAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE +BwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNVBAMM +GEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEBBQADggEP +ADCCAQoCggEBAL5AnfRu4ep2hxxNRUSOvkbIgwadwSr+GB+O5AL686tdUIoWMQua +BtDFcCLNSS1UY8y2bmhGC1Pqy0wkwLxyTurxFa70VJoSCsN6sjNg4tqJVfMiWPPe +3M/vg4aijJRPn2jymJBGhCfHdr/jzDUsi14HZGWCwEiwqJH5YZ92IFCokcdmtet4 +YgNW8IoaE+oxox6gmf049vYnMlhvB/VruPsUK6+3qszWY19zjNoFmag4qMsXeDZR +rOme9Hg6jc8P2ULimAyrL58OAd7vn5lJ8S3frHRNG5i1R8XlKdH5kBjHYpy+g8cm +ez6KJcfA3Z3mNWgQIJ2P2N7Sw4ScDV7oL8kCAwEAAaOBwDCBvTAdBgNVHQ4EFgQU +oBEKIz6W8Qfs4q8p74Klf9AwpLQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF +MAMBAf8wewYDVR0fBHQwcjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5jb20v +QUFBQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmwwNqA0oDKGMGh0dHA6Ly9jcmwuY29t +b2RvLm5ldC9BQUFDZXJ0aWZpY2F0ZVNlcnZpY2VzLmNybDANBgkqhkiG9w0BAQUF +AAOCAQEACFb8AvCb6P+k+tZ7xkSAzk/ExfYAWMymtrwUSWgEdujm7l3sAg9g1o1Q +GE8mTgHj5rCl7r+8dFRBv/38ErjHT1r0iWAFf2C3BUrz9vHCv8S5dIa2LX1rzNLz +Rt0vxuBqw8M0Ayx9lt1awg6nCpnBBYurDC/zXDrPbDdVCYfeU0BsWO/8tqtlbgT2 +G9w84FoVxp7Z8VlIMCFlA2zs6SFz7JsDoeA3raAVGI/6ugLOpyypEBMs1OUIJqsi +l2D4kF501KKaU73yqWjgom7C12yxow+ev+to51byrvLjKzg6CYG1a4XXvi3tPxq3 +smPi9WIsgtRqAEFQ8TmDn5XpNpaYbg== +-----END CERTIFICATE----- + +# Issuer: CN=Secure Certificate Services O=Comodo CA Limited +# Subject: CN=Secure Certificate Services O=Comodo CA Limited +# Label: "Comodo Secure Services root" +# Serial: 1 +# MD5 Fingerprint: d3:d9:bd:ae:9f:ac:67:24:b3:c8:1b:52:e1:b9:a9:bd +# SHA1 Fingerprint: 4a:65:d5:f4:1d:ef:39:b8:b8:90:4a:4a:d3:64:81:33:cf:c7:a1:d1 +# SHA256 Fingerprint: bd:81:ce:3b:4f:65:91:d1:1a:67:b5:fc:7a:47:fd:ef:25:52:1b:f9:aa:4e:18:b9:e3:df:2e:34:a7:80:3b:e8 +-----BEGIN CERTIFICATE----- +MIIEPzCCAyegAwIBAgIBATANBgkqhkiG9w0BAQUFADB+MQswCQYDVQQGEwJHQjEb +MBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow +GAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDEkMCIGA1UEAwwbU2VjdXJlIENlcnRp +ZmljYXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAwMFoXDTI4MTIzMTIzNTk1OVow +fjELMAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G +A1UEBwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxJDAiBgNV +BAMMG1NlY3VyZSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEB +BQADggEPADCCAQoCggEBAMBxM4KK0HDrc4eCQNUd5MvJDkKQ+d40uaG6EfQlhfPM +cm3ye5drswfxdySRXyWP9nQ95IDC+DwN879A6vfIUtFyb+/Iq0G4bi4XKpVpDM3S +HpR7LZQdqnXXs5jLrLxkU0C8j6ysNstcrbvd4JQX7NFc0L/vpZXJkMWwrPsbQ996 +CF23uPJAGysnnlDOXmWCiIxe004MeuoIkbY2qitC++rCoznl2yY4rYsK7hljxxwk +3wN42ubqwUcaCwtGCd0C/N7Lh1/XMGNooa7cMqG6vv5Eq2i2pRcV/b3Vp6ea5EQz +6YiO/O1R65NxTq0B50SOqy3LqP4BSUjwwN3HaNiS/j0CAwEAAaOBxzCBxDAdBgNV +HQ4EFgQUPNiTiMLAggnMAZkGkyDpnnAJY08wDgYDVR0PAQH/BAQDAgEGMA8GA1Ud +EwEB/wQFMAMBAf8wgYEGA1UdHwR6MHgwO6A5oDeGNWh0dHA6Ly9jcmwuY29tb2Rv +Y2EuY29tL1NlY3VyZUNlcnRpZmljYXRlU2VydmljZXMuY3JsMDmgN6A1hjNodHRw +Oi8vY3JsLmNvbW9kby5uZXQvU2VjdXJlQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmww +DQYJKoZIhvcNAQEFBQADggEBAIcBbSMdflsXfcFhMs+P5/OKlFlm4J4oqF7Tt/Q0 +5qo5spcWxYJvMqTpjOev/e/C6LlLqqP05tqNZSH7uoDrJiiFGv45jN5bBAS0VPmj +Z55B+glSzAVIqMk/IQQezkhr/IXownuvf7fM+F86/TXGDe+X3EyrEeFryzHRbPtI +gKvcnDe4IRRLDXE97IMzbtFuMhbsmMcWi1mmNKsFVy2T96oTy9IT4rcuO81rUBcJ +aD61JlfutuC23bkpgHl9j6PwpCikFcSF9CfUa7/lXORlAnZUtOM3ZiTTGWHIUhDl +izeauan5Hb/qmZJhlv8BzaFfDbxxvA6sCx1HRR3B7Hzs/Sk= +-----END CERTIFICATE----- + +# Issuer: CN=Trusted Certificate Services O=Comodo CA Limited +# Subject: CN=Trusted Certificate Services O=Comodo CA Limited +# Label: "Comodo Trusted Services root" +# Serial: 1 +# MD5 Fingerprint: 91:1b:3f:6e:cd:9e:ab:ee:07:fe:1f:71:d2:b3:61:27 +# SHA1 Fingerprint: e1:9f:e3:0e:8b:84:60:9e:80:9b:17:0d:72:a8:c5:ba:6e:14:09:bd +# SHA256 Fingerprint: 3f:06:e5:56:81:d4:96:f5:be:16:9e:b5:38:9f:9f:2b:8f:f6:1e:17:08:df:68:81:72:48:49:cd:5d:27:cb:69 +-----BEGIN CERTIFICATE----- +MIIEQzCCAyugAwIBAgIBATANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJHQjEb +MBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow +GAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDElMCMGA1UEAwwcVHJ1c3RlZCBDZXJ0 +aWZpY2F0ZSBTZXJ2aWNlczAeFw0wNDAxMDEwMDAwMDBaFw0yODEyMzEyMzU5NTla +MH8xCzAJBgNVBAYTAkdCMRswGQYDVQQIDBJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAO +BgNVBAcMB1NhbGZvcmQxGjAYBgNVBAoMEUNvbW9kbyBDQSBMaW1pdGVkMSUwIwYD +VQQDDBxUcnVzdGVkIENlcnRpZmljYXRlIFNlcnZpY2VzMIIBIjANBgkqhkiG9w0B +AQEFAAOCAQ8AMIIBCgKCAQEA33FvNlhTWvI2VFeAxHQIIO0Yfyod5jWaHiWsnOWW +fnJSoBVC21ndZHoa0Lh73TkVvFVIxO06AOoxEbrycXQaZ7jPM8yoMa+j49d/vzMt +TGo87IvDktJTdyR0nAducPy9C1t2ul/y/9c3S0pgePfw+spwtOpZqqPOSC+pw7IL +fhdyFgymBwwbOM/JYrc/oJOlh0Hyt3BAd9i+FHzjqMB6juljatEPmsbS9Is6FARW +1O24zG71++IsWL1/T2sr92AkWCTOJu80kTrV44HQsvAEAtdbtz6SrGsSivnkBbA7 +kUlcsutT6vifR4buv5XAwAaf0lteERv0xwQ1KdJVXOTt6wIDAQABo4HJMIHGMB0G +A1UdDgQWBBTFe1i97doladL3WRaoszLAeydb9DAOBgNVHQ8BAf8EBAMCAQYwDwYD +VR0TAQH/BAUwAwEB/zCBgwYDVR0fBHwwejA8oDqgOIY2aHR0cDovL2NybC5jb21v +ZG9jYS5jb20vVHJ1c3RlZENlcnRpZmljYXRlU2VydmljZXMuY3JsMDqgOKA2hjRo +dHRwOi8vY3JsLmNvbW9kby5uZXQvVHJ1c3RlZENlcnRpZmljYXRlU2VydmljZXMu +Y3JsMA0GCSqGSIb3DQEBBQUAA4IBAQDIk4E7ibSvuIQSTI3S8NtwuleGFTQQuS9/ +HrCoiWChisJ3DFBKmwCL2Iv0QeLQg4pKHBQGsKNoBXAxMKdTmw7pSqBYaWcOrp32 +pSxBvzwGa+RZzG0Q8ZZvH9/0BAKkn0U+yNj6NkZEUD+Cl5EfKNsYEYwq5GWDVxIS +jBc/lDb+XbDABHcTuPQV1T84zJQ6VdCsmPW6AF/ghhmBeC8owH7TzEIK9a5QoNE+ +xqFx7D+gIIxmOom0jtTYsU0lR+4viMi14QVFwL4Ucd56/Y57fU0IlqUSc/Atyjcn +dBInTMu2l+nZrghtWjlA3QVHdWpaIbOjGM9O9y5Xt5hwXsjEeLBi +-----END CERTIFICATE----- + +# Issuer: CN=QuoVadis Root Certification Authority O=QuoVadis Limited OU=Root Certification Authority +# Subject: CN=QuoVadis Root Certification Authority O=QuoVadis Limited OU=Root Certification Authority +# Label: "QuoVadis Root CA" +# Serial: 985026699 +# MD5 Fingerprint: 27:de:36:fe:72:b7:00:03:00:9d:f4:f0:1e:6c:04:24 +# SHA1 Fingerprint: de:3f:40:bd:50:93:d3:9b:6c:60:f6:da:bc:07:62:01:00:89:76:c9 +# SHA256 Fingerprint: a4:5e:de:3b:bb:f0:9c:8a:e1:5c:72:ef:c0:72:68:d6:93:a2:1c:99:6f:d5:1e:67:ca:07:94:60:fd:6d:88:73 +-----BEGIN CERTIFICATE----- +MIIF0DCCBLigAwIBAgIEOrZQizANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJC +TTEZMBcGA1UEChMQUXVvVmFkaXMgTGltaXRlZDElMCMGA1UECxMcUm9vdCBDZXJ0 +aWZpY2F0aW9uIEF1dGhvcml0eTEuMCwGA1UEAxMlUXVvVmFkaXMgUm9vdCBDZXJ0 +aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wMTAzMTkxODMzMzNaFw0yMTAzMTcxODMz +MzNaMH8xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMSUw +IwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MS4wLAYDVQQDEyVR +dW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv2G1lVO6V/z68mcLOhrfEYBklbTRvM16z/Yp +li4kVEAkOPcahdxYTMukJ0KX0J+DisPkBgNbAKVRHnAEdOLB1Dqr1607BxgFjv2D +rOpm2RgbaIr1VxqYuvXtdj182d6UajtLF8HVj71lODqV0D1VNk7feVcxKh7YWWVJ +WCCYfqtffp/p1k3sg3Spx2zY7ilKhSoGFPlU5tPaZQeLYzcS19Dsw3sgQUSj7cug +F+FxZc4dZjH3dgEZyH0DWLaVSR2mEiboxgx24ONmy+pdpibu5cxfvWenAScOospU +xbF6lR1xHkopigPcakXBpBlebzbNw6Kwt/5cOOJSvPhEQ+aQuwIDAQABo4ICUjCC +Ak4wPQYIKwYBBQUHAQEEMTAvMC0GCCsGAQUFBzABhiFodHRwczovL29jc3AucXVv +dmFkaXNvZmZzaG9yZS5jb20wDwYDVR0TAQH/BAUwAwEB/zCCARoGA1UdIASCAREw +ggENMIIBCQYJKwYBBAG+WAABMIH7MIHUBggrBgEFBQcCAjCBxxqBxFJlbGlhbmNl +IG9uIHRoZSBRdW9WYWRpcyBSb290IENlcnRpZmljYXRlIGJ5IGFueSBwYXJ0eSBh +c3N1bWVzIGFjY2VwdGFuY2Ugb2YgdGhlIHRoZW4gYXBwbGljYWJsZSBzdGFuZGFy +ZCB0ZXJtcyBhbmQgY29uZGl0aW9ucyBvZiB1c2UsIGNlcnRpZmljYXRpb24gcHJh +Y3RpY2VzLCBhbmQgdGhlIFF1b1ZhZGlzIENlcnRpZmljYXRlIFBvbGljeS4wIgYI +KwYBBQUHAgEWFmh0dHA6Ly93d3cucXVvdmFkaXMuYm0wHQYDVR0OBBYEFItLbe3T +KbkGGew5Oanwl4Rqy+/fMIGuBgNVHSMEgaYwgaOAFItLbe3TKbkGGew5Oanwl4Rq +y+/foYGEpIGBMH8xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1p +dGVkMSUwIwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MS4wLAYD +VQQDEyVRdW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggQ6tlCL +MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOCAQEAitQUtf70mpKnGdSk +fnIYj9lofFIk3WdvOXrEql494liwTXCYhGHoG+NpGA7O+0dQoE7/8CQfvbLO9Sf8 +7C9TqnN7Az10buYWnuulLsS/VidQK2K6vkscPFVcQR0kvoIgR13VRH56FmjffU1R +cHhXHTMe/QKZnAzNCgVPx7uOpHX6Sm2xgI4JVrmcGmD+XcHXetwReNDWXcG31a0y +mQM6isxUJTkxgXsTIlG6Rmyhu576BGxJJnSP0nPrzDCi5upZIof4l/UO/erMkqQW +xFIY6iHOsfHmhIHluqmGKPJDWl0Snawe2ajlCmqnf6CHKc/yiU3U7MXi5nrQNiOK +SnQ2+Q== +-----END CERTIFICATE----- + +# Issuer: CN=QuoVadis Root CA 2 O=QuoVadis Limited +# Subject: CN=QuoVadis Root CA 2 O=QuoVadis Limited +# Label: "QuoVadis Root CA 2" +# Serial: 1289 +# MD5 Fingerprint: 5e:39:7b:dd:f8:ba:ec:82:e9:ac:62:ba:0c:54:00:2b +# SHA1 Fingerprint: ca:3a:fb:cf:12:40:36:4b:44:b2:16:20:88:80:48:39:19:93:7c:f7 +# SHA256 Fingerprint: 85:a0:dd:7d:d7:20:ad:b7:ff:05:f8:3d:54:2b:20:9d:c7:ff:45:28:f7:d6:77:b1:83:89:fe:a5:e5:c4:9e:86 +-----BEGIN CERTIFICATE----- +MIIFtzCCA5+gAwIBAgICBQkwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0x +GTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJv +b3QgQ0EgMjAeFw0wNjExMjQxODI3MDBaFw0zMTExMjQxODIzMzNaMEUxCzAJBgNV +BAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9W +YWRpcyBSb290IENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCa +GMpLlA0ALa8DKYrwD4HIrkwZhR0In6spRIXzL4GtMh6QRr+jhiYaHv5+HBg6XJxg +Fyo6dIMzMH1hVBHL7avg5tKifvVrbxi3Cgst/ek+7wrGsxDp3MJGF/hd/aTa/55J +WpzmM+Yklvc/ulsrHHo1wtZn/qtmUIttKGAr79dgw8eTvI02kfN/+NsRE8Scd3bB +rrcCaoF6qUWD4gXmuVbBlDePSHFjIuwXZQeVikvfj8ZaCuWw419eaxGrDPmF60Tp ++ARz8un+XJiM9XOva7R+zdRcAitMOeGylZUtQofX1bOQQ7dsE/He3fbE+Ik/0XX1 +ksOR1YqI0JDs3G3eicJlcZaLDQP9nL9bFqyS2+r+eXyt66/3FsvbzSUr5R/7mp/i +Ucw6UwxI5g69ybR2BlLmEROFcmMDBOAENisgGQLodKcftslWZvB1JdxnwQ5hYIiz +PtGo/KPaHbDRsSNU30R2be1B2MGyIrZTHN81Hdyhdyox5C315eXbyOD/5YDXC2Og +/zOhD7osFRXql7PSorW+8oyWHhqPHWykYTe5hnMz15eWniN9gqRMgeKh0bpnX5UH +oycR7hYQe7xFSkyyBNKr79X9DFHOUGoIMfmR2gyPZFwDwzqLID9ujWc9Otb+fVuI +yV77zGHcizN300QyNQliBJIWENieJ0f7OyHj+OsdWwIDAQABo4GwMIGtMA8GA1Ud +EwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1UdDgQWBBQahGK8SEwzJQTU7tD2 +A8QZRtGUazBuBgNVHSMEZzBlgBQahGK8SEwzJQTU7tD2A8QZRtGUa6FJpEcwRTEL +MAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMT +ElF1b1ZhZGlzIFJvb3QgQ0EgMoICBQkwDQYJKoZIhvcNAQEFBQADggIBAD4KFk2f +BluornFdLwUvZ+YTRYPENvbzwCYMDbVHZF34tHLJRqUDGCdViXh9duqWNIAXINzn +g/iN/Ae42l9NLmeyhP3ZRPx3UIHmfLTJDQtyU/h2BwdBR5YM++CCJpNVjP4iH2Bl +fF/nJrP3MpCYUNQ3cVX2kiF495V5+vgtJodmVjB3pjd4M1IQWK4/YY7yarHvGH5K +WWPKjaJW1acvvFYfzznB4vsKqBUsfU16Y8Zsl0Q80m/DShcK+JDSV6IZUaUtl0Ha +B0+pUNqQjZRG4T7wlP0QADj1O+hA4bRuVhogzG9Yje0uRY/W6ZM/57Es3zrWIozc +hLsib9D45MY56QSIPMO661V6bYCZJPVsAfv4l7CUW+v90m/xd2gNNWQjrLhVoQPR +TUIZ3Ph1WVaj+ahJefivDrkRoHy3au000LYmYjgahwz46P0u05B/B5EqHdZ+XIWD +mbA4CD/pXvk1B+TJYm5Xf6dQlfe6yJvmjqIBxdZmv3lh8zwc4bmCXF2gw+nYSL0Z +ohEUGW6yhhtoPkg3Goi3XZZenMfvJ2II4pEZXNLxId26F0KCl3GBUzGpn/Z9Yr9y +4aOTHcyKJloJONDO1w2AFrR4pTqHTI2KpdVGl/IsELm8VCLAAVBpQ570su9t+Oza +8eOx79+Rj1QqCyXBJhnEUhAFZdWCEOrCMc0u +-----END CERTIFICATE----- + +# Issuer: CN=QuoVadis Root CA 3 O=QuoVadis Limited +# Subject: CN=QuoVadis Root CA 3 O=QuoVadis Limited +# Label: "QuoVadis Root CA 3" +# Serial: 1478 +# MD5 Fingerprint: 31:85:3c:62:94:97:63:b9:aa:fd:89:4e:af:6f:e0:cf +# SHA1 Fingerprint: 1f:49:14:f7:d8:74:95:1d:dd:ae:02:c0:be:fd:3a:2d:82:75:51:85 +# SHA256 Fingerprint: 18:f1:fc:7f:20:5d:f8:ad:dd:eb:7f:e0:07:dd:57:e3:af:37:5a:9c:4d:8d:73:54:6b:f4:f1:fe:d1:e1:8d:35 +-----BEGIN CERTIFICATE----- +MIIGnTCCBIWgAwIBAgICBcYwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0x +GTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJv +b3QgQ0EgMzAeFw0wNjExMjQxOTExMjNaFw0zMTExMjQxOTA2NDRaMEUxCzAJBgNV +BAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9W +YWRpcyBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDM +V0IWVJzmmNPTTe7+7cefQzlKZbPoFog02w1ZkXTPkrgEQK0CSzGrvI2RaNggDhoB +4hp7Thdd4oq3P5kazethq8Jlph+3t723j/z9cI8LoGe+AaJZz3HmDyl2/7FWeUUr +H556VOijKTVopAFPD6QuN+8bv+OPEKhyq1hX51SGyMnzW9os2l2ObjyjPtr7guXd +8lyyBTNvijbO0BNO/79KDDRMpsMhvVAEVeuxu537RR5kFd5VAYwCdrXLoT9Cabwv +vWhDFlaJKjdhkf2mrk7AyxRllDdLkgbvBNDInIjbC3uBr7E9KsRlOni27tyAsdLT +mZw67mtaa7ONt9XOnMK+pUsvFrGeaDsGb659n/je7Mwpp5ijJUMv7/FfJuGITfhe +btfZFG4ZM2mnO4SJk8RTVROhUXhA+LjJou57ulJCg54U7QVSWllWp5f8nT8KKdjc +T5EOE7zelaTfi5m+rJsziO+1ga8bxiJTyPbH7pcUsMV8eFLI8M5ud2CEpukqdiDt +WAEXMJPpGovgc2PZapKUSU60rUqFxKMiMPwJ7Wgic6aIDFUhWMXhOp8q3crhkODZ +c6tsgLjoC2SToJyMGf+z0gzskSaHirOi4XCPLArlzW1oUevaPwV/izLmE1xr/l9A +4iLItLRkT9a6fUg+qGkM17uGcclzuD87nSVL2v9A6wIDAQABo4IBlTCCAZEwDwYD +VR0TAQH/BAUwAwEB/zCB4QYDVR0gBIHZMIHWMIHTBgkrBgEEAb5YAAMwgcUwgZMG +CCsGAQUFBwICMIGGGoGDQW55IHVzZSBvZiB0aGlzIENlcnRpZmljYXRlIGNvbnN0 +aXR1dGVzIGFjY2VwdGFuY2Ugb2YgdGhlIFF1b1ZhZGlzIFJvb3QgQ0EgMyBDZXJ0 +aWZpY2F0ZSBQb2xpY3kgLyBDZXJ0aWZpY2F0aW9uIFByYWN0aWNlIFN0YXRlbWVu +dC4wLQYIKwYBBQUHAgEWIWh0dHA6Ly93d3cucXVvdmFkaXNnbG9iYWwuY29tL2Nw +czALBgNVHQ8EBAMCAQYwHQYDVR0OBBYEFPLAE+CCQz777i9nMpY1XNu4ywLQMG4G +A1UdIwRnMGWAFPLAE+CCQz777i9nMpY1XNu4ywLQoUmkRzBFMQswCQYDVQQGEwJC +TTEZMBcGA1UEChMQUXVvVmFkaXMgTGltaXRlZDEbMBkGA1UEAxMSUXVvVmFkaXMg +Um9vdCBDQSAzggIFxjANBgkqhkiG9w0BAQUFAAOCAgEAT62gLEz6wPJv92ZVqyM0 +7ucp2sNbtrCD2dDQ4iH782CnO11gUyeim/YIIirnv6By5ZwkajGxkHon24QRiSem +d1o417+shvzuXYO8BsbRd2sPbSQvS3pspweWyuOEn62Iix2rFo1bZhfZFvSLgNLd ++LJ2w/w4E6oM3kJpK27zPOuAJ9v1pkQNn1pVWQvVDVJIxa6f8i+AxeoyUDUSly7B +4f/xI4hROJ/yZlZ25w9Rl6VSDE1JUZU2Pb+iSwwQHYaZTKrzchGT5Or2m9qoXadN +t54CrnMAyNojA+j56hl0YgCUyyIgvpSnWbWCar6ZeXqp8kokUvd0/bpO5qgdAm6x +DYBEwa7TIzdfu4V8K5Iu6H6li92Z4b8nby1dqnuH/grdS/yO9SbkbnBCbjPsMZ57 +k8HkyWkaPcBrTiJt7qtYTcbQQcEr6k8Sh17rRdhs9ZgC06DYVYoGmRmioHfRMJ6s +zHXug/WwYjnPbFfiTNKRCw51KBuav/0aQ/HKd/s7j2G4aSgWQgRecCocIdiP4b0j +Wy10QJLZYxkNc91pvGJHvOB0K7Lrfb5BG7XARsWhIstfTsEokt4YutUqKLsRixeT +mJlglFwjz1onl14LBQaTNx47aTbrqZ5hHY8y2o4M1nQ+ewkk2gF3R8Q7zTSMmfXK +4SVhM7JZG+Ju1zdXtg2pEto= +-----END CERTIFICATE----- + +# Issuer: O=SECOM Trust.net OU=Security Communication RootCA1 +# Subject: O=SECOM Trust.net OU=Security Communication RootCA1 +# Label: "Security Communication Root CA" +# Serial: 0 +# MD5 Fingerprint: f1:bc:63:6a:54:e0:b5:27:f5:cd:e7:1a:e3:4d:6e:4a +# SHA1 Fingerprint: 36:b1:2b:49:f9:81:9e:d7:4c:9e:bc:38:0f:c6:56:8f:5d:ac:b2:f7 +# SHA256 Fingerprint: e7:5e:72:ed:9f:56:0e:ec:6e:b4:80:00:73:a4:3f:c3:ad:19:19:5a:39:22:82:01:78:95:97:4a:99:02:6b:6c +-----BEGIN CERTIFICATE----- +MIIDWjCCAkKgAwIBAgIBADANBgkqhkiG9w0BAQUFADBQMQswCQYDVQQGEwJKUDEY +MBYGA1UEChMPU0VDT00gVHJ1c3QubmV0MScwJQYDVQQLEx5TZWN1cml0eSBDb21t +dW5pY2F0aW9uIFJvb3RDQTEwHhcNMDMwOTMwMDQyMDQ5WhcNMjMwOTMwMDQyMDQ5 +WjBQMQswCQYDVQQGEwJKUDEYMBYGA1UEChMPU0VDT00gVHJ1c3QubmV0MScwJQYD +VQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTEwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQCzs/5/022x7xZ8V6UMbXaKL0u/ZPtM7orw8yl8 +9f/uKuDp6bpbZCKamm8sOiZpUQWZJtzVHGpxxpp9Hp3dfGzGjGdnSj74cbAZJ6kJ +DKaVv0uMDPpVmDvY6CKhS3E4eayXkmmziX7qIWgGmBSWh9JhNrxtJ1aeV+7AwFb9 +Ms+k2Y7CI9eNqPPYJayX5HA49LY6tJ07lyZDo6G8SVlyTCMwhwFY9k6+HGhWZq/N +QV3Is00qVUarH9oe4kA92819uZKAnDfdDJZkndwi92SL32HeFZRSFaB9UslLqCHJ +xrHty8OVYNEP8Ktw+N/LTX7s1vqr2b1/VPKl6Xn62dZ2JChzAgMBAAGjPzA9MB0G +A1UdDgQWBBSgc0mZaNyFW2XjmygvV5+9M7wHSDALBgNVHQ8EBAMCAQYwDwYDVR0T +AQH/BAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAaECpqLvkT115swW1F7NgE+vG +kl3g0dNq/vu+m22/xwVtWSDEHPC32oRYAmP6SBbvT6UL90qY8j+eG61Ha2POCEfr +Uj94nK9NrvjVT8+amCoQQTlSxN3Zmw7vkwGusi7KaEIkQmywszo+zenaSMQVy+n5 +Bw+SUEmK3TGXX8npN6o7WWWXlDLJs58+OmJYxUmtYg5xpTKqL8aJdkNAExNnPaJU +JRDL8Try2frbSVa7pv6nQTXD4IhhyYjH3zYQIphZ6rBK+1YWc26sTfcioU+tHXot +RSflMMFe8toTyyVCUZVHA4xsIcx0Qu1T/zOLjw9XARYvz6buyXAiFL39vmwLAw== +-----END CERTIFICATE----- + +# Issuer: CN=Sonera Class2 CA O=Sonera +# Subject: CN=Sonera Class2 CA O=Sonera +# Label: "Sonera Class 2 Root CA" +# Serial: 29 +# MD5 Fingerprint: a3:ec:75:0f:2e:88:df:fa:48:01:4e:0b:5c:48:6f:fb +# SHA1 Fingerprint: 37:f7:6d:e6:07:7c:90:c5:b1:3e:93:1a:b7:41:10:b4:f2:e4:9a:27 +# SHA256 Fingerprint: 79:08:b4:03:14:c1:38:10:0b:51:8d:07:35:80:7f:fb:fc:f8:51:8a:00:95:33:71:05:ba:38:6b:15:3d:d9:27 +-----BEGIN CERTIFICATE----- +MIIDIDCCAgigAwIBAgIBHTANBgkqhkiG9w0BAQUFADA5MQswCQYDVQQGEwJGSTEP +MA0GA1UEChMGU29uZXJhMRkwFwYDVQQDExBTb25lcmEgQ2xhc3MyIENBMB4XDTAx +MDQwNjA3Mjk0MFoXDTIxMDQwNjA3Mjk0MFowOTELMAkGA1UEBhMCRkkxDzANBgNV +BAoTBlNvbmVyYTEZMBcGA1UEAxMQU29uZXJhIENsYXNzMiBDQTCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAJAXSjWdyvANlsdE+hY3/Ei9vX+ALTU74W+o +Z6m/AxxNjG8yR9VBaKQTBME1DJqEQ/xcHf+Js+gXGM2RX/uJ4+q/Tl18GybTdXnt +5oTjV+WtKcT0OijnpXuENmmz/V52vaMtmdOQTiMofRhj8VQ7Jp12W5dCsv+u8E7s +3TmVToMGf+dJQMjFAbJUWmYdPfz56TwKnoG4cPABi+QjVHzIrviQHgCWctRUz2Ej +vOr7nQKV0ba5cTppCD8PtOFCx4j1P5iop7oc4HFx71hXgVB6XGt0Rg6DA5jDjqhu +8nYybieDwnPz3BjotJPqdURrBGAgcVeHnfO+oJAjPYok4doh28MCAwEAAaMzMDEw +DwYDVR0TAQH/BAUwAwEB/zARBgNVHQ4ECgQISqCqWITTXjwwCwYDVR0PBAQDAgEG +MA0GCSqGSIb3DQEBBQUAA4IBAQBazof5FnIVV0sd2ZvnoiYw7JNn39Yt0jSv9zil +zqsWuasvfDXLrNAPtEwr/IDva4yRXzZ299uzGxnq9LIR/WFxRL8oszodv7ND6J+/ +3DEIcbCdjdY0RzKQxmUk96BKfARzjzlvF4xytb1LyHr4e4PDKE6cCepnP7JnBBvD +FNr450kkkdAdavphOe9r5yF1BgfYErQhIHBCcYHaPJo2vqZbDWpsmh+Re/n570K6 +Tk6ezAyNlNzZRZxe7EJQY670XcSxEtzKO6gunRRaBXW37Ndj4ro1tgQIkejanZz2 +ZrUYrAqmVCY0M9IbwdR/GjqOC6oybtv8TyWf2TLHllpwrN9M +-----END CERTIFICATE----- + +# Issuer: CN=Staat der Nederlanden Root CA O=Staat der Nederlanden +# Subject: CN=Staat der Nederlanden Root CA O=Staat der Nederlanden +# Label: "Staat der Nederlanden Root CA" +# Serial: 10000010 +# MD5 Fingerprint: 60:84:7c:5a:ce:db:0c:d4:cb:a7:e9:fe:02:c6:a9:c0 +# SHA1 Fingerprint: 10:1d:fa:3f:d5:0b:cb:bb:9b:b5:60:0c:19:55:a4:1a:f4:73:3a:04 +# SHA256 Fingerprint: d4:1d:82:9e:8c:16:59:82:2a:f9:3f:ce:62:bf:fc:de:26:4f:c8:4e:8b:95:0c:5f:f2:75:d0:52:35:46:95:a3 +-----BEGIN CERTIFICATE----- +MIIDujCCAqKgAwIBAgIEAJiWijANBgkqhkiG9w0BAQUFADBVMQswCQYDVQQGEwJO +TDEeMBwGA1UEChMVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSYwJAYDVQQDEx1TdGFh +dCBkZXIgTmVkZXJsYW5kZW4gUm9vdCBDQTAeFw0wMjEyMTcwOTIzNDlaFw0xNTEy +MTYwOTE1MzhaMFUxCzAJBgNVBAYTAk5MMR4wHAYDVQQKExVTdGFhdCBkZXIgTmVk +ZXJsYW5kZW4xJjAkBgNVBAMTHVN0YWF0IGRlciBOZWRlcmxhbmRlbiBSb290IENB +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmNK1URF6gaYUmHFtvszn +ExvWJw56s2oYHLZhWtVhCb/ekBPHZ+7d89rFDBKeNVU+LCeIQGv33N0iYfXCxw71 +9tV2U02PjLwYdjeFnejKScfST5gTCaI+Ioicf9byEGW07l8Y1Rfj+MX94p2i71MO +hXeiD+EwR+4A5zN9RGcaC1Hoi6CeUJhoNFIfLm0B8mBF8jHrqTFoKbt6QZ7GGX+U +tFE5A3+y3qcym7RHjm+0Sq7lr7HcsBthvJly3uSJt3omXdozSVtSnA71iq3DuD3o +BmrC1SoLbHuEvVYFy4ZlkuxEK7COudxwC0barbxjiDn622r+I/q85Ej0ZytqERAh +SQIDAQABo4GRMIGOMAwGA1UdEwQFMAMBAf8wTwYDVR0gBEgwRjBEBgRVHSAAMDww +OgYIKwYBBQUHAgEWLmh0dHA6Ly93d3cucGtpb3ZlcmhlaWQubmwvcG9saWNpZXMv +cm9vdC1wb2xpY3kwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSofeu8Y6R0E3QA +7Jbg0zTBLL9s+DANBgkqhkiG9w0BAQUFAAOCAQEABYSHVXQ2YcG70dTGFagTtJ+k +/rvuFbQvBgwp8qiSpGEN/KtcCFtREytNwiphyPgJWPwtArI5fZlmgb9uXJVFIGzm +eafR2Bwp/MIgJ1HI8XxdNGdphREwxgDS1/PTfLbwMVcoEoJz6TMvplW0C5GUR5z6 +u3pCMuiufi3IvKwUv9kP2Vv8wfl6leF9fpb8cbDCTMjfRTTJzg3ynGQI0DvDKcWy +7ZAEwbEpkcUwb8GpcjPM/l0WFywRaed+/sWDCN+83CI6LiBpIzlWYGeQiy52OfsR +iJf2fL1LuCAWZwWN4jvBcj+UlTfHXbme2JOhF4//DGYVwSR8MnwDHTuhWEUykw== +-----END CERTIFICATE----- + +# Issuer: CN=UTN - DATACorp SGC O=The USERTRUST Network OU=http://www.usertrust.com +# Subject: CN=UTN - DATACorp SGC O=The USERTRUST Network OU=http://www.usertrust.com +# Label: "UTN DATACorp SGC Root CA" +# Serial: 91374294542884689855167577680241077609 +# MD5 Fingerprint: b3:a5:3e:77:21:6d:ac:4a:c0:c9:fb:d5:41:3d:ca:06 +# SHA1 Fingerprint: 58:11:9f:0e:12:82:87:ea:50:fd:d9:87:45:6f:4f:78:dc:fa:d6:d4 +# SHA256 Fingerprint: 85:fb:2f:91:dd:12:27:5a:01:45:b6:36:53:4f:84:02:4a:d6:8b:69:b8:ee:88:68:4f:f7:11:37:58:05:b3:48 +-----BEGIN CERTIFICATE----- +MIIEXjCCA0agAwIBAgIQRL4Mi1AAIbQR0ypoBqmtaTANBgkqhkiG9w0BAQUFADCB +kzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2Ug +Q2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExho +dHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xGzAZBgNVBAMTElVUTiAtIERBVEFDb3Jw +IFNHQzAeFw05OTA2MjQxODU3MjFaFw0xOTA2MjQxOTA2MzBaMIGTMQswCQYDVQQG +EwJVUzELMAkGA1UECBMCVVQxFzAVBgNVBAcTDlNhbHQgTGFrZSBDaXR5MR4wHAYD +VQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxITAfBgNVBAsTGGh0dHA6Ly93d3cu +dXNlcnRydXN0LmNvbTEbMBkGA1UEAxMSVVROIC0gREFUQUNvcnAgU0dDMIIBIjAN +BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3+5YEKIrblXEjr8uRgnn4AgPLit6 +E5Qbvfa2gI5lBZMAHryv4g+OGQ0SR+ysraP6LnD43m77VkIVni5c7yPeIbkFdicZ +D0/Ww5y0vpQZY/KmEQrrU0icvvIpOxboGqBMpsn0GFlowHDyUwDAXlCCpVZvNvlK +4ESGoE1O1kduSUrLZ9emxAW5jh70/P/N5zbgnAVssjMiFdC04MwXwLLA9P4yPykq +lXvY8qdOD1R8oQ2AswkDwf9c3V6aPryuvEeKaq5xyh+xKrhfQgUL7EYw0XILyulW +bfXv33i+Ybqypa4ETLyorGkVl73v67SMvzX41MPRKA5cOp9wGDMgd8SirwIDAQAB +o4GrMIGoMAsGA1UdDwQEAwIBxjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRT +MtGzz3/64PGgXYVOktKeRR20TzA9BgNVHR8ENjA0MDKgMKAuhixodHRwOi8vY3Js +LnVzZXJ0cnVzdC5jb20vVVROLURBVEFDb3JwU0dDLmNybDAqBgNVHSUEIzAhBggr +BgEFBQcDAQYKKwYBBAGCNwoDAwYJYIZIAYb4QgQBMA0GCSqGSIb3DQEBBQUAA4IB +AQAnNZcAiosovcYzMB4p/OL31ZjUQLtgyr+rFywJNn9Q+kHcrpY6CiM+iVnJowft +Gzet/Hy+UUla3joKVAgWRcKZsYfNjGjgaQPpxE6YsjuMFrMOoAyYUJuTqXAJyCyj +j98C5OBxOvG0I3KgqgHf35g+FFCgMSa9KOlaMCZ1+XtgHI3zzVAmbQQnmt/VDUVH +KWss5nbZqSl9Mt3JNjy9rjXxEZ4du5A/EkdOjtd+D2JzHVImOBwYSf0wdJrE5SIv +2MCN7ZF6TACPcn9d2t0bi0Vr591pl6jFVkwPDPafepE39peC4N1xaf92P2BNPM/3 +mfnGV/TJVTl4uix5yaaIK/QI +-----END CERTIFICATE----- + +# Issuer: CN=UTN-USERFirst-Hardware O=The USERTRUST Network OU=http://www.usertrust.com +# Subject: CN=UTN-USERFirst-Hardware O=The USERTRUST Network OU=http://www.usertrust.com +# Label: "UTN USERFirst Hardware Root CA" +# Serial: 91374294542884704022267039221184531197 +# MD5 Fingerprint: 4c:56:41:e5:0d:bb:2b:e8:ca:a3:ed:18:08:ad:43:39 +# SHA1 Fingerprint: 04:83:ed:33:99:ac:36:08:05:87:22:ed:bc:5e:46:00:e3:be:f9:d7 +# SHA256 Fingerprint: 6e:a5:47:41:d0:04:66:7e:ed:1b:48:16:63:4a:a3:a7:9e:6e:4b:96:95:0f:82:79:da:fc:8d:9b:d8:81:21:37 +-----BEGIN CERTIFICATE----- +MIIEdDCCA1ygAwIBAgIQRL4Mi1AAJLQR0zYq/mUK/TANBgkqhkiG9w0BAQUFADCB +lzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2Ug +Q2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExho +dHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3Qt +SGFyZHdhcmUwHhcNOTkwNzA5MTgxMDQyWhcNMTkwNzA5MTgxOTIyWjCBlzELMAkG +A1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2UgQ2l0eTEe +MBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExhodHRwOi8v +d3d3LnVzZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3QtSGFyZHdh +cmUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCx98M4P7Sof885glFn +0G2f0v9Y8+efK+wNiVSZuTiZFvfgIXlIwrthdBKWHTxqctU8EGc6Oe0rE81m65UJ +M6Rsl7HoxuzBdXmcRl6Nq9Bq/bkqVRcQVLMZ8Jr28bFdtqdt++BxF2uiiPsA3/4a +MXcMmgF6sTLjKwEHOG7DpV4jvEWbe1DByTCP2+UretNb+zNAHqDVmBe8i4fDidNd +oI6yqqr2jmmIBsX6iSHzCJ1pLgkzmykNRg+MzEk0sGlRvfkGzWitZky8PqxhvQqI +DsjfPe58BEydCl5rkdbux+0ojatNh4lz0G6k0B4WixThdkQDf2Os5M1JnMWS9Ksy +oUhbAgMBAAGjgbkwgbYwCwYDVR0PBAQDAgHGMA8GA1UdEwEB/wQFMAMBAf8wHQYD +VR0OBBYEFKFyXyYbKJhDlV0HN9WFlp1L0sNFMEQGA1UdHwQ9MDswOaA3oDWGM2h0 +dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9VVE4tVVNFUkZpcnN0LUhhcmR3YXJlLmNy +bDAxBgNVHSUEKjAoBggrBgEFBQcDAQYIKwYBBQUHAwUGCCsGAQUFBwMGBggrBgEF +BQcDBzANBgkqhkiG9w0BAQUFAAOCAQEARxkP3nTGmZev/K0oXnWO6y1n7k57K9cM +//bey1WiCuFMVGWTYGufEpytXoMs61quwOQt9ABjHbjAbPLPSbtNk28Gpgoiskli +CE7/yMgUsogWXecB5BKV5UU0s4tpvc+0hY91UZ59Ojg6FEgSxvunOxqNDYJAB+gE +CJChicsZUN/KHAG8HQQZexB2lzvukJDKxA4fFm517zP4029bHpbj4HR3dHuKom4t +3XbWOTCC8KucUvIqx69JXn7HaOWCgchqJ/kniCrVWFCVH/A7HFe7fRQ5YiuayZSS +KqMiDP+JJn1fIytH1xUdqWqeUQ0qUZ6B+dQ7XnASfxAynB67nfhmqA== +-----END CERTIFICATE----- + +# Issuer: CN=Chambers of Commerce Root O=AC Camerfirma SA CIF A82743287 OU=http://www.chambersign.org +# Subject: CN=Chambers of Commerce Root O=AC Camerfirma SA CIF A82743287 OU=http://www.chambersign.org +# Label: "Camerfirma Chambers of Commerce Root" +# Serial: 0 +# MD5 Fingerprint: b0:01:ee:14:d9:af:29:18:94:76:8e:f1:69:33:2a:84 +# SHA1 Fingerprint: 6e:3a:55:a4:19:0c:19:5c:93:84:3c:c0:db:72:2e:31:30:61:f0:b1 +# SHA256 Fingerprint: 0c:25:8a:12:a5:67:4a:ef:25:f2:8b:a7:dc:fa:ec:ee:a3:48:e5:41:e6:f5:cc:4e:e6:3b:71:b3:61:60:6a:c3 +-----BEGIN CERTIFICATE----- +MIIEvTCCA6WgAwIBAgIBADANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJFVTEn +MCUGA1UEChMeQUMgQ2FtZXJmaXJtYSBTQSBDSUYgQTgyNzQzMjg3MSMwIQYDVQQL +ExpodHRwOi8vd3d3LmNoYW1iZXJzaWduLm9yZzEiMCAGA1UEAxMZQ2hhbWJlcnMg +b2YgQ29tbWVyY2UgUm9vdDAeFw0wMzA5MzAxNjEzNDNaFw0zNzA5MzAxNjEzNDRa +MH8xCzAJBgNVBAYTAkVVMScwJQYDVQQKEx5BQyBDYW1lcmZpcm1hIFNBIENJRiBB +ODI3NDMyODcxIzAhBgNVBAsTGmh0dHA6Ly93d3cuY2hhbWJlcnNpZ24ub3JnMSIw +IAYDVQQDExlDaGFtYmVycyBvZiBDb21tZXJjZSBSb290MIIBIDANBgkqhkiG9w0B +AQEFAAOCAQ0AMIIBCAKCAQEAtzZV5aVdGDDg2olUkfzIx1L4L1DZ77F1c2VHfRtb +unXF/KGIJPov7coISjlUxFF6tdpg6jg8gbLL8bvZkSM/SAFwdakFKq0fcfPJVD0d +BmpAPrMMhe5cG3nCYsS4No41XQEMIwRHNaqbYE6gZj3LJgqcQKH0XZi/caulAGgq +7YN6D6IUtdQis4CwPAxaUWktWBiP7Zme8a7ileb2R6jWDA+wWFjbw2Y3npuRVDM3 +0pQcakjJyfKl2qUMI/cjDpwyVV5xnIQFUZot/eZOKjRa3spAN2cMVCFVd9oKDMyX +roDclDZK9D7ONhMeU+SsTjoF7Nuucpw4i9A5O4kKPnf+dQIBA6OCAUQwggFAMBIG +A1UdEwEB/wQIMAYBAf8CAQwwPAYDVR0fBDUwMzAxoC+gLYYraHR0cDovL2NybC5j +aGFtYmVyc2lnbi5vcmcvY2hhbWJlcnNyb290LmNybDAdBgNVHQ4EFgQU45T1sU3p +26EpW1eLTXYGduHRooowDgYDVR0PAQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIA +BzAnBgNVHREEIDAegRxjaGFtYmVyc3Jvb3RAY2hhbWJlcnNpZ24ub3JnMCcGA1Ud +EgQgMB6BHGNoYW1iZXJzcm9vdEBjaGFtYmVyc2lnbi5vcmcwWAYDVR0gBFEwTzBN +BgsrBgEEAYGHLgoDATA+MDwGCCsGAQUFBwIBFjBodHRwOi8vY3BzLmNoYW1iZXJz +aWduLm9yZy9jcHMvY2hhbWJlcnNyb290Lmh0bWwwDQYJKoZIhvcNAQEFBQADggEB +AAxBl8IahsAifJ/7kPMa0QOx7xP5IV8EnNrJpY0nbJaHkb5BkAFyk+cefV/2icZd +p0AJPaxJRUXcLo0waLIJuvvDL8y6C98/d3tGfToSJI6WjzwFCm/SlCgdbQzALogi +1djPHRPH8EjX1wWnz8dHnjs8NMiAT9QUu/wNUPf6s+xCX6ndbcj0dc97wXImsQEc +XCz9ek60AcUFV7nnPKoF2YjpB0ZBzu9Bga5Y34OirsrXdx/nADydb47kMgkdTXg0 +eDQ8lJsm7U9xxhl6vSAiSFr+S30Dt+dYvsYyTnQeaN2oaFuzPu5ifdmA6Ap1erfu +tGWaIZDgqtCYvDi1czyL+Nw= +-----END CERTIFICATE----- + +# Issuer: CN=Global Chambersign Root O=AC Camerfirma SA CIF A82743287 OU=http://www.chambersign.org +# Subject: CN=Global Chambersign Root O=AC Camerfirma SA CIF A82743287 OU=http://www.chambersign.org +# Label: "Camerfirma Global Chambersign Root" +# Serial: 0 +# MD5 Fingerprint: c5:e6:7b:bf:06:d0:4f:43:ed:c4:7a:65:8a:fb:6b:19 +# SHA1 Fingerprint: 33:9b:6b:14:50:24:9b:55:7a:01:87:72:84:d9:e0:2f:c3:d2:d8:e9 +# SHA256 Fingerprint: ef:3c:b4:17:fc:8e:bf:6f:97:87:6c:9e:4e:ce:39:de:1e:a5:fe:64:91:41:d1:02:8b:7d:11:c0:b2:29:8c:ed +-----BEGIN CERTIFICATE----- +MIIExTCCA62gAwIBAgIBADANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJFVTEn +MCUGA1UEChMeQUMgQ2FtZXJmaXJtYSBTQSBDSUYgQTgyNzQzMjg3MSMwIQYDVQQL +ExpodHRwOi8vd3d3LmNoYW1iZXJzaWduLm9yZzEgMB4GA1UEAxMXR2xvYmFsIENo +YW1iZXJzaWduIFJvb3QwHhcNMDMwOTMwMTYxNDE4WhcNMzcwOTMwMTYxNDE4WjB9 +MQswCQYDVQQGEwJFVTEnMCUGA1UEChMeQUMgQ2FtZXJmaXJtYSBTQSBDSUYgQTgy +NzQzMjg3MSMwIQYDVQQLExpodHRwOi8vd3d3LmNoYW1iZXJzaWduLm9yZzEgMB4G +A1UEAxMXR2xvYmFsIENoYW1iZXJzaWduIFJvb3QwggEgMA0GCSqGSIb3DQEBAQUA +A4IBDQAwggEIAoIBAQCicKLQn0KuWxfH2H3PFIP8T8mhtxOviteePgQKkotgVvq0 +Mi+ITaFgCPS3CU6gSS9J1tPfnZdan5QEcOw/Wdm3zGaLmFIoCQLfxS+EjXqXd7/s +QJ0lcqu1PzKY+7e3/HKE5TWH+VX6ox8Oby4o3Wmg2UIQxvi1RMLQQ3/bvOSiPGpV +eAp3qdjqGTK3L/5cPxvusZjsyq16aUXjlg9V9ubtdepl6DJWk0aJqCWKZQbua795 +B9Dxt6/tLE2Su8CoX6dnfQTyFQhwrJLWfQTSM/tMtgsL+xrJxI0DqX5c8lCrEqWh +z0hQpe/SyBoT+rB/sYIcd2oPX9wLlY/vQ37mRQklAgEDo4IBUDCCAUwwEgYDVR0T +AQH/BAgwBgEB/wIBDDA/BgNVHR8EODA2MDSgMqAwhi5odHRwOi8vY3JsLmNoYW1i +ZXJzaWduLm9yZy9jaGFtYmVyc2lnbnJvb3QuY3JsMB0GA1UdDgQWBBRDnDafsJ4w +TcbOX60Qq+UDpfqpFDAOBgNVHQ8BAf8EBAMCAQYwEQYJYIZIAYb4QgEBBAQDAgAH +MCoGA1UdEQQjMCGBH2NoYW1iZXJzaWducm9vdEBjaGFtYmVyc2lnbi5vcmcwKgYD +VR0SBCMwIYEfY2hhbWJlcnNpZ25yb290QGNoYW1iZXJzaWduLm9yZzBbBgNVHSAE +VDBSMFAGCysGAQQBgYcuCgEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly9jcHMuY2hh +bWJlcnNpZ24ub3JnL2Nwcy9jaGFtYmVyc2lnbnJvb3QuaHRtbDANBgkqhkiG9w0B +AQUFAAOCAQEAPDtwkfkEVCeR4e3t/mh/YV3lQWVPMvEYBZRqHN4fcNs+ezICNLUM +bKGKfKX0j//U2K0X1S0E0T9YgOKBWYi+wONGkyT+kL0mojAt6JcmVzWJdJYY9hXi +ryQZVgICsroPFOrGimbBhkVVi76SvpykBMdJPJ7oKXqJ1/6v/2j1pReQvayZzKWG +VwlnRtvWFsJG8eSpUPWP0ZIV018+xgBJOm5YstHRJw0lyDL4IBHNfTIzSJRUTN3c +ecQwn+uOuFW114hcxWokPbLTBQNRxgfvzBRydD1ucs4YKIxKoHflCStFREest2d/ +AYoFWpO+ocH/+OcOZ6RHSXZddZAa9SaP8A== +-----END CERTIFICATE----- + +# Issuer: CN=NetLock Kozjegyzoi (Class A) Tanusitvanykiado O=NetLock Halozatbiztonsagi Kft. OU=Tanusitvanykiadok +# Subject: CN=NetLock Kozjegyzoi (Class A) Tanusitvanykiado O=NetLock Halozatbiztonsagi Kft. OU=Tanusitvanykiadok +# Label: "NetLock Notary (Class A) Root" +# Serial: 259 +# MD5 Fingerprint: 86:38:6d:5e:49:63:6c:85:5c:db:6d:dc:94:b7:d0:f7 +# SHA1 Fingerprint: ac:ed:5f:65:53:fd:25:ce:01:5f:1f:7a:48:3b:6a:74:9f:61:78:c6 +# SHA256 Fingerprint: 7f:12:cd:5f:7e:5e:29:0e:c7:d8:51:79:d5:b7:2c:20:a5:be:75:08:ff:db:5b:f8:1a:b9:68:4a:7f:c9:f6:67 +-----BEGIN CERTIFICATE----- +MIIGfTCCBWWgAwIBAgICAQMwDQYJKoZIhvcNAQEEBQAwga8xCzAJBgNVBAYTAkhV +MRAwDgYDVQQIEwdIdW5nYXJ5MREwDwYDVQQHEwhCdWRhcGVzdDEnMCUGA1UEChMe +TmV0TG9jayBIYWxvemF0Yml6dG9uc2FnaSBLZnQuMRowGAYDVQQLExFUYW51c2l0 +dmFueWtpYWRvazE2MDQGA1UEAxMtTmV0TG9jayBLb3pqZWd5em9pIChDbGFzcyBB +KSBUYW51c2l0dmFueWtpYWRvMB4XDTk5MDIyNDIzMTQ0N1oXDTE5MDIxOTIzMTQ0 +N1owga8xCzAJBgNVBAYTAkhVMRAwDgYDVQQIEwdIdW5nYXJ5MREwDwYDVQQHEwhC +dWRhcGVzdDEnMCUGA1UEChMeTmV0TG9jayBIYWxvemF0Yml6dG9uc2FnaSBLZnQu +MRowGAYDVQQLExFUYW51c2l0dmFueWtpYWRvazE2MDQGA1UEAxMtTmV0TG9jayBL +b3pqZWd5em9pIChDbGFzcyBBKSBUYW51c2l0dmFueWtpYWRvMIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvHSMD7tM9DceqQWC2ObhbHDqeLVu0ThEDaiD +zl3S1tWBxdRL51uUcCbbO51qTGL3cfNk1mE7PetzozfZz+qMkjvN9wfcZnSX9EUi +3fRc4L9t875lM+QVOr/bmJBVOMTtplVjC7B4BPTjbsE/jvxReB+SnoPC/tmwqcm8 +WgD/qaiYdPv2LD4VOQ22BFWoDpggQrOxJa1+mm9dU7GrDPzr4PN6s6iz/0b2Y6LY +Oph7tqyF/7AlT3Rj5xMHpQqPBffAZG9+pyeAlt7ULoZgx2srXnN7F+eRP2QM2Esi +NCubMvJIH5+hCoR64sKtlz2O1cH5VqNQ6ca0+pii7pXmKgOM3wIDAQABo4ICnzCC +ApswDgYDVR0PAQH/BAQDAgAGMBIGA1UdEwEB/wQIMAYBAf8CAQQwEQYJYIZIAYb4 +QgEBBAQDAgAHMIICYAYJYIZIAYb4QgENBIICURaCAk1GSUdZRUxFTSEgRXplbiB0 +YW51c2l0dmFueSBhIE5ldExvY2sgS2Z0LiBBbHRhbGFub3MgU3pvbGdhbHRhdGFz +aSBGZWx0ZXRlbGVpYmVuIGxlaXJ0IGVsamFyYXNvayBhbGFwamFuIGtlc3p1bHQu +IEEgaGl0ZWxlc2l0ZXMgZm9seWFtYXRhdCBhIE5ldExvY2sgS2Z0LiB0ZXJtZWtm +ZWxlbG9zc2VnLWJpenRvc2l0YXNhIHZlZGkuIEEgZGlnaXRhbGlzIGFsYWlyYXMg +ZWxmb2dhZGFzYW5hayBmZWx0ZXRlbGUgYXogZWxvaXJ0IGVsbGVub3J6ZXNpIGVs +amFyYXMgbWVndGV0ZWxlLiBBeiBlbGphcmFzIGxlaXJhc2EgbWVndGFsYWxoYXRv +IGEgTmV0TG9jayBLZnQuIEludGVybmV0IGhvbmxhcGphbiBhIGh0dHBzOi8vd3d3 +Lm5ldGxvY2submV0L2RvY3MgY2ltZW4gdmFneSBrZXJoZXRvIGF6IGVsbGVub3J6 +ZXNAbmV0bG9jay5uZXQgZS1tYWlsIGNpbWVuLiBJTVBPUlRBTlQhIFRoZSBpc3N1 +YW5jZSBhbmQgdGhlIHVzZSBvZiB0aGlzIGNlcnRpZmljYXRlIGlzIHN1YmplY3Qg +dG8gdGhlIE5ldExvY2sgQ1BTIGF2YWlsYWJsZSBhdCBodHRwczovL3d3dy5uZXRs +b2NrLm5ldC9kb2NzIG9yIGJ5IGUtbWFpbCBhdCBjcHNAbmV0bG9jay5uZXQuMA0G +CSqGSIb3DQEBBAUAA4IBAQBIJEb3ulZv+sgoA0BO5TE5ayZrU3/b39/zcT0mwBQO +xmd7I6gMc90Bu8bKbjc5VdXHjFYgDigKDtIqpLBJUsY4B/6+CgmM0ZjPytoUMaFP +0jn8DxEsQ8Pdq5PHVT5HfBgaANzze9jyf1JsIPQLX2lS9O74silg6+NJMSEN1rUQ +QeJBCWziGppWS3cC9qCbmieH6FUpccKQn0V4GuEVZD3QDtigdp+uxdAu6tYPVuxk +f1qbFFgBJ34TUMdrKuZoPL9coAob4Q566eKAw+np9v1sEZ7Q5SgnK1QyQhSCdeZK +8CtmdWOMovsEPoMOmzbwGOQmIMOM8CgHrTwXZoi1/baI +-----END CERTIFICATE----- + +# Issuer: CN=XRamp Global Certification Authority O=XRamp Security Services Inc OU=www.xrampsecurity.com +# Subject: CN=XRamp Global Certification Authority O=XRamp Security Services Inc OU=www.xrampsecurity.com +# Label: "XRamp Global CA Root" +# Serial: 107108908803651509692980124233745014957 +# MD5 Fingerprint: a1:0b:44:b3:ca:10:d8:00:6e:9d:0f:d8:0f:92:0a:d1 +# SHA1 Fingerprint: b8:01:86:d1:eb:9c:86:a5:41:04:cf:30:54:f3:4c:52:b7:e5:58:c6 +# SHA256 Fingerprint: ce:cd:dc:90:50:99:d8:da:df:c5:b1:d2:09:b7:37:cb:e2:c1:8c:fb:2c:10:c0:ff:0b:cf:0d:32:86:fc:1a:a2 +-----BEGIN CERTIFICATE----- +MIIEMDCCAxigAwIBAgIQUJRs7Bjq1ZxN1ZfvdY+grTANBgkqhkiG9w0BAQUFADCB +gjELMAkGA1UEBhMCVVMxHjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEk +MCIGA1UEChMbWFJhbXAgU2VjdXJpdHkgU2VydmljZXMgSW5jMS0wKwYDVQQDEyRY +UmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQxMTAxMTcx +NDA0WhcNMzUwMTAxMDUzNzE5WjCBgjELMAkGA1UEBhMCVVMxHjAcBgNVBAsTFXd3 +dy54cmFtcHNlY3VyaXR5LmNvbTEkMCIGA1UEChMbWFJhbXAgU2VjdXJpdHkgU2Vy +dmljZXMgSW5jMS0wKwYDVQQDEyRYUmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBB +dXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCYJB69FbS6 +38eMpSe2OAtp87ZOqCwuIR1cRN8hXX4jdP5efrRKt6atH67gBhbim1vZZ3RrXYCP +KZ2GG9mcDZhtdhAoWORlsH9KmHmf4MMxfoArtYzAQDsRhtDLooY2YKTVMIJt2W7Q +DxIEM5dfT2Fa8OT5kavnHTu86M/0ay00fOJIYRyO82FEzG+gSqmUsE3a56k0enI4 +qEHMPJQRfevIpoy3hsvKMzvZPTeL+3o+hiznc9cKV6xkmxnr9A8ECIqsAxcZZPRa +JSKNNCyy9mgdEm3Tih4U2sSPpuIjhdV6Db1q4Ons7Be7QhtnqiXtRYMh/MHJfNVi +PvryxS3T/dRlAgMBAAGjgZ8wgZwwEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0P +BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFMZPoj0GY4QJnM5i5ASs +jVy16bYbMDYGA1UdHwQvMC0wK6ApoCeGJWh0dHA6Ly9jcmwueHJhbXBzZWN1cml0 +eS5jb20vWEdDQS5jcmwwEAYJKwYBBAGCNxUBBAMCAQEwDQYJKoZIhvcNAQEFBQAD +ggEBAJEVOQMBG2f7Shz5CmBbodpNl2L5JFMn14JkTpAuw0kbK5rc/Kh4ZzXxHfAR +vbdI4xD2Dd8/0sm2qlWkSLoC295ZLhVbO50WfUfXN+pfTXYSNrsf16GBBEYgoyxt +qZ4Bfj8pzgCT3/3JknOJiWSe5yvkHJEs0rnOfc5vMZnT5r7SHpDwCRR5XCOrTdLa +IR9NmXmd4c8nnxCbHIgNsIpkQTG4DmyQJKSbXHGPurt+HBvbaoAPIbzp26a3QPSy +i6mx5O+aGtA9aZnuqCij4Tyz8LIRnM98QObd50N9otg6tamN8jSZxNQQ4Qb9CYQQ +O+7ETPTsJ3xCwnR8gooJybQDJbw= +-----END CERTIFICATE----- + +# Issuer: O=The Go Daddy Group, Inc. OU=Go Daddy Class 2 Certification Authority +# Subject: O=The Go Daddy Group, Inc. OU=Go Daddy Class 2 Certification Authority +# Label: "Go Daddy Class 2 CA" +# Serial: 0 +# MD5 Fingerprint: 91:de:06:25:ab:da:fd:32:17:0c:bb:25:17:2a:84:67 +# SHA1 Fingerprint: 27:96:ba:e6:3f:18:01:e2:77:26:1b:a0:d7:77:70:02:8f:20:ee:e4 +# SHA256 Fingerprint: c3:84:6b:f2:4b:9e:93:ca:64:27:4c:0e:c6:7c:1e:cc:5e:02:4f:fc:ac:d2:d7:40:19:35:0e:81:fe:54:6a:e4 +-----BEGIN CERTIFICATE----- +MIIEADCCAuigAwIBAgIBADANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEh +MB8GA1UEChMYVGhlIEdvIERhZGR5IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBE +YWRkeSBDbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA0MDYyOTE3 +MDYyMFoXDTM0MDYyOTE3MDYyMFowYzELMAkGA1UEBhMCVVMxITAfBgNVBAoTGFRo +ZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28gRGFkZHkgQ2xhc3Mg +MiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQADggEN +ADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCA +PVYYYwhv2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6w +wdhFJ2+qN1j3hybX2C32qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXi +EqITLdiOr18SPaAIBQi2XKVlOARFmR6jYGB0xUGlcmIbYsUfb18aQr4CUWWoriMY +avx4A6lNf4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmYvLEHZ6IVDd2gWMZEewo+ +YihfukEHU1jPEX44dMX4/7VpkI+EdOqXG68CAQOjgcAwgb0wHQYDVR0OBBYEFNLE +sNKR1EwRcbNhyz2h/t2oatTjMIGNBgNVHSMEgYUwgYKAFNLEsNKR1EwRcbNhyz2h +/t2oatTjoWekZTBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYVGhlIEdvIERhZGR5 +IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRpZmlj +YXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQAD +ggEBADJL87LKPpH8EsahB4yOd6AzBhRckB4Y9wimPQoZ+YeAEW5p5JYXMP80kWNy +OO7MHAGjHZQopDH2esRU1/blMVgDoszOYtuURXO1v0XJJLXVggKtI3lpjbi2Tc7P +TMozI+gciKqdi0FuFskg5YmezTvacPd+mSYgFFQlq25zheabIZ0KbIIOqPjCDPoQ +HmyW74cNxA9hi63ugyuV+I6ShHI56yDqg+2DzZduCLzrTia2cyvk0/ZM/iZx4mER +dEr/VxqHD3VILs9RaRegAhJhldXRQLIQTO7ErBBDpqWeCtWVYpoNz4iCxTIM5Cuf +ReYNnyicsbkqWletNw+vHX/bvZ8= +-----END CERTIFICATE----- + +# Issuer: O=Starfield Technologies, Inc. OU=Starfield Class 2 Certification Authority +# Subject: O=Starfield Technologies, Inc. OU=Starfield Class 2 Certification Authority +# Label: "Starfield Class 2 CA" +# Serial: 0 +# MD5 Fingerprint: 32:4a:4b:bb:c8:63:69:9b:be:74:9a:c6:dd:1d:46:24 +# SHA1 Fingerprint: ad:7e:1c:28:b0:64:ef:8f:60:03:40:20:14:c3:d0:e3:37:0e:b5:8a +# SHA256 Fingerprint: 14:65:fa:20:53:97:b8:76:fa:a6:f0:a9:95:8e:55:90:e4:0f:cc:7f:aa:4f:b7:c2:c8:67:75:21:fb:5f:b6:58 +-----BEGIN CERTIFICATE----- +MIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzEl +MCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMp +U3RhcmZpZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQw +NjI5MTczOTE2WhcNMzQwNjI5MTczOTE2WjBoMQswCQYDVQQGEwJVUzElMCMGA1UE +ChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMpU3RhcmZp +ZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEgMA0GCSqGSIb3 +DQEBAQUAA4IBDQAwggEIAoIBAQC3Msj+6XGmBIWtDBFk385N78gDGIc/oav7PKaf +8MOh2tTYbitTkPskpD6E8J7oX+zlJ0T1KKY/e97gKvDIr1MvnsoFAZMej2YcOadN ++lq2cwQlZut3f+dZxkqZJRRU6ybH838Z1TBwj6+wRir/resp7defqgSHo9T5iaU0 +X9tDkYI22WY8sbi5gv2cOj4QyDvvBmVmepsZGD3/cVE8MC5fvj13c7JdBmzDI1aa +K4UmkhynArPkPw2vCHmCuDY96pzTNbO8acr1zJ3o/WSNF4Azbl5KXZnJHoe0nRrA +1W4TNSNe35tfPe/W93bC6j67eA0cQmdrBNj41tpvi/JEoAGrAgEDo4HFMIHCMB0G +A1UdDgQWBBS/X7fRzt0fhvRbVazc1xDCDqmI5zCBkgYDVR0jBIGKMIGHgBS/X7fR +zt0fhvRbVazc1xDCDqmI56FspGowaDELMAkGA1UEBhMCVVMxJTAjBgNVBAoTHFN0 +YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAsTKVN0YXJmaWVsZCBD +bGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8w +DQYJKoZIhvcNAQEFBQADggEBAAWdP4id0ckaVaGsafPzWdqbAYcaT1epoXkJKtv3 +L7IezMdeatiDh6GX70k1PncGQVhiv45YuApnP+yz3SFmH8lU+nLMPUxA2IGvd56D +eruix/U0F47ZEUD0/CwqTRV/p2JdLiXTAAsgGh1o+Re49L2L7ShZ3U0WixeDyLJl +xy16paq8U4Zt3VekyvggQQto8PT7dL5WXXp59fkdheMtlb71cZBDzI0fmgAKhynp +VSJYACPq4xJDKVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEY +WQPJIrSPnNVeKtelttQKbfi3QBFGmh95DmK/D5fs4C8fF5Q= +-----END CERTIFICATE----- + +# Issuer: CN=StartCom Certification Authority O=StartCom Ltd. OU=Secure Digital Certificate Signing +# Subject: CN=StartCom Certification Authority O=StartCom Ltd. OU=Secure Digital Certificate Signing +# Label: "StartCom Certification Authority" +# Serial: 1 +# MD5 Fingerprint: 22:4d:8f:8a:fc:f7:35:c2:bb:57:34:90:7b:8b:22:16 +# SHA1 Fingerprint: 3e:2b:f7:f2:03:1b:96:f3:8c:e6:c4:d8:a8:5d:3e:2d:58:47:6a:0f +# SHA256 Fingerprint: c7:66:a9:be:f2:d4:07:1c:86:3a:31:aa:49:20:e8:13:b2:d1:98:60:8c:b7:b7:cf:e2:11:43:b8:36:df:09:ea +-----BEGIN CERTIFICATE----- +MIIHyTCCBbGgAwIBAgIBATANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJJTDEW +MBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwg +Q2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkwHhcNMDYwOTE3MTk0NjM2WhcNMzYwOTE3MTk0NjM2WjB9 +MQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMi +U2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3Rh +cnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUA +A4ICDwAwggIKAoICAQDBiNsJvGxGfHiflXu1M5DycmLWwTYgIiRezul38kMKogZk +pMyONvg45iPwbm2xPN1yo4UcodM9tDMr0y+v/uqwQVlntsQGfQqedIXWeUyAN3rf +OQVSWff0G0ZDpNKFhdLDcfN1YjS6LIp/Ho/u7TTQEceWzVI9ujPW3U3eCztKS5/C +Ji/6tRYccjV3yjxd5srhJosaNnZcAdt0FCX+7bWgiA/deMotHweXMAEtcnn6RtYT +Kqi5pquDSR3l8u/d5AGOGAqPY1MWhWKpDhk6zLVmpsJrdAfkK+F2PrRt2PZE4XNi +HzvEvqBTViVsUQn3qqvKv3b9bZvzndu/PWa8DFaqr5hIlTpL36dYUNk4dalb6kMM +Av+Z6+hsTXBbKWWc3apdzK8BMewM69KN6Oqce+Zu9ydmDBpI125C4z/eIT574Q1w ++2OqqGwaVLRcJXrJosmLFqa7LH4XXgVNWG4SHQHuEhANxjJ/GP/89PrNbpHoNkm+ +Gkhpi8KWTRoSsmkXwQqQ1vp5Iki/untp+HDH+no32NgN0nZPV/+Qt+OR0t3vwmC3 +Zzrd/qqc8NSLf3Iizsafl7b4r4qgEKjZ+xjGtrVcUjyJthkqcwEKDwOzEmDyei+B +26Nu/yYwl/WL3YlXtq09s68rxbd2AvCl1iuahhQqcvbjM4xdCUsT37uMdBNSSwID +AQABo4ICUjCCAk4wDAYDVR0TBAUwAwEB/zALBgNVHQ8EBAMCAa4wHQYDVR0OBBYE +FE4L7xqkQFulF2mHMMo0aEPQQa7yMGQGA1UdHwRdMFswLKAqoCiGJmh0dHA6Ly9j +ZXJ0LnN0YXJ0Y29tLm9yZy9zZnNjYS1jcmwuY3JsMCugKaAnhiVodHRwOi8vY3Js +LnN0YXJ0Y29tLm9yZy9zZnNjYS1jcmwuY3JsMIIBXQYDVR0gBIIBVDCCAVAwggFM +BgsrBgEEAYG1NwEBATCCATswLwYIKwYBBQUHAgEWI2h0dHA6Ly9jZXJ0LnN0YXJ0 +Y29tLm9yZy9wb2xpY3kucGRmMDUGCCsGAQUFBwIBFilodHRwOi8vY2VydC5zdGFy +dGNvbS5vcmcvaW50ZXJtZWRpYXRlLnBkZjCB0AYIKwYBBQUHAgIwgcMwJxYgU3Rh +cnQgQ29tbWVyY2lhbCAoU3RhcnRDb20pIEx0ZC4wAwIBARqBl0xpbWl0ZWQgTGlh +YmlsaXR5LCByZWFkIHRoZSBzZWN0aW9uICpMZWdhbCBMaW1pdGF0aW9ucyogb2Yg +dGhlIFN0YXJ0Q29tIENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFBvbGljeSBhdmFp +bGFibGUgYXQgaHR0cDovL2NlcnQuc3RhcnRjb20ub3JnL3BvbGljeS5wZGYwEQYJ +YIZIAYb4QgEBBAQDAgAHMDgGCWCGSAGG+EIBDQQrFilTdGFydENvbSBGcmVlIFNT +TCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTANBgkqhkiG9w0BAQUFAAOCAgEAFmyZ +9GYMNPXQhV59CuzaEE44HF7fpiUFS5Eyweg78T3dRAlbB0mKKctmArexmvclmAk8 +jhvh3TaHK0u7aNM5Zj2gJsfyOZEdUauCe37Vzlrk4gNXcGmXCPleWKYK34wGmkUW +FjgKXlf2Ysd6AgXmvB618p70qSmD+LIU424oh0TDkBreOKk8rENNZEXO3SipXPJz +ewT4F+irsfMuXGRuczE6Eri8sxHkfY+BUZo7jYn0TZNmezwD7dOaHZrzZVD1oNB1 +ny+v8OqCQ5j4aZyJecRDjkZy42Q2Eq/3JR44iZB3fsNrarnDy0RLrHiQi+fHLB5L +EUTINFInzQpdn4XBidUaePKVEFMy3YCEZnXZtWgo+2EuvoSoOMCZEoalHmdkrQYu +L6lwhceWD3yJZfWOQ1QOq92lgDmUYMA0yZZwLKMS9R9Ie70cfmu3nZD0Ijuu+Pwq +yvqCUqDvr0tVk+vBtfAii6w0TiYiBKGHLHVKt+V9E9e4DGTANtLJL4YSjCMJwRuC +O3NJo2pXh5Tl1njFmUNj403gdy3hZZlyaQQaRwnmDwFWJPsfvw55qVguucQJAX6V +um0ABj6y6koQOdjQK/W/7HW/lwLFCRsI3FU34oH7N4RDYiDK51ZLZer+bMEkkySh +NOsF/5oirpt9P/FlUQqmMGqz9IgcgA38corog14= +-----END CERTIFICATE----- + +# Issuer: O=Government Root Certification Authority +# Subject: O=Government Root Certification Authority +# Label: "Taiwan GRCA" +# Serial: 42023070807708724159991140556527066870 +# MD5 Fingerprint: 37:85:44:53:32:45:1f:20:f0:f3:95:e1:25:c4:43:4e +# SHA1 Fingerprint: f4:8b:11:bf:de:ab:be:94:54:20:71:e6:41:de:6b:be:88:2b:40:b9 +# SHA256 Fingerprint: 76:00:29:5e:ef:e8:5b:9e:1f:d6:24:db:76:06:2a:aa:ae:59:81:8a:54:d2:77:4c:d4:c0:b2:c0:11:31:e1:b3 +-----BEGIN CERTIFICATE----- +MIIFcjCCA1qgAwIBAgIQH51ZWtcvwgZEpYAIaeNe9jANBgkqhkiG9w0BAQUFADA/ +MQswCQYDVQQGEwJUVzEwMC4GA1UECgwnR292ZXJubWVudCBSb290IENlcnRpZmlj +YXRpb24gQXV0aG9yaXR5MB4XDTAyMTIwNTEzMjMzM1oXDTMyMTIwNTEzMjMzM1ow +PzELMAkGA1UEBhMCVFcxMDAuBgNVBAoMJ0dvdmVybm1lbnQgUm9vdCBDZXJ0aWZp +Y2F0aW9uIEF1dGhvcml0eTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB +AJoluOzMonWoe/fOW1mKydGGEghU7Jzy50b2iPN86aXfTEc2pBsBHH8eV4qNw8XR +IePaJD9IK/ufLqGU5ywck9G/GwGHU5nOp/UKIXZ3/6m3xnOUT0b3EEk3+qhZSV1q +gQdW8or5BtD3cCJNtLdBuTK4sfCxw5w/cP1T3YGq2GN49thTbqGsaoQkclSGxtKy +yhwOeYHWtXBiCAEuTk8O1RGvqa/lmr/czIdtJuTJV6L7lvnM4T9TjGxMfptTCAts +F/tnyMKtsc2AtJfcdgEWFelq16TheEfOhtX7MfP6Mb40qij7cEwdScevLJ1tZqa2 +jWR+tSBqnTuBto9AAGdLiYa4zGX+FVPpBMHWXx1E1wovJ5pGfaENda1UhhXcSTvx +ls4Pm6Dso3pdvtUqdULle96ltqqvKKyskKw4t9VoNSZ63Pc78/1Fm9G7Q3hub/FC +VGqY8A2tl+lSXunVanLeavcbYBT0peS2cWeqH+riTcFCQP5nRhc4L0c/cZyu5SHK +YS1tB6iEfC3uUSXxY5Ce/eFXiGvviiNtsea9P63RPZYLhY3Naye7twWb7LuRqQoH +EgKXTiCQ8P8NHuJBO9NAOueNXdpm5AKwB1KYXA6OM5zCppX7VRluTI6uSw+9wThN +Xo+EHWbNxWCWtFJaBYmOlXqYwZE8lSOyDvR5tMl8wUohAgMBAAGjajBoMB0GA1Ud +DgQWBBTMzO/MKWCkO7GStjz6MmKPrCUVOzAMBgNVHRMEBTADAQH/MDkGBGcqBwAE +MTAvMC0CAQAwCQYFKw4DAhoFADAHBgVnKgMAAAQUA5vwIhP/lSg209yewDL7MTqK +UWUwDQYJKoZIhvcNAQEFBQADggIBAECASvomyc5eMN1PhnR2WPWus4MzeKR6dBcZ +TulStbngCnRiqmjKeKBMmo4sIy7VahIkv9Ro04rQ2JyftB8M3jh+Vzj8jeJPXgyf +qzvS/3WXy6TjZwj/5cAWtUgBfen5Cv8b5Wppv3ghqMKnI6mGq3ZW6A4M9hPdKmaK +ZEk9GhiHkASfQlK3T8v+R0F2Ne//AHY2RTKbxkaFXeIksB7jSJaYV0eUVXoPQbFE +JPPB/hprv4j9wabak2BegUqZIJxIZhm1AHlUD7gsL0u8qV1bYH+Mh6XgUmMqvtg7 +hUAV/h62ZT/FS9p+tXo1KaMuephgIqP0fSdOLeq0dDzpD6QzDxARvBMB1uUO07+1 +EqLhRSPAzAhuYbeJq4PjJB7mXQfnHyA+z2fI56wwbSdLaG5LKlwCCDTb+HbkZ6Mm +nD+iMsJKxYEYMRBWqoTvLQr/uB930r+lWKBi5NdLkXWNiYCYfm3LU05er/ayl4WX +udpVBrkk7tfGOB5jGxI7leFYrPLfhNVfmS8NVVvmONsuP3LpSIXLuykTjx44Vbnz +ssQwmSNOXfJIoRIM3BKQCZBUkQM8R+XVyWXgt0t97EfTsws+rZ7QdAAO671RrcDe +LMDDav7v3Aun+kbfYNucpllQdSNpc5Oy+fwC00fmcc4QAu4njIT/rEUNE1yDMuAl +pYYsfPQS +-----END CERTIFICATE----- + +# Issuer: CN=Swisscom Root CA 1 O=Swisscom OU=Digital Certificate Services +# Subject: CN=Swisscom Root CA 1 O=Swisscom OU=Digital Certificate Services +# Label: "Swisscom Root CA 1" +# Serial: 122348795730808398873664200247279986742 +# MD5 Fingerprint: f8:38:7c:77:88:df:2c:16:68:2e:c2:e2:52:4b:b8:f9 +# SHA1 Fingerprint: 5f:3a:fc:0a:8b:64:f6:86:67:34:74:df:7e:a9:a2:fe:f9:fa:7a:51 +# SHA256 Fingerprint: 21:db:20:12:36:60:bb:2e:d4:18:20:5d:a1:1e:e7:a8:5a:65:e2:bc:6e:55:b5:af:7e:78:99:c8:a2:66:d9:2e +-----BEGIN CERTIFICATE----- +MIIF2TCCA8GgAwIBAgIQXAuFXAvnWUHfV8w/f52oNjANBgkqhkiG9w0BAQUFADBk +MQswCQYDVQQGEwJjaDERMA8GA1UEChMIU3dpc3Njb20xJTAjBgNVBAsTHERpZ2l0 +YWwgQ2VydGlmaWNhdGUgU2VydmljZXMxGzAZBgNVBAMTElN3aXNzY29tIFJvb3Qg +Q0EgMTAeFw0wNTA4MTgxMjA2MjBaFw0yNTA4MTgyMjA2MjBaMGQxCzAJBgNVBAYT +AmNoMREwDwYDVQQKEwhTd2lzc2NvbTElMCMGA1UECxMcRGlnaXRhbCBDZXJ0aWZp +Y2F0ZSBTZXJ2aWNlczEbMBkGA1UEAxMSU3dpc3Njb20gUm9vdCBDQSAxMIICIjAN +BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA0LmwqAzZuz8h+BvVM5OAFmUgdbI9 +m2BtRsiMMW8Xw/qabFbtPMWRV8PNq5ZJkCoZSx6jbVfd8StiKHVFXqrWW/oLJdih +FvkcxC7mlSpnzNApbjyFNDhhSbEAn9Y6cV9Nbc5fuankiX9qUvrKm/LcqfmdmUc/ +TilftKaNXXsLmREDA/7n29uj/x2lzZAeAR81sH8A25Bvxn570e56eqeqDFdvpG3F +EzuwpdntMhy0XmeLVNxzh+XTF3xmUHJd1BpYwdnP2IkCb6dJtDZd0KTeByy2dbco +kdaXvij1mB7qWybJvbCXc9qukSbraMH5ORXWZ0sKbU/Lz7DkQnGMU3nn7uHbHaBu +HYwadzVcFh4rUx80i9Fs/PJnB3r1re3WmquhsUvhzDdf/X/NTa64H5xD+SpYVUNF +vJbNcA78yeNmuk6NO4HLFWR7uZToXTNShXEuT46iBhFRyePLoW4xCGQMwtI89Tbo +19AOeCMgkckkKmUpWyL3Ic6DXqTz3kvTaI9GdVyDCW4pa8RwjPWd1yAv/0bSKzjC +L3UcPX7ape8eYIVpQtPM+GP+HkM5haa2Y0EQs3MevNP6yn0WR+Kn1dCjigoIlmJW +bjTb2QK5MHXjBNLnj8KwEUAKrNVxAmKLMb7dxiNYMUJDLXT5xp6mig/p/r+D5kNX +JLrvRjSq1xIBOO0CAwEAAaOBhjCBgzAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0hBBYw +FDASBgdghXQBUwABBgdghXQBUwABMBIGA1UdEwEB/wQIMAYBAf8CAQcwHwYDVR0j +BBgwFoAUAyUv3m+CATpcLNwroWm1Z9SM0/0wHQYDVR0OBBYEFAMlL95vggE6XCzc +K6FptWfUjNP9MA0GCSqGSIb3DQEBBQUAA4ICAQA1EMvspgQNDQ/NwNurqPKIlwzf +ky9NfEBWMXrrpA9gzXrzvsMnjgM+pN0S734edAY8PzHyHHuRMSG08NBsl9Tpl7Ik +Vh5WwzW9iAUPWxAaZOHHgjD5Mq2eUCzneAXQMbFamIp1TpBcahQq4FJHgmDmHtqB +sfsUC1rxn9KVuj7QG9YVHaO+htXbD8BJZLsuUBlL0iT43R4HVtA4oJVwIHaM190e +3p9xxCPvgxNcoyQVTSlAPGrEqdi3pkSlDfTgnXceQHAm/NrZNuR55LU/vJtlvrsR +ls/bxig5OgjOR1tTWsWZ/l2p3e9M1MalrQLmjAcSHm8D0W+go/MpvRLHUKKwf4ip +mXeascClOS5cfGniLLDqN2qk4Vrh9VDlg++luyqI54zb/W1elxmofmZ1a3Hqv7HH +b6D0jqTsNFFbjCYDcKF31QESVwA12yPeDooomf2xEG9L/zgtYE4snOtnta1J7ksf +rK/7DZBaZmBwXarNeNQk7shBoJMBkpxqnvy5JMWzFYJ+vq6VK+uxwNrjAWALXmms +hFZhvnEX/h0TD/7Gh0Xp/jKgGg0TpJRVcaUWi7rKibCyx/yP2FS1k2Kdzs9Z+z0Y +zirLNRWCXf9UIltxUvu3yf5gmwBBZPCqKuy2QkPOiWaByIufOVQDJdMWNY6E0F/6 +MBr1mmz0DlP5OlvRHA== +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Assured ID Root CA O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Assured ID Root CA O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Assured ID Root CA" +# Serial: 17154717934120587862167794914071425081 +# MD5 Fingerprint: 87:ce:0b:7b:2a:0e:49:00:e1:58:71:9b:37:a8:93:72 +# SHA1 Fingerprint: 05:63:b8:63:0d:62:d7:5a:bb:c8:ab:1e:4b:df:b5:a8:99:b2:4d:43 +# SHA256 Fingerprint: 3e:90:99:b5:01:5e:8f:48:6c:00:bc:ea:9d:11:1e:e7:21:fa:ba:35:5a:89:bc:f1:df:69:56:1e:3d:c6:32:5c +-----BEGIN CERTIFICATE----- +MIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0BAQUFADBl +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv +b3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMzExMTEwMDAwMDAwWjBlMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl +cnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0EwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtDhXO5EOAXLGH87dg+XESpa7c +JpSIqvTO9SA5KFhgDPiA2qkVlTJhPLWxKISKityfCgyDF3qPkKyK53lTXDGEKvYP +mDI2dsze3Tyoou9q+yHyUmHfnyDXH+Kx2f4YZNISW1/5WBg1vEfNoTb5a3/UsDg+ +wRvDjDPZ2C8Y/igPs6eD1sNuRMBhNZYW/lmci3Zt1/GiSw0r/wty2p5g0I6QNcZ4 +VYcgoc/lbQrISXwxmDNsIumH0DJaoroTghHtORedmTpyoeb6pNnVFzF1roV9Iq4/ +AUaG9ih5yLHa5FcXxH4cDrC0kqZWs72yl+2qp/C3xag/lRbQ/6GW6whfGHdPAgMB +AAGjYzBhMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW +BBRF66Kv9JLLgjEtUYunpyGd823IDzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYun +pyGd823IDzANBgkqhkiG9w0BAQUFAAOCAQEAog683+Lt8ONyc3pklL/3cmbYMuRC +dWKuh+vy1dneVrOfzM4UKLkNl2BcEkxY5NM9g0lFWJc1aRqoR+pWxnmrEthngYTf +fwk8lOa4JiwgvT2zKIn3X/8i4peEH+ll74fg38FnSbNd67IJKusm7Xi+fT8r87cm +NW1fiQG2SVufAQWbqz0lwcy2f8Lxb4bG+mRo64EtlOtCt/qMHt1i8b5QZ7dsvfPx +H2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu838fYxAe ++o0bJW1sj6W3YQGx0qMmoRBxna3iw/nDmVG3KwcIzi7mULKn+gpFL6Lw8g== +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Global Root CA O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Global Root CA O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Global Root CA" +# Serial: 10944719598952040374951832963794454346 +# MD5 Fingerprint: 79:e4:a9:84:0d:7d:3a:96:d7:c0:4f:e2:43:4c:89:2e +# SHA1 Fingerprint: a8:98:5d:3a:65:e5:e5:c4:b2:d7:d6:6d:40:c6:dd:2f:b1:9c:54:36 +# SHA256 Fingerprint: 43:48:a0:e9:44:4c:78:cb:26:5e:05:8d:5e:89:44:b4:d8:4f:96:62:bd:26:db:25:7f:89:34:a4:43:c7:01:61 +-----BEGIN CERTIFICATE----- +MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD +QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT +MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j +b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB +CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97 +nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt +43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P +T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4 +gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO +BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR +TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw +DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr +hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg +06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF +PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls +YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk +CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4= +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert High Assurance EV Root CA O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert High Assurance EV Root CA O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert High Assurance EV Root CA" +# Serial: 3553400076410547919724730734378100087 +# MD5 Fingerprint: d4:74:de:57:5c:39:b2:d3:9c:85:83:c5:c0:65:49:8a +# SHA1 Fingerprint: 5f:b7:ee:06:33:e2:59:db:ad:0c:4c:9a:e6:d3:8f:1a:61:c7:dc:25 +# SHA256 Fingerprint: 74:31:e5:f4:c3:c1:ce:46:90:77:4f:0b:61:e0:54:40:88:3b:a9:a0:1e:d0:0b:a6:ab:d7:80:6e:d3:b1:18:cf +-----BEGIN CERTIFICATE----- +MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j +ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL +MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3 +LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug +RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm ++9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW +PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM +xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB +Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3 +hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg +EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF +MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA +FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec +nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z +eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF +hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2 +Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe +vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep ++OkuE6N36B9K +-----END CERTIFICATE----- + +# Issuer: CN=Class 2 Primary CA O=Certplus +# Subject: CN=Class 2 Primary CA O=Certplus +# Label: "Certplus Class 2 Primary CA" +# Serial: 177770208045934040241468760488327595043 +# MD5 Fingerprint: 88:2c:8c:52:b8:a2:3c:f3:f7:bb:03:ea:ae:ac:42:0b +# SHA1 Fingerprint: 74:20:74:41:72:9c:dd:92:ec:79:31:d8:23:10:8d:c2:81:92:e2:bb +# SHA256 Fingerprint: 0f:99:3c:8a:ef:97:ba:af:56:87:14:0e:d5:9a:d1:82:1b:b4:af:ac:f0:aa:9a:58:b5:d5:7a:33:8a:3a:fb:cb +-----BEGIN CERTIFICATE----- +MIIDkjCCAnqgAwIBAgIRAIW9S/PY2uNp9pTXX8OlRCMwDQYJKoZIhvcNAQEFBQAw +PTELMAkGA1UEBhMCRlIxETAPBgNVBAoTCENlcnRwbHVzMRswGQYDVQQDExJDbGFz +cyAyIFByaW1hcnkgQ0EwHhcNOTkwNzA3MTcwNTAwWhcNMTkwNzA2MjM1OTU5WjA9 +MQswCQYDVQQGEwJGUjERMA8GA1UEChMIQ2VydHBsdXMxGzAZBgNVBAMTEkNsYXNz +IDIgUHJpbWFyeSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANxQ +ltAS+DXSCHh6tlJw/W/uz7kRy1134ezpfgSN1sxvc0NXYKwzCkTsA18cgCSR5aiR +VhKC9+Ar9NuuYS6JEI1rbLqzAr3VNsVINyPi8Fo3UjMXEuLRYE2+L0ER4/YXJQyL +kcAbmXuZVg2v7tK8R1fjeUl7NIknJITesezpWE7+Tt9avkGtrAjFGA7v0lPubNCd +EgETjdyAYveVqUSISnFOYFWe2yMZeVYHDD9jC1yw4r5+FfyUM1hBOHTE4Y+L3yas +H7WLO7dDWWuwJKZtkIvEcupdM5i3y95ee++U8Rs+yskhwcWYAqqi9lt3m/V+llU0 +HGdpwPFC40es/CgcZlUCAwEAAaOBjDCBiTAPBgNVHRMECDAGAQH/AgEKMAsGA1Ud +DwQEAwIBBjAdBgNVHQ4EFgQU43Mt38sOKAze3bOkynm4jrvoMIkwEQYJYIZIAYb4 +QgEBBAQDAgEGMDcGA1UdHwQwMC4wLKAqoCiGJmh0dHA6Ly93d3cuY2VydHBsdXMu +Y29tL0NSTC9jbGFzczIuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQCnVM+IRBnL39R/ +AN9WM2K191EBkOvDP9GIROkkXe/nFL0gt5o8AP5tn9uQ3Nf0YtaLcF3n5QRIqWh8 +yfFC82x/xXp8HVGIutIKPidd3i1RTtMTZGnkLuPT55sJmabglZvOGtd/vjzOUrMR +FcEPF80Du5wlFbqidon8BvEY0JNLDnyCt6X09l/+7UCmnYR0ObncHoUW2ikbhiMA +ybuJfm6AiB4vFLQDJKgybwOaRywwvlbGp0ICcBvqQNi6BQNwB6SW//1IMwrh3KWB +kJtN3X3n57LNXMhqlfil9o3EXXgIvnsG1knPGTZQIy4I5p4FTUcY1Rbpsda2ENW7 +l7+ijrRU +-----END CERTIFICATE----- + +# Issuer: CN=DST Root CA X3 O=Digital Signature Trust Co. +# Subject: CN=DST Root CA X3 O=Digital Signature Trust Co. +# Label: "DST Root CA X3" +# Serial: 91299735575339953335919266965803778155 +# MD5 Fingerprint: 41:03:52:dc:0f:f7:50:1b:16:f0:02:8e:ba:6f:45:c5 +# SHA1 Fingerprint: da:c9:02:4f:54:d8:f6:df:94:93:5f:b1:73:26:38:ca:6a:d7:7c:13 +# SHA256 Fingerprint: 06:87:26:03:31:a7:24:03:d9:09:f1:05:e6:9b:cf:0d:32:e1:bd:24:93:ff:c6:d9:20:6d:11:bc:d6:77:07:39 +-----BEGIN CERTIFICATE----- +MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/ +MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT +DkRTVCBSb290IENBIFgzMB4XDTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVow +PzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQD +Ew5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +AN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmTrE4O +rz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEq +OLl5CjH9UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9b +xiqKqy69cK3FCxolkHRyxXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw +7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40dutolucbY38EVAjqr2m7xPi71XAicPNaD +aeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV +HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQMA0GCSqG +SIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69 +ikugdB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXr +AvHRAosZy5Q6XkjEGB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZz +R8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5 +JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYo +Ob8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ +-----END CERTIFICATE----- + +# Issuer: CN=DST ACES CA X6 O=Digital Signature Trust OU=DST ACES +# Subject: CN=DST ACES CA X6 O=Digital Signature Trust OU=DST ACES +# Label: "DST ACES CA X6" +# Serial: 17771143917277623872238992636097467865 +# MD5 Fingerprint: 21:d8:4c:82:2b:99:09:33:a2:eb:14:24:8d:8e:5f:e8 +# SHA1 Fingerprint: 40:54:da:6f:1c:3f:40:74:ac:ed:0f:ec:cd:db:79:d1:53:fb:90:1d +# SHA256 Fingerprint: 76:7c:95:5a:76:41:2c:89:af:68:8e:90:a1:c7:0f:55:6c:fd:6b:60:25:db:ea:10:41:6d:7e:b6:83:1f:8c:40 +-----BEGIN CERTIFICATE----- +MIIECTCCAvGgAwIBAgIQDV6ZCtadt3js2AdWO4YV2TANBgkqhkiG9w0BAQUFADBb +MQswCQYDVQQGEwJVUzEgMB4GA1UEChMXRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3Qx +ETAPBgNVBAsTCERTVCBBQ0VTMRcwFQYDVQQDEw5EU1QgQUNFUyBDQSBYNjAeFw0w +MzExMjAyMTE5NThaFw0xNzExMjAyMTE5NThaMFsxCzAJBgNVBAYTAlVTMSAwHgYD +VQQKExdEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdDERMA8GA1UECxMIRFNUIEFDRVMx +FzAVBgNVBAMTDkRTVCBBQ0VTIENBIFg2MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEAuT31LMmU3HWKlV1j6IR3dma5WZFcRt2SPp/5DgO0PWGSvSMmtWPu +ktKe1jzIDZBfZIGxqAgNTNj50wUoUrQBJcWVHAx+PhCEdc/BGZFjz+iokYi5Q1K7 +gLFViYsx+tC3dr5BPTCapCIlF3PoHuLTrCq9Wzgh1SpL11V94zpVvddtawJXa+ZH +fAjIgrrep4c9oW24MFbCswKBXy314powGCi4ZtPLAZZv6opFVdbgnf9nKxcCpk4a +ahELfrd755jWjHZvwTvbUJN+5dCOHze4vbrGn2zpfDPyMjwmR/onJALJfh1biEIT +ajV8fTXpLmaRcpPVMibEdPVTo7NdmvYJywIDAQABo4HIMIHFMA8GA1UdEwEB/wQF +MAMBAf8wDgYDVR0PAQH/BAQDAgHGMB8GA1UdEQQYMBaBFHBraS1vcHNAdHJ1c3Rk +c3QuY29tMGIGA1UdIARbMFkwVwYKYIZIAWUDAgEBATBJMEcGCCsGAQUFBwIBFjto +dHRwOi8vd3d3LnRydXN0ZHN0LmNvbS9jZXJ0aWZpY2F0ZXMvcG9saWN5L0FDRVMt +aW5kZXguaHRtbDAdBgNVHQ4EFgQUCXIGThhDD+XWzMNqizF7eI+og7gwDQYJKoZI +hvcNAQEFBQADggEBAKPYjtay284F5zLNAdMEA+V25FYrnJmQ6AgwbN99Pe7lv7Uk +QIRJ4dEorsTCOlMwiPH1d25Ryvr/ma8kXxug/fKshMrfqfBfBC6tFr8hlxCBPeP/ +h40y3JTlR4peahPJlJU90u7INJXQgNStMgiAVDzgvVJT11J8smk/f3rPanTK+gQq +nExaBqXpIK1FZg9p8d2/6eMyi/rgwYZNcjwu2JN4Cir42NInPRmJX1p7ijvMDNpR +rscL9yuwNwXsvFcj4jjSm2jzVhKIT0J8uDHEtdvkyCE06UgRNe76x5JXxZ805Mf2 +9w4LTJxoeHtxMcfrHuBnQfO3oKfN5XozNmr6mis= +-----END CERTIFICATE----- + +# Issuer: CN=TÜRKTRUST Elektronik Sertifika Hizmet Sağlayıcısı O=TÜRKTRUST Bilgi İletişim ve Bilişim Güvenliği Hizmetleri A.Ş. (c) Kasım 2005 +# Subject: CN=TÜRKTRUST Elektronik Sertifika Hizmet Sağlayıcısı O=TÜRKTRUST Bilgi İletişim ve Bilişim Güvenliği Hizmetleri A.Ş. (c) Kasım 2005 +# Label: "TURKTRUST Certificate Services Provider Root 2" +# Serial: 1 +# MD5 Fingerprint: 37:a5:6e:d4:b1:25:84:97:b7:fd:56:15:7a:f9:a2:00 +# SHA1 Fingerprint: b4:35:d4:e1:11:9d:1c:66:90:a7:49:eb:b3:94:bd:63:7b:a7:82:b7 +# SHA256 Fingerprint: c4:70:cf:54:7e:23:02:b9:77:fb:29:dd:71:a8:9a:7b:6c:1f:60:77:7b:03:29:f5:60:17:f3:28:bf:4f:6b:e6 +-----BEGIN CERTIFICATE----- +MIIEPDCCAySgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBvjE/MD0GA1UEAww2VMOc +UktUUlVTVCBFbGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sx +c8SxMQswCQYDVQQGEwJUUjEPMA0GA1UEBwwGQW5rYXJhMV0wWwYDVQQKDFRUw5xS +S1RSVVNUIEJpbGdpIMSwbGV0acWfaW0gdmUgQmlsacWfaW0gR8O8dmVubGnEn2kg +SGl6bWV0bGVyaSBBLsWeLiAoYykgS2FzxLFtIDIwMDUwHhcNMDUxMTA3MTAwNzU3 +WhcNMTUwOTE2MTAwNzU3WjCBvjE/MD0GA1UEAww2VMOcUktUUlVTVCBFbGVrdHJv +bmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxMQswCQYDVQQGEwJU +UjEPMA0GA1UEBwwGQW5rYXJhMV0wWwYDVQQKDFRUw5xSS1RSVVNUIEJpbGdpIMSw +bGV0acWfaW0gdmUgQmlsacWfaW0gR8O8dmVubGnEn2kgSGl6bWV0bGVyaSBBLsWe +LiAoYykgS2FzxLFtIDIwMDUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB +AQCpNn7DkUNMwxmYCMjHWHtPFoylzkkBH3MOrHUTpvqeLCDe2JAOCtFp0if7qnef +J1Il4std2NiDUBd9irWCPwSOtNXwSadktx4uXyCcUHVPr+G1QRT0mJKIx+XlZEdh +R3n9wFHxwZnn3M5q+6+1ATDcRhzviuyV79z/rxAc653YsKpqhRgNF8k+v/Gb0AmJ +Qv2gQrSdiVFVKc8bcLyEVK3BEx+Y9C52YItdP5qtygy/p1Zbj3e41Z55SZI/4PGX +JHpsmxcPbe9TmJEr5A++WXkHeLuXlfSfadRYhwqp48y2WBmfJiGxxFmNskF1wK1p +zpwACPI2/z7woQ8arBT9pmAPAgMBAAGjQzBBMB0GA1UdDgQWBBTZN7NOBf3Zz58S +Fq62iS/rJTqIHDAPBgNVHQ8BAf8EBQMDBwYAMA8GA1UdEwEB/wQFMAMBAf8wDQYJ +KoZIhvcNAQEFBQADggEBAHJglrfJ3NgpXiOFX7KzLXb7iNcX/nttRbj2hWyfIvwq +ECLsqrkw9qtY1jkQMZkpAL2JZkH7dN6RwRgLn7Vhy506vvWolKMiVW4XSf/SKfE4 +Jl3vpao6+XF75tpYHdN0wgH6PmlYX63LaL4ULptswLbcoCb6dxriJNoaN+BnrdFz +gw2lGh1uEpJ+hGIAF728JRhX8tepb1mIvDS3LoV4nZbcFMMsilKbloxSZj2GFotH +uFEJjOp9zYhys2AzsfAKRO8P9Qk3iCQOLGsgOqL6EfJANZxEaGM7rDNvY7wsu/LS +y3Z9fYjYHcgFHW68lKlmjHdxx/qR+i9Rnuk5UrbnBEI= +-----END CERTIFICATE----- + +# Issuer: CN=SwissSign Gold CA - G2 O=SwissSign AG +# Subject: CN=SwissSign Gold CA - G2 O=SwissSign AG +# Label: "SwissSign Gold CA - G2" +# Serial: 13492815561806991280 +# MD5 Fingerprint: 24:77:d9:a8:91:d1:3b:fa:88:2d:c2:ff:f8:cd:33:93 +# SHA1 Fingerprint: d8:c5:38:8a:b7:30:1b:1b:6e:d4:7a:e6:45:25:3a:6f:9f:1a:27:61 +# SHA256 Fingerprint: 62:dd:0b:e9:b9:f5:0a:16:3e:a0:f8:e7:5c:05:3b:1e:ca:57:ea:55:c8:68:8f:64:7c:68:81:f2:c8:35:7b:95 +-----BEGIN CERTIFICATE----- +MIIFujCCA6KgAwIBAgIJALtAHEP1Xk+wMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV +BAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxHzAdBgNVBAMTFlN3aXNzU2ln +biBHb2xkIENBIC0gRzIwHhcNMDYxMDI1MDgzMDM1WhcNMzYxMDI1MDgzMDM1WjBF +MQswCQYDVQQGEwJDSDEVMBMGA1UEChMMU3dpc3NTaWduIEFHMR8wHQYDVQQDExZT +d2lzc1NpZ24gR29sZCBDQSAtIEcyMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC +CgKCAgEAr+TufoskDhJuqVAtFkQ7kpJcyrhdhJJCEyq8ZVeCQD5XJM1QiyUqt2/8 +76LQwB8CJEoTlo8jE+YoWACjR8cGp4QjK7u9lit/VcyLwVcfDmJlD909Vopz2q5+ +bbqBHH5CjCA12UNNhPqE21Is8w4ndwtrvxEvcnifLtg+5hg3Wipy+dpikJKVyh+c +6bM8K8vzARO/Ws/BtQpgvd21mWRTuKCWs2/iJneRjOBiEAKfNA+k1ZIzUd6+jbqE +emA8atufK+ze3gE/bk3lUIbLtK/tREDFylqM2tIrfKjuvqblCqoOpd8FUrdVxyJd +MmqXl2MT28nbeTZ7hTpKxVKJ+STnnXepgv9VHKVxaSvRAiTysybUa9oEVeXBCsdt +MDeQKuSeFDNeFhdVxVu1yzSJkvGdJo+hB9TGsnhQ2wwMC3wLjEHXuendjIj3o02y +MszYF9rNt85mndT9Xv+9lz4pded+p2JYryU0pUHHPbwNUMoDAw8IWh+Vc3hiv69y +FGkOpeUDDniOJihC8AcLYiAQZzlG+qkDzAQ4embvIIO1jEpWjpEA/I5cgt6IoMPi +aG59je883WX0XaxR7ySArqpWl2/5rX3aYT+YdzylkbYcjCbaZaIJbcHiVOO5ykxM +gI93e2CaHt+28kgeDrpOVG2Y4OGiGqJ3UM/EY5LsRxmd6+ZrzsECAwEAAaOBrDCB +qTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUWyV7 +lqRlUX64OfPAeGZe6Drn8O4wHwYDVR0jBBgwFoAUWyV7lqRlUX64OfPAeGZe6Drn +8O4wRgYDVR0gBD8wPTA7BglghXQBWQECAQEwLjAsBggrBgEFBQcCARYgaHR0cDov +L3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIBACe6 +45R88a7A3hfm5djV9VSwg/S7zV4Fe0+fdWavPOhWfvxyeDgD2StiGwC5+OlgzczO +UYrHUDFu4Up+GC9pWbY9ZIEr44OE5iKHjn3g7gKZYbge9LgriBIWhMIxkziWMaa5 +O1M/wySTVltpkuzFwbs4AOPsF6m43Md8AYOfMke6UiI0HTJ6CVanfCU2qT1L2sCC +bwq7EsiHSycR+R4tx5M/nttfJmtS2S6K8RTGRI0Vqbe/vd6mGu6uLftIdxf+u+yv +GPUqUfA5hJeVbG4bwyvEdGB5JbAKJ9/fXtI5z0V9QkvfsywexcZdylU6oJxpmo/a +77KwPJ+HbBIrZXAVUjEaJM9vMSNQH4xPjyPDdEFjHFWoFN0+4FFQz/EbMFYOkrCC +hdiDyyJkvC24JdVUorgG6q2SpCSgwYa1ShNqR88uC1aVVMvOmttqtKay20EIhid3 +92qgQmwLOM7XdVAyksLfKzAiSNDVQTglXaTpXZ/GlHXQRf0wl0OPkKsKx4ZzYEpp +Ld6leNcG2mqeSz53OiATIgHQv2ieY2BrNU0LbbqhPcCT4H8js1WtciVORvnSFu+w +ZMEBnunKoGqYDs/YYPIvSbjkQuE4NRb0yG5P94FW6LqjviOvrv1vA+ACOzB2+htt +Qc8Bsem4yWb02ybzOqR08kkkW8mw0FfB+j564ZfJ +-----END CERTIFICATE----- + +# Issuer: CN=SwissSign Silver CA - G2 O=SwissSign AG +# Subject: CN=SwissSign Silver CA - G2 O=SwissSign AG +# Label: "SwissSign Silver CA - G2" +# Serial: 5700383053117599563 +# MD5 Fingerprint: e0:06:a1:c9:7d:cf:c9:fc:0d:c0:56:75:96:d8:62:13 +# SHA1 Fingerprint: 9b:aa:e5:9f:56:ee:21:cb:43:5a:be:25:93:df:a7:f0:40:d1:1d:cb +# SHA256 Fingerprint: be:6c:4d:a2:bb:b9:ba:59:b6:f3:93:97:68:37:42:46:c3:c0:05:99:3f:a9:8f:02:0d:1d:ed:be:d4:8a:81:d5 +-----BEGIN CERTIFICATE----- +MIIFvTCCA6WgAwIBAgIITxvUL1S7L0swDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UE +BhMCQ0gxFTATBgNVBAoTDFN3aXNzU2lnbiBBRzEhMB8GA1UEAxMYU3dpc3NTaWdu +IFNpbHZlciBDQSAtIEcyMB4XDTA2MTAyNTA4MzI0NloXDTM2MTAyNTA4MzI0Nlow +RzELMAkGA1UEBhMCQ0gxFTATBgNVBAoTDFN3aXNzU2lnbiBBRzEhMB8GA1UEAxMY +U3dpc3NTaWduIFNpbHZlciBDQSAtIEcyMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A +MIICCgKCAgEAxPGHf9N4Mfc4yfjDmUO8x/e8N+dOcbpLj6VzHVxumK4DV644N0Mv +Fz0fyM5oEMF4rhkDKxD6LHmD9ui5aLlV8gREpzn5/ASLHvGiTSf5YXu6t+WiE7br +YT7QbNHm+/pe7R20nqA1W6GSy/BJkv6FCgU+5tkL4k+73JU3/JHpMjUi0R86TieF +nbAVlDLaYQ1HTWBCrpJH6INaUFjpiou5XaHc3ZlKHzZnu0jkg7Y360g6rw9njxcH +6ATK72oxh9TAtvmUcXtnZLi2kUpCe2UuMGoM9ZDulebyzYLs2aFK7PayS+VFheZt +eJMELpyCbTapxDFkH4aDCyr0NQp4yVXPQbBH6TCfmb5hqAaEuSh6XzjZG6k4sIN/ +c8HDO0gqgg8hm7jMqDXDhBuDsz6+pJVpATqJAHgE2cn0mRmrVn5bi4Y5FZGkECwJ +MoBgs5PAKrYYC51+jUnyEEp/+dVGLxmSo5mnJqy7jDzmDrxHB9xzUfFwZC8I+bRH +HTBsROopN4WSaGa8gzj+ezku01DwH/teYLappvonQfGbGHLy9YR0SslnxFSuSGTf +jNFusB3hB48IHpmccelM2KX3RxIfdNFRnobzwqIjQAtz20um53MGjMGg6cFZrEb6 +5i/4z3GcRm25xBWNOHkDRUjvxF3XCO6HOSKGsg0PWEP3calILv3q1h8CAwEAAaOB +rDCBqTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU +F6DNweRBtjpbO8tFnb0cwpj6hlgwHwYDVR0jBBgwFoAUF6DNweRBtjpbO8tFnb0c +wpj6hlgwRgYDVR0gBD8wPTA7BglghXQBWQEDAQEwLjAsBggrBgEFBQcCARYgaHR0 +cDovL3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIB +AHPGgeAn0i0P4JUw4ppBf1AsX19iYamGamkYDHRJ1l2E6kFSGG9YrVBWIGrGvShp +WJHckRE1qTodvBqlYJ7YH39FkWnZfrt4csEGDyrOj4VwYaygzQu4OSlWhDJOhrs9 +xCrZ1x9y7v5RoSJBsXECYxqCsGKrXlcSH9/L3XWgwF15kIwb4FDm3jH+mHtwX6WQ +2K34ArZv02DdQEsixT2tOnqfGhpHkXkzuoLcMmkDlm4fS/Bx/uNncqCxv1yL5PqZ +IseEuRuNI5c/7SXgz2W79WEE790eslpBIlqhn10s6FvJbakMDHiqYMZWjwFaDGi8 +aRl5xB9+lwW/xekkUV7U1UtT7dkjWjYDZaPBA61BMPNGG4WQr2W11bHkFlt4dR2X +em1ZqSqPe97Dh4kQmUlzeMg9vVE1dCrV8X5pGyq7O70luJpaPXJhkGaH7gzWTdQR +dAtq/gsD/KNVV4n+SsuuWxcFyPKNIzFTONItaj+CuY0IavdeQXRuwxF+B6wpYJE/ +OMpXEA29MC/HpeZBoNquBYeaoKRlbEwJDIm6uNO5wJOKMPqN5ZprFQFOZ6raYlY+ +hAhm0sQ2fac+EPyI4NSA5QC9qvNOBqN6avlicuMJT+ubDgEj8Z+7fNzcbBGXJbLy +tGMU0gYqZ4yD9c7qB9iaah7s5Aq7KkzrCWA5zspi2C5u +-----END CERTIFICATE----- + +# Issuer: CN=GeoTrust Primary Certification Authority O=GeoTrust Inc. +# Subject: CN=GeoTrust Primary Certification Authority O=GeoTrust Inc. +# Label: "GeoTrust Primary Certification Authority" +# Serial: 32798226551256963324313806436981982369 +# MD5 Fingerprint: 02:26:c3:01:5e:08:30:37:43:a9:d0:7d:cf:37:e6:bf +# SHA1 Fingerprint: 32:3c:11:8e:1b:f7:b8:b6:52:54:e2:e2:10:0d:d6:02:90:37:f0:96 +# SHA256 Fingerprint: 37:d5:10:06:c5:12:ea:ab:62:64:21:f1:ec:8c:92:01:3f:c5:f8:2a:e9:8e:e5:33:eb:46:19:b8:de:b4:d0:6c +-----BEGIN CERTIFICATE----- +MIIDfDCCAmSgAwIBAgIQGKy1av1pthU6Y2yv2vrEoTANBgkqhkiG9w0BAQUFADBY +MQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjExMC8GA1UEAxMo +R2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjEx +MjcwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMFgxCzAJBgNVBAYTAlVTMRYwFAYDVQQK +Ew1HZW9UcnVzdCBJbmMuMTEwLwYDVQQDEyhHZW9UcnVzdCBQcmltYXJ5IENlcnRp +ZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC +AQEAvrgVe//UfH1nrYNke8hCUy3f9oQIIGHWAVlqnEQRr+92/ZV+zmEwu3qDXwK9 +AWbK7hWNb6EwnL2hhZ6UOvNWiAAxz9juapYC2e0DjPt1befquFUWBRaa9OBesYjA +ZIVcFU2Ix7e64HXprQU9nceJSOC7KMgD4TCTZF5SwFlwIjVXiIrxlQqD17wxcwE0 +7e9GceBrAqg1cmuXm2bgyxx5X9gaBGgeRwLmnWDiNpcB3841kt++Z8dtd1k7j53W +kBWUvEI0EME5+bEnPn7WinXFsq+W06Lem+SYvn3h6YGttm/81w7a4DSwDRp35+MI +mO9Y+pyEtzavwt+s0vQQBnBxNQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4G +A1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQULNVQQZcVi/CPNmFbSvtr2ZnJM5IwDQYJ +KoZIhvcNAQEFBQADggEBAFpwfyzdtzRP9YZRqSa+S7iq8XEN3GHHoOo0Hnp3DwQ1 +6CePbJC/kRYkRj5KTs4rFtULUh38H2eiAkUxT87z+gOneZ1TatnaYzr4gNfTmeGl +4b7UVXGYNTq+k+qurUKykG/g/CFNNWMziUnWm07Kx+dOCQD32sfvmWKZd7aVIl6K +oKv0uHiYyjgZmclynnjNS6yvGaBzEi38wkG6gZHaFloxt/m0cYASSJlyc1pZU8Fj +UjPtp8nSOQJw+uCxQmYpqptR7TBUIhRf2asdweSU8Pj1K/fqynhG1riR/aYNKxoU +AT6A8EKglQdebc3MS6RFjasS6LPeWuWgfOgPIh1a6Vk= +-----END CERTIFICATE----- + +# Issuer: CN=thawte Primary Root CA O=thawte, Inc. OU=Certification Services Division/(c) 2006 thawte, Inc. - For authorized use only +# Subject: CN=thawte Primary Root CA O=thawte, Inc. OU=Certification Services Division/(c) 2006 thawte, Inc. - For authorized use only +# Label: "thawte Primary Root CA" +# Serial: 69529181992039203566298953787712940909 +# MD5 Fingerprint: 8c:ca:dc:0b:22:ce:f5:be:72:ac:41:1a:11:a8:d8:12 +# SHA1 Fingerprint: 91:c6:d6:ee:3e:8a:c8:63:84:e5:48:c2:99:29:5c:75:6c:81:7b:81 +# SHA256 Fingerprint: 8d:72:2f:81:a9:c1:13:c0:79:1d:f1:36:a2:96:6d:b2:6c:95:0a:97:1d:b4:6b:41:99:f4:ea:54:b7:8b:fb:9f +-----BEGIN CERTIFICATE----- +MIIEIDCCAwigAwIBAgIQNE7VVyDV7exJ9C/ON9srbTANBgkqhkiG9w0BAQUFADCB +qTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMf +Q2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIw +MDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxHzAdBgNV +BAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwHhcNMDYxMTE3MDAwMDAwWhcNMzYw +NzE2MjM1OTU5WjCBqTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5j +LjEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYG +A1UECxMvKGMpIDIwMDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNl +IG9ubHkxHzAdBgNVBAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCsoPD7gFnUnMekz52hWXMJEEUMDSxuaPFs +W0hoSVk3/AszGcJ3f8wQLZU0HObrTQmnHNK4yZc2AreJ1CRfBsDMRJSUjQJib+ta +3RGNKJpchJAQeg29dGYvajig4tVUROsdB58Hum/u6f1OCyn1PoSgAfGcq/gcfomk +6KHYcWUNo1F77rzSImANuVud37r8UVsLr5iy6S7pBOhih94ryNdOwUxkHt3Ph1i6 +Sk/KaAcdHJ1KxtUvkcx8cXIcxcBn6zL9yZJclNqFwJu/U30rCfSMnZEfl2pSy94J +NqR32HuHUETVPm4pafs5SSYeCaWAe0At6+gnhcn+Yf1+5nyXHdWdAgMBAAGjQjBA +MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBR7W0XP +r87Lev0xkhpqtvNG61dIUDANBgkqhkiG9w0BAQUFAAOCAQEAeRHAS7ORtvzw6WfU +DW5FvlXok9LOAz/t2iWwHVfLHjp2oEzsUHboZHIMpKnxuIvW1oeEuzLlQRHAd9mz +YJ3rG9XRbkREqaYB7FViHXe4XI5ISXycO1cRrK1zN44veFyQaEfZYGDm/Ac9IiAX +xPcW6cTYcvnIc3zfFi8VqT79aie2oetaupgf1eNNZAqdE8hhuvU5HIe6uL17In/2 +/qxAeeWsEG89jxt5dovEN7MhGITlNgDrYyCZuen+MwS7QcjBAvlEYyCegc5C09Y/ +LHbTY5xZ3Y+m4Q6gLkH3LpVHz7z9M/P2C2F+fpErgUfCJzDupxBdN49cOSvkBPB7 +jVaMaA== +-----END CERTIFICATE----- + +# Issuer: CN=VeriSign Class 3 Public Primary Certification Authority - G5 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2006 VeriSign, Inc. - For authorized use only +# Subject: CN=VeriSign Class 3 Public Primary Certification Authority - G5 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2006 VeriSign, Inc. - For authorized use only +# Label: "VeriSign Class 3 Public Primary Certification Authority - G5" +# Serial: 33037644167568058970164719475676101450 +# MD5 Fingerprint: cb:17:e4:31:67:3e:e2:09:fe:45:57:93:f3:0a:fa:1c +# SHA1 Fingerprint: 4e:b6:d5:78:49:9b:1c:cf:5f:58:1e:ad:56:be:3d:9b:67:44:a5:e5 +# SHA256 Fingerprint: 9a:cf:ab:7e:43:c8:d8:80:d0:6b:26:2a:94:de:ee:e4:b4:65:99:89:c3:d0:ca:f1:9b:af:64:05:e4:1a:b7:df +-----BEGIN CERTIFICATE----- +MIIE0zCCA7ugAwIBAgIQGNrRniZ96LtKIVjNzGs7SjANBgkqhkiG9w0BAQUFADCB +yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL +ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJp +U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxW +ZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0 +aG9yaXR5IC0gRzUwHhcNMDYxMTA4MDAwMDAwWhcNMzYwNzE2MjM1OTU5WjCByjEL +MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZW +ZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJpU2ln +biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJp +U2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9y +aXR5IC0gRzUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvJAgIKXo1 +nmAMqudLO07cfLw8RRy7K+D+KQL5VwijZIUVJ/XxrcgxiV0i6CqqpkKzj/i5Vbex +t0uz/o9+B1fs70PbZmIVYc9gDaTY3vjgw2IIPVQT60nKWVSFJuUrjxuf6/WhkcIz +SdhDY2pSS9KP6HBRTdGJaXvHcPaz3BJ023tdS1bTlr8Vd6Gw9KIl8q8ckmcY5fQG +BO+QueQA5N06tRn/Arr0PO7gi+s3i+z016zy9vA9r911kTMZHRxAy3QkGSGT2RT+ +rCpSx4/VBEnkjWNHiDxpg8v+R70rfk/Fla4OndTRQ8Bnc+MUCH7lP59zuDMKz10/ +NIeWiu5T6CUVAgMBAAGjgbIwga8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8E +BAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJaW1hZ2UvZ2lmMCEwHzAH +BgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYjaHR0cDovL2xvZ28udmVy +aXNpZ24uY29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFH/TZafC3ey78DAJ80M5+gKv +MzEzMA0GCSqGSIb3DQEBBQUAA4IBAQCTJEowX2LP2BqYLz3q3JktvXf2pXkiOOzE +p6B4Eq1iDkVwZMXnl2YtmAl+X6/WzChl8gGqCBpH3vn5fJJaCGkgDdk+bW48DW7Y +5gaRQBi5+MHt39tBquCWIMnNZBU4gcmU7qKEKQsTb47bDN0lAtukixlE0kF6BWlK +WE9gyn6CagsCqiUXObXbf+eEZSqVir2G3l6BFoMtEMze/aiCKm0oHw0LxOXnGiYZ +4fQRbxC1lfznQgUy286dUV4otp6F01vvpX1FQHKOtw5rDgb7MzVIcbidJ4vEZV8N +hnacRHr2lVz2XTIIM6RUthg/aFzyQkqFOFSDX9HoLPKsEdao7WNq +-----END CERTIFICATE----- + +# Issuer: CN=SecureTrust CA O=SecureTrust Corporation +# Subject: CN=SecureTrust CA O=SecureTrust Corporation +# Label: "SecureTrust CA" +# Serial: 17199774589125277788362757014266862032 +# MD5 Fingerprint: dc:32:c3:a7:6d:25:57:c7:68:09:9d:ea:2d:a9:a2:d1 +# SHA1 Fingerprint: 87:82:c6:c3:04:35:3b:cf:d2:96:92:d2:59:3e:7d:44:d9:34:ff:11 +# SHA256 Fingerprint: f1:c1:b5:0a:e5:a2:0d:d8:03:0e:c9:f6:bc:24:82:3d:d3:67:b5:25:57:59:b4:e7:1b:61:fc:e9:f7:37:5d:73 +-----BEGIN CERTIFICATE----- +MIIDuDCCAqCgAwIBAgIQDPCOXAgWpa1Cf/DrJxhZ0DANBgkqhkiG9w0BAQUFADBI +MQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24x +FzAVBgNVBAMTDlNlY3VyZVRydXN0IENBMB4XDTA2MTEwNzE5MzExOFoXDTI5MTIz +MTE5NDA1NVowSDELMAkGA1UEBhMCVVMxIDAeBgNVBAoTF1NlY3VyZVRydXN0IENv +cnBvcmF0aW9uMRcwFQYDVQQDEw5TZWN1cmVUcnVzdCBDQTCCASIwDQYJKoZIhvcN +AQEBBQADggEPADCCAQoCggEBAKukgeWVzfX2FI7CT8rU4niVWJxB4Q2ZQCQXOZEz +Zum+4YOvYlyJ0fwkW2Gz4BERQRwdbvC4u/jep4G6pkjGnx29vo6pQT64lO0pGtSO +0gMdA+9tDWccV9cGrcrI9f4Or2YlSASWC12juhbDCE/RRvgUXPLIXgGZbf2IzIao +wW8xQmxSPmjL8xk037uHGFaAJsTQ3MBv396gwpEWoGQRS0S8Hvbn+mPeZqx2pHGj +7DaUaHp3pLHnDi+BeuK1cobvomuL8A/b01k/unK8RCSc43Oz969XL0Imnal0ugBS +8kvNU3xHCzaFDmapCJcWNFfBZveA4+1wVMeT4C4oFVmHursCAwEAAaOBnTCBmjAT +BgkrBgEEAYI3FAIEBh4EAEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB +/zAdBgNVHQ4EFgQUQjK2FvoE/f5dS3rD/fdMQB1aQ68wNAYDVR0fBC0wKzApoCeg +JYYjaHR0cDovL2NybC5zZWN1cmV0cnVzdC5jb20vU1RDQS5jcmwwEAYJKwYBBAGC +NxUBBAMCAQAwDQYJKoZIhvcNAQEFBQADggEBADDtT0rhWDpSclu1pqNlGKa7UTt3 +6Z3q059c4EVlew3KW+JwULKUBRSuSceNQQcSc5R+DCMh/bwQf2AQWnL1mA6s7Ll/ +3XpvXdMc9P+IBWlCqQVxyLesJugutIxq/3HcuLHfmbx8IVQr5Fiiu1cprp6poxkm +D5kuCLDv/WnPmRoJjeOnnyvJNjR7JLN4TJUXpAYmHrZkUjZfYGfZnMUFdAvnZyPS +CPyI6a6Lf+Ew9Dd+/cYy2i2eRDAwbO4H3tI0/NL/QPZL9GZGBlSm8jIKYyYwa5vR +3ItHuuG51WLQoqD0ZwV4KWMabwTW+MZMo5qxN7SN5ShLHZ4swrhovO0C7jE= +-----END CERTIFICATE----- + +# Issuer: CN=Secure Global CA O=SecureTrust Corporation +# Subject: CN=Secure Global CA O=SecureTrust Corporation +# Label: "Secure Global CA" +# Serial: 9751836167731051554232119481456978597 +# MD5 Fingerprint: cf:f4:27:0d:d4:ed:dc:65:16:49:6d:3d:da:bf:6e:de +# SHA1 Fingerprint: 3a:44:73:5a:e5:81:90:1f:24:86:61:46:1e:3b:9c:c4:5f:f5:3a:1b +# SHA256 Fingerprint: 42:00:f5:04:3a:c8:59:0e:bb:52:7d:20:9e:d1:50:30:29:fb:cb:d4:1c:a1:b5:06:ec:27:f1:5a:de:7d:ac:69 +-----BEGIN CERTIFICATE----- +MIIDvDCCAqSgAwIBAgIQB1YipOjUiolN9BPI8PjqpTANBgkqhkiG9w0BAQUFADBK +MQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24x +GTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwgQ0EwHhcNMDYxMTA3MTk0MjI4WhcNMjkx +MjMxMTk1MjA2WjBKMQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3Qg +Q29ycG9yYXRpb24xGTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwgQ0EwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvNS7YrGxVaQZx5RNoJLNP2MwhR/jxYDiJ +iQPpvepeRlMJ3Fz1Wuj3RSoC6zFh1ykzTM7HfAo3fg+6MpjhHZevj8fcyTiW89sa +/FHtaMbQbqR8JNGuQsiWUGMu4P51/pinX0kuleM5M2SOHqRfkNJnPLLZ/kG5VacJ +jnIFHovdRIWCQtBJwB1g8NEXLJXr9qXBkqPFwqcIYA1gBBCWeZ4WNOaptvolRTnI +HmX5k/Wq8VLcmZg9pYYaDDUz+kulBAYVHDGA76oYa8J719rO+TMg1fW9ajMtgQT7 +sFzUnKPiXB3jqUJ1XnvUd+85VLrJChgbEplJL4hL/VBi0XPnj3pDAgMBAAGjgZ0w +gZowEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1UdEwEB/wQF +MAMBAf8wHQYDVR0OBBYEFK9EBMJBfkiD2045AuzshHrmzsmkMDQGA1UdHwQtMCsw +KaAnoCWGI2h0dHA6Ly9jcmwuc2VjdXJldHJ1c3QuY29tL1NHQ0EuY3JsMBAGCSsG +AQQBgjcVAQQDAgEAMA0GCSqGSIb3DQEBBQUAA4IBAQBjGghAfaReUw132HquHw0L +URYD7xh8yOOvaliTFGCRsoTciE6+OYo68+aCiV0BN7OrJKQVDpI1WkpEXk5X+nXO +H0jOZvQ8QCaSmGwb7iRGDBezUqXbpZGRzzfTb+cnCDpOGR86p1hcF895P4vkp9Mm +I50mD1hp/Ed+stCNi5O/KU9DaXR2Z0vPB4zmAve14bRDtUstFJ/53CYNv6ZHdAbY +iNE6KTCEztI5gGIbqMdXSbxqVVFnFUq+NQfk1XWYN3kwFNspnWzFacxHVaIw98xc +f8LDmBxrThaA63p4ZUWiABqvDA1VZDRIuJK58bRQKfJPIx/abKwfROHdI3hRW8cW +-----END CERTIFICATE----- + +# Issuer: CN=COMODO Certification Authority O=COMODO CA Limited +# Subject: CN=COMODO Certification Authority O=COMODO CA Limited +# Label: "COMODO Certification Authority" +# Serial: 104350513648249232941998508985834464573 +# MD5 Fingerprint: 5c:48:dc:f7:42:72:ec:56:94:6d:1c:cc:71:35:80:75 +# SHA1 Fingerprint: 66:31:bf:9e:f7:4f:9e:b6:c9:d5:a6:0c:ba:6a:be:d1:f7:bd:ef:7b +# SHA256 Fingerprint: 0c:2c:d6:3d:f7:80:6f:a3:99:ed:e8:09:11:6b:57:5b:f8:79:89:f0:65:18:f9:80:8c:86:05:03:17:8b:af:66 +-----BEGIN CERTIFICATE----- +MIIEHTCCAwWgAwIBAgIQToEtioJl4AsC7j41AkblPTANBgkqhkiG9w0BAQUFADCB +gTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G +A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxJzAlBgNV +BAMTHkNPTU9ETyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjEyMDEwMDAw +MDBaFw0yOTEyMzEyMzU5NTlaMIGBMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3Jl +YXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01P +RE8gQ0EgTGltaXRlZDEnMCUGA1UEAxMeQ09NT0RPIENlcnRpZmljYXRpb24gQXV0 +aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0ECLi3LjkRv3 +UcEbVASY06m/weaKXTuH+7uIzg3jLz8GlvCiKVCZrts7oVewdFFxze1CkU1B/qnI +2GqGd0S7WWaXUF601CxwRM/aN5VCaTwwxHGzUvAhTaHYujl8HJ6jJJ3ygxaYqhZ8 +Q5sVW7euNJH+1GImGEaaP+vB+fGQV+useg2L23IwambV4EajcNxo2f8ESIl33rXp ++2dtQem8Ob0y2WIC8bGoPW43nOIv4tOiJovGuFVDiOEjPqXSJDlqR6sA1KGzqSX+ +DT+nHbrTUcELpNqsOO9VUCQFZUaTNE8tja3G1CEZ0o7KBWFxB3NH5YoZEr0ETc5O +nKVIrLsm9wIDAQABo4GOMIGLMB0GA1UdDgQWBBQLWOWLxkwVN6RAqTCpIb5HNlpW +/zAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zBJBgNVHR8EQjBAMD6g +PKA6hjhodHRwOi8vY3JsLmNvbW9kb2NhLmNvbS9DT01PRE9DZXJ0aWZpY2F0aW9u +QXV0aG9yaXR5LmNybDANBgkqhkiG9w0BAQUFAAOCAQEAPpiem/Yb6dc5t3iuHXIY +SdOH5EOC6z/JqvWote9VfCFSZfnVDeFs9D6Mk3ORLgLETgdxb8CPOGEIqB6BCsAv +IC9Bi5HcSEW88cbeunZrM8gALTFGTO3nnc+IlP8zwFboJIYmuNg4ON8qa90SzMc/ +RxdMosIGlgnW2/4/PEZB31jiVg88O8EckzXZOFKs7sjsLjBOlDW0JB9LeGna8gI4 +zJVSk/BwJVmcIGfE7vmLV2H0knZ9P4SNVbfo5azV8fUZVqZa+5Acr5Pr5RzUZ5dd +BA6+C4OmF4O5MBKgxTMVBbkN+8cFduPYSo38NBejxiEovjBFMR7HeL5YYTisO+IB +ZQ== +-----END CERTIFICATE----- + +# Issuer: CN=Network Solutions Certificate Authority O=Network Solutions L.L.C. +# Subject: CN=Network Solutions Certificate Authority O=Network Solutions L.L.C. +# Label: "Network Solutions Certificate Authority" +# Serial: 116697915152937497490437556386812487904 +# MD5 Fingerprint: d3:f3:a6:16:c0:fa:6b:1d:59:b1:2d:96:4d:0e:11:2e +# SHA1 Fingerprint: 74:f8:a3:c3:ef:e7:b3:90:06:4b:83:90:3c:21:64:60:20:e5:df:ce +# SHA256 Fingerprint: 15:f0:ba:00:a3:ac:7a:f3:ac:88:4c:07:2b:10:11:a0:77:bd:77:c0:97:f4:01:64:b2:f8:59:8a:bd:83:86:0c +-----BEGIN CERTIFICATE----- +MIID5jCCAs6gAwIBAgIQV8szb8JcFuZHFhfjkDFo4DANBgkqhkiG9w0BAQUFADBi +MQswCQYDVQQGEwJVUzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMu +MTAwLgYDVQQDEydOZXR3b3JrIFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3Jp +dHkwHhcNMDYxMjAxMDAwMDAwWhcNMjkxMjMxMjM1OTU5WjBiMQswCQYDVQQGEwJV +UzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMuMTAwLgYDVQQDEydO +ZXR3b3JrIFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDkvH6SMG3G2I4rC7xGzuAnlt7e+foS0zwz +c7MEL7xxjOWftiJgPl9dzgn/ggwbmlFQGiaJ3dVhXRncEg8tCqJDXRfQNJIg6nPP +OCwGJgl6cvf6UDL4wpPTaaIjzkGxzOTVHzbRijr4jGPiFFlp7Q3Tf2vouAPlT2rl +mGNpSAW+Lv8ztumXWWn4Zxmuk2GWRBXTcrA/vGp97Eh/jcOrqnErU2lBUzS1sLnF +BgrEsEX1QV1uiUV7PTsmjHTC5dLRfbIR1PtYMiKagMnc/Qzpf14Dl847ABSHJ3A4 +qY5usyd2mFHgBeMhqxrVhSI8KbWaFsWAqPS7azCPL0YCorEMIuDTAgMBAAGjgZcw +gZQwHQYDVR0OBBYEFCEwyfsA106Y2oeqKtCnLrFAMadMMA4GA1UdDwEB/wQEAwIB +BjAPBgNVHRMBAf8EBTADAQH/MFIGA1UdHwRLMEkwR6BFoEOGQWh0dHA6Ly9jcmwu +bmV0c29sc3NsLmNvbS9OZXR3b3JrU29sdXRpb25zQ2VydGlmaWNhdGVBdXRob3Jp +dHkuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQC7rkvnt1frf6ott3NHhWrB5KUd5Oc8 +6fRZZXe1eltajSU24HqXLjjAV2CDmAaDn7l2em5Q4LqILPxFzBiwmZVRDuwduIj/ +h1AcgsLj4DKAv6ALR8jDMe+ZZzKATxcheQxpXN5eNK4CtSbqUN9/GGUsyfJj4akH +/nxxH2szJGoeBfcFaMBqEssuXmHLrijTfsK0ZpEmXzwuJF/LWA/rKOyvEZbz3Htv +wKeI8lN3s2Berq4o2jUsbzRF0ybh3uxbTydrFny9RAQYgrOJeRcQcT16ohZO9QHN +pGxlaKFJdlxDydi8NmdspZS11My5vWo1ViHe2MPr+8ukYEywVaCge1ey +-----END CERTIFICATE----- + +# Issuer: CN=WellsSecure Public Root Certificate Authority O=Wells Fargo WellsSecure OU=Wells Fargo Bank NA +# Subject: CN=WellsSecure Public Root Certificate Authority O=Wells Fargo WellsSecure OU=Wells Fargo Bank NA +# Label: "WellsSecure Public Root Certificate Authority" +# Serial: 1 +# MD5 Fingerprint: 15:ac:a5:c2:92:2d:79:bc:e8:7f:cb:67:ed:02:cf:36 +# SHA1 Fingerprint: e7:b4:f6:9d:61:ec:90:69:db:7e:90:a7:40:1a:3c:f4:7d:4f:e8:ee +# SHA256 Fingerprint: a7:12:72:ae:aa:a3:cf:e8:72:7f:7f:b3:9f:0f:b3:d1:e5:42:6e:90:60:b0:6e:e6:f1:3e:9a:3c:58:33:cd:43 +-----BEGIN CERTIFICATE----- +MIIEvTCCA6WgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBhTELMAkGA1UEBhMCVVMx +IDAeBgNVBAoMF1dlbGxzIEZhcmdvIFdlbGxzU2VjdXJlMRwwGgYDVQQLDBNXZWxs +cyBGYXJnbyBCYW5rIE5BMTYwNAYDVQQDDC1XZWxsc1NlY3VyZSBQdWJsaWMgUm9v +dCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcNMDcxMjEzMTcwNzU0WhcNMjIxMjE0 +MDAwNzU0WjCBhTELMAkGA1UEBhMCVVMxIDAeBgNVBAoMF1dlbGxzIEZhcmdvIFdl +bGxzU2VjdXJlMRwwGgYDVQQLDBNXZWxscyBGYXJnbyBCYW5rIE5BMTYwNAYDVQQD +DC1XZWxsc1NlY3VyZSBQdWJsaWMgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkw +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDub7S9eeKPCCGeOARBJe+r +WxxTkqxtnt3CxC5FlAM1iGd0V+PfjLindo8796jE2yljDpFoNoqXjopxaAkH5OjU +Dk/41itMpBb570OYj7OeUt9tkTmPOL13i0Nj67eT/DBMHAGTthP796EfvyXhdDcs +HqRePGj4S78NuR4uNuip5Kf4D8uCdXw1LSLWwr8L87T8bJVhHlfXBIEyg1J55oNj +z7fLY4sR4r1e6/aN7ZVyKLSsEmLpSjPmgzKuBXWVvYSV2ypcm44uDLiBK0HmOFaf +SZtsdvqKXfcBeYF8wYNABf5x/Qw/zE5gCQ5lRxAvAcAFP4/4s0HvWkJ+We/Slwxl +AgMBAAGjggE0MIIBMDAPBgNVHRMBAf8EBTADAQH/MDkGA1UdHwQyMDAwLqAsoCqG +KGh0dHA6Ly9jcmwucGtpLndlbGxzZmFyZ28uY29tL3dzcHJjYS5jcmwwDgYDVR0P +AQH/BAQDAgHGMB0GA1UdDgQWBBQmlRkQ2eihl5H/3BnZtQQ+0nMKajCBsgYDVR0j +BIGqMIGngBQmlRkQ2eihl5H/3BnZtQQ+0nMKaqGBi6SBiDCBhTELMAkGA1UEBhMC +VVMxIDAeBgNVBAoMF1dlbGxzIEZhcmdvIFdlbGxzU2VjdXJlMRwwGgYDVQQLDBNX +ZWxscyBGYXJnbyBCYW5rIE5BMTYwNAYDVQQDDC1XZWxsc1NlY3VyZSBQdWJsaWMg +Um9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHmCAQEwDQYJKoZIhvcNAQEFBQADggEB +ALkVsUSRzCPIK0134/iaeycNzXK7mQDKfGYZUMbVmO2rvwNa5U3lHshPcZeG1eMd +/ZDJPHV3V3p9+N701NX3leZ0bh08rnyd2wIDBSxxSyU+B+NemvVmFymIGjifz6pB +A4SXa5M4esowRBskRDPQ5NHcKDj0E0M1NSljqHyita04pO2t/caaH/+Xc/77szWn +k4bGdpEA5qxRFsQnMlzbc9qlk1eOPm01JghZ1edE13YgY+esE2fDbbFwRnzVlhE9 +iW9dqKHrjQrawx0zbKPqZxmamX9LPYNRKh3KL4YMon4QLSvUFpULB6ouFJJJtylv +2G0xffX8oRAHh84vWdw+WNs= +-----END CERTIFICATE----- + +# Issuer: CN=COMODO ECC Certification Authority O=COMODO CA Limited +# Subject: CN=COMODO ECC Certification Authority O=COMODO CA Limited +# Label: "COMODO ECC Certification Authority" +# Serial: 41578283867086692638256921589707938090 +# MD5 Fingerprint: 7c:62:ff:74:9d:31:53:5e:68:4a:d5:78:aa:1e:bf:23 +# SHA1 Fingerprint: 9f:74:4e:9f:2b:4d:ba:ec:0f:31:2c:50:b6:56:3b:8e:2d:93:c3:11 +# SHA256 Fingerprint: 17:93:92:7a:06:14:54:97:89:ad:ce:2f:8f:34:f7:f0:b6:6d:0f:3a:e3:a3:b8:4d:21:ec:15:db:ba:4f:ad:c7 +-----BEGIN CERTIFICATE----- +MIICiTCCAg+gAwIBAgIQH0evqmIAcFBUTAGem2OZKjAKBggqhkjOPQQDAzCBhTEL +MAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE +BxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMT +IkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDgwMzA2MDAw +MDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdy +ZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09N +T0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQDR3svdcmCFYX7deSR +FtSrYpn1PlILBs5BAH+X4QokPB0BBO490o0JlwzgdeT6+3eKKvUDYEs2ixYjFq0J +cfRK9ChQtP6IHG4/bC8vCVlbpVsLM5niwz2J+Wos77LTBumjQjBAMB0GA1UdDgQW +BBR1cacZSBm8nZ3qQUfflMRId5nTeTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/ +BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjEA7wNbeqy3eApyt4jf/7VGFAkK+qDm +fQjGGoe9GKhzvSbKYAydzpmfz1wPMOG+FDHqAjAU9JM8SaczepBGR7NjfRObTrdv +GDeAU/7dIOA1mjbRxwG55tzd8/8dLDoWV9mSOdY= +-----END CERTIFICATE----- + +# Issuer: CN=IGC/A O=PM/SGDN OU=DCSSI +# Subject: CN=IGC/A O=PM/SGDN OU=DCSSI +# Label: "IGC/A" +# Serial: 245102874772 +# MD5 Fingerprint: 0c:7f:dd:6a:f4:2a:b9:c8:9b:bd:20:7e:a9:db:5c:37 +# SHA1 Fingerprint: 60:d6:89:74:b5:c2:65:9e:8a:0f:c1:88:7c:88:d2:46:69:1b:18:2c +# SHA256 Fingerprint: b9:be:a7:86:0a:96:2e:a3:61:1d:ab:97:ab:6d:a3:e2:1c:10:68:b9:7d:55:57:5e:d0:e1:12:79:c1:1c:89:32 +-----BEGIN CERTIFICATE----- +MIIEAjCCAuqgAwIBAgIFORFFEJQwDQYJKoZIhvcNAQEFBQAwgYUxCzAJBgNVBAYT +AkZSMQ8wDQYDVQQIEwZGcmFuY2UxDjAMBgNVBAcTBVBhcmlzMRAwDgYDVQQKEwdQ +TS9TR0ROMQ4wDAYDVQQLEwVEQ1NTSTEOMAwGA1UEAxMFSUdDL0ExIzAhBgkqhkiG +9w0BCQEWFGlnY2FAc2dkbi5wbS5nb3V2LmZyMB4XDTAyMTIxMzE0MjkyM1oXDTIw +MTAxNzE0MjkyMlowgYUxCzAJBgNVBAYTAkZSMQ8wDQYDVQQIEwZGcmFuY2UxDjAM +BgNVBAcTBVBhcmlzMRAwDgYDVQQKEwdQTS9TR0ROMQ4wDAYDVQQLEwVEQ1NTSTEO +MAwGA1UEAxMFSUdDL0ExIzAhBgkqhkiG9w0BCQEWFGlnY2FAc2dkbi5wbS5nb3V2 +LmZyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsh/R0GLFMzvABIaI +s9z4iPf930Pfeo2aSVz2TqrMHLmh6yeJ8kbpO0px1R2OLc/mratjUMdUC24SyZA2 +xtgv2pGqaMVy/hcKshd+ebUyiHDKcMCWSo7kVc0dJ5S/znIq7Fz5cyD+vfcuiWe4 +u0dzEvfRNWk68gq5rv9GQkaiv6GFGvm/5P9JhfejcIYyHF2fYPepraX/z9E0+X1b +F8bc1g4oa8Ld8fUzaJ1O/Id8NhLWo4DoQw1VYZTqZDdH6nfK0LJYBcNdfrGoRpAx +Vs5wKpayMLh35nnAvSk7/ZR3TL0gzUEl4C7HG7vupARB0l2tEmqKm0f7yd1GQOGd +PDPQtQIDAQABo3cwdTAPBgNVHRMBAf8EBTADAQH/MAsGA1UdDwQEAwIBRjAVBgNV +HSAEDjAMMAoGCCqBegF5AQEBMB0GA1UdDgQWBBSjBS8YYFDCiQrdKyFP/45OqDAx +NjAfBgNVHSMEGDAWgBSjBS8YYFDCiQrdKyFP/45OqDAxNjANBgkqhkiG9w0BAQUF +AAOCAQEABdwm2Pp3FURo/C9mOnTgXeQp/wYHE4RKq89toB9RlPhJy3Q2FLwV3duJ +L92PoF189RLrn544pEfMs5bZvpwlqwN+Mw+VgQ39FuCIvjfwbF3QMZsyK10XZZOY +YLxuj7GoPB7ZHPOpJkL5ZB3C55L29B5aqhlSXa/oovdgoPaN8In1buAKBQGVyYsg +Crpa/JosPL3Dt8ldeCUFP1YUmwza+zpI/pdpXsoQhvdOlgQITeywvl3cO45Pwf2a +NjSaTFR+FwNIlQgRHAdvhQh+XU3Endv7rs6y0bO4g2wdsrN58dhwmX7wEwLOXt1R +0982gaEbeC9xs/FZTEYYKKuF0mBWWg== +-----END CERTIFICATE----- + +# Issuer: O=SECOM Trust Systems CO.,LTD. OU=Security Communication EV RootCA1 +# Subject: O=SECOM Trust Systems CO.,LTD. OU=Security Communication EV RootCA1 +# Label: "Security Communication EV RootCA1" +# Serial: 0 +# MD5 Fingerprint: 22:2d:a6:01:ea:7c:0a:f7:f0:6c:56:43:3f:77:76:d3 +# SHA1 Fingerprint: fe:b8:c4:32:dc:f9:76:9a:ce:ae:3d:d8:90:8f:fd:28:86:65:64:7d +# SHA256 Fingerprint: a2:2d:ba:68:1e:97:37:6e:2d:39:7d:72:8a:ae:3a:9b:62:96:b9:fd:ba:60:bc:2e:11:f6:47:f2:c6:75:fb:37 +-----BEGIN CERTIFICATE----- +MIIDfTCCAmWgAwIBAgIBADANBgkqhkiG9w0BAQUFADBgMQswCQYDVQQGEwJKUDEl +MCMGA1UEChMcU0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEqMCgGA1UECxMh +U2VjdXJpdHkgQ29tbXVuaWNhdGlvbiBFViBSb290Q0ExMB4XDTA3MDYwNjAyMTIz +MloXDTM3MDYwNjAyMTIzMlowYDELMAkGA1UEBhMCSlAxJTAjBgNVBAoTHFNFQ09N +IFRydXN0IFN5c3RlbXMgQ08uLExURC4xKjAoBgNVBAsTIVNlY3VyaXR5IENvbW11 +bmljYXRpb24gRVYgUm9vdENBMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBALx/7FebJOD+nLpCeamIivqA4PUHKUPqjgo0No0c+qe1OXj/l3X3L+SqawSE +RMqm4miO/VVQYg+kcQ7OBzgtQoVQrTyWb4vVog7P3kmJPdZkLjjlHmy1V4qe70gO +zXppFodEtZDkBp2uoQSXWHnvIEqCa4wiv+wfD+mEce3xDuS4GBPMVjZd0ZoeUWs5 +bmB2iDQL87PRsJ3KYeJkHcFGB7hj3R4zZbOOCVVSPbW9/wfrrWFVGCypaZhKqkDF +MxRldAD5kd6vA0jFQFTcD4SQaCDFkpbcLuUCRarAX1T4bepJz11sS6/vmsJWXMY1 +VkJqMF/Cq/biPT+zyRGPMUzXn0kCAwEAAaNCMEAwHQYDVR0OBBYEFDVK9U2vP9eC +OKyrcWUXdYydVZPmMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0G +CSqGSIb3DQEBBQUAA4IBAQCoh+ns+EBnXcPBZsdAS5f8hxOQWsTvoMpfi7ent/HW +tWS3irO4G8za+6xmiEHO6Pzk2x6Ipu0nUBsCMCRGef4Eh3CXQHPRwMFXGZpppSeZ +q51ihPZRwSzJIxXYKLerJRO1RuGGAv8mjMSIkh1W/hln8lXkgKNrnKt34VFxDSDb +EJrbvXZ5B3eZKK2aXtqxT0QsNY6llsf9g/BYxnnWmHyojf6GPgcWkuF75x3sM3Z+ +Qi5KhfmRiWiEA4Glm5q+4zfFVKtWOxgtQaQM+ELbmaDgcm+7XeEWT1MKZPlO9L9O +VL14bIjqv5wTJMJwaaJ/D8g8rQjJsJhAoyrniIPtd490 +-----END CERTIFICATE----- + +# Issuer: CN=OISTE WISeKey Global Root GA CA O=WISeKey OU=Copyright (c) 2005/OISTE Foundation Endorsed +# Subject: CN=OISTE WISeKey Global Root GA CA O=WISeKey OU=Copyright (c) 2005/OISTE Foundation Endorsed +# Label: "OISTE WISeKey Global Root GA CA" +# Serial: 86718877871133159090080555911823548314 +# MD5 Fingerprint: bc:6c:51:33:a7:e9:d3:66:63:54:15:72:1b:21:92:93 +# SHA1 Fingerprint: 59:22:a1:e1:5a:ea:16:35:21:f8:98:39:6a:46:46:b0:44:1b:0f:a9 +# SHA256 Fingerprint: 41:c9:23:86:6a:b4:ca:d6:b7:ad:57:80:81:58:2e:02:07:97:a6:cb:df:4f:ff:78:ce:83:96:b3:89:37:d7:f5 +-----BEGIN CERTIFICATE----- +MIID8TCCAtmgAwIBAgIQQT1yx/RrH4FDffHSKFTfmjANBgkqhkiG9w0BAQUFADCB +ijELMAkGA1UEBhMCQ0gxEDAOBgNVBAoTB1dJU2VLZXkxGzAZBgNVBAsTEkNvcHly +aWdodCAoYykgMjAwNTEiMCAGA1UECxMZT0lTVEUgRm91bmRhdGlvbiBFbmRvcnNl +ZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwgUm9vdCBHQSBDQTAeFw0w +NTEyMTExNjAzNDRaFw0zNzEyMTExNjA5NTFaMIGKMQswCQYDVQQGEwJDSDEQMA4G +A1UEChMHV0lTZUtleTEbMBkGA1UECxMSQ29weXJpZ2h0IChjKSAyMDA1MSIwIAYD +VQQLExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBX +SVNlS2V5IEdsb2JhbCBSb290IEdBIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEAy0+zAJs9Nt350UlqaxBJH+zYK7LG+DKBKUOVTJoZIyEVRd7jyBxR +VVuuk+g3/ytr6dTqvirdqFEr12bDYVxgAsj1znJ7O7jyTmUIms2kahnBAbtzptf2 +w93NvKSLtZlhuAGio9RN1AU9ka34tAhxZK9w8RxrfvbDd50kc3vkDIzh2TbhmYsF +mQvtRTEJysIA2/dyoJaqlYfQjse2YXMNdmaM3Bu0Y6Kff5MTMPGhJ9vZ/yxViJGg +4E8HsChWjBgbl0SOid3gF27nKu+POQoxhILYQBRJLnpB5Kf+42TMwVlxSywhp1t9 +4B3RLoGbw9ho972WG6xwsRYUC9tguSYBBQIDAQABo1EwTzALBgNVHQ8EBAMCAYYw +DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUswN+rja8sHnR3JQmthG+IbJphpQw +EAYJKwYBBAGCNxUBBAMCAQAwDQYJKoZIhvcNAQEFBQADggEBAEuh/wuHbrP5wUOx +SPMowB0uyQlB+pQAHKSkq0lPjz0e701vvbyk9vImMMkQyh2I+3QZH4VFvbBsUfk2 +ftv1TDI6QU9bR8/oCy22xBmddMVHxjtqD6wU2zz0c5ypBd8A3HR4+vg1YFkCExh8 +vPtNsCBtQ7tgMHpnM1zFmdH4LTlSc/uMqpclXHLZCB6rTjzjgTGfA6b7wP4piFXa +hNVQA7bihKOmNqoROgHhGEvWRGizPflTdISzRpFGlgC3gCy24eMQ4tui5yiPAZZi +Fj4A4xylNoEYokxSdsARo27mHbrjWr42U8U+dY+GaSlYU7Wcu2+fXMUY7N0v4ZjJ +/L7fCg0= +-----END CERTIFICATE----- + +# Issuer: CN=Microsec e-Szigno Root CA O=Microsec Ltd. OU=e-Szigno CA +# Subject: CN=Microsec e-Szigno Root CA O=Microsec Ltd. OU=e-Szigno CA +# Label: "Microsec e-Szigno Root CA" +# Serial: 272122594155480254301341951808045322001 +# MD5 Fingerprint: f0:96:b6:2f:c5:10:d5:67:8e:83:25:32:e8:5e:2e:e5 +# SHA1 Fingerprint: 23:88:c9:d3:71:cc:9e:96:3d:ff:7d:3c:a7:ce:fc:d6:25:ec:19:0d +# SHA256 Fingerprint: 32:7a:3d:76:1a:ba:de:a0:34:eb:99:84:06:27:5c:b1:a4:77:6e:fd:ae:2f:df:6d:01:68:ea:1c:4f:55:67:d0 +-----BEGIN CERTIFICATE----- +MIIHqDCCBpCgAwIBAgIRAMy4579OKRr9otxmpRwsDxEwDQYJKoZIhvcNAQEFBQAw +cjELMAkGA1UEBhMCSFUxETAPBgNVBAcTCEJ1ZGFwZXN0MRYwFAYDVQQKEw1NaWNy +b3NlYyBMdGQuMRQwEgYDVQQLEwtlLVN6aWdubyBDQTEiMCAGA1UEAxMZTWljcm9z +ZWMgZS1Temlnbm8gUm9vdCBDQTAeFw0wNTA0MDYxMjI4NDRaFw0xNzA0MDYxMjI4 +NDRaMHIxCzAJBgNVBAYTAkhVMREwDwYDVQQHEwhCdWRhcGVzdDEWMBQGA1UEChMN +TWljcm9zZWMgTHRkLjEUMBIGA1UECxMLZS1Temlnbm8gQ0ExIjAgBgNVBAMTGU1p +Y3Jvc2VjIGUtU3ppZ25vIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw +ggEKAoIBAQDtyADVgXvNOABHzNuEwSFpLHSQDCHZU4ftPkNEU6+r+ICbPHiN1I2u +uO/TEdyB5s87lozWbxXGd36hL+BfkrYn13aaHUM86tnsL+4582pnS4uCzyL4ZVX+ +LMsvfUh6PXX5qqAnu3jCBspRwn5mS6/NoqdNAoI/gqyFxuEPkEeZlApxcpMqyabA +vjxWTHOSJ/FrtfX9/DAFYJLG65Z+AZHCabEeHXtTRbjcQR/Ji3HWVBTji1R4P770 +Yjtb9aPs1ZJ04nQw7wHb4dSrmZsqa/i9phyGI0Jf7Enemotb9HI6QMVJPqW+jqpx +62z69Rrkav17fVVA71hu5tnVvCSrwe+3AgMBAAGjggQ3MIIEMzBnBggrBgEFBQcB +AQRbMFkwKAYIKwYBBQUHMAGGHGh0dHBzOi8vcmNhLmUtc3ppZ25vLmh1L29jc3Aw +LQYIKwYBBQUHMAKGIWh0dHA6Ly93d3cuZS1zemlnbm8uaHUvUm9vdENBLmNydDAP +BgNVHRMBAf8EBTADAQH/MIIBcwYDVR0gBIIBajCCAWYwggFiBgwrBgEEAYGoGAIB +AQEwggFQMCgGCCsGAQUFBwIBFhxodHRwOi8vd3d3LmUtc3ppZ25vLmh1L1NaU1ov +MIIBIgYIKwYBBQUHAgIwggEUHoIBEABBACAAdABhAG4A+gBzAO0AdAB2AOEAbgB5 +ACAA6QByAHQAZQBsAG0AZQB6AOkAcwDpAGgAZQB6ACAA6QBzACAAZQBsAGYAbwBn +AGEAZADhAHMA4QBoAG8AegAgAGEAIABTAHoAbwBsAGcA4QBsAHQAYQB0APMAIABT +AHoAbwBsAGcA4QBsAHQAYQB0AOEAcwBpACAAUwB6AGEAYgDhAGwAeQB6AGEAdABh +ACAAcwB6AGUAcgBpAG4AdAAgAGsAZQBsAGwAIABlAGwAagDhAHIAbgBpADoAIABo +AHQAdABwADoALwAvAHcAdwB3AC4AZQAtAHMAegBpAGcAbgBvAC4AaAB1AC8AUwBa +AFMAWgAvMIHIBgNVHR8EgcAwgb0wgbqggbeggbSGIWh0dHA6Ly93d3cuZS1zemln +bm8uaHUvUm9vdENBLmNybIaBjmxkYXA6Ly9sZGFwLmUtc3ppZ25vLmh1L0NOPU1p +Y3Jvc2VjJTIwZS1Temlnbm8lMjBSb290JTIwQ0EsT1U9ZS1Temlnbm8lMjBDQSxP +PU1pY3Jvc2VjJTIwTHRkLixMPUJ1ZGFwZXN0LEM9SFU/Y2VydGlmaWNhdGVSZXZv +Y2F0aW9uTGlzdDtiaW5hcnkwDgYDVR0PAQH/BAQDAgEGMIGWBgNVHREEgY4wgYuB +EGluZm9AZS1zemlnbm8uaHWkdzB1MSMwIQYDVQQDDBpNaWNyb3NlYyBlLVN6aWdu +w7MgUm9vdCBDQTEWMBQGA1UECwwNZS1TemlnbsOzIEhTWjEWMBQGA1UEChMNTWlj +cm9zZWMgS2Z0LjERMA8GA1UEBxMIQnVkYXBlc3QxCzAJBgNVBAYTAkhVMIGsBgNV +HSMEgaQwgaGAFMegSXUWYYTbMUuE0vE3QJDvTtz3oXakdDByMQswCQYDVQQGEwJI +VTERMA8GA1UEBxMIQnVkYXBlc3QxFjAUBgNVBAoTDU1pY3Jvc2VjIEx0ZC4xFDAS +BgNVBAsTC2UtU3ppZ25vIENBMSIwIAYDVQQDExlNaWNyb3NlYyBlLVN6aWdubyBS +b290IENBghEAzLjnv04pGv2i3GalHCwPETAdBgNVHQ4EFgQUx6BJdRZhhNsxS4TS +8TdAkO9O3PcwDQYJKoZIhvcNAQEFBQADggEBANMTnGZjWS7KXHAM/IO8VbH0jgds +ZifOwTsgqRy7RlRw7lrMoHfqaEQn6/Ip3Xep1fvj1KcExJW4C+FEaGAHQzAxQmHl +7tnlJNUb3+FKG6qfx1/4ehHqE5MAyopYse7tDk2016g2JnzgOsHVV4Lxdbb9iV/a +86g4nzUGCM4ilb7N1fy+W955a9x6qWVmvrElWl/tftOsRm1M9DKHtCAE4Gx4sHfR +hUZLphK3dehKyVZs15KrnfVJONJPU+NVkBHbmJbGSfI+9J8b4PeI3CVimUTYc78/ +MPMMNz7UwiiAc7EBt51alhQBS6kRnSlqLtBdgcDPsiBDxwPgN05dCtxZICU= +-----END CERTIFICATE----- + +# Issuer: CN=Certigna O=Dhimyotis +# Subject: CN=Certigna O=Dhimyotis +# Label: "Certigna" +# Serial: 18364802974209362175 +# MD5 Fingerprint: ab:57:a6:5b:7d:42:82:19:b5:d8:58:26:28:5e:fd:ff +# SHA1 Fingerprint: b1:2e:13:63:45:86:a4:6f:1a:b2:60:68:37:58:2d:c4:ac:fd:94:97 +# SHA256 Fingerprint: e3:b6:a2:db:2e:d7:ce:48:84:2f:7a:c5:32:41:c7:b7:1d:54:14:4b:fb:40:c1:1f:3f:1d:0b:42:f5:ee:a1:2d +-----BEGIN CERTIFICATE----- +MIIDqDCCApCgAwIBAgIJAP7c4wEPyUj/MA0GCSqGSIb3DQEBBQUAMDQxCzAJBgNV +BAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hMB4X +DTA3MDYyOTE1MTMwNVoXDTI3MDYyOTE1MTMwNVowNDELMAkGA1UEBhMCRlIxEjAQ +BgNVBAoMCURoaW15b3RpczERMA8GA1UEAwwIQ2VydGlnbmEwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQDIaPHJ1tazNHUmgh7stL7qXOEm7RFHYeGifBZ4 +QCHkYJ5ayGPhxLGWkv8YbWkj4Sti993iNi+RB7lIzw7sebYs5zRLcAglozyHGxny +gQcPOJAZ0xH+hrTy0V4eHpbNgGzOOzGTtvKg0KmVEn2lmsxryIRWijOp5yIVUxbw +zBfsV1/pogqYCd7jX5xv3EjjhQsVWqa6n6xI4wmy9/Qy3l40vhx4XUJbzg4ij02Q +130yGLMLLGq/jj8UEYkgDncUtT2UCIf3JR7VsmAA7G8qKCVuKj4YYxclPz5EIBb2 +JsglrgVKtOdjLPOMFlN+XPsRGgjBRmKfIrjxwo1p3Po6WAbfAgMBAAGjgbwwgbkw +DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUGu3+QTmQtCRZvgHyUtVF9lo53BEw +ZAYDVR0jBF0wW4AUGu3+QTmQtCRZvgHyUtVF9lo53BGhOKQ2MDQxCzAJBgNVBAYT +AkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hggkA/tzj +AQ/JSP8wDgYDVR0PAQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIABzANBgkqhkiG +9w0BAQUFAAOCAQEAhQMeknH2Qq/ho2Ge6/PAD/Kl1NqV5ta+aDY9fm4fTIrv0Q8h +bV6lUmPOEvjvKtpv6zf+EwLHyzs+ImvaYS5/1HI93TDhHkxAGYwP15zRgzB7mFnc +fca5DClMoTOi62c6ZYTTluLtdkVwj7Ur3vkj1kluPBS1xp81HlDQwY9qcEQCYsuu +HWhBp6pX6FOqB9IG9tUUBguRA3UsbHK1YZWaDYu5Def131TN3ubY1gkIl2PlwS6w +t0QmwCbAr1UwnjvVNioZBPRcHv/PLLf/0P2HQBHVESO7SMAhqaQoLf0V+LBOK/Qw +WyH8EZE0vkHve52Xdf+XlcCWWC/qu0bXu+TZLg== +-----END CERTIFICATE----- + +# Issuer: CN=Deutsche Telekom Root CA 2 O=Deutsche Telekom AG OU=T-TeleSec Trust Center +# Subject: CN=Deutsche Telekom Root CA 2 O=Deutsche Telekom AG OU=T-TeleSec Trust Center +# Label: "Deutsche Telekom Root CA 2" +# Serial: 38 +# MD5 Fingerprint: 74:01:4a:91:b1:08:c4:58:ce:47:cd:f0:dd:11:53:08 +# SHA1 Fingerprint: 85:a4:08:c0:9c:19:3e:5d:51:58:7d:cd:d6:13:30:fd:8c:de:37:bf +# SHA256 Fingerprint: b6:19:1a:50:d0:c3:97:7f:7d:a9:9b:cd:aa:c8:6a:22:7d:ae:b9:67:9e:c7:0b:a3:b0:c9:d9:22:71:c1:70:d3 +-----BEGIN CERTIFICATE----- +MIIDnzCCAoegAwIBAgIBJjANBgkqhkiG9w0BAQUFADBxMQswCQYDVQQGEwJERTEc +MBoGA1UEChMTRGV1dHNjaGUgVGVsZWtvbSBBRzEfMB0GA1UECxMWVC1UZWxlU2Vj +IFRydXN0IENlbnRlcjEjMCEGA1UEAxMaRGV1dHNjaGUgVGVsZWtvbSBSb290IENB +IDIwHhcNOTkwNzA5MTIxMTAwWhcNMTkwNzA5MjM1OTAwWjBxMQswCQYDVQQGEwJE +RTEcMBoGA1UEChMTRGV1dHNjaGUgVGVsZWtvbSBBRzEfMB0GA1UECxMWVC1UZWxl +U2VjIFRydXN0IENlbnRlcjEjMCEGA1UEAxMaRGV1dHNjaGUgVGVsZWtvbSBSb290 +IENBIDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCrC6M14IspFLEU +ha88EOQ5bzVdSq7d6mGNlUn0b2SjGmBmpKlAIoTZ1KXleJMOaAGtuU1cOs7TuKhC +QN/Po7qCWWqSG6wcmtoIKyUn+WkjR/Hg6yx6m/UTAtB+NHzCnjwAWav12gz1Mjwr +rFDa1sPeg5TKqAyZMg4ISFZbavva4VhYAUlfckE8FQYBjl2tqriTtM2e66foai1S +NNs671x1Udrb8zH57nGYMsRUFUQM+ZtV7a3fGAigo4aKSe5TBY8ZTNXeWHmb0moc +QqvF1afPaA+W5OFhmHZhyJF81j4A4pFQh+GdCuatl9Idxjp9y7zaAzTVjlsB9WoH +txa2bkp/AgMBAAGjQjBAMB0GA1UdDgQWBBQxw3kbuvVT1xfgiXotF2wKsyudMzAP +BgNVHRMECDAGAQH/AgEFMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOC +AQEAlGRZrTlk5ynrE/5aw4sTV8gEJPB0d8Bg42f76Ymmg7+Wgnxu1MM9756Abrsp +tJh6sTtU6zkXR34ajgv8HzFZMQSyzhfzLMdiNlXiItiJVbSYSKpk+tYcNthEeFpa +IzpXl/V6ME+un2pMSyuOoAPjPuCp1NJ70rOo4nI8rZ7/gFnkm0W09juwzTkZmDLl +6iFhkOQxIY40sfcvNUqFENrnijchvllj4PKFiDFT1FQUhXB59C4Gdyd1Lx+4ivn+ +xbrYNuSD7Odlt79jWvNGr4GUN9RBjNYj1h7P9WgbRGOiWrqnNVmh5XAFmw4jV5mU +Cm26OWMohpLzGITY+9HPBVZkVw== +-----END CERTIFICATE----- + +# Issuer: CN=Cybertrust Global Root O=Cybertrust, Inc +# Subject: CN=Cybertrust Global Root O=Cybertrust, Inc +# Label: "Cybertrust Global Root" +# Serial: 4835703278459682877484360 +# MD5 Fingerprint: 72:e4:4a:87:e3:69:40:80:77:ea:bc:e3:f4:ff:f0:e1 +# SHA1 Fingerprint: 5f:43:e5:b1:bf:f8:78:8c:ac:1c:c7:ca:4a:9a:c6:22:2b:cc:34:c6 +# SHA256 Fingerprint: 96:0a:df:00:63:e9:63:56:75:0c:29:65:dd:0a:08:67:da:0b:9c:bd:6e:77:71:4a:ea:fb:23:49:ab:39:3d:a3 +-----BEGIN CERTIFICATE----- +MIIDoTCCAomgAwIBAgILBAAAAAABD4WqLUgwDQYJKoZIhvcNAQEFBQAwOzEYMBYG +A1UEChMPQ3liZXJ0cnVzdCwgSW5jMR8wHQYDVQQDExZDeWJlcnRydXN0IEdsb2Jh +bCBSb290MB4XDTA2MTIxNTA4MDAwMFoXDTIxMTIxNTA4MDAwMFowOzEYMBYGA1UE +ChMPQ3liZXJ0cnVzdCwgSW5jMR8wHQYDVQQDExZDeWJlcnRydXN0IEdsb2JhbCBS +b290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA+Mi8vRRQZhP/8NN5 +7CPytxrHjoXxEnOmGaoQ25yiZXRadz5RfVb23CO21O1fWLE3TdVJDm71aofW0ozS +J8bi/zafmGWgE07GKmSb1ZASzxQG9Dvj1Ci+6A74q05IlG2OlTEQXO2iLb3VOm2y +HLtgwEZLAfVJrn5GitB0jaEMAs7u/OePuGtm839EAL9mJRQr3RAwHQeWP032a7iP +t3sMpTjr3kfb1V05/Iin89cqdPHoWqI7n1C6poxFNcJQZZXcY4Lv3b93TZxiyWNz +FtApD0mpSPCzqrdsxacwOUBdrsTiXSZT8M4cIwhhqJQZugRiQOwfOHB3EgZxpzAY +XSUnpQIDAQABo4GlMIGiMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/ +MB0GA1UdDgQWBBS2CHsNesysIEyGVjJez6tuhS1wVzA/BgNVHR8EODA2MDSgMqAw +hi5odHRwOi8vd3d3Mi5wdWJsaWMtdHJ1c3QuY29tL2NybC9jdC9jdHJvb3QuY3Js +MB8GA1UdIwQYMBaAFLYIew16zKwgTIZWMl7Pq26FLXBXMA0GCSqGSIb3DQEBBQUA +A4IBAQBW7wojoFROlZfJ+InaRcHUowAl9B8Tq7ejhVhpwjCt2BWKLePJzYFa+HMj +Wqd8BfP9IjsO0QbE2zZMcwSO5bAi5MXzLqXZI+O4Tkogp24CJJ8iYGd7ix1yCcUx +XOl5n4BHPa2hCwcUPUf/A2kaDAtE52Mlp3+yybh2hO0j9n0Hq0V+09+zv+mKts2o +omcrUtW3ZfA5TGOgkXmTUg9U3YO7n9GPp1Nzw8v/MOx8BLjYRB+TX3EJIrduPuoc +A06dGiBh+4E37F78CkWr1+cXVdCg6mCbpvbjjFspwgZgFJ0tl0ypkxWdYcQBX0jW +WL1WMRJOEcgh4LMRkWXbtKaIOM5V +-----END CERTIFICATE----- + +# Issuer: O=Chunghwa Telecom Co., Ltd. OU=ePKI Root Certification Authority +# Subject: O=Chunghwa Telecom Co., Ltd. OU=ePKI Root Certification Authority +# Label: "ePKI Root Certification Authority" +# Serial: 28956088682735189655030529057352760477 +# MD5 Fingerprint: 1b:2e:00:ca:26:06:90:3d:ad:fe:6f:15:68:d3:6b:b3 +# SHA1 Fingerprint: 67:65:0d:f1:7e:8e:7e:5b:82:40:a4:f4:56:4b:cf:e2:3d:69:c6:f0 +# SHA256 Fingerprint: c0:a6:f4:dc:63:a2:4b:fd:cf:54:ef:2a:6a:08:2a:0a:72:de:35:80:3e:2f:f5:ff:52:7a:e5:d8:72:06:df:d5 +-----BEGIN CERTIFICATE----- +MIIFsDCCA5igAwIBAgIQFci9ZUdcr7iXAF7kBtK8nTANBgkqhkiG9w0BAQUFADBe +MQswCQYDVQQGEwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0 +ZC4xKjAoBgNVBAsMIWVQS0kgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAe +Fw0wNDEyMjAwMjMxMjdaFw0zNDEyMjAwMjMxMjdaMF4xCzAJBgNVBAYTAlRXMSMw +IQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEqMCgGA1UECwwhZVBL +SSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkqhkiG9w0BAQEF +AAOCAg8AMIICCgKCAgEA4SUP7o3biDN1Z82tH306Tm2d0y8U82N0ywEhajfqhFAH +SyZbCUNsIZ5qyNUD9WBpj8zwIuQf5/dqIjG3LBXy4P4AakP/h2XGtRrBp0xtInAh +ijHyl3SJCRImHJ7K2RKilTza6We/CKBk49ZCt0Xvl/T29de1ShUCWH2YWEtgvM3X +DZoTM1PRYfl61dd4s5oz9wCGzh1NlDivqOx4UXCKXBCDUSH3ET00hl7lSM2XgYI1 +TBnsZfZrxQWh7kcT1rMhJ5QQCtkkO7q+RBNGMD+XPNjX12ruOzjjK9SXDrkb5wdJ +fzcq+Xd4z1TtW0ado4AOkUPB1ltfFLqfpo0kR0BZv3I4sjZsN/+Z0V0OWQqraffA +sgRFelQArr5T9rXn4fg8ozHSqf4hUmTFpmfwdQcGlBSBVcYn5AGPF8Fqcde+S/uU +WH1+ETOxQvdibBjWzwloPn9s9h6PYq2lY9sJpx8iQkEeb5mKPtf5P0B6ebClAZLS +nT0IFaUQAS2zMnaolQ2zepr7BxB4EW/hj8e6DyUadCrlHJhBmd8hh+iVBmoKs2pH +dmX2Os+PYhcZewoozRrSgx4hxyy/vv9haLdnG7t4TY3OZ+XkwY63I2binZB1NJip +NiuKmpS5nezMirH4JYlcWrYvjB9teSSnUmjDhDXiZo1jDiVN1Rmy5nk3pyKdVDEC +AwEAAaNqMGgwHQYDVR0OBBYEFB4M97Zn8uGSJglFwFU5Lnc/QkqiMAwGA1UdEwQF +MAMBAf8wOQYEZyoHAAQxMC8wLQIBADAJBgUrDgMCGgUAMAcGBWcqAwAABBRFsMLH +ClZ87lt4DJX5GFPBphzYEDANBgkqhkiG9w0BAQUFAAOCAgEACbODU1kBPpVJufGB +uvl2ICO1J2B01GqZNF5sAFPZn/KmsSQHRGoqxqWOeBLoR9lYGxMqXnmbnwoqZ6Yl +PwZpVnPDimZI+ymBV3QGypzqKOg4ZyYr8dW1P2WT+DZdjo2NQCCHGervJ8A9tDkP +JXtoUHRVnAxZfVo9QZQlUgjgRywVMRnVvwdVxrsStZf0X4OFunHB2WyBEXYKCrC/ +gpf36j36+uwtqSiUO1bd0lEursC9CBWMd1I0ltabrNMdjmEPNXubrjlpC2JgQCA2 +j6/7Nu4tCEoduL+bXPjqpRugc6bY+G7gMwRfaKonh+3ZwZCc7b3jajWvY9+rGNm6 +5ulK6lCKD2GTHuItGeIwlDWSXQ62B68ZgI9HkFFLLk3dheLSClIKF5r8GrBQAuUB +o2M3IUxExJtRmREOc5wGj1QupyheRDmHVi03vYVElOEMSyycw5KFNGHLD7ibSkNS +/jQ6fbjpKdx2qcgw+BRxgMYeNkh0IkFch4LoGHGLQYlE535YW6i4jRPpp2zDR+2z +Gp1iro2C6pSe3VkQw63d4k3jMdXH7OjysP6SHhYKGvzZ8/gntsm+HbRsZJB/9OTE +W9c3rkIO3aQab3yIVMUWbuF6aC74Or8NpDyJO3inTmODBCEIZ43ygknQW/2xzQ+D +hNQ+IIX3Sj0rnP0qCglN6oH4EZw= +-----END CERTIFICATE----- + +# Issuer: CN=TÜBİTAK UEKAE Kök Sertifika Hizmet Sağlayıcısı - Sürüm 3 O=Türkiye Bilimsel ve Teknolojik Araştırma Kurumu - TÜBİTAK OU=Ulusal Elektronik ve Kriptoloji Araştırma Enstitüsü - UEKAE/Kamu Sertifikasyon Merkezi +# Subject: CN=TÜBİTAK UEKAE Kök Sertifika Hizmet Sağlayıcısı - Sürüm 3 O=Türkiye Bilimsel ve Teknolojik Araştırma Kurumu - TÜBİTAK OU=Ulusal Elektronik ve Kriptoloji Araştırma Enstitüsü - UEKAE/Kamu Sertifikasyon Merkezi +# Label: "T\xc3\x9c\x42\xC4\xB0TAK UEKAE K\xC3\xB6k Sertifika Hizmet Sa\xC4\x9Flay\xc4\xb1\x63\xc4\xb1s\xc4\xb1 - S\xC3\xBCr\xC3\xBCm 3" +# Serial: 17 +# MD5 Fingerprint: ed:41:f5:8c:50:c5:2b:9c:73:e6:ee:6c:eb:c2:a8:26 +# SHA1 Fingerprint: 1b:4b:39:61:26:27:6b:64:91:a2:68:6d:d7:02:43:21:2d:1f:1d:96 +# SHA256 Fingerprint: e4:c7:34:30:d7:a5:b5:09:25:df:43:37:0a:0d:21:6e:9a:79:b9:d6:db:83:73:a0:c6:9e:b1:cc:31:c7:c5:2a +-----BEGIN CERTIFICATE----- +MIIFFzCCA/+gAwIBAgIBETANBgkqhkiG9w0BAQUFADCCASsxCzAJBgNVBAYTAlRS +MRgwFgYDVQQHDA9HZWJ6ZSAtIEtvY2FlbGkxRzBFBgNVBAoMPlTDvHJraXllIEJp +bGltc2VsIHZlIFRla25vbG9qaWsgQXJhxZ90xLFybWEgS3VydW11IC0gVMOcQsSw +VEFLMUgwRgYDVQQLDD9VbHVzYWwgRWxla3Ryb25payB2ZSBLcmlwdG9sb2ppIEFy +YcWfdMSxcm1hIEVuc3RpdMO8c8O8IC0gVUVLQUUxIzAhBgNVBAsMGkthbXUgU2Vy +dGlmaWthc3lvbiBNZXJrZXppMUowSAYDVQQDDEFUw5xCxLBUQUsgVUVLQUUgS8O2 +ayBTZXJ0aWZpa2EgSGl6bWV0IFNhxJ9sYXnEsWPEsXPEsSAtIFPDvHLDvG0gMzAe +Fw0wNzA4MjQxMTM3MDdaFw0xNzA4MjExMTM3MDdaMIIBKzELMAkGA1UEBhMCVFIx +GDAWBgNVBAcMD0dlYnplIC0gS29jYWVsaTFHMEUGA1UECgw+VMO8cmtpeWUgQmls +aW1zZWwgdmUgVGVrbm9sb2ppayBBcmHFn3TEsXJtYSBLdXJ1bXUgLSBUw5xCxLBU +QUsxSDBGBgNVBAsMP1VsdXNhbCBFbGVrdHJvbmlrIHZlIEtyaXB0b2xvamkgQXJh +xZ90xLFybWEgRW5zdGl0w7xzw7wgLSBVRUtBRTEjMCEGA1UECwwaS2FtdSBTZXJ0 +aWZpa2FzeW9uIE1lcmtlemkxSjBIBgNVBAMMQVTDnELEsFRBSyBVRUtBRSBLw7Zr +IFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxIC0gU8O8csO8bSAzMIIB +IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAim1L/xCIOsP2fpTo6iBkcK4h +gb46ezzb8R1Sf1n68yJMlaCQvEhOEav7t7WNeoMojCZG2E6VQIdhn8WebYGHV2yK +O7Rm6sxA/OOqbLLLAdsyv9Lrhc+hDVXDWzhXcLh1xnnRFDDtG1hba+818qEhTsXO +fJlfbLm4IpNQp81McGq+agV/E5wrHur+R84EpW+sky58K5+eeROR6Oqeyjh1jmKw +lZMq5d/pXpduIF9fhHpEORlAHLpVK/swsoHvhOPc7Jg4OQOFCKlUAwUp8MmPi+oL +hmUZEdPpCSPeaJMDyTYcIW7OjGbxmTDY17PDHfiBLqi9ggtm/oLL4eAagsNAgQID +AQABo0IwQDAdBgNVHQ4EFgQUvYiHyY/2pAoLquvF/pEjnatKijIwDgYDVR0PAQH/ +BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAB18+kmP +NOm3JpIWmgV050vQbTlswyb2zrgxvMTfvCr4N5EY3ATIZJkrGG2AA1nJrvhY0D7t +wyOfaTyGOBye79oneNGEN3GKPEs5z35FBtYt2IpNeBLWrcLTy9LQQfMmNkqblWwM +7uXRQydmwYj3erMgbOqwaSvHIOgMA8RBBZniP+Rr+KCGgceExh/VS4ESshYhLBOh +gLJeDEoTniDYYkCrkOpkSi+sDQESeUWoL4cZaMjihccwsnX5OD+ywJO0a+IDRM5n +oN+J1q2MdqMTw5RhK2vZbMEHCiIHhWyFJEapvj+LeISCfiQMnf2BN+MlqO02TpUs +yZyQ2uypQjyttgI= +-----END CERTIFICATE----- + +# Issuer: CN=Buypass Class 2 CA 1 O=Buypass AS-983163327 +# Subject: CN=Buypass Class 2 CA 1 O=Buypass AS-983163327 +# Label: "Buypass Class 2 CA 1" +# Serial: 1 +# MD5 Fingerprint: b8:08:9a:f0:03:cc:1b:0d:c8:6c:0b:76:a1:75:64:23 +# SHA1 Fingerprint: a0:a1:ab:90:c9:fc:84:7b:3b:12:61:e8:97:7d:5f:d3:22:61:d3:cc +# SHA256 Fingerprint: 0f:4e:9c:dd:26:4b:02:55:50:d1:70:80:63:40:21:4f:e9:44:34:c9:b0:2f:69:7e:c7:10:fc:5f:ea:fb:5e:38 +-----BEGIN CERTIFICATE----- +MIIDUzCCAjugAwIBAgIBATANBgkqhkiG9w0BAQUFADBLMQswCQYDVQQGEwJOTzEd +MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxHTAbBgNVBAMMFEJ1eXBhc3Mg +Q2xhc3MgMiBDQSAxMB4XDTA2MTAxMzEwMjUwOVoXDTE2MTAxMzEwMjUwOVowSzEL +MAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MR0wGwYD +VQQDDBRCdXlwYXNzIENsYXNzIDIgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEP +ADCCAQoCggEBAIs8B0XY9t/mx8q6jUPFR42wWsE425KEHK8T1A9vNkYgxC7McXA0 +ojTTNy7Y3Tp3L8DrKehc0rWpkTSHIln+zNvnma+WwajHQN2lFYxuyHyXA8vmIPLX +l18xoS830r7uvqmtqEyeIWZDO6i88wmjONVZJMHCR3axiFyCO7srpgTXjAePzdVB +HfCuuCkslFJgNJQ72uA40Z0zPhX0kzLFANq1KWYOOngPIVJfAuWSeyXTkh4vFZ2B +5J2O6O+JzhRMVB0cgRJNcKi+EAUXfh/RuFdV7c27UsKwHnjCTTZoy1YmwVLBvXb3 +WNVyfh9EdrsAiR0WnVE1703CVu9r4Iw7DekCAwEAAaNCMEAwDwYDVR0TAQH/BAUw +AwEB/zAdBgNVHQ4EFgQUP42aWYv8e3uco684sDntkHGA1sgwDgYDVR0PAQH/BAQD +AgEGMA0GCSqGSIb3DQEBBQUAA4IBAQAVGn4TirnoB6NLJzKyQJHyIdFkhb5jatLP +gcIV1Xp+DCmsNx4cfHZSldq1fyOhKXdlyTKdqC5Wq2B2zha0jX94wNWZUYN/Xtm+ +DKhQ7SLHrQVMdvvt7h5HZPb3J31cKA9FxVxiXqaakZG3Uxcu3K1gnZZkOb1naLKu +BctN518fV4bVIJwo+28TOPX2EZL2fZleHwzoq0QkKXJAPTZSr4xYkHPB7GEseaHs +h7U/2k3ZIQAw3pDaDtMaSKk+hQsUi4y8QZ5q9w5wwDX3OaJdZtB7WZ+oRxKaJyOk +LY4ng5IgodcVf/EuGO70SH8vf/GhGLWhC5SgYiAynB321O+/TIho +-----END CERTIFICATE----- + +# Issuer: CN=EBG Elektronik Sertifika Hizmet Sağlayıcısı O=EBG Bilişim Teknolojileri ve Hizmetleri A.Ş. +# Subject: CN=EBG Elektronik Sertifika Hizmet Sağlayıcısı O=EBG Bilişim Teknolojileri ve Hizmetleri A.Ş. +# Label: "EBG Elektronik Sertifika Hizmet Sa\xC4\x9Flay\xc4\xb1\x63\xc4\xb1s\xc4\xb1" +# Serial: 5525761995591021570 +# MD5 Fingerprint: 2c:20:26:9d:cb:1a:4a:00:85:b5:b7:5a:ae:c2:01:37 +# SHA1 Fingerprint: 8c:96:ba:eb:dd:2b:07:07:48:ee:30:32:66:a0:f3:98:6e:7c:ae:58 +# SHA256 Fingerprint: 35:ae:5b:dd:d8:f7:ae:63:5c:ff:ba:56:82:a8:f0:0b:95:f4:84:62:c7:10:8e:e9:a0:e5:29:2b:07:4a:af:b2 +-----BEGIN CERTIFICATE----- +MIIF5zCCA8+gAwIBAgIITK9zQhyOdAIwDQYJKoZIhvcNAQEFBQAwgYAxODA2BgNV +BAMML0VCRyBFbGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sx +c8SxMTcwNQYDVQQKDC5FQkcgQmlsacWfaW0gVGVrbm9sb2ppbGVyaSB2ZSBIaXpt +ZXRsZXJpIEEuxZ4uMQswCQYDVQQGEwJUUjAeFw0wNjA4MTcwMDIxMDlaFw0xNjA4 +MTQwMDMxMDlaMIGAMTgwNgYDVQQDDC9FQkcgRWxla3Ryb25payBTZXJ0aWZpa2Eg +SGl6bWV0IFNhxJ9sYXnEsWPEsXPEsTE3MDUGA1UECgwuRUJHIEJpbGnFn2ltIFRl +a25vbG9qaWxlcmkgdmUgSGl6bWV0bGVyaSBBLsWeLjELMAkGA1UEBhMCVFIwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDuoIRh0DpqZhAy2DE4f6en5f2h +4fuXd7hxlugTlkaDT7byX3JWbhNgpQGR4lvFzVcfd2NR/y8927k/qqk153nQ9dAk +tiHq6yOU/im/+4mRDGSaBUorzAzu8T2bgmmkTPiab+ci2hC6X5L8GCcKqKpE+i4s +tPtGmggDg3KriORqcsnlZR9uKg+ds+g75AxuetpX/dfreYteIAbTdgtsApWjluTL +dlHRKJ2hGvxEok3MenaoDT2/F08iiFD9rrbskFBKW5+VQarKD7JK/oCZTqNGFav4 +c0JqwmZ2sQomFd2TkuzbqV9UIlKRcF0T6kjsbgNs2d1s/OsNA/+mgxKb8amTD8Um +TDGyY5lhcucqZJnSuOl14nypqZoaqsNW2xCaPINStnuWt6yHd6i58mcLlEOzrz5z ++kI2sSXFCjEmN1ZnuqMLfdb3ic1nobc6HmZP9qBVFCVMLDMNpkGMvQQxahByCp0O +Lna9XvNRiYuoP1Vzv9s6xiQFlpJIqkuNKgPlV5EQ9GooFW5Hd4RcUXSfGenmHmMW +OeMRFeNYGkS9y8RsZteEBt8w9DeiQyJ50hBs37vmExH8nYQKE3vwO9D8owrXieqW +fo1IhR5kX9tUoqzVegJ5a9KK8GfaZXINFHDk6Y54jzJ0fFfy1tb0Nokb+Clsi7n2 +l9GkLqq+CxnCRelwXQIDAJ3Zo2MwYTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB +/wQEAwIBBjAdBgNVHQ4EFgQU587GT/wWZ5b6SqMHwQSny2re2kcwHwYDVR0jBBgw +FoAU587GT/wWZ5b6SqMHwQSny2re2kcwDQYJKoZIhvcNAQEFBQADggIBAJuYml2+ +8ygjdsZs93/mQJ7ANtyVDR2tFcU22NU57/IeIl6zgrRdu0waypIN30ckHrMk2pGI +6YNw3ZPX6bqz3xZaPt7gyPvT/Wwp+BVGoGgmzJNSroIBk5DKd8pNSe/iWtkqvTDO +TLKBtjDOWU/aWR1qeqRFsIImgYZ29fUQALjuswnoT4cCB64kXPBfrAowzIpAoHME +wfuJJPaaHFy3PApnNgUIMbOv2AFoKuB4j3TeuFGkjGwgPaL7s9QJ/XvCgKqTbCmY +Iai7FvOpEl90tYeY8pUm3zTvilORiF0alKM/fCL414i6poyWqD1SNGKfAB5UVUJn +xk1Gj7sURT0KlhaOEKGXmdXTMIXM3rRyt7yKPBgpaP3ccQfuJDlq+u2lrDgv+R4Q +DgZxGhBM/nV+/x5XOULK1+EVoVZVWRvRo68R2E7DpSvvkL/A7IITW43WciyTTo9q +Kd+FPNMN4KIYEsxVL0e3p5sC/kH2iExt2qkBR4NkJ2IQgtYSe14DHzSpyZH+r11t +hie3I6p1GMog57AP14kOpmciY/SDQSsGS7tY1dHXt7kQY9iJSrSq3RZj9W6+YKH4 +7ejWkE8axsWgKdOnIaj1Wjz3x0miIZpKlVIglnKaZsv30oZDfCK+lvm9AahH3eU7 +QPl1K5srRmSGjR70j/sHd9DqSaIcjVIUpgqT +-----END CERTIFICATE----- + +# Issuer: O=certSIGN OU=certSIGN ROOT CA +# Subject: O=certSIGN OU=certSIGN ROOT CA +# Label: "certSIGN ROOT CA" +# Serial: 35210227249154 +# MD5 Fingerprint: 18:98:c0:d6:e9:3a:fc:f9:b0:f5:0c:f7:4b:01:44:17 +# SHA1 Fingerprint: fa:b7:ee:36:97:26:62:fb:2d:b0:2a:f6:bf:03:fd:e8:7c:4b:2f:9b +# SHA256 Fingerprint: ea:a9:62:c4:fa:4a:6b:af:eb:e4:15:19:6d:35:1c:cd:88:8d:4f:53:f3:fa:8a:e6:d7:c4:66:a9:4e:60:42:bb +-----BEGIN CERTIFICATE----- +MIIDODCCAiCgAwIBAgIGIAYFFnACMA0GCSqGSIb3DQEBBQUAMDsxCzAJBgNVBAYT +AlJPMREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBD +QTAeFw0wNjA3MDQxNzIwMDRaFw0zMTA3MDQxNzIwMDRaMDsxCzAJBgNVBAYTAlJP +MREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBDQTCC +ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALczuX7IJUqOtdu0KBuqV5Do +0SLTZLrTk+jUrIZhQGpgV2hUhE28alQCBf/fm5oqrl0Hj0rDKH/v+yv6efHHrfAQ +UySQi2bJqIirr1qjAOm+ukbuW3N7LBeCgV5iLKECZbO9xSsAfsT8AzNXDe3i+s5d +RdY4zTW2ssHQnIFKquSyAVwdj1+ZxLGt24gh65AIgoDzMKND5pCCrlUoSe1b16kQ +OA7+j0xbm0bqQfWwCHTD0IgztnzXdN/chNFDDnU5oSVAKOp4yw4sLjmdjItuFhwv +JoIQ4uNllAoEwF73XVv4EOLQunpL+943AAAaWyjj0pxzPjKHmKHJUS/X3qwzs08C +AwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAcYwHQYDVR0O +BBYEFOCMm9slSbPxfIbWskKHC9BroNnkMA0GCSqGSIb3DQEBBQUAA4IBAQA+0hyJ +LjX8+HXd5n9liPRyTMks1zJO890ZeUe9jjtbkw9QSSQTaxQGcu8J06Gh40CEyecY +MnQ8SG4Pn0vU9x7Tk4ZkVJdjclDVVc/6IJMCopvDI5NOFlV2oHB5bc0hH88vLbwZ +44gx+FkagQnIl6Z0x2DEW8xXjrJ1/RsCCdtZb3KTafcxQdaIOL+Hsr0Wefmq5L6I +Jd1hJyMctTEHBDa0GpC9oHRxUIltvBTjD4au8as+x6AJzKNI0eDbZOeStc+vckNw +i/nDhDwTqn6Sm1dTk/pwwpEOMfmbZ13pljheX7NzTogVZ96edhBiIL5VaZVDADlN +9u6wWk5JRFRYX0KD +-----END CERTIFICATE----- + +# Issuer: CN=CNNIC ROOT O=CNNIC +# Subject: CN=CNNIC ROOT O=CNNIC +# Label: "CNNIC ROOT" +# Serial: 1228079105 +# MD5 Fingerprint: 21:bc:82:ab:49:c4:13:3b:4b:b2:2b:5c:6b:90:9c:19 +# SHA1 Fingerprint: 8b:af:4c:9b:1d:f0:2a:92:f7:da:12:8e:b9:1b:ac:f4:98:60:4b:6f +# SHA256 Fingerprint: e2:83:93:77:3d:a8:45:a6:79:f2:08:0c:c7:fb:44:a3:b7:a1:c3:79:2c:b7:eb:77:29:fd:cb:6a:8d:99:ae:a7 +-----BEGIN CERTIFICATE----- +MIIDVTCCAj2gAwIBAgIESTMAATANBgkqhkiG9w0BAQUFADAyMQswCQYDVQQGEwJD +TjEOMAwGA1UEChMFQ05OSUMxEzARBgNVBAMTCkNOTklDIFJPT1QwHhcNMDcwNDE2 +MDcwOTE0WhcNMjcwNDE2MDcwOTE0WjAyMQswCQYDVQQGEwJDTjEOMAwGA1UEChMF +Q05OSUMxEzARBgNVBAMTCkNOTklDIFJPT1QwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQDTNfc/c3et6FtzF8LRb+1VvG7q6KR5smzDo+/hn7E7SIX1mlwh +IhAsxYLO2uOabjfhhyzcuQxauohV3/2q2x8x6gHx3zkBwRP9SFIhxFXf2tizVHa6 +dLG3fdfA6PZZxU3Iva0fFNrfWEQlMhkqx35+jq44sDB7R3IJMfAw28Mbdim7aXZO +V/kbZKKTVrdvmW7bCgScEeOAH8tjlBAKqeFkgjH5jCftppkA9nCTGPihNIaj3XrC +GHn2emU1z5DrvTOTn1OrczvmmzQgLx3vqR1jGqCA2wMv+SYahtKNu6m+UjqHZ0gN +v7Sg2Ca+I19zN38m5pIEo3/PIKe38zrKy5nLAgMBAAGjczBxMBEGCWCGSAGG+EIB +AQQEAwIABzAfBgNVHSMEGDAWgBRl8jGtKvf33VKWCscCwQ7vptU7ETAPBgNVHRMB +Af8EBTADAQH/MAsGA1UdDwQEAwIB/jAdBgNVHQ4EFgQUZfIxrSr3991SlgrHAsEO +76bVOxEwDQYJKoZIhvcNAQEFBQADggEBAEs17szkrr/Dbq2flTtLP1se31cpolnK +OOK5Gv+e5m4y3R6u6jW39ZORTtpC4cMXYFDy0VwmuYK36m3knITnA3kXr5g9lNvH +ugDnuL8BV8F3RTIMO/G0HAiw/VGgod2aHRM2mm23xzy54cXZF/qD1T0VoDy7Hgvi +yJA/qIYM/PmLXoXLT1tLYhFHxUV8BS9BsZ4QaRuZluBVeftOhpm4lNqGOGqTo+fL +buXf6iFViZx9fX+Y9QCJ7uOEwFyWtcVG6kbghVW2G8kS1sHNzYDzAgE8yGnLRUhj +2JTQ7IUOO04RZfSCjKY9ri4ilAnIXOo8gV0WKgOXFlUJ24pBgp5mmxE= +-----END CERTIFICATE----- + +# Issuer: O=Japanese Government OU=ApplicationCA +# Subject: O=Japanese Government OU=ApplicationCA +# Label: "ApplicationCA - Japanese Government" +# Serial: 49 +# MD5 Fingerprint: 7e:23:4e:5b:a7:a5:b4:25:e9:00:07:74:11:62:ae:d6 +# SHA1 Fingerprint: 7f:8a:b0:cf:d0:51:87:6a:66:f3:36:0f:47:c8:8d:8c:d3:35:fc:74 +# SHA256 Fingerprint: 2d:47:43:7d:e1:79:51:21:5a:12:f3:c5:8e:51:c7:29:a5:80:26:ef:1f:cc:0a:5f:b3:d9:dc:01:2f:60:0d:19 +-----BEGIN CERTIFICATE----- +MIIDoDCCAoigAwIBAgIBMTANBgkqhkiG9w0BAQUFADBDMQswCQYDVQQGEwJKUDEc +MBoGA1UEChMTSmFwYW5lc2UgR292ZXJubWVudDEWMBQGA1UECxMNQXBwbGljYXRp +b25DQTAeFw0wNzEyMTIxNTAwMDBaFw0xNzEyMTIxNTAwMDBaMEMxCzAJBgNVBAYT +AkpQMRwwGgYDVQQKExNKYXBhbmVzZSBHb3Zlcm5tZW50MRYwFAYDVQQLEw1BcHBs +aWNhdGlvbkNBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp23gdE6H +j6UG3mii24aZS2QNcfAKBZuOquHMLtJqO8F6tJdhjYq+xpqcBrSGUeQ3DnR4fl+K +f5Sk10cI/VBaVuRorChzoHvpfxiSQE8tnfWuREhzNgaeZCw7NCPbXCbkcXmP1G55 +IrmTwcrNwVbtiGrXoDkhBFcsovW8R0FPXjQilbUfKW1eSvNNcr5BViCH/OlQR9cw +FO5cjFW6WY2H/CPek9AEjP3vbb3QesmlOmpyM8ZKDQUXKi17safY1vC+9D/qDiht +QWEjdnjDuGWk81quzMKq2edY3rZ+nYVunyoKb58DKTCXKB28t89UKU5RMfkntigm +/qJj5kEW8DOYRwIDAQABo4GeMIGbMB0GA1UdDgQWBBRUWssmP3HMlEYNllPqa0jQ +k/5CdTAOBgNVHQ8BAf8EBAMCAQYwWQYDVR0RBFIwUKROMEwxCzAJBgNVBAYTAkpQ +MRgwFgYDVQQKDA/ml6XmnKzlm73mlL/lupwxIzAhBgNVBAsMGuOCouODl+ODquOC +seODvOOCt+ODp+ODs0NBMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQAD +ggEBADlqRHZ3ODrso2dGD/mLBqj7apAxzn7s2tGJfHrrLgy9mTLnsCTWw//1sogJ +hyzjVOGjprIIC8CFqMjSnHH2HZ9g/DgzE+Ge3Atf2hZQKXsvcJEPmbo0NI2VdMV+ +eKlmXb3KIXdCEKxmJj3ekav9FfBv7WxfEPjzFvYDio+nEhEMy/0/ecGc/WLuo89U +DNErXxc+4z6/wCs+CZv+iKZ+tJIX/COUgb1up8WMwusRRdv4QcmWdupwX3kSa+Sj +B1oF7ydJzyGfikwJcGapJsErEU4z0g781mzSDjJkaP+tBXhfAx2o45CsJOAPQKdL +rosot4LKGAfmt1t06SAZf7IbiVQ= +-----END CERTIFICATE----- + +# Issuer: CN=GeoTrust Primary Certification Authority - G3 O=GeoTrust Inc. OU=(c) 2008 GeoTrust Inc. - For authorized use only +# Subject: CN=GeoTrust Primary Certification Authority - G3 O=GeoTrust Inc. OU=(c) 2008 GeoTrust Inc. - For authorized use only +# Label: "GeoTrust Primary Certification Authority - G3" +# Serial: 28809105769928564313984085209975885599 +# MD5 Fingerprint: b5:e8:34:36:c9:10:44:58:48:70:6d:2e:83:d4:b8:05 +# SHA1 Fingerprint: 03:9e:ed:b8:0b:e7:a0:3c:69:53:89:3b:20:d2:d9:32:3a:4c:2a:fd +# SHA256 Fingerprint: b4:78:b8:12:25:0d:f8:78:63:5c:2a:a7:ec:7d:15:5e:aa:62:5e:e8:29:16:e2:cd:29:43:61:88:6c:d1:fb:d4 +-----BEGIN CERTIFICATE----- +MIID/jCCAuagAwIBAgIQFaxulBmyeUtB9iepwxgPHzANBgkqhkiG9w0BAQsFADCB +mDELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsT +MChjKSAyMDA4IEdlb1RydXN0IEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25s +eTE2MDQGA1UEAxMtR2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhv +cml0eSAtIEczMB4XDTA4MDQwMjAwMDAwMFoXDTM3MTIwMTIzNTk1OVowgZgxCzAJ +BgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTkwNwYDVQQLEzAoYykg +MjAwOCBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxNjA0 +BgNVBAMTLUdlb1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg +LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANziXmJYHTNXOTIz ++uvLh4yn1ErdBojqZI4xmKU4kB6Yzy5jK/BGvESyiaHAKAxJcCGVn2TAppMSAmUm +hsalifD614SgcK9PGpc/BkTVyetyEH3kMSj7HGHmKAdEc5IiaacDiGydY8hS2pgn +5whMcD60yRLBxWeDXTPzAxHsatBT4tG6NmCUgLthY2xbF37fQJQeqw3CIShwiP/W +JmxsYAQlTlV+fe+/lEjetx3dcI0FX4ilm/LC7urRQEFtYjgdVgbFA0dRIBn8exAL +DmKudlW/X3e+PkkBUz2YJQN2JFodtNuJ6nnltrM7P7pMKEF/BqxqjsHQ9gUdfeZC +huOl1UcCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYw +HQYDVR0OBBYEFMR5yo6hTgMdHNxr2zFblD4/MH8tMA0GCSqGSIb3DQEBCwUAA4IB +AQAtxRPPVoB7eni9n64smefv2t+UXglpp+duaIy9cr5HqQ6XErhK8WTTOd8lNNTB +zU6B8A8ExCSzNJbGpqow32hhc9f5joWJ7w5elShKKiePEI4ufIbEAp7aDHdlDkQN +kv39sxY2+hENHYwOB4lqKVb3cvTdFZx3NWZXqxNT2I7BQMXXExZacse3aQHEerGD +AWh9jUGhlBjBJVz88P6DAod8DQ3PLghcSkANPuyBYeYk28rgDi0Hsj5W3I31QYUH +SJsMC8tJP33st/3LjWeJGqvtux6jAAgIFyqCXDFdRootD4abdNlF+9RAsXqqaC2G +spki4cErx5z481+oghLrGREt +-----END CERTIFICATE----- + +# Issuer: CN=thawte Primary Root CA - G2 O=thawte, Inc. OU=(c) 2007 thawte, Inc. - For authorized use only +# Subject: CN=thawte Primary Root CA - G2 O=thawte, Inc. OU=(c) 2007 thawte, Inc. - For authorized use only +# Label: "thawte Primary Root CA - G2" +# Serial: 71758320672825410020661621085256472406 +# MD5 Fingerprint: 74:9d:ea:60:24:c4:fd:22:53:3e:cc:3a:72:d9:29:4f +# SHA1 Fingerprint: aa:db:bc:22:23:8f:c4:01:a1:27:bb:38:dd:f4:1d:db:08:9e:f0:12 +# SHA256 Fingerprint: a4:31:0d:50:af:18:a6:44:71:90:37:2a:86:af:af:8b:95:1f:fb:43:1d:83:7f:1e:56:88:b4:59:71:ed:15:57 +-----BEGIN CERTIFICATE----- +MIICiDCCAg2gAwIBAgIQNfwmXNmET8k9Jj1Xm67XVjAKBggqhkjOPQQDAzCBhDEL +MAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjE4MDYGA1UECxMvKGMp +IDIwMDcgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxJDAi +BgNVBAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EgLSBHMjAeFw0wNzExMDUwMDAw +MDBaFw0zODAxMTgyMzU5NTlaMIGEMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMdGhh +d3RlLCBJbmMuMTgwNgYDVQQLEy8oYykgMjAwNyB0aGF3dGUsIEluYy4gLSBGb3Ig +YXV0aG9yaXplZCB1c2Ugb25seTEkMCIGA1UEAxMbdGhhd3RlIFByaW1hcnkgUm9v +dCBDQSAtIEcyMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEotWcgnuVnfFSeIf+iha/ +BebfowJPDQfGAFG6DAJSLSKkQjnE/o/qycG+1E3/n3qe4rF8mq2nhglzh9HnmuN6 +papu+7qzcMBniKI11KOasf2twu8x+qi58/sIxpHR+ymVo0IwQDAPBgNVHRMBAf8E +BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUmtgAMADna3+FGO6Lts6K +DPgR4bswCgYIKoZIzj0EAwMDaQAwZgIxAN344FdHW6fmCsO99YCKlzUNG4k8VIZ3 +KMqh9HneteY4sPBlcIx/AlTCv//YoT7ZzwIxAMSNlPzcU9LcnXgWHxUzI1NS41ox +XZ3Krr0TKUQNJ1uo52icEvdYPy5yAlejj6EULg== +-----END CERTIFICATE----- + +# Issuer: CN=thawte Primary Root CA - G3 O=thawte, Inc. OU=Certification Services Division/(c) 2008 thawte, Inc. - For authorized use only +# Subject: CN=thawte Primary Root CA - G3 O=thawte, Inc. OU=Certification Services Division/(c) 2008 thawte, Inc. - For authorized use only +# Label: "thawte Primary Root CA - G3" +# Serial: 127614157056681299805556476275995414779 +# MD5 Fingerprint: fb:1b:5d:43:8a:94:cd:44:c6:76:f2:43:4b:47:e7:31 +# SHA1 Fingerprint: f1:8b:53:8d:1b:e9:03:b6:a6:f0:56:43:5b:17:15:89:ca:f3:6b:f2 +# SHA256 Fingerprint: 4b:03:f4:58:07:ad:70:f2:1b:fc:2c:ae:71:c9:fd:e4:60:4c:06:4c:f5:ff:b6:86:ba:e5:db:aa:d7:fd:d3:4c +-----BEGIN CERTIFICATE----- +MIIEKjCCAxKgAwIBAgIQYAGXt0an6rS0mtZLL/eQ+zANBgkqhkiG9w0BAQsFADCB +rjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMf +Q2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIw +MDggdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxJDAiBgNV +BAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EgLSBHMzAeFw0wODA0MDIwMDAwMDBa +Fw0zNzEyMDEyMzU5NTlaMIGuMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMdGhhd3Rl +LCBJbmMuMSgwJgYDVQQLEx9DZXJ0aWZpY2F0aW9uIFNlcnZpY2VzIERpdmlzaW9u +MTgwNgYDVQQLEy8oYykgMjAwOCB0aGF3dGUsIEluYy4gLSBGb3IgYXV0aG9yaXpl +ZCB1c2Ugb25seTEkMCIGA1UEAxMbdGhhd3RlIFByaW1hcnkgUm9vdCBDQSAtIEcz +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsr8nLPvb2FvdeHsbnndm +gcs+vHyu86YnmjSjaDFxODNi5PNxZnmxqWWjpYvVj2AtP0LMqmsywCPLLEHd5N/8 +YZzic7IilRFDGF/Eth9XbAoFWCLINkw6fKXRz4aviKdEAhN0cXMKQlkC+BsUa0Lf +b1+6a4KinVvnSr0eAXLbS3ToO39/fR8EtCab4LRarEc9VbjXsCZSKAExQGbY2SS9 +9irY7CFJXJv2eul/VTV+lmuNk5Mny5K76qxAwJ/C+IDPXfRa3M50hqY+bAtTyr2S +zhkGcuYMXDhpxwTWvGzOW/b3aJzcJRVIiKHpqfiYnODz1TEoYRFsZ5aNOZnLwkUk +OQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNV +HQ4EFgQUrWyqlGCc7eT/+j4KdCtjA/e2Wb8wDQYJKoZIhvcNAQELBQADggEBABpA +2JVlrAmSicY59BDlqQ5mU1143vokkbvnRFHfxhY0Cu9qRFHqKweKA3rD6z8KLFIW +oCtDuSWQP3CpMyVtRRooOyfPqsMpQhvfO0zAMzRbQYi/aytlryjvsvXDqmbOe1bu +t8jLZ8HJnBoYuMTDSQPxYA5QzUbF83d597YV4Djbxy8ooAw/dyZ02SUS2jHaGh7c +KUGRIjxpp7sC8rZcJwOJ9Abqm+RyguOhCcHpABnTPtRwa7pxpqpYrvS76Wy274fM +m7v/OeZWYdMKp8RcTGB7BXcmer/YB1IsYvdwY9k5vG8cwnncdimvzsUsZAReiDZu +MdRAGmI0Nj81Aa6sY6A= +-----END CERTIFICATE----- + +# Issuer: CN=GeoTrust Primary Certification Authority - G2 O=GeoTrust Inc. OU=(c) 2007 GeoTrust Inc. - For authorized use only +# Subject: CN=GeoTrust Primary Certification Authority - G2 O=GeoTrust Inc. OU=(c) 2007 GeoTrust Inc. - For authorized use only +# Label: "GeoTrust Primary Certification Authority - G2" +# Serial: 80682863203381065782177908751794619243 +# MD5 Fingerprint: 01:5e:d8:6b:bd:6f:3d:8e:a1:31:f8:12:e0:98:73:6a +# SHA1 Fingerprint: 8d:17:84:d5:37:f3:03:7d:ec:70:fe:57:8b:51:9a:99:e6:10:d7:b0 +# SHA256 Fingerprint: 5e:db:7a:c4:3b:82:a0:6a:87:61:e8:d7:be:49:79:eb:f2:61:1f:7d:d7:9b:f9:1c:1c:6b:56:6a:21:9e:d7:66 +-----BEGIN CERTIFICATE----- +MIICrjCCAjWgAwIBAgIQPLL0SAoA4v7rJDteYD7DazAKBggqhkjOPQQDAzCBmDEL +MAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsTMChj +KSAyMDA3IEdlb1RydXN0IEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTE2 +MDQGA1UEAxMtR2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0 +eSAtIEcyMB4XDTA3MTEwNTAwMDAwMFoXDTM4MDExODIzNTk1OVowgZgxCzAJBgNV +BAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTkwNwYDVQQLEzAoYykgMjAw +NyBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxNjA0BgNV +BAMTLUdlb1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBH +MjB2MBAGByqGSM49AgEGBSuBBAAiA2IABBWx6P0DFUPlrOuHNxFi79KDNlJ9RVcL +So17VDs6bl8VAsBQps8lL33KSLjHUGMcKiEIfJo22Av+0SbFWDEwKCXzXV2juLal +tJLtbCyf691DiaI8S0iRHVDsJt/WYC69IaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAO +BgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFBVfNVdRVfslsq0DafwBo/q+EVXVMAoG +CCqGSM49BAMDA2cAMGQCMGSWWaboCd6LuvpaiIjwH5HTRqjySkwCY/tsXzjbLkGT +qQ7mndwxHLKgpxgceeHHNgIwOlavmnRs9vuD4DPTCF+hnMJbn0bWtsuRBmOiBucz +rD6ogRLQy7rQkgu2npaqBA+K +-----END CERTIFICATE----- + +# Issuer: CN=VeriSign Universal Root Certification Authority O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2008 VeriSign, Inc. - For authorized use only +# Subject: CN=VeriSign Universal Root Certification Authority O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2008 VeriSign, Inc. - For authorized use only +# Label: "VeriSign Universal Root Certification Authority" +# Serial: 85209574734084581917763752644031726877 +# MD5 Fingerprint: 8e:ad:b5:01:aa:4d:81:e4:8c:1d:d1:e1:14:00:95:19 +# SHA1 Fingerprint: 36:79:ca:35:66:87:72:30:4d:30:a5:fb:87:3b:0f:a7:7b:b7:0d:54 +# SHA256 Fingerprint: 23:99:56:11:27:a5:71:25:de:8c:ef:ea:61:0d:df:2f:a0:78:b5:c8:06:7f:4e:82:82:90:bf:b8:60:e8:4b:3c +-----BEGIN CERTIFICATE----- +MIIEuTCCA6GgAwIBAgIQQBrEZCGzEyEDDrvkEhrFHTANBgkqhkiG9w0BAQsFADCB +vTELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL +ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwOCBWZXJp +U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MTgwNgYDVQQDEy9W +ZXJpU2lnbiBVbml2ZXJzYWwgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAe +Fw0wODA0MDIwMDAwMDBaFw0zNzEyMDEyMzU5NTlaMIG9MQswCQYDVQQGEwJVUzEX +MBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0 +IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAyMDA4IFZlcmlTaWduLCBJbmMuIC0gRm9y +IGF1dGhvcml6ZWQgdXNlIG9ubHkxODA2BgNVBAMTL1ZlcmlTaWduIFVuaXZlcnNh +bCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEAx2E3XrEBNNti1xWb/1hajCMj1mCOkdeQmIN65lgZOIzF +9uVkhbSicfvtvbnazU0AtMgtc6XHaXGVHzk8skQHnOgO+k1KxCHfKWGPMiJhgsWH +H26MfF8WIFFE0XBPV+rjHOPMee5Y2A7Cs0WTwCznmhcrewA3ekEzeOEz4vMQGn+H +LL729fdC4uW/h2KJXwBL38Xd5HVEMkE6HnFuacsLdUYI0crSK5XQz/u5QGtkjFdN +/BMReYTtXlT2NJ8IAfMQJQYXStrxHXpma5hgZqTZ79IugvHw7wnqRMkVauIDbjPT +rJ9VAMf2CGqUuV/c4DPxhGD5WycRtPwW8rtWaoAljQIDAQABo4GyMIGvMA8GA1Ud +EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMG0GCCsGAQUFBwEMBGEwX6FdoFsw +WTBXMFUWCWltYWdlL2dpZjAhMB8wBwYFKw4DAhoEFI/l0xqGrI2Oa8PPgGrUSBgs +exkuMCUWI2h0dHA6Ly9sb2dvLnZlcmlzaWduLmNvbS92c2xvZ28uZ2lmMB0GA1Ud +DgQWBBS2d/ppSEefUxLVwuoHMnYH0ZcHGTANBgkqhkiG9w0BAQsFAAOCAQEASvj4 +sAPmLGd75JR3Y8xuTPl9Dg3cyLk1uXBPY/ok+myDjEedO2Pzmvl2MpWRsXe8rJq+ +seQxIcaBlVZaDrHC1LGmWazxY8u4TB1ZkErvkBYoH1quEPuBUDgMbMzxPcP1Y+Oz +4yHJJDnp/RVmRvQbEdBNc6N9Rvk97ahfYtTxP/jgdFcrGJ2BtMQo2pSXpXDrrB2+ +BxHw1dvd5Yzw1TKwg+ZX4o+/vqGqvz0dtdQ46tewXDpPaj+PwGZsY6rp2aQW9IHR +lRQOfc2VNNnSj3BzgXucfr2YYdhFh5iQxeuGMMY1v/D/w1WIg0vvBZIGcfK4mJO3 +7M2CYfE45k+XmCpajQ== +-----END CERTIFICATE----- + +# Issuer: CN=VeriSign Class 3 Public Primary Certification Authority - G4 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2007 VeriSign, Inc. - For authorized use only +# Subject: CN=VeriSign Class 3 Public Primary Certification Authority - G4 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2007 VeriSign, Inc. - For authorized use only +# Label: "VeriSign Class 3 Public Primary Certification Authority - G4" +# Serial: 63143484348153506665311985501458640051 +# MD5 Fingerprint: 3a:52:e1:e7:fd:6f:3a:e3:6f:f3:6f:99:1b:f9:22:41 +# SHA1 Fingerprint: 22:d5:d8:df:8f:02:31:d1:8d:f7:9d:b7:cf:8a:2d:64:c9:3f:6c:3a +# SHA256 Fingerprint: 69:dd:d7:ea:90:bb:57:c9:3e:13:5d:c8:5e:a6:fc:d5:48:0b:60:32:39:bd:c4:54:fc:75:8b:2a:26:cf:7f:79 +-----BEGIN CERTIFICATE----- +MIIDhDCCAwqgAwIBAgIQL4D+I4wOIg9IZxIokYesszAKBggqhkjOPQQDAzCByjEL +MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZW +ZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2ln +biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJp +U2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9y +aXR5IC0gRzQwHhcNMDcxMTA1MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCByjELMAkG +A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJp +U2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2lnbiwg +SW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2ln +biBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5 +IC0gRzQwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAASnVnp8Utpkmw4tXNherJI9/gHm +GUo9FANL+mAnINmDiWn6VMaaGF5VKmTeBvaNSjutEDxlPZCIBIngMGGzrl0Bp3ve +fLK+ymVhAIau2o970ImtTR1ZmkGxvEeA3J5iw/mjgbIwga8wDwYDVR0TAQH/BAUw +AwEB/zAOBgNVHQ8BAf8EBAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJ +aW1hZ2UvZ2lmMCEwHzAHBgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYj +aHR0cDovL2xvZ28udmVyaXNpZ24uY29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFLMW +kf3upm7ktS5Jj4d4gYDs5bG1MAoGCCqGSM49BAMDA2gAMGUCMGYhDBgmYFo4e1ZC +4Kf8NoRRkSAsdk1DPcQdhCPQrNZ8NQbOzWm9kA3bbEhCHQ6qQgIxAJw9SDkjOVga +FRJZap7v1VmyHVIsmXHNxynfGyphe3HR3vPA5Q06Sqotp9iGKt0uEA== +-----END CERTIFICATE----- + +# Issuer: CN=NetLock Arany (Class Gold) Főtanúsítvány O=NetLock Kft. OU=Tanúsítványkiadók (Certification Services) +# Subject: CN=NetLock Arany (Class Gold) Főtanúsítvány O=NetLock Kft. OU=Tanúsítványkiadók (Certification Services) +# Label: "NetLock Arany (Class Gold) Főtanúsítvány" +# Serial: 80544274841616 +# MD5 Fingerprint: c5:a1:b7:ff:73:dd:d6:d7:34:32:18:df:fc:3c:ad:88 +# SHA1 Fingerprint: 06:08:3f:59:3f:15:a1:04:a0:69:a4:6b:a9:03:d0:06:b7:97:09:91 +# SHA256 Fingerprint: 6c:61:da:c3:a2:de:f0:31:50:6b:e0:36:d2:a6:fe:40:19:94:fb:d1:3d:f9:c8:d4:66:59:92:74:c4:46:ec:98 +-----BEGIN CERTIFICATE----- +MIIEFTCCAv2gAwIBAgIGSUEs5AAQMA0GCSqGSIb3DQEBCwUAMIGnMQswCQYDVQQG +EwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3 +MDUGA1UECwwuVGFuw7pzw610dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNl +cnZpY2VzKTE1MDMGA1UEAwwsTmV0TG9jayBBcmFueSAoQ2xhc3MgR29sZCkgRsWR +dGFuw7pzw610dsOhbnkwHhcNMDgxMjExMTUwODIxWhcNMjgxMjA2MTUwODIxWjCB +pzELMAkGA1UEBhMCSFUxETAPBgNVBAcMCEJ1ZGFwZXN0MRUwEwYDVQQKDAxOZXRM +b2NrIEtmdC4xNzA1BgNVBAsMLlRhbsO6c8OtdHbDoW55a2lhZMOzayAoQ2VydGlm +aWNhdGlvbiBTZXJ2aWNlcykxNTAzBgNVBAMMLE5ldExvY2sgQXJhbnkgKENsYXNz +IEdvbGQpIEbFkXRhbsO6c8OtdHbDoW55MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEAxCRec75LbRTDofTjl5Bu0jBFHjzuZ9lk4BqKf8owyoPjIMHj9DrT +lF8afFttvzBPhCf2nx9JvMaZCpDyD/V/Q4Q3Y1GLeqVw/HpYzY6b7cNGbIRwXdrz +AZAj/E4wqX7hJ2Pn7WQ8oLjJM2P+FpD/sLj916jAwJRDC7bVWaaeVtAkH3B5r9s5 +VA1lddkVQZQBr17s9o3x/61k/iCa11zr/qYfCGSji3ZVrR47KGAuhyXoqq8fxmRG +ILdwfzzeSNuWU7c5d+Qa4scWhHaXWy+7GRWF+GmF9ZmnqfI0p6m2pgP8b4Y9VHx2 +BJtr+UBdADTHLpl1neWIA6pN+APSQnbAGwIDAKiLo0UwQzASBgNVHRMBAf8ECDAG +AQH/AgEEMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUzPpnk/C2uNClwB7zU/2M +U9+D15YwDQYJKoZIhvcNAQELBQADggEBAKt/7hwWqZw8UQCgwBEIBaeZ5m8BiFRh +bvG5GK1Krf6BQCOUL/t1fC8oS2IkgYIL9WHxHG64YTjrgfpioTtaYtOUZcTh5m2C ++C8lcLIhJsFyUR+MLMOEkMNaj7rP9KdlpeuY0fsFskZ1FSNqb4VjMIDw1Z4fKRzC +bLBQWV2QWzuoDTDPv31/zvGdg73JRm4gpvlhUbohL3u+pRVjodSVh/GeufOJ8z2F +uLjbvrW5KfnaNwUASZQDhETnv0Mxz3WLJdH0pmT1kvarBes96aULNmLazAZfNou2 +XjG4Kvte9nHfRCaexOYNkbQudZWAUWpLMKawYqGT8ZvYzsRjdT9ZR7E= +-----END CERTIFICATE----- + +# Issuer: CN=Staat der Nederlanden Root CA - G2 O=Staat der Nederlanden +# Subject: CN=Staat der Nederlanden Root CA - G2 O=Staat der Nederlanden +# Label: "Staat der Nederlanden Root CA - G2" +# Serial: 10000012 +# MD5 Fingerprint: 7c:a5:0f:f8:5b:9a:7d:6d:30:ae:54:5a:e3:42:a2:8a +# SHA1 Fingerprint: 59:af:82:79:91:86:c7:b4:75:07:cb:cf:03:57:46:eb:04:dd:b7:16 +# SHA256 Fingerprint: 66:8c:83:94:7d:a6:3b:72:4b:ec:e1:74:3c:31:a0:e6:ae:d0:db:8e:c5:b3:1b:e3:77:bb:78:4f:91:b6:71:6f +-----BEGIN CERTIFICATE----- +MIIFyjCCA7KgAwIBAgIEAJiWjDANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJO +TDEeMBwGA1UECgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSswKQYDVQQDDCJTdGFh +dCBkZXIgTmVkZXJsYW5kZW4gUm9vdCBDQSAtIEcyMB4XDTA4MDMyNjExMTgxN1oX +DTIwMDMyNTExMDMxMFowWjELMAkGA1UEBhMCTkwxHjAcBgNVBAoMFVN0YWF0IGRl +ciBOZWRlcmxhbmRlbjErMCkGA1UEAwwiU3RhYXQgZGVyIE5lZGVybGFuZGVuIFJv +b3QgQ0EgLSBHMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMVZ5291 +qj5LnLW4rJ4L5PnZyqtdj7U5EILXr1HgO+EASGrP2uEGQxGZqhQlEq0i6ABtQ8Sp +uOUfiUtnvWFI7/3S4GCI5bkYYCjDdyutsDeqN95kWSpGV+RLufg3fNU254DBtvPU +Z5uW6M7XxgpT0GtJlvOjCwV3SPcl5XCsMBQgJeN/dVrlSPhOewMHBPqCYYdu8DvE +pMfQ9XQ+pV0aCPKbJdL2rAQmPlU6Yiile7Iwr/g3wtG61jj99O9JMDeZJiFIhQGp +5Rbn3JBV3w/oOM2ZNyFPXfUib2rFEhZgF1XyZWampzCROME4HYYEhLoaJXhena/M +UGDWE4dS7WMfbWV9whUYdMrhfmQpjHLYFhN9C0lK8SgbIHRrxT3dsKpICT0ugpTN +GmXZK4iambwYfp/ufWZ8Pr2UuIHOzZgweMFvZ9C+X+Bo7d7iscksWXiSqt8rYGPy +5V6548r6f1CGPqI0GAwJaCgRHOThuVw+R7oyPxjMW4T182t0xHJ04eOLoEq9jWYv +6q012iDTiIJh8BIitrzQ1aTsr1SIJSQ8p22xcik/Plemf1WvbibG/ufMQFxRRIEK +eN5KzlW/HdXZt1bv8Hb/C3m1r737qWmRRpdogBQ2HbN/uymYNqUg+oJgYjOk7Na6 +B6duxc8UpufWkjTYgfX8HV2qXB72o007uPc5AgMBAAGjgZcwgZQwDwYDVR0TAQH/ +BAUwAwEB/zBSBgNVHSAESzBJMEcGBFUdIAAwPzA9BggrBgEFBQcCARYxaHR0cDov +L3d3dy5wa2lvdmVyaGVpZC5ubC9wb2xpY2llcy9yb290LXBvbGljeS1HMjAOBgNV +HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJFoMocVHYnitfGsNig0jQt8YojrMA0GCSqG +SIb3DQEBCwUAA4ICAQCoQUpnKpKBglBu4dfYszk78wIVCVBR7y29JHuIhjv5tLyS +CZa59sCrI2AGeYwRTlHSeYAz+51IvuxBQ4EffkdAHOV6CMqqi3WtFMTC6GY8ggen +5ieCWxjmD27ZUD6KQhgpxrRW/FYQoAUXvQwjf/ST7ZwaUb7dRUG/kSS0H4zpX897 +IZmflZ85OkYcbPnNe5yQzSipx6lVu6xiNGI1E0sUOlWDuYaNkqbG9AclVMwWVxJK +gnjIFNkXgiYtXSAfea7+1HAWFpWD2DU5/1JddRwWxRNVz0fMdWVSSt7wsKfkCpYL ++63C4iWEst3kvX5ZbJvw8NjnyvLplzh+ib7M+zkXYT9y2zqR2GUBGR2tUKRXCnxL +vJxxcypFURmFzI79R6d0lR2o0a9OF7FpJsKqeFdbxU2n5Z4FF5TKsl+gSRiNNOkm +bEgeqmiSBeGCc1qb3AdbCG19ndeNIdn8FCCqwkXfP+cAslHkwvgFuXkajDTznlvk +N1trSt8sV4pAWja63XVECDdCcAz+3F4hoKOKwJCcaNpQ5kUQR3i2TtJlycM33+FC +Y7BXN0Ute4qcvwXqZVUz9zkQxSgqIXobisQk+T8VyJoVIPVVYpbtbZNQvOSqeK3Z +ywplh6ZmwcSBo3c6WB4L7oOLnR7SUqTMHW+wmG2UMbX4cQrcufx9MmDm66+KAQ== +-----END CERTIFICATE----- + +# Issuer: CN=CA Disig O=Disig a.s. +# Subject: CN=CA Disig O=Disig a.s. +# Label: "CA Disig" +# Serial: 1 +# MD5 Fingerprint: 3f:45:96:39:e2:50:87:f7:bb:fe:98:0c:3c:20:98:e6 +# SHA1 Fingerprint: 2a:c8:d5:8b:57:ce:bf:2f:49:af:f2:fc:76:8f:51:14:62:90:7a:41 +# SHA256 Fingerprint: 92:bf:51:19:ab:ec:ca:d0:b1:33:2d:c4:e1:d0:5f:ba:75:b5:67:90:44:ee:0c:a2:6e:93:1f:74:4f:2f:33:cf +-----BEGIN CERTIFICATE----- +MIIEDzCCAvegAwIBAgIBATANBgkqhkiG9w0BAQUFADBKMQswCQYDVQQGEwJTSzET +MBEGA1UEBxMKQnJhdGlzbGF2YTETMBEGA1UEChMKRGlzaWcgYS5zLjERMA8GA1UE +AxMIQ0EgRGlzaWcwHhcNMDYwMzIyMDEzOTM0WhcNMTYwMzIyMDEzOTM0WjBKMQsw +CQYDVQQGEwJTSzETMBEGA1UEBxMKQnJhdGlzbGF2YTETMBEGA1UEChMKRGlzaWcg +YS5zLjERMA8GA1UEAxMIQ0EgRGlzaWcwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw +ggEKAoIBAQCS9jHBfYj9mQGp2HvycXXxMcbzdWb6UShGhJd4NLxs/LxFWYgmGErE +Nx+hSkS943EE9UQX4j/8SFhvXJ56CbpRNyIjZkMhsDxkovhqFQ4/61HhVKndBpnX +mjxUizkDPw/Fzsbrg3ICqB9x8y34dQjbYkzo+s7552oftms1grrijxaSfQUMbEYD +XcDtab86wYqg6I7ZuUUohwjstMoVvoLdtUSLLa2GDGhibYVW8qwUYzrG0ZmsNHhW +S8+2rT+MitcE5eN4TPWGqvWP+j1scaMtymfraHtuM6kMgiioTGohQBUgDCZbg8Kp +FhXAJIJdKxatymP2dACw30PEEGBWZ2NFAgMBAAGjgf8wgfwwDwYDVR0TAQH/BAUw +AwEB/zAdBgNVHQ4EFgQUjbJJaJ1yCCW5wCf1UJNWSEZx+Y8wDgYDVR0PAQH/BAQD +AgEGMDYGA1UdEQQvMC2BE2Nhb3BlcmF0b3JAZGlzaWcuc2uGFmh0dHA6Ly93d3cu +ZGlzaWcuc2svY2EwZgYDVR0fBF8wXTAtoCugKYYnaHR0cDovL3d3dy5kaXNpZy5z +ay9jYS9jcmwvY2FfZGlzaWcuY3JsMCygKqAohiZodHRwOi8vY2EuZGlzaWcuc2sv +Y2EvY3JsL2NhX2Rpc2lnLmNybDAaBgNVHSAEEzARMA8GDSuBHpGT5goAAAABAQEw +DQYJKoZIhvcNAQEFBQADggEBAF00dGFMrzvY/59tWDYcPQuBDRIrRhCA/ec8J9B6 +yKm2fnQwM6M6int0wHl5QpNt/7EpFIKrIYwvF/k/Ji/1WcbvgAa3mkkp7M5+cTxq +EEHA9tOasnxakZzArFvITV734VP/Q3f8nktnbNfzg9Gg4H8l37iYC5oyOGwwoPP/ +CBUz91BKez6jPiCp3C9WgArtQVCwyfTssuMmRAAOb54GvCKWU3BlxFAKRmukLyeB +EicTXxChds6KezfqwzlhA5WYOudsiCUI/HloDYd9Yvi0X/vF2Ey9WLw/Q1vUHgFN +PGO+I++MzVpQuGhU+QqZMxEA4Z7CRneC9VkGjCFMhwnN5ag= +-----END CERTIFICATE----- + +# Issuer: CN=Juur-SK O=AS Sertifitseerimiskeskus +# Subject: CN=Juur-SK O=AS Sertifitseerimiskeskus +# Label: "Juur-SK" +# Serial: 999181308 +# MD5 Fingerprint: aa:8e:5d:d9:f8:db:0a:58:b7:8d:26:87:6c:82:35:55 +# SHA1 Fingerprint: 40:9d:4b:d9:17:b5:5c:27:b6:9b:64:cb:98:22:44:0d:cd:09:b8:89 +# SHA256 Fingerprint: ec:c3:e9:c3:40:75:03:be:e0:91:aa:95:2f:41:34:8f:f8:8b:aa:86:3b:22:64:be:fa:c8:07:90:15:74:e9:39 +-----BEGIN CERTIFICATE----- +MIIE5jCCA86gAwIBAgIEO45L/DANBgkqhkiG9w0BAQUFADBdMRgwFgYJKoZIhvcN +AQkBFglwa2lAc2suZWUxCzAJBgNVBAYTAkVFMSIwIAYDVQQKExlBUyBTZXJ0aWZp +dHNlZXJpbWlza2Vza3VzMRAwDgYDVQQDEwdKdXVyLVNLMB4XDTAxMDgzMDE0MjMw +MVoXDTE2MDgyNjE0MjMwMVowXTEYMBYGCSqGSIb3DQEJARYJcGtpQHNrLmVlMQsw +CQYDVQQGEwJFRTEiMCAGA1UEChMZQVMgU2VydGlmaXRzZWVyaW1pc2tlc2t1czEQ +MA4GA1UEAxMHSnV1ci1TSzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +AIFxNj4zB9bjMI0TfncyRsvPGbJgMUaXhvSYRqTCZUXP00B841oiqBB4M8yIsdOB +SvZiF3tfTQou0M+LI+5PAk676w7KvRhj6IAcjeEcjT3g/1tf6mTll+g/mX8MCgkz +ABpTpyHhOEvWgxutr2TC+Rx6jGZITWYfGAriPrsfB2WThbkasLnE+w0R9vXW+RvH +LCu3GFH+4Hv2qEivbDtPL+/40UceJlfwUR0zlv/vWT3aTdEVNMfqPxZIe5EcgEMP +PbgFPtGzlc3Yyg/CQ2fbt5PgIoIuvvVoKIO5wTtpeyDaTpxt4brNj3pssAki14sL +2xzVWiZbDcDq5WDQn/413z8CAwEAAaOCAawwggGoMA8GA1UdEwEB/wQFMAMBAf8w +ggEWBgNVHSAEggENMIIBCTCCAQUGCisGAQQBzh8BAQEwgfYwgdAGCCsGAQUFBwIC +MIHDHoHAAFMAZQBlACAAcwBlAHIAdABpAGYAaQBrAGEAYQB0ACAAbwBuACAAdgDk +AGwAagBhAHMAdABhAHQAdQBkACAAQQBTAC0AaQBzACAAUwBlAHIAdABpAGYAaQB0 +AHMAZQBlAHIAaQBtAGkAcwBrAGUAcwBrAHUAcwAgAGEAbABhAG0ALQBTAEsAIABz +AGUAcgB0AGkAZgBpAGsAYQBhAHQAaQBkAGUAIABrAGkAbgBuAGkAdABhAG0AaQBz +AGUAawBzMCEGCCsGAQUFBwIBFhVodHRwOi8vd3d3LnNrLmVlL2Nwcy8wKwYDVR0f +BCQwIjAgoB6gHIYaaHR0cDovL3d3dy5zay5lZS9qdXVyL2NybC8wHQYDVR0OBBYE +FASqekej5ImvGs8KQKcYP2/v6X2+MB8GA1UdIwQYMBaAFASqekej5ImvGs8KQKcY +P2/v6X2+MA4GA1UdDwEB/wQEAwIB5jANBgkqhkiG9w0BAQUFAAOCAQEAe8EYlFOi +CfP+JmeaUOTDBS8rNXiRTHyoERF5TElZrMj3hWVcRrs7EKACr81Ptcw2Kuxd/u+g +kcm2k298gFTsxwhwDY77guwqYHhpNjbRxZyLabVAyJRld/JXIWY7zoVAtjNjGr95 +HvxcHdMdkxuLDF2FvZkwMhgJkVLpfKG6/2SSmuz+Ne6ML678IIbsSt4beDI3poHS +na9aEhbKmVv8b20OxaAehsmR0FyYgl9jDIpaq9iVpszLita/ZEuOyoqysOkhMp6q +qIWYNIE5ITuoOlIyPfZrN4YGWhWY3PARZv40ILcD9EEQfTmEeZZyY7aWAuVrua0Z +TbvGRNs2yyqcjg== +-----END CERTIFICATE----- + +# Issuer: CN=Hongkong Post Root CA 1 O=Hongkong Post +# Subject: CN=Hongkong Post Root CA 1 O=Hongkong Post +# Label: "Hongkong Post Root CA 1" +# Serial: 1000 +# MD5 Fingerprint: a8:0d:6f:39:78:b9:43:6d:77:42:6d:98:5a:cc:23:ca +# SHA1 Fingerprint: d6:da:a8:20:8d:09:d2:15:4d:24:b5:2f:cb:34:6e:b2:58:b2:8a:58 +# SHA256 Fingerprint: f9:e6:7d:33:6c:51:00:2a:c0:54:c6:32:02:2d:66:dd:a2:e7:e3:ff:f1:0a:d0:61:ed:31:d8:bb:b4:10:cf:b2 +-----BEGIN CERTIFICATE----- +MIIDMDCCAhigAwIBAgICA+gwDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UEBhMCSEsx +FjAUBgNVBAoTDUhvbmdrb25nIFBvc3QxIDAeBgNVBAMTF0hvbmdrb25nIFBvc3Qg +Um9vdCBDQSAxMB4XDTAzMDUxNTA1MTMxNFoXDTIzMDUxNTA0NTIyOVowRzELMAkG +A1UEBhMCSEsxFjAUBgNVBAoTDUhvbmdrb25nIFBvc3QxIDAeBgNVBAMTF0hvbmdr +b25nIFBvc3QgUm9vdCBDQSAxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC +AQEArP84tulmAknjorThkPlAj3n54r15/gK97iSSHSL22oVyaf7XPwnU3ZG1ApzQ +jVrhVcNQhrkpJsLj2aDxaQMoIIBFIi1WpztUlVYiWR8o3x8gPW2iNr4joLFutbEn +PzlTCeqrauh0ssJlXI6/fMN4hM2eFvz1Lk8gKgifd/PFHsSaUmYeSF7jEAaPIpjh +ZY4bXSNmO7ilMlHIhqqhqZ5/dpTCpmy3QfDVyAY45tQM4vM7TG1QjMSDJ8EThFk9 +nnV0ttgCXjqQesBCNnLsak3c78QA3xMYV18meMjWCnl3v/evt3a5pQuEF10Q6m/h +q5URX208o1xNg1vysxmKgIsLhwIDAQABoyYwJDASBgNVHRMBAf8ECDAGAQH/AgED +MA4GA1UdDwEB/wQEAwIBxjANBgkqhkiG9w0BAQUFAAOCAQEADkbVPK7ih9legYsC +mEEIjEy82tvuJxuC52pF7BaLT4Wg87JwvVqWuspube5Gi27nKi6Wsxkz67SfqLI3 +7piol7Yutmcn1KZJ/RyTZXaeQi/cImyaT/JaFTmxcdcrUehtHJjA2Sr0oYJ71clB +oiMBdDhViw+5LmeiIAQ32pwL0xch4I+XeTRvhEgCIDMb5jREn5Fw9IBehEPCKdJs +EhTkYY2sEJCehFC78JZvRZ+K88psT/oROhUVRsPNH4NbLUES7VBnQRM9IauUiqpO +fMGx+6fWtScvl6tu4B3i0RwsH0Ti/L6RoZz71ilTc4afU9hDDl3WY4JxHYB0yvbi +AmvZWg== +-----END CERTIFICATE----- + +# Issuer: CN=SecureSign RootCA11 O=Japan Certification Services, Inc. +# Subject: CN=SecureSign RootCA11 O=Japan Certification Services, Inc. +# Label: "SecureSign RootCA11" +# Serial: 1 +# MD5 Fingerprint: b7:52:74:e2:92:b4:80:93:f2:75:e4:cc:d7:f2:ea:26 +# SHA1 Fingerprint: 3b:c4:9f:48:f8:f3:73:a0:9c:1e:bd:f8:5b:b1:c3:65:c7:d8:11:b3 +# SHA256 Fingerprint: bf:0f:ee:fb:9e:3a:58:1a:d5:f9:e9:db:75:89:98:57:43:d2:61:08:5c:4d:31:4f:6f:5d:72:59:aa:42:16:12 +-----BEGIN CERTIFICATE----- +MIIDbTCCAlWgAwIBAgIBATANBgkqhkiG9w0BAQUFADBYMQswCQYDVQQGEwJKUDEr +MCkGA1UEChMiSmFwYW4gQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcywgSW5jLjEcMBoG +A1UEAxMTU2VjdXJlU2lnbiBSb290Q0ExMTAeFw0wOTA0MDgwNDU2NDdaFw0yOTA0 +MDgwNDU2NDdaMFgxCzAJBgNVBAYTAkpQMSswKQYDVQQKEyJKYXBhbiBDZXJ0aWZp +Y2F0aW9uIFNlcnZpY2VzLCBJbmMuMRwwGgYDVQQDExNTZWN1cmVTaWduIFJvb3RD +QTExMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA/XeqpRyQBTvLTJsz +i1oURaTnkBbR31fSIRCkF/3frNYfp+TbfPfs37gD2pRY/V1yfIw/XwFndBWW4wI8 +h9uuywGOwvNmxoVF9ALGOrVisq/6nL+k5tSAMJjzDbaTj6nU2DbysPyKyiyhFTOV +MdrAG/LuYpmGYz+/3ZMqg6h2uRMft85OQoWPIucuGvKVCbIFtUROd6EgvanyTgp9 +UK31BQ1FT0Zx/Sg+U/sE2C3XZR1KG/rPO7AxmjVuyIsG0wCR8pQIZUyxNAYAeoni +8McDWc/V1uinMrPmmECGxc0nEovMe863ETxiYAcjPitAbpSACW22s293bzUIUPsC +h8U+iQIDAQABo0IwQDAdBgNVHQ4EFgQUW/hNT7KlhtQ60vFjmqC+CfZXt94wDgYD +VR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEB +AKChOBZmLqdWHyGcBvod7bkixTgm2E5P7KN/ed5GIaGHd48HCJqypMWvDzKYC3xm +KbabfSVSSUOrTC4rbnpwrxYO4wJs+0LmGJ1F2FXI6Dvd5+H0LgscNFxsWEr7jIhQ +X5Ucv+2rIrVls4W6ng+4reV6G4pQOh29Dbx7VFALuUKvVaAYga1lme++5Jy/xIWr +QbJUb9wlze144o4MjQlJ3WN7WmmWAiGovVJZ6X01y8hSyn+B/tlr0/cR7SXf+Of5 +pPpyl4RTDaXQMhhRdlkUbA/r7F+AjHVDg8OFmP9Mni0N5HeDk061lgeLKBObjBmN +QSdJQO7e5iNEOdyhIta6A/I= +-----END CERTIFICATE----- + +# Issuer: CN=ACEDICOM Root O=EDICOM OU=PKI +# Subject: CN=ACEDICOM Root O=EDICOM OU=PKI +# Label: "ACEDICOM Root" +# Serial: 7029493972724711941 +# MD5 Fingerprint: 42:81:a0:e2:1c:e3:55:10:de:55:89:42:65:96:22:e6 +# SHA1 Fingerprint: e0:b4:32:2e:b2:f6:a5:68:b6:54:53:84:48:18:4a:50:36:87:43:84 +# SHA256 Fingerprint: 03:95:0f:b4:9a:53:1f:3e:19:91:94:23:98:df:a9:e0:ea:32:d7:ba:1c:dd:9b:c8:5d:b5:7e:d9:40:0b:43:4a +-----BEGIN CERTIFICATE----- +MIIFtTCCA52gAwIBAgIIYY3HhjsBggUwDQYJKoZIhvcNAQEFBQAwRDEWMBQGA1UE +AwwNQUNFRElDT00gUm9vdDEMMAoGA1UECwwDUEtJMQ8wDQYDVQQKDAZFRElDT00x +CzAJBgNVBAYTAkVTMB4XDTA4MDQxODE2MjQyMloXDTI4MDQxMzE2MjQyMlowRDEW +MBQGA1UEAwwNQUNFRElDT00gUm9vdDEMMAoGA1UECwwDUEtJMQ8wDQYDVQQKDAZF +RElDT00xCzAJBgNVBAYTAkVTMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKC +AgEA/5KV4WgGdrQsyFhIyv2AVClVYyT/kGWbEHV7w2rbYgIB8hiGtXxaOLHkWLn7 +09gtn70yN78sFW2+tfQh0hOR2QetAQXW8713zl9CgQr5auODAKgrLlUTY4HKRxx7 +XBZXehuDYAQ6PmXDzQHe3qTWDLqO3tkE7hdWIpuPY/1NFgu3e3eM+SW10W2ZEi5P +Grjm6gSSrj0RuVFCPYewMYWveVqc/udOXpJPQ/yrOq2lEiZmueIM15jO1FillUAK +t0SdE3QrwqXrIhWYENiLxQSfHY9g5QYbm8+5eaA9oiM/Qj9r+hwDezCNzmzAv+Yb +X79nuIQZ1RXve8uQNjFiybwCq0Zfm/4aaJQ0PZCOrfbkHQl/Sog4P75n/TSW9R28 +MHTLOO7VbKvU/PQAtwBbhTIWdjPp2KOZnQUAqhbm84F9b32qhm2tFXTTxKJxqvQU +fecyuB+81fFOvW8XAjnXDpVCOscAPukmYxHqC9FK/xidstd7LzrZlvvoHpKuE1XI +2Sf23EgbsCTBheN3nZqk8wwRHQ3ItBTutYJXCb8gWH8vIiPYcMt5bMlL8qkqyPyH +K9caUPgn6C9D4zq92Fdx/c6mUlv53U3t5fZvie27k5x2IXXwkkwp9y+cAS7+UEae +ZAwUswdbxcJzbPEHXEUkFDWug/FqTYl6+rPYLWbwNof1K1MCAwEAAaOBqjCBpzAP +BgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKaz4SsrSbbXc6GqlPUB53NlTKxQ +MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUprPhKytJttdzoaqU9QHnc2VMrFAw +RAYDVR0gBD0wOzA5BgRVHSAAMDEwLwYIKwYBBQUHAgEWI2h0dHA6Ly9hY2VkaWNv +bS5lZGljb21ncm91cC5jb20vZG9jMA0GCSqGSIb3DQEBBQUAA4ICAQDOLAtSUWIm +fQwng4/F9tqgaHtPkl7qpHMyEVNEskTLnewPeUKzEKbHDZ3Ltvo/Onzqv4hTGzz3 +gvoFNTPhNahXwOf9jU8/kzJPeGYDdwdY6ZXIfj7QeQCM8htRM5u8lOk6e25SLTKe +I6RF+7YuE7CLGLHdztUdp0J/Vb77W7tH1PwkzQSulgUV1qzOMPPKC8W64iLgpq0i +5ALudBF/TP94HTXa5gI06xgSYXcGCRZj6hitoocf8seACQl1ThCojz2GuHURwCRi +ipZ7SkXp7FnFvmuD5uHorLUwHv4FB4D54SMNUI8FmP8sX+g7tq3PgbUhh8oIKiMn +MCArz+2UW6yyetLHKKGKC5tNSixthT8Jcjxn4tncB7rrZXtaAWPWkFtPF2Y9fwsZ +o5NjEFIqnxQWWOLcpfShFosOkYuByptZ+thrkQdlVV9SH686+5DdaaVbnG0OLLb6 +zqylfDJKZ0DcMDQj3dcEI2bw/FWAp/tmGYI1Z2JwOV5vx+qQQEQIHriy1tvuWacN +GHk0vFQYXlPKNFHtRQrmjseCNj6nOGOpMCwXEGCSn1WHElkQwg9naRHMTh5+Spqt +r0CodaxWkHS4oJyleW/c6RrIaQXpuvoDs3zk4E7Czp3otkYNbn5XOmeUwssfnHdK +Z05phkOTOPu220+DkdRgfks+KzgHVZhepA== +-----END CERTIFICATE----- + +# Issuer: CN=Microsec e-Szigno Root CA 2009 O=Microsec Ltd. +# Subject: CN=Microsec e-Szigno Root CA 2009 O=Microsec Ltd. +# Label: "Microsec e-Szigno Root CA 2009" +# Serial: 14014712776195784473 +# MD5 Fingerprint: f8:49:f4:03:bc:44:2d:83:be:48:69:7d:29:64:fc:b1 +# SHA1 Fingerprint: 89:df:74:fe:5c:f4:0f:4a:80:f9:e3:37:7d:54:da:91:e1:01:31:8e +# SHA256 Fingerprint: 3c:5f:81:fe:a5:fa:b8:2c:64:bf:a2:ea:ec:af:cd:e8:e0:77:fc:86:20:a7:ca:e5:37:16:3d:f3:6e:db:f3:78 +-----BEGIN CERTIFICATE----- +MIIECjCCAvKgAwIBAgIJAMJ+QwRORz8ZMA0GCSqGSIb3DQEBCwUAMIGCMQswCQYD +VQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0 +ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUtU3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0G +CSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5odTAeFw0wOTA2MTYxMTMwMThaFw0y +OTEyMzAxMTMwMThaMIGCMQswCQYDVQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3Qx +FjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUtU3pp +Z25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5o +dTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOn4j/NjrdqG2KfgQvvP +kd6mJviZpWNwrZuuyjNAfW2WbqEORO7hE52UQlKavXWFdCyoDh2Tthi3jCyoz/tc +cbna7P7ofo/kLx2yqHWH2Leh5TvPmUpG0IMZfcChEhyVbUr02MelTTMuhTlAdX4U +fIASmFDHQWe4oIBhVKZsTh/gnQ4H6cm6M+f+wFUoLAKApxn1ntxVUwOXewdI/5n7 +N4okxFnMUBBjjqqpGrCEGob5X7uxUG6k0QrM1XF+H6cbfPVTbiJfyyvm1HxdrtbC +xkzlBQHZ7Vf8wSN5/PrIJIOV87VqUQHQd9bpEqH5GoP7ghu5sJf0dgYzQ0mg/wu1 ++rUCAwEAAaOBgDB+MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G +A1UdDgQWBBTLD8bfQkPMPcu1SCOhGnqmKrs0aDAfBgNVHSMEGDAWgBTLD8bfQkPM +Pcu1SCOhGnqmKrs0aDAbBgNVHREEFDASgRBpbmZvQGUtc3ppZ25vLmh1MA0GCSqG +SIb3DQEBCwUAA4IBAQDJ0Q5eLtXMs3w+y/w9/w0olZMEyL/azXm4Q5DwpL7v8u8h +mLzU1F0G9u5C7DBsoKqpyvGvivo/C3NqPuouQH4frlRheesuCDfXI/OMn74dseGk +ddug4lQUsbocKaQY9hK6ohQU4zE1yED/t+AFdlfBHFny+L/k7SViXITwfn4fs775 +tyERzAMBVnCnEJIeGzSBHq2cGsMEPO0CYdYeBvNfOofyK/FFh+U9rNHHV4S9a67c +2Pm2G2JwCz02yULyMtd6YebS2z3PyKnJm9zbWETXbzivf3jTo60adbocwTZ8jx5t +HMN1Rq41Bab2XD0h7lbwyYIiLXpUq3DDfSJlgnCW +-----END CERTIFICATE----- + +# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R3 +# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R3 +# Label: "GlobalSign Root CA - R3" +# Serial: 4835703278459759426209954 +# MD5 Fingerprint: c5:df:b8:49:ca:05:13:55:ee:2d:ba:1a:c3:3e:b0:28 +# SHA1 Fingerprint: d6:9b:56:11:48:f0:1c:77:c5:45:78:c1:09:26:df:5b:85:69:76:ad +# SHA256 Fingerprint: cb:b5:22:d7:b7:f1:27:ad:6a:01:13:86:5b:df:1c:d4:10:2e:7d:07:59:af:63:5a:7c:f4:72:0d:c9:63:c5:3b +-----BEGIN CERTIFICATE----- +MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4G +A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNp +Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4 +MTAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMzETMBEG +A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5BngiFvXAg7aEyiie/QV2EcWtiHL8 +RgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0MK66X17YUhhB5uzsT +gHeMCOFJ0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL0gRgykmm +KPZpO/bLyCiR5Z2KYVc3rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zd +QQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjlOCZgdbKfd/+RFO+uIEn8rUAVSNECMWEZ +XriX7613t2Saer9fwRPvm2L7DWzgVGkWqQPabumDk3F2xmmFghcCAwEAAaNCMEAw +DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFI/wS3+o +LkUkrk1Q+mOai97i3Ru8MA0GCSqGSIb3DQEBCwUAA4IBAQBLQNvAUKr+yAzv95ZU +RUm7lgAJQayzE4aGKAczymvmdLm6AC2upArT9fHxD4q/c2dKg8dEe3jgr25sbwMp +jjM5RcOO5LlXbKr8EpbsU8Yt5CRsuZRj+9xTaGdWPoO4zzUhw8lo/s7awlOqzJCK +6fBdRoyV3XpYKBovHd7NADdBj+1EbddTKJd+82cEHhXXipa0095MJ6RMG3NzdvQX +mcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18YIvDQVETI53O9zJrlAGomecs +Mx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7rkpeDMdmztcpH +WD9f +-----END CERTIFICATE----- + +# Issuer: CN=Autoridad de Certificacion Firmaprofesional CIF A62634068 +# Subject: CN=Autoridad de Certificacion Firmaprofesional CIF A62634068 +# Label: "Autoridad de Certificacion Firmaprofesional CIF A62634068" +# Serial: 6047274297262753887 +# MD5 Fingerprint: 73:3a:74:7a:ec:bb:a3:96:a6:c2:e4:e2:c8:9b:c0:c3 +# SHA1 Fingerprint: ae:c5:fb:3f:c8:e1:bf:c4:e5:4f:03:07:5a:9a:e8:00:b7:f7:b6:fa +# SHA256 Fingerprint: 04:04:80:28:bf:1f:28:64:d4:8f:9a:d4:d8:32:94:36:6a:82:88:56:55:3f:3b:14:30:3f:90:14:7f:5d:40:ef +-----BEGIN CERTIFICATE----- +MIIGFDCCA/ygAwIBAgIIU+w77vuySF8wDQYJKoZIhvcNAQEFBQAwUTELMAkGA1UE +BhMCRVMxQjBABgNVBAMMOUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1h +cHJvZmVzaW9uYWwgQ0lGIEE2MjYzNDA2ODAeFw0wOTA1MjAwODM4MTVaFw0zMDEy +MzEwODM4MTVaMFExCzAJBgNVBAYTAkVTMUIwQAYDVQQDDDlBdXRvcmlkYWQgZGUg +Q2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBBNjI2MzQwNjgwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKlmuO6vj78aI14H9M2uDDUtd9 +thDIAl6zQyrET2qyyhxdKJp4ERppWVevtSBC5IsP5t9bpgOSL/UR5GLXMnE42QQM +cas9UX4PB99jBVzpv5RvwSmCwLTaUbDBPLutN0pcyvFLNg4kq7/DhHf9qFD0sefG +L9ItWY16Ck6WaVICqjaY7Pz6FIMMNx/Jkjd/14Et5cS54D40/mf0PmbR0/RAz15i +NA9wBj4gGFrO93IbJWyTdBSTo3OxDqqHECNZXyAFGUftaI6SEspd/NYrspI8IM/h +X68gvqB2f3bl7BqGYTM+53u0P6APjqK5am+5hyZvQWyIplD9amML9ZMWGxmPsu2b +m8mQ9QEM3xk9Dz44I8kvjwzRAv4bVdZO0I08r0+k8/6vKtMFnXkIoctXMbScyJCy +Z/QYFpM6/EfY0XiWMR+6KwxfXZmtY4laJCB22N/9q06mIqqdXuYnin1oKaPnirja +EbsXLZmdEyRG98Xi2J+Of8ePdG1asuhy9azuJBCtLxTa/y2aRnFHvkLfuwHb9H/T +KI8xWVvTyQKmtFLKbpf7Q8UIJm+K9Lv9nyiqDdVF8xM6HdjAeI9BZzwelGSuewvF +6NkBiDkal4ZkQdU7hwxu+g/GvUgUvzlN1J5Bto+WHWOWk9mVBngxaJ43BjuAiUVh +OSPHG0SjFeUc+JIwuwIDAQABo4HvMIHsMBIGA1UdEwEB/wQIMAYBAf8CAQEwDgYD +VR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRlzeurNR4APn7VdMActHNHDhpkLzCBpgYD +VR0gBIGeMIGbMIGYBgRVHSAAMIGPMC8GCCsGAQUFBwIBFiNodHRwOi8vd3d3LmZp +cm1hcHJvZmVzaW9uYWwuY29tL2NwczBcBggrBgEFBQcCAjBQHk4AUABhAHMAZQBv +ACAAZABlACAAbABhACAAQgBvAG4AYQBuAG8AdgBhACAANAA3ACAAQgBhAHIAYwBl +AGwAbwBuAGEAIAAwADgAMAAxADcwDQYJKoZIhvcNAQEFBQADggIBABd9oPm03cXF +661LJLWhAqvdpYhKsg9VSytXjDvlMd3+xDLx51tkljYyGOylMnfX40S2wBEqgLk9 +am58m9Ot/MPWo+ZkKXzR4Tgegiv/J2Wv+xYVxC5xhOW1//qkR71kMrv2JYSiJ0L1 +ILDCExARzRAVukKQKtJE4ZYm6zFIEv0q2skGz3QeqUvVhyj5eTSSPi5E6PaPT481 +PyWzOdxjKpBrIF/EUhJOlywqrJ2X3kjyo2bbwtKDlaZmp54lD+kLM5FlClrD2VQS +3a/DTg4fJl4N3LON7NWBcN7STyQF82xO9UxJZo3R/9ILJUFI/lGExkKvgATP0H5k +SeTy36LssUzAKh3ntLFlosS88Zj0qnAHY7S42jtM+kAiMFsRpvAFDsYCA0irhpuF +3dvd6qJ2gHN99ZwExEWN57kci57q13XRcrHedUTnQn3iV2t93Jm8PYMo6oCTjcVM +ZcFwgbg4/EMxsvYDNEeyrPsiBsse3RdHHF9mudMaotoRsaS8I8nkvof/uZS2+F0g +StRf571oe2XyFR7SOqkt6dhrJKyXWERHrVkY8SFlcN7ONGCoQPHzPKTDKCOM/icz +Q0CgFzzr6juwcqajuUpLXhZI9LK8yIySxZ2frHI2vDSANGupi5LAuBft7HZT9SQB +jLMi6Et8Vcad+qMUu2WFbm5PEn4KPJ2V +-----END CERTIFICATE----- + +# Issuer: CN=Izenpe.com O=IZENPE S.A. +# Subject: CN=Izenpe.com O=IZENPE S.A. +# Label: "Izenpe.com" +# Serial: 917563065490389241595536686991402621 +# MD5 Fingerprint: a6:b0:cd:85:80:da:5c:50:34:a3:39:90:2f:55:67:73 +# SHA1 Fingerprint: 2f:78:3d:25:52:18:a7:4a:65:39:71:b5:2c:a2:9c:45:15:6f:e9:19 +# SHA256 Fingerprint: 25:30:cc:8e:98:32:15:02:ba:d9:6f:9b:1f:ba:1b:09:9e:2d:29:9e:0f:45:48:bb:91:4f:36:3b:c0:d4:53:1f +-----BEGIN CERTIFICATE----- +MIIF8TCCA9mgAwIBAgIQALC3WhZIX7/hy/WL1xnmfTANBgkqhkiG9w0BAQsFADA4 +MQswCQYDVQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6 +ZW5wZS5jb20wHhcNMDcxMjEzMTMwODI4WhcNMzcxMjEzMDgyNzI1WjA4MQswCQYD +VQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6ZW5wZS5j +b20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDJ03rKDx6sp4boFmVq +scIbRTJxldn+EFvMr+eleQGPicPK8lVx93e+d5TzcqQsRNiekpsUOqHnJJAKClaO +xdgmlOHZSOEtPtoKct2jmRXagaKH9HtuJneJWK3W6wyyQXpzbm3benhB6QiIEn6H +LmYRY2xU+zydcsC8Lv/Ct90NduM61/e0aL6i9eOBbsFGb12N4E3GVFWJGjMxCrFX +uaOKmMPsOzTFlUFpfnXCPCDFYbpRR6AgkJOhkEvzTnyFRVSa0QUmQbC1TR0zvsQD +yCV8wXDbO/QJLVQnSKwv4cSsPsjLkkxTOTcj7NMB+eAJRE1NZMDhDVqHIrytG6P+ +JrUV86f8hBnp7KGItERphIPzidF0BqnMC9bC3ieFUCbKF7jJeodWLBoBHmy+E60Q +rLUk9TiRodZL2vG70t5HtfG8gfZZa88ZU+mNFctKy6lvROUbQc/hhqfK0GqfvEyN +BjNaooXlkDWgYlwWTvDjovoDGrQscbNYLN57C9saD+veIR8GdwYDsMnvmfzAuU8L +hij+0rnq49qlw0dpEuDb8PYZi+17cNcC1u2HGCgsBCRMd+RIihrGO5rUD8r6ddIB +QFqNeb+Lz0vPqhbBleStTIo+F5HUsWLlguWABKQDfo2/2n+iD5dPDNMN+9fR5XJ+ +HMh3/1uaD7euBUbl8agW7EekFwIDAQABo4H2MIHzMIGwBgNVHREEgagwgaWBD2lu +Zm9AaXplbnBlLmNvbaSBkTCBjjFHMEUGA1UECgw+SVpFTlBFIFMuQS4gLSBDSUYg +QTAxMzM3MjYwLVJNZXJjLlZpdG9yaWEtR2FzdGVpeiBUMTA1NSBGNjIgUzgxQzBB +BgNVBAkMOkF2ZGEgZGVsIE1lZGl0ZXJyYW5lbyBFdG9yYmlkZWEgMTQgLSAwMTAx +MCBWaXRvcmlhLUdhc3RlaXowDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AQYwHQYDVR0OBBYEFB0cZQ6o8iV7tJHP5LGx5r1VdGwFMA0GCSqGSIb3DQEBCwUA +A4ICAQB4pgwWSp9MiDrAyw6lFn2fuUhfGI8NYjb2zRlrrKvV9pF9rnHzP7MOeIWb +laQnIUdCSnxIOvVFfLMMjlF4rJUT3sb9fbgakEyrkgPH7UIBzg/YsfqikuFgba56 +awmqxinuaElnMIAkejEWOVt+8Rwu3WwJrfIxwYJOubv5vr8qhT/AQKM6WfxZSzwo +JNu0FXWuDYi6LnPAvViH5ULy617uHjAimcs30cQhbIHsvm0m5hzkQiCeR7Csg1lw +LDXWrzY0tM07+DKo7+N4ifuNRSzanLh+QBxh5z6ikixL8s36mLYp//Pye6kfLqCT +VyvehQP5aTfLnnhqBbTFMXiJ7HqnheG5ezzevh55hM6fcA5ZwjUukCox2eRFekGk +LhObNA5me0mrZJfQRsN5nXJQY6aYWwa9SG3YOYNw6DXwBdGqvOPbyALqfP2C2sJb +UjWumDqtujWTI6cfSN01RpiyEGjkpTHCClguGYEQyVB1/OpaFs4R1+7vUIgtYf8/ +QnMFlEPVjjxOAToZpR9GTnfQXeWBIiGH/pR9hNiTrdZoQ0iy2+tzJOeRf1SktoA+ +naM8THLCV8Sg1Mw4J87VBp6iSNnpn86CcDaTmjvfliHjWbcM2pE38P1ZWrOZyGls +QyYBNWNgVYkDOnXYukrZVP/u3oDYLdE41V4tC5h9Pmzb/CaIxw== +-----END CERTIFICATE----- + +# Issuer: CN=Chambers of Commerce Root - 2008 O=AC Camerfirma S.A. +# Subject: CN=Chambers of Commerce Root - 2008 O=AC Camerfirma S.A. +# Label: "Chambers of Commerce Root - 2008" +# Serial: 11806822484801597146 +# MD5 Fingerprint: 5e:80:9e:84:5a:0e:65:0b:17:02:f3:55:18:2a:3e:d7 +# SHA1 Fingerprint: 78:6a:74:ac:76:ab:14:7f:9c:6a:30:50:ba:9e:a8:7e:fe:9a:ce:3c +# SHA256 Fingerprint: 06:3e:4a:fa:c4:91:df:d3:32:f3:08:9b:85:42:e9:46:17:d8:93:d7:fe:94:4e:10:a7:93:7e:e2:9d:96:93:c0 +-----BEGIN CERTIFICATE----- +MIIHTzCCBTegAwIBAgIJAKPaQn6ksa7aMA0GCSqGSIb3DQEBBQUAMIGuMQswCQYD +VQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0 +IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3 +MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xKTAnBgNVBAMTIENoYW1iZXJz +IG9mIENvbW1lcmNlIFJvb3QgLSAyMDA4MB4XDTA4MDgwMTEyMjk1MFoXDTM4MDcz +MTEyMjk1MFowga4xCzAJBgNVBAYTAkVVMUMwQQYDVQQHEzpNYWRyaWQgKHNlZSBj +dXJyZW50IGFkZHJlc3MgYXQgd3d3LmNhbWVyZmlybWEuY29tL2FkZHJlc3MpMRIw +EAYDVQQFEwlBODI3NDMyODcxGzAZBgNVBAoTEkFDIENhbWVyZmlybWEgUy5BLjEp +MCcGA1UEAxMgQ2hhbWJlcnMgb2YgQ29tbWVyY2UgUm9vdCAtIDIwMDgwggIiMA0G +CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCvAMtwNyuAWko6bHiUfaN/Gh/2NdW9 +28sNRHI+JrKQUrpjOyhYb6WzbZSm891kDFX29ufyIiKAXuFixrYp4YFs8r/lfTJq +VKAyGVn+H4vXPWCGhSRv4xGzdz4gljUha7MI2XAuZPeEklPWDrCQiorjh40G072Q +DuKZoRuGDtqaCrsLYVAGUvGef3bsyw/QHg3PmTA9HMRFEFis1tPo1+XqxQEHd9ZR +5gN/ikilTWh1uem8nk4ZcfUyS5xtYBkL+8ydddy/Js2Pk3g5eXNeJQ7KXOt3EgfL +ZEFHcpOrUMPrCXZkNNI5t3YRCQ12RcSprj1qr7V9ZS+UWBDsXHyvfuK2GNnQm05a +Sd+pZgvMPMZ4fKecHePOjlO+Bd5gD2vlGts/4+EhySnB8esHnFIbAURRPHsl18Tl +UlRdJQfKFiC4reRB7noI/plvg6aRArBsNlVq5331lubKgdaX8ZSD6e2wsWsSaR6s ++12pxZjptFtYer49okQ6Y1nUCyXeG0+95QGezdIp1Z8XGQpvvwyQ0wlf2eOKNcx5 +Wk0ZN5K3xMGtr/R5JJqyAQuxr1yW84Ay+1w9mPGgP0revq+ULtlVmhduYJ1jbLhj +ya6BXBg14JC7vjxPNyK5fuvPnnchpj04gftI2jE9K+OJ9dC1vX7gUMQSibMjmhAx +hduub+84Mxh2EQIDAQABo4IBbDCCAWgwEgYDVR0TAQH/BAgwBgEB/wIBDDAdBgNV +HQ4EFgQU+SSsD7K1+HnA+mCIG8TZTQKeFxkwgeMGA1UdIwSB2zCB2IAU+SSsD7K1 ++HnA+mCIG8TZTQKeFxmhgbSkgbEwga4xCzAJBgNVBAYTAkVVMUMwQQYDVQQHEzpN +YWRyaWQgKHNlZSBjdXJyZW50IGFkZHJlc3MgYXQgd3d3LmNhbWVyZmlybWEuY29t +L2FkZHJlc3MpMRIwEAYDVQQFEwlBODI3NDMyODcxGzAZBgNVBAoTEkFDIENhbWVy +ZmlybWEgUy5BLjEpMCcGA1UEAxMgQ2hhbWJlcnMgb2YgQ29tbWVyY2UgUm9vdCAt +IDIwMDiCCQCj2kJ+pLGu2jAOBgNVHQ8BAf8EBAMCAQYwPQYDVR0gBDYwNDAyBgRV +HSAAMCowKAYIKwYBBQUHAgEWHGh0dHA6Ly9wb2xpY3kuY2FtZXJmaXJtYS5jb20w +DQYJKoZIhvcNAQEFBQADggIBAJASryI1wqM58C7e6bXpeHxIvj99RZJe6dqxGfwW +PJ+0W2aeaufDuV2I6A+tzyMP3iU6XsxPpcG1Lawk0lgH3qLPaYRgM+gQDROpI9CF +5Y57pp49chNyM/WqfcZjHwj0/gF/JM8rLFQJ3uIrbZLGOU8W6jx+ekbURWpGqOt1 +glanq6B8aBMz9p0w8G8nOSQjKpD9kCk18pPfNKXG9/jvjA9iSnyu0/VU+I22mlaH +FoI6M6taIgj3grrqLuBHmrS1RaMFO9ncLkVAO+rcf+g769HsJtg1pDDFOqxXnrN2 +pSB7+R5KBWIBpih1YJeSDW4+TTdDDZIVnBgizVGZoCkaPF+KMjNbMMeJL0eYD6MD +xvbxrN8y8NmBGuScvfaAFPDRLLmF9dijscilIeUcE5fuDr3fKanvNFNb0+RqE4QG +tjICxFKuItLcsiFCGtpA8CnJ7AoMXOLQusxI0zcKzBIKinmwPQN/aUv0NCB9szTq +jktk9T79syNnFQ0EuPAtwQlRPLJsFfClI9eDdOTlLsn+mCdCxqvGnrDQWzilm1De +fhiYtUU79nm06PcaewaD+9CL2rvHvRirCG88gGtAPxkZumWK5r7VXNM21+9AUiRg +OGcEMeyP84LG3rlV8zsxkVrctQgVrXYlCg17LofiDKYGvCYQbTed7N14jHyAxfDZ +d0jQ +-----END CERTIFICATE----- + +# Issuer: CN=Global Chambersign Root - 2008 O=AC Camerfirma S.A. +# Subject: CN=Global Chambersign Root - 2008 O=AC Camerfirma S.A. +# Label: "Global Chambersign Root - 2008" +# Serial: 14541511773111788494 +# MD5 Fingerprint: 9e:80:ff:78:01:0c:2e:c1:36:bd:fe:96:90:6e:08:f3 +# SHA1 Fingerprint: 4a:bd:ee:ec:95:0d:35:9c:89:ae:c7:52:a1:2c:5b:29:f6:d6:aa:0c +# SHA256 Fingerprint: 13:63:35:43:93:34:a7:69:80:16:a0:d3:24:de:72:28:4e:07:9d:7b:52:20:bb:8f:bd:74:78:16:ee:be:ba:ca +-----BEGIN CERTIFICATE----- +MIIHSTCCBTGgAwIBAgIJAMnN0+nVfSPOMA0GCSqGSIb3DQEBBQUAMIGsMQswCQYD +VQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0 +IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3 +MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xJzAlBgNVBAMTHkdsb2JhbCBD +aGFtYmVyc2lnbiBSb290IC0gMjAwODAeFw0wODA4MDExMjMxNDBaFw0zODA3MzEx +MjMxNDBaMIGsMQswCQYDVQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUgY3Vy +cmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAG +A1UEBRMJQTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xJzAl +BgNVBAMTHkdsb2JhbCBDaGFtYmVyc2lnbiBSb290IC0gMjAwODCCAiIwDQYJKoZI +hvcNAQEBBQADggIPADCCAgoCggIBAMDfVtPkOpt2RbQT2//BthmLN0EYlVJH6xed +KYiONWwGMi5HYvNJBL99RDaxccy9Wglz1dmFRP+RVyXfXjaOcNFccUMd2drvXNL7 +G706tcuto8xEpw2uIRU/uXpbknXYpBI4iRmKt4DS4jJvVpyR1ogQC7N0ZJJ0YPP2 +zxhPYLIj0Mc7zmFLmY/CDNBAspjcDahOo7kKrmCgrUVSY7pmvWjg+b4aqIG7HkF4 +ddPB/gBVsIdU6CeQNR1MM62X/JcumIS/LMmjv9GYERTtY/jKmIhYF5ntRQOXfjyG +HoiMvvKRhI9lNNgATH23MRdaKXoKGCQwoze1eqkBfSbW+Q6OWfH9GzO1KTsXO0G2 +Id3UwD2ln58fQ1DJu7xsepeY7s2MH/ucUa6LcL0nn3HAa6x9kGbo1106DbDVwo3V +yJ2dwW3Q0L9R5OP4wzg2rtandeavhENdk5IMagfeOx2YItaswTXbo6Al/3K1dh3e +beksZixShNBFks4c5eUzHdwHU1SjqoI7mjcv3N2gZOnm3b2u/GSFHTynyQbehP9r +6GsaPMWis0L7iwk+XwhSx2LE1AVxv8Rk5Pihg+g+EpuoHtQ2TS9x9o0o9oOpE9Jh +wZG7SMA0j0GMS0zbaRL/UJScIINZc+18ofLx/d33SdNDWKBWY8o9PeU1VlnpDsog +zCtLkykPAgMBAAGjggFqMIIBZjASBgNVHRMBAf8ECDAGAQH/AgEMMB0GA1UdDgQW +BBS5CcqcHtvTbDprru1U8VuTBjUuXjCB4QYDVR0jBIHZMIHWgBS5CcqcHtvTbDpr +ru1U8VuTBjUuXqGBsqSBrzCBrDELMAkGA1UEBhMCRVUxQzBBBgNVBAcTOk1hZHJp +ZCAoc2VlIGN1cnJlbnQgYWRkcmVzcyBhdCB3d3cuY2FtZXJmaXJtYS5jb20vYWRk +cmVzcykxEjAQBgNVBAUTCUE4Mjc0MzI4NzEbMBkGA1UEChMSQUMgQ2FtZXJmaXJt +YSBTLkEuMScwJQYDVQQDEx5HbG9iYWwgQ2hhbWJlcnNpZ24gUm9vdCAtIDIwMDiC +CQDJzdPp1X0jzjAOBgNVHQ8BAf8EBAMCAQYwPQYDVR0gBDYwNDAyBgRVHSAAMCow +KAYIKwYBBQUHAgEWHGh0dHA6Ly9wb2xpY3kuY2FtZXJmaXJtYS5jb20wDQYJKoZI +hvcNAQEFBQADggIBAICIf3DekijZBZRG/5BXqfEv3xoNa/p8DhxJJHkn2EaqbylZ +UohwEurdPfWbU1Rv4WCiqAm57OtZfMY18dwY6fFn5a+6ReAJ3spED8IXDneRRXoz +X1+WLGiLwUePmJs9wOzL9dWCkoQ10b42OFZyMVtHLaoXpGNR6woBrX/sdZ7LoR/x +fxKxueRkf2fWIyr0uDldmOghp+G9PUIadJpwr2hsUF1Jz//7Dl3mLEfXgTpZALVz +a2Mg9jFFCDkO9HB+QHBaP9BrQql0PSgvAm11cpUJjUhjxsYjV5KTXjXBjfkK9yyd +Yhz2rXzdpjEetrHHfoUm+qRqtdpjMNHvkzeyZi99Bffnt0uYlDXA2TopwZ2yUDMd +SqlapskD7+3056huirRXhOukP9DuqqqHW2Pok+JrqNS4cnhrG+055F3Lm6qH1U9O +AP7Zap88MQ8oAgF9mOinsKJknnn4SPIVqczmyETrP3iZ8ntxPjzxmKfFGBI/5rso +M0LpRQp8bfKGeS/Fghl9CYl8slR2iK7ewfPM4W7bMdaTrpmg7yVqc5iJWzouE4ge +v8CSlDQb4ye3ix5vQv/n6TebUB0tovkC7stYWDpxvGjjqsGvHCgfotwjZT+B6q6Z +09gwzxMNTxXJhLynSC34MCN32EZLeW32jO06f2ARePTpm67VVMB0gNELQp/B +-----END CERTIFICATE----- + +# Issuer: CN=Go Daddy Root Certificate Authority - G2 O=GoDaddy.com, Inc. +# Subject: CN=Go Daddy Root Certificate Authority - G2 O=GoDaddy.com, Inc. +# Label: "Go Daddy Root Certificate Authority - G2" +# Serial: 0 +# MD5 Fingerprint: 80:3a:bc:22:c1:e6:fb:8d:9b:3b:27:4a:32:1b:9a:01 +# SHA1 Fingerprint: 47:be:ab:c9:22:ea:e8:0e:78:78:34:62:a7:9f:45:c2:54:fd:e6:8b +# SHA256 Fingerprint: 45:14:0b:32:47:eb:9c:c8:c5:b4:f0:d7:b5:30:91:f7:32:92:08:9e:6e:5a:63:e2:74:9d:d3:ac:a9:19:8e:da +-----BEGIN CERTIFICATE----- +MIIDxTCCAq2gAwIBAgIBADANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMx +EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAYBgNVBAoT +EUdvRGFkZHkuY29tLCBJbmMuMTEwLwYDVQQDEyhHbyBEYWRkeSBSb290IENlcnRp +ZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIz +NTk1OVowgYMxCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6b25hMRMwEQYDVQQH +EwpTY290dHNkYWxlMRowGAYDVQQKExFHb0RhZGR5LmNvbSwgSW5jLjExMC8GA1UE +AxMoR28gRGFkZHkgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL9xYgjx+lk09xvJGKP3gElY6SKD +E6bFIEMBO4Tx5oVJnyfq9oQbTqC023CYxzIBsQU+B07u9PpPL1kwIuerGVZr4oAH +/PMWdYA5UXvl+TW2dE6pjYIT5LY/qQOD+qK+ihVqf94Lw7YZFAXK6sOoBJQ7Rnwy +DfMAZiLIjWltNowRGLfTshxgtDj6AozO091GB94KPutdfMh8+7ArU6SSYmlRJQVh +GkSBjCypQ5Yj36w6gZoOKcUcqeldHraenjAKOc7xiID7S13MMuyFYkMlNAJWJwGR +tDtwKj9useiciAF9n9T521NtYJ2/LOdYq7hfRvzOxBsDPAnrSTFcaUaz4EcCAwEA +AaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE +FDqahQcQZyi27/a9BUFuIMGU2g/eMA0GCSqGSIb3DQEBCwUAA4IBAQCZ21151fmX +WWcDYfF+OwYxdS2hII5PZYe096acvNjpL9DbWu7PdIxztDhC2gV7+AJ1uP2lsdeu +9tfeE8tTEH6KRtGX+rcuKxGrkLAngPnon1rpN5+r5N9ss4UXnT3ZJE95kTXWXwTr +gIOrmgIttRD02JDHBHNA7XIloKmf7J6raBKZV8aPEjoJpL1E/QYVN8Gb5DKj7Tjo +2GTzLH4U/ALqn83/B2gX2yKQOC16jdFU8WnjXzPKej17CuPKf1855eJ1usV2GDPO +LPAvTK33sefOT6jEm0pUBsV/fdUID+Ic/n4XuKxe9tQWskMJDE32p2u0mYRlynqI +4uJEvlz36hz1 +-----END CERTIFICATE----- + +# Issuer: CN=Starfield Root Certificate Authority - G2 O=Starfield Technologies, Inc. +# Subject: CN=Starfield Root Certificate Authority - G2 O=Starfield Technologies, Inc. +# Label: "Starfield Root Certificate Authority - G2" +# Serial: 0 +# MD5 Fingerprint: d6:39:81:c6:52:7e:96:69:fc:fc:ca:66:ed:05:f2:96 +# SHA1 Fingerprint: b5:1c:06:7c:ee:2b:0c:3d:f8:55:ab:2d:92:f4:fe:39:d4:e7:0f:0e +# SHA256 Fingerprint: 2c:e1:cb:0b:f9:d2:f9:e1:02:99:3f:be:21:51:52:c3:b2:dd:0c:ab:de:1c:68:e5:31:9b:83:91:54:db:b7:f5 +-----BEGIN CERTIFICATE----- +MIID3TCCAsWgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBjzELMAkGA1UEBhMCVVMx +EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT +HFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAMTKVN0YXJmaWVs +ZCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAw +MFoXDTM3MTIzMTIzNTk1OVowgY8xCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6 +b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFyZmllbGQgVGVj +aG5vbG9naWVzLCBJbmMuMTIwMAYDVQQDEylTdGFyZmllbGQgUm9vdCBDZXJ0aWZp +Y2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAL3twQP89o/8ArFvW59I2Z154qK3A2FWGMNHttfKPTUuiUP3oWmb3ooa/RMg +nLRJdzIpVv257IzdIvpy3Cdhl+72WoTsbhm5iSzchFvVdPtrX8WJpRBSiUZV9Lh1 +HOZ/5FSuS/hVclcCGfgXcVnrHigHdMWdSL5stPSksPNkN3mSwOxGXn/hbVNMYq/N +Hwtjuzqd+/x5AJhhdM8mgkBj87JyahkNmcrUDnXMN/uLicFZ8WJ/X7NfZTD4p7dN +dloedl40wOiWVpmKs/B/pM293DIxfJHP4F8R+GuqSVzRmZTRouNjWwl2tVZi4Ut0 +HZbUJtQIBFnQmA4O5t78w+wfkPECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAO +BgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFHwMMh+n2TB/xH1oo2Kooc6rB1snMA0G +CSqGSIb3DQEBCwUAA4IBAQARWfolTwNvlJk7mh+ChTnUdgWUXuEok21iXQnCoKjU +sHU48TRqneSfioYmUeYs0cYtbpUgSpIB7LiKZ3sx4mcujJUDJi5DnUox9g61DLu3 +4jd/IroAow57UvtruzvE03lRTs2Q9GcHGcg8RnoNAX3FWOdt5oUwF5okxBDgBPfg +8n/Uqgr/Qh037ZTlZFkSIHc40zI+OIF1lnP6aI+xy84fxez6nH7PfrHxBy22/L/K +pL/QlwVKvOoYKAKQvVR4CSFx09F9HdkWsKlhPdAKACL8x3vLCWRFCztAgfd9fDL1 +mMpYjn0q7pBZc2T5NnReJaH1ZgUufzkVqSr7UIuOhWn0 +-----END CERTIFICATE----- + +# Issuer: CN=Starfield Services Root Certificate Authority - G2 O=Starfield Technologies, Inc. +# Subject: CN=Starfield Services Root Certificate Authority - G2 O=Starfield Technologies, Inc. +# Label: "Starfield Services Root Certificate Authority - G2" +# Serial: 0 +# MD5 Fingerprint: 17:35:74:af:7b:61:1c:eb:f4:f9:3c:e2:ee:40:f9:a2 +# SHA1 Fingerprint: 92:5a:8f:8d:2c:6d:04:e0:66:5f:59:6a:ff:22:d8:63:e8:25:6f:3f +# SHA256 Fingerprint: 56:8d:69:05:a2:c8:87:08:a4:b3:02:51:90:ed:cf:ed:b1:97:4a:60:6a:13:c6:e5:29:0f:cb:2a:e6:3e:da:b5 +-----BEGIN CERTIFICATE----- +MIID7zCCAtegAwIBAgIBADANBgkqhkiG9w0BAQsFADCBmDELMAkGA1UEBhMCVVMx +EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT +HFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xOzA5BgNVBAMTMlN0YXJmaWVs +ZCBTZXJ2aWNlcyBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5 +MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgZgxCzAJBgNVBAYTAlVTMRAwDgYD +VQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFy +ZmllbGQgVGVjaG5vbG9naWVzLCBJbmMuMTswOQYDVQQDEzJTdGFyZmllbGQgU2Vy +dmljZXMgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBANUMOsQq+U7i9b4Zl1+OiFOxHz/Lz58gE20p +OsgPfTz3a3Y4Y9k2YKibXlwAgLIvWX/2h/klQ4bnaRtSmpDhcePYLQ1Ob/bISdm2 +8xpWriu2dBTrz/sm4xq6HZYuajtYlIlHVv8loJNwU4PahHQUw2eeBGg6345AWh1K +Ts9DkTvnVtYAcMtS7nt9rjrnvDH5RfbCYM8TWQIrgMw0R9+53pBlbQLPLJGmpufe +hRhJfGZOozptqbXuNC66DQO4M99H67FrjSXZm86B0UVGMpZwh94CDklDhbZsc7tk +6mFBrMnUVN+HL8cisibMn1lUaJ/8viovxFUcdUBgF4UCVTmLfwUCAwEAAaNCMEAw +DwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJxfAN+q +AdcwKziIorhtSpzyEZGDMA0GCSqGSIb3DQEBCwUAA4IBAQBLNqaEd2ndOxmfZyMI +bw5hyf2E3F/YNoHN2BtBLZ9g3ccaaNnRbobhiCPPE95Dz+I0swSdHynVv/heyNXB +ve6SbzJ08pGCL72CQnqtKrcgfU28elUSwhXqvfdqlS5sdJ/PHLTyxQGjhdByPq1z +qwubdQxtRbeOlKyWN7Wg0I8VRw7j6IPdj/3vQQF3zCepYoUz8jcI73HPdwbeyBkd +iEDPfUYd/x7H4c7/I9vG+o1VTqkC50cRRj70/b17KSa7qWFiNyi2LSr2EIZkyXCn +0q23KXB56jzaYyWf/Wi3MOxw+3WKt21gZ7IeyLnp2KhvAotnDU0mV3HaIPzBSlCN +sSi6 +-----END CERTIFICATE----- + +# Issuer: CN=AffirmTrust Commercial O=AffirmTrust +# Subject: CN=AffirmTrust Commercial O=AffirmTrust +# Label: "AffirmTrust Commercial" +# Serial: 8608355977964138876 +# MD5 Fingerprint: 82:92:ba:5b:ef:cd:8a:6f:a6:3d:55:f9:84:f6:d6:b7 +# SHA1 Fingerprint: f9:b5:b6:32:45:5f:9c:be:ec:57:5f:80:dc:e9:6e:2c:c7:b2:78:b7 +# SHA256 Fingerprint: 03:76:ab:1d:54:c5:f9:80:3c:e4:b2:e2:01:a0:ee:7e:ef:7b:57:b6:36:e8:a9:3c:9b:8d:48:60:c9:6f:5f:a7 +-----BEGIN CERTIFICATE----- +MIIDTDCCAjSgAwIBAgIId3cGJyapsXwwDQYJKoZIhvcNAQELBQAwRDELMAkGA1UE +BhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVz +dCBDb21tZXJjaWFsMB4XDTEwMDEyOTE0MDYwNloXDTMwMTIzMTE0MDYwNlowRDEL +MAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZp +cm1UcnVzdCBDb21tZXJjaWFsMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC +AQEA9htPZwcroRX1BiLLHwGy43NFBkRJLLtJJRTWzsO3qyxPxkEylFf6EqdbDuKP +Hx6GGaeqtS25Xw2Kwq+FNXkyLbscYjfysVtKPcrNcV/pQr6U6Mje+SJIZMblq8Yr +ba0F8PrVC8+a5fBQpIs7R6UjW3p6+DM/uO+Zl+MgwdYoic+U+7lF7eNAFxHUdPAL +MeIrJmqbTFeurCA+ukV6BfO9m2kVrn1OIGPENXY6BwLJN/3HR+7o8XYdcxXyl6S1 +yHp52UKqK39c/s4mT6NmgTWvRLpUHhwwMmWd5jyTXlBOeuM61G7MGvv50jeuJCqr +VwMiKA1JdX+3KNp1v47j3A55MQIDAQABo0IwQDAdBgNVHQ4EFgQUnZPGU4teyq8/ +nx4P5ZmVvCT2lI8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwDQYJ +KoZIhvcNAQELBQADggEBAFis9AQOzcAN/wr91LoWXym9e2iZWEnStB03TX8nfUYG +XUPGhi4+c7ImfU+TqbbEKpqrIZcUsd6M06uJFdhrJNTxFq7YpFzUf1GO7RgBsZNj +vbz4YYCanrHOQnDiqX0GJX0nof5v7LMeJNrjS1UaADs1tDvZ110w/YETifLCBivt +Z8SOyUOyXGsViQK8YvxO8rUzqrJv0wqiUOP2O+guRMLbZjipM1ZI8W0bM40NjD9g +N53Tym1+NH4Nn3J2ixufcv1SNUFFApYvHLKac0khsUlHRUe072o0EclNmsxZt9YC +nlpOZbWUrhvfKbAW8b8Angc6F2S1BLUjIZkKlTuXfO8= +-----END CERTIFICATE----- + +# Issuer: CN=AffirmTrust Networking O=AffirmTrust +# Subject: CN=AffirmTrust Networking O=AffirmTrust +# Label: "AffirmTrust Networking" +# Serial: 8957382827206547757 +# MD5 Fingerprint: 42:65:ca:be:01:9a:9a:4c:a9:8c:41:49:cd:c0:d5:7f +# SHA1 Fingerprint: 29:36:21:02:8b:20:ed:02:f5:66:c5:32:d1:d6:ed:90:9f:45:00:2f +# SHA256 Fingerprint: 0a:81:ec:5a:92:97:77:f1:45:90:4a:f3:8d:5d:50:9f:66:b5:e2:c5:8f:cd:b5:31:05:8b:0e:17:f3:f0:b4:1b +-----BEGIN CERTIFICATE----- +MIIDTDCCAjSgAwIBAgIIfE8EORzUmS0wDQYJKoZIhvcNAQEFBQAwRDELMAkGA1UE +BhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVz +dCBOZXR3b3JraW5nMB4XDTEwMDEyOTE0MDgyNFoXDTMwMTIzMTE0MDgyNFowRDEL +MAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZp +cm1UcnVzdCBOZXR3b3JraW5nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC +AQEAtITMMxcua5Rsa2FSoOujz3mUTOWUgJnLVWREZY9nZOIG41w3SfYvm4SEHi3y +YJ0wTsyEheIszx6e/jarM3c1RNg1lho9Nuh6DtjVR6FqaYvZ/Ls6rnla1fTWcbua +kCNrmreIdIcMHl+5ni36q1Mr3Lt2PpNMCAiMHqIjHNRqrSK6mQEubWXLviRmVSRL +QESxG9fhwoXA3hA/Pe24/PHxI1Pcv2WXb9n5QHGNfb2V1M6+oF4nI979ptAmDgAp +6zxG8D1gvz9Q0twmQVGeFDdCBKNwV6gbh+0t+nvujArjqWaJGctB+d1ENmHP4ndG +yH329JKBNv3bNPFyfvMMFr20FQIDAQABo0IwQDAdBgNVHQ4EFgQUBx/S55zawm6i +QLSwelAQUHTEyL0wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwDQYJ +KoZIhvcNAQEFBQADggEBAIlXshZ6qML91tmbmzTCnLQyFE2npN/svqe++EPbkTfO +tDIuUFUaNU52Q3Eg75N3ThVwLofDwR1t3Mu1J9QsVtFSUzpE0nPIxBsFZVpikpzu +QY0x2+c06lkh1QF612S4ZDnNye2v7UsDSKegmQGA3GWjNq5lWUhPgkvIZfFXHeVZ +Lgo/bNjR9eUJtGxUAArgFU2HdW23WJZa3W3SAKD0m0i+wzekujbgfIeFlxoVot4u +olu9rxj5kFDNcFn4J2dHy8egBzp90SxdbBk6ZrV9/ZFvgrG+CJPbFEfxojfHRZ48 +x3evZKiT3/Zpg4Jg8klCNO1aAFSFHBY2kgxc+qatv9s= +-----END CERTIFICATE----- + +# Issuer: CN=AffirmTrust Premium O=AffirmTrust +# Subject: CN=AffirmTrust Premium O=AffirmTrust +# Label: "AffirmTrust Premium" +# Serial: 7893706540734352110 +# MD5 Fingerprint: c4:5d:0e:48:b6:ac:28:30:4e:0a:bc:f9:38:16:87:57 +# SHA1 Fingerprint: d8:a6:33:2c:e0:03:6f:b1:85:f6:63:4f:7d:6a:06:65:26:32:28:27 +# SHA256 Fingerprint: 70:a7:3f:7f:37:6b:60:07:42:48:90:45:34:b1:14:82:d5:bf:0e:69:8e:cc:49:8d:f5:25:77:eb:f2:e9:3b:9a +-----BEGIN CERTIFICATE----- +MIIFRjCCAy6gAwIBAgIIbYwURrGmCu4wDQYJKoZIhvcNAQEMBQAwQTELMAkGA1UE +BhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MRwwGgYDVQQDDBNBZmZpcm1UcnVz +dCBQcmVtaXVtMB4XDTEwMDEyOTE0MTAzNloXDTQwMTIzMTE0MTAzNlowQTELMAkG +A1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MRwwGgYDVQQDDBNBZmZpcm1U +cnVzdCBQcmVtaXVtMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxBLf +qV/+Qd3d9Z+K4/as4Tx4mrzY8H96oDMq3I0gW64tb+eT2TZwamjPjlGjhVtnBKAQ +JG9dKILBl1fYSCkTtuG+kU3fhQxTGJoeJKJPj/CihQvL9Cl/0qRY7iZNyaqoe5rZ ++jjeRFcV5fiMyNlI4g0WJx0eyIOFJbe6qlVBzAMiSy2RjYvmia9mx+n/K+k8rNrS +s8PhaJyJ+HoAVt70VZVs+7pk3WKL3wt3MutizCaam7uqYoNMtAZ6MMgpv+0GTZe5 +HMQxK9VfvFMSF5yZVylmd2EhMQcuJUmdGPLu8ytxjLW6OQdJd/zvLpKQBY0tL3d7 +70O/Nbua2Plzpyzy0FfuKE4mX4+QaAkvuPjcBukumj5Rp9EixAqnOEhss/n/fauG +V+O61oV4d7pD6kh/9ti+I20ev9E2bFhc8e6kGVQa9QPSdubhjL08s9NIS+LI+H+S +qHZGnEJlPqQewQcDWkYtuJfzt9WyVSHvutxMAJf7FJUnM7/oQ0dG0giZFmA7mn7S +5u046uwBHjxIVkkJx0w3AJ6IDsBz4W9m6XJHMD4Q5QsDyZpCAGzFlH5hxIrff4Ia +C1nEWTJ3s7xgaVY5/bQGeyzWZDbZvUjthB9+pSKPKrhC9IK31FOQeE4tGv2Bb0TX +OwF0lkLgAOIua+rF7nKsu7/+6qqo+Nz2snmKtmcCAwEAAaNCMEAwHQYDVR0OBBYE +FJ3AZ6YMItkm9UWrpmVSESfYRaxjMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/ +BAQDAgEGMA0GCSqGSIb3DQEBDAUAA4ICAQCzV00QYk465KzquByvMiPIs0laUZx2 +KI15qldGF9X1Uva3ROgIRL8YhNILgM3FEv0AVQVhh0HctSSePMTYyPtwni94loMg +Nt58D2kTiKV1NpgIpsbfrM7jWNa3Pt668+s0QNiigfV4Py/VpfzZotReBA4Xrf5B +8OWycvpEgjNC6C1Y91aMYj+6QrCcDFx+LmUmXFNPALJ4fqENmS2NuB2OosSw/WDQ +MKSOyARiqcTtNd56l+0OOF6SL5Nwpamcb6d9Ex1+xghIsV5n61EIJenmJWtSKZGc +0jlzCFfemQa0W50QBuHCAKi4HEoCChTQwUHK+4w1IX2COPKpVJEZNZOUbWo6xbLQ +u4mGk+ibyQ86p3q4ofB4Rvr8Ny/lioTz3/4E2aFooC8k4gmVBtWVyuEklut89pMF +u+1z6S3RdTnX5yTb2E5fQ4+e0BQ5v1VwSJlXMbSc7kqYA5YwH2AG7hsj/oFgIxpH +YoWlzBk0gG+zrBrjn/B7SK3VAdlntqlyk+otZrWyuOQ9PLLvTIzq6we/qzWaVYa8 +GKa1qF60g2xraUDTn9zxw2lrueFtCfTxqlB2Cnp9ehehVZZCmTEJ3WARjQUwfuaO +RtGdFNrHF+QFlozEJLUbzxQHskD4o55BhrwE0GuWyCqANP2/7waj3VjFhT0+j/6e +KeC2uAloGRwYQw== +-----END CERTIFICATE----- + +# Issuer: CN=AffirmTrust Premium ECC O=AffirmTrust +# Subject: CN=AffirmTrust Premium ECC O=AffirmTrust +# Label: "AffirmTrust Premium ECC" +# Serial: 8401224907861490260 +# MD5 Fingerprint: 64:b0:09:55:cf:b1:d5:99:e2:be:13:ab:a6:5d:ea:4d +# SHA1 Fingerprint: b8:23:6b:00:2f:1d:16:86:53:01:55:6c:11:a4:37:ca:eb:ff:c3:bb +# SHA256 Fingerprint: bd:71:fd:f6:da:97:e4:cf:62:d1:64:7a:dd:25:81:b0:7d:79:ad:f8:39:7e:b4:ec:ba:9c:5e:84:88:82:14:23 +-----BEGIN CERTIFICATE----- +MIIB/jCCAYWgAwIBAgIIdJclisc/elQwCgYIKoZIzj0EAwMwRTELMAkGA1UEBhMC +VVMxFDASBgNVBAoMC0FmZmlybVRydXN0MSAwHgYDVQQDDBdBZmZpcm1UcnVzdCBQ +cmVtaXVtIEVDQzAeFw0xMDAxMjkxNDIwMjRaFw00MDEyMzExNDIwMjRaMEUxCzAJ +BgNVBAYTAlVTMRQwEgYDVQQKDAtBZmZpcm1UcnVzdDEgMB4GA1UEAwwXQWZmaXJt +VHJ1c3QgUHJlbWl1bSBFQ0MwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQNMF4bFZ0D +0KF5Nbc6PJJ6yhUczWLznCZcBz3lVPqj1swS6vQUX+iOGasvLkjmrBhDeKzQN8O9 +ss0s5kfiGuZjuD0uL3jET9v0D6RoTFVya5UdThhClXjMNzyR4ptlKymjQjBAMB0G +A1UdDgQWBBSaryl6wBE1NSZRMADDav5A1a7WPDAPBgNVHRMBAf8EBTADAQH/MA4G +A1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNnADBkAjAXCfOHiFBar8jAQr9HX/Vs +aobgxCd05DhT1wV/GzTjxi+zygk8N53X57hG8f2h4nECMEJZh0PUUd+60wkyWs6I +flc9nF9Ca/UHLbXwgpP5WW+uZPpY5Yse42O+tYHNbwKMeQ== +-----END CERTIFICATE----- + +# Issuer: CN=Certum Trusted Network CA O=Unizeto Technologies S.A. OU=Certum Certification Authority +# Subject: CN=Certum Trusted Network CA O=Unizeto Technologies S.A. OU=Certum Certification Authority +# Label: "Certum Trusted Network CA" +# Serial: 279744 +# MD5 Fingerprint: d5:e9:81:40:c5:18:69:fc:46:2c:89:75:62:0f:aa:78 +# SHA1 Fingerprint: 07:e0:32:e0:20:b7:2c:3f:19:2f:06:28:a2:59:3a:19:a7:0f:06:9e +# SHA256 Fingerprint: 5c:58:46:8d:55:f5:8e:49:7e:74:39:82:d2:b5:00:10:b6:d1:65:37:4a:cf:83:a7:d4:a3:2d:b7:68:c4:40:8e +-----BEGIN CERTIFICATE----- +MIIDuzCCAqOgAwIBAgIDBETAMA0GCSqGSIb3DQEBBQUAMH4xCzAJBgNVBAYTAlBM +MSIwIAYDVQQKExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5D +ZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBU +cnVzdGVkIE5ldHdvcmsgQ0EwHhcNMDgxMDIyMTIwNzM3WhcNMjkxMjMxMTIwNzM3 +WjB+MQswCQYDVQQGEwJQTDEiMCAGA1UEChMZVW5pemV0byBUZWNobm9sb2dpZXMg +Uy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MSIw +IAYDVQQDExlDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENBMIIBIjANBgkqhkiG9w0B +AQEFAAOCAQ8AMIIBCgKCAQEA4/t9o3K6wvDJFIf1awFO4W5AB7ptJ11/91sts1rH +UV+rpDKmYYe2bg+G0jACl/jXaVehGDldamR5xgFZrDwxSjh80gTSSyjoIF87B6LM +TXPb865Px1bVWqeWifrzq2jUI4ZZJ88JJ7ysbnKDHDBy3+Ci6dLhdHUZvSqeexVU +BBvXQzmtVSjF4hq79MDkrjhJM8x2hZ85RdKknvISjFH4fOQtf/WsX+sWn7Et0brM +kUJ3TCXJkDhv2/DM+44el1k+1WBO5gUo7Ul5E0u6SNsv+XLTOcr+H9g0cvW0QM8x +AcPs3hEtF10fuFDRXhmnad4HMyjKUJX5p1TLVIZQRan5SQIDAQABo0IwQDAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBQIds3LB/8k9sXN7buQvOKEN0Z19zAOBgNV +HQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQEFBQADggEBAKaorSLOAT2mo/9i0Eidi15y +sHhE49wcrwn9I0j6vSrEuVUEtRCjjSfeC4Jj0O7eDDd5QVsisrCaQVymcODU0HfL +I9MA4GxWL+FpDQ3Zqr8hgVDZBqWo/5U30Kr+4rP1mS1FhIrlQgnXdAIv94nYmem8 +J9RHjboNRhx3zxSkHLmkMcScKHQDNP8zGSal6Q10tz6XxnboJ5ajZt3hrvJBW8qY +VoNzcOSGGtIxQbovvi0TWnZvTuhOgQ4/WwMioBK+ZlgRSssDxLQqKi2WF+A5VLxI +03YnnZotBqbJ7DnSq9ufmgsnAjUpsUCV5/nonFWIGUbWtzT1fs45mtk48VH3Tyw= +-----END CERTIFICATE----- + +# Issuer: CN=Certinomis - Autorité Racine O=Certinomis OU=0002 433998903 +# Subject: CN=Certinomis - Autorité Racine O=Certinomis OU=0002 433998903 +# Label: "Certinomis - Autorité Racine" +# Serial: 1 +# MD5 Fingerprint: 7f:30:78:8c:03:e3:ca:c9:0a:e2:c9:ea:1e:aa:55:1a +# SHA1 Fingerprint: 2e:14:da:ec:28:f0:fa:1e:8e:38:9a:4e:ab:eb:26:c0:0a:d3:83:c3 +# SHA256 Fingerprint: fc:bf:e2:88:62:06:f7:2b:27:59:3c:8b:07:02:97:e1:2d:76:9e:d1:0e:d7:93:07:05:a8:09:8e:ff:c1:4d:17 +-----BEGIN CERTIFICATE----- +MIIFnDCCA4SgAwIBAgIBATANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJGUjET +MBEGA1UEChMKQ2VydGlub21pczEXMBUGA1UECxMOMDAwMiA0MzM5OTg5MDMxJjAk +BgNVBAMMHUNlcnRpbm9taXMgLSBBdXRvcml0w6kgUmFjaW5lMB4XDTA4MDkxNzA4 +Mjg1OVoXDTI4MDkxNzA4Mjg1OVowYzELMAkGA1UEBhMCRlIxEzARBgNVBAoTCkNl +cnRpbm9taXMxFzAVBgNVBAsTDjAwMDIgNDMzOTk4OTAzMSYwJAYDVQQDDB1DZXJ0 +aW5vbWlzIC0gQXV0b3JpdMOpIFJhY2luZTCCAiIwDQYJKoZIhvcNAQEBBQADggIP +ADCCAgoCggIBAJ2Fn4bT46/HsmtuM+Cet0I0VZ35gb5j2CN2DpdUzZlMGvE5x4jY +F1AMnmHawE5V3udauHpOd4cN5bjr+p5eex7Ezyh0x5P1FMYiKAT5kcOrJ3NqDi5N +8y4oH3DfVS9O7cdxbwlyLu3VMpfQ8Vh30WC8Tl7bmoT2R2FFK/ZQpn9qcSdIhDWe +rP5pqZ56XjUl+rSnSTV3lqc2W+HN3yNw2F1MpQiD8aYkOBOo7C+ooWfHpi2GR+6K +/OybDnT0K0kCe5B1jPyZOQE51kqJ5Z52qz6WKDgmi92NjMD2AR5vpTESOH2VwnHu +7XSu5DaiQ3XV8QCb4uTXzEIDS3h65X27uK4uIJPT5GHfceF2Z5c/tt9qc1pkIuVC +28+BA5PY9OMQ4HL2AHCs8MF6DwV/zzRpRbWT5BnbUhYjBYkOjUjkJW+zeL9i9Qf6 +lSTClrLooyPCXQP8w9PlfMl1I9f09bze5N/NgL+RiH2nE7Q5uiy6vdFrzPOlKO1E +nn1So2+WLhl+HPNbxxaOu2B9d2ZHVIIAEWBsMsGoOBvrbpgT1u449fCfDu/+MYHB +0iSVL1N6aaLwD4ZFjliCK0wi1F6g530mJ0jfJUaNSih8hp75mxpZuWW/Bd22Ql09 +5gBIgl4g9xGC3srYn+Y3RyYe63j3YcNBZFgCQfna4NH4+ej9Uji29YnfAgMBAAGj +WzBZMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBQN +jLZh2kS40RR9w759XkjwzspqsDAXBgNVHSAEEDAOMAwGCiqBegFWAgIAAQEwDQYJ +KoZIhvcNAQEFBQADggIBACQ+YAZ+He86PtvqrxyaLAEL9MW12Ukx9F1BjYkMTv9s +ov3/4gbIOZ/xWqndIlgVqIrTseYyCYIDbNc/CMf4uboAbbnW/FIyXaR/pDGUu7ZM +OH8oMDX/nyNTt7buFHAAQCvaR6s0fl6nVjBhK4tDrP22iCj1a7Y+YEq6QpA0Z43q +619FVDsXrIvkxmUP7tCMXWY5zjKn2BCXwH40nJ+U8/aGH88bc62UeYdocMMzpXDn +2NU4lG9jeeu/Cg4I58UvD0KgKxRA/yHgBcUn4YQRE7rWhh1BCxMjidPJC+iKunqj +o3M3NYB9Ergzd0A4wPpeMNLytqOx1qKVl4GbUu1pTP+A5FPbVFsDbVRfsbjvJL1v +nxHDx2TCDyhihWZeGnuyt++uNckZM6i4J9szVb9o4XVIRFb7zdNIu0eJOqxp9YDG +5ERQL1TEqkPFMTFYvZbF6nVsmnWxTfj3l/+WFvKXTej28xH5On2KOG4Ey+HTRRWq +pdEdnV1j6CTmNhTih60bWfVEm/vXd3wfAXBioSAaosUaKPQhA+4u2cGA6rnZgtZb +dsLLO7XSAPCjDuGtbkD326C00EauFddEwk01+dIL8hf2rGbVJLJP0RyZwG71fet0 +BLj5TXcJ17TPBzAJ8bgAVtkXFhYKK4bfjwEZGuW7gmP/vgt2Fl43N+bYdJeimUV5 +-----END CERTIFICATE----- + +# Issuer: CN=Root CA Generalitat Valenciana O=Generalitat Valenciana OU=PKIGVA +# Subject: CN=Root CA Generalitat Valenciana O=Generalitat Valenciana OU=PKIGVA +# Label: "Root CA Generalitat Valenciana" +# Serial: 994436456 +# MD5 Fingerprint: 2c:8c:17:5e:b1:54:ab:93:17:b5:36:5a:db:d1:c6:f2 +# SHA1 Fingerprint: a0:73:e5:c5:bd:43:61:0d:86:4c:21:13:0a:85:58:57:cc:9c:ea:46 +# SHA256 Fingerprint: 8c:4e:df:d0:43:48:f3:22:96:9e:7e:29:a4:cd:4d:ca:00:46:55:06:1c:16:e1:b0:76:42:2e:f3:42:ad:63:0e +-----BEGIN CERTIFICATE----- +MIIGizCCBXOgAwIBAgIEO0XlaDANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJF +UzEfMB0GA1UEChMWR2VuZXJhbGl0YXQgVmFsZW5jaWFuYTEPMA0GA1UECxMGUEtJ +R1ZBMScwJQYDVQQDEx5Sb290IENBIEdlbmVyYWxpdGF0IFZhbGVuY2lhbmEwHhcN +MDEwNzA2MTYyMjQ3WhcNMjEwNzAxMTUyMjQ3WjBoMQswCQYDVQQGEwJFUzEfMB0G +A1UEChMWR2VuZXJhbGl0YXQgVmFsZW5jaWFuYTEPMA0GA1UECxMGUEtJR1ZBMScw +JQYDVQQDEx5Sb290IENBIEdlbmVyYWxpdGF0IFZhbGVuY2lhbmEwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDGKqtXETcvIorKA3Qdyu0togu8M1JAJke+ +WmmmO3I2F0zo37i7L3bhQEZ0ZQKQUgi0/6iMweDHiVYQOTPvaLRfX9ptI6GJXiKj +SgbwJ/BXufjpTjJ3Cj9BZPPrZe52/lSqfR0grvPXdMIKX/UIKFIIzFVd0g/bmoGl +u6GzwZTNVOAydTGRGmKy3nXiz0+J2ZGQD0EbtFpKd71ng+CT516nDOeB0/RSrFOy +A8dEJvt55cs0YFAQexvba9dHq198aMpunUEDEO5rmXteJajCq+TA81yc477OMUxk +Hl6AovWDfgzWyoxVjr7gvkkHD6MkQXpYHYTqWBLI4bft75PelAgxAgMBAAGjggM7 +MIIDNzAyBggrBgEFBQcBAQQmMCQwIgYIKwYBBQUHMAGGFmh0dHA6Ly9vY3NwLnBr +aS5ndmEuZXMwEgYDVR0TAQH/BAgwBgEB/wIBAjCCAjQGA1UdIASCAiswggInMIIC +IwYKKwYBBAG/VQIBADCCAhMwggHoBggrBgEFBQcCAjCCAdoeggHWAEEAdQB0AG8A +cgBpAGQAYQBkACAAZABlACAAQwBlAHIAdABpAGYAaQBjAGEAYwBpAPMAbgAgAFIA +YQDtAHoAIABkAGUAIABsAGEAIABHAGUAbgBlAHIAYQBsAGkAdABhAHQAIABWAGEA +bABlAG4AYwBpAGEAbgBhAC4ADQAKAEwAYQAgAEQAZQBjAGwAYQByAGEAYwBpAPMA +bgAgAGQAZQAgAFAAcgDhAGMAdABpAGMAYQBzACAAZABlACAAQwBlAHIAdABpAGYA +aQBjAGEAYwBpAPMAbgAgAHEAdQBlACAAcgBpAGcAZQAgAGUAbAAgAGYAdQBuAGMA +aQBvAG4AYQBtAGkAZQBuAHQAbwAgAGQAZQAgAGwAYQAgAHAAcgBlAHMAZQBuAHQA +ZQAgAEEAdQB0AG8AcgBpAGQAYQBkACAAZABlACAAQwBlAHIAdABpAGYAaQBjAGEA +YwBpAPMAbgAgAHMAZQAgAGUAbgBjAHUAZQBuAHQAcgBhACAAZQBuACAAbABhACAA +ZABpAHIAZQBjAGMAaQDzAG4AIAB3AGUAYgAgAGgAdAB0AHAAOgAvAC8AdwB3AHcA +LgBwAGsAaQAuAGcAdgBhAC4AZQBzAC8AYwBwAHMwJQYIKwYBBQUHAgEWGWh0dHA6 +Ly93d3cucGtpLmd2YS5lcy9jcHMwHQYDVR0OBBYEFHs100DSHHgZZu90ECjcPk+y +eAT8MIGVBgNVHSMEgY0wgYqAFHs100DSHHgZZu90ECjcPk+yeAT8oWykajBoMQsw +CQYDVQQGEwJFUzEfMB0GA1UEChMWR2VuZXJhbGl0YXQgVmFsZW5jaWFuYTEPMA0G +A1UECxMGUEtJR1ZBMScwJQYDVQQDEx5Sb290IENBIEdlbmVyYWxpdGF0IFZhbGVu +Y2lhbmGCBDtF5WgwDQYJKoZIhvcNAQEFBQADggEBACRhTvW1yEICKrNcda3Fbcrn +lD+laJWIwVTAEGmiEi8YPyVQqHxK6sYJ2fR1xkDar1CdPaUWu20xxsdzCkj+IHLt +b8zog2EWRpABlUt9jppSCS/2bxzkoXHPjCpaF3ODR00PNvsETUlR4hTJZGH71BTg +9J63NI8KJr2XXPR5OkowGcytT6CYirQxlyric21+eLj4iIlPsSKRZEv1UN4D2+XF +ducTZnV+ZfsBn5OHiJ35Rld8TWCvmHMTI6QgkYH60GFmuH3Rr9ZvHmw96RH9qfmC +IoaZM3Fa6hlXPZHNqcCjbgcTpsnt+GijnsNacgmHKNHEc8RzGF9QdRYxn7fofMM= +-----END CERTIFICATE----- + +# Issuer: CN=A-Trust-nQual-03 O=A-Trust Ges. f. Sicherheitssysteme im elektr. Datenverkehr GmbH OU=A-Trust-nQual-03 +# Subject: CN=A-Trust-nQual-03 O=A-Trust Ges. f. Sicherheitssysteme im elektr. Datenverkehr GmbH OU=A-Trust-nQual-03 +# Label: "A-Trust-nQual-03" +# Serial: 93214 +# MD5 Fingerprint: 49:63:ae:27:f4:d5:95:3d:d8:db:24:86:b8:9c:07:53 +# SHA1 Fingerprint: d3:c0:63:f2:19:ed:07:3e:34:ad:5d:75:0b:32:76:29:ff:d5:9a:f2 +# SHA256 Fingerprint: 79:3c:bf:45:59:b9:fd:e3:8a:b2:2d:f1:68:69:f6:98:81:ae:14:c4:b0:13:9a:c7:88:a7:8a:1a:fc:ca:02:fb +-----BEGIN CERTIFICATE----- +MIIDzzCCAregAwIBAgIDAWweMA0GCSqGSIb3DQEBBQUAMIGNMQswCQYDVQQGEwJB +VDFIMEYGA1UECgw/QS1UcnVzdCBHZXMuIGYuIFNpY2hlcmhlaXRzc3lzdGVtZSBp +bSBlbGVrdHIuIERhdGVudmVya2VociBHbWJIMRkwFwYDVQQLDBBBLVRydXN0LW5R +dWFsLTAzMRkwFwYDVQQDDBBBLVRydXN0LW5RdWFsLTAzMB4XDTA1MDgxNzIyMDAw +MFoXDTE1MDgxNzIyMDAwMFowgY0xCzAJBgNVBAYTAkFUMUgwRgYDVQQKDD9BLVRy +dXN0IEdlcy4gZi4gU2ljaGVyaGVpdHNzeXN0ZW1lIGltIGVsZWt0ci4gRGF0ZW52 +ZXJrZWhyIEdtYkgxGTAXBgNVBAsMEEEtVHJ1c3QtblF1YWwtMDMxGTAXBgNVBAMM +EEEtVHJ1c3QtblF1YWwtMDMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB +AQCtPWFuA/OQO8BBC4SAzewqo51ru27CQoT3URThoKgtUaNR8t4j8DRE/5TrzAUj +lUC5B3ilJfYKvUWG6Nm9wASOhURh73+nyfrBJcyFLGM/BWBzSQXgYHiVEEvc+RFZ +znF/QJuKqiTfC0Li21a8StKlDJu3Qz7dg9MmEALP6iPESU7l0+m0iKsMrmKS1GWH +2WrX9IWf5DMiJaXlyDO6w8dB3F/GaswADm0yqLaHNgBid5seHzTLkDx4iHQF63n1 +k3Flyp3HaxgtPVxO59X4PzF9j4fsCiIvI+n+u33J4PTs63zEsMMtYrWacdaxaujs +2e3Vcuy+VwHOBVWf3tFgiBCzAgMBAAGjNjA0MA8GA1UdEwEB/wQFMAMBAf8wEQYD +VR0OBAoECERqlWdVeRFPMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOC +AQEAVdRU0VlIXLOThaq/Yy/kgM40ozRiPvbY7meIMQQDbwvUB/tOdQ/TLtPAF8fG +KOwGDREkDg6lXb+MshOWcdzUzg4NCmgybLlBMRmrsQd7TZjTXLDR8KdCoLXEjq/+ +8T/0709GAHbrAvv5ndJAlseIOrifEXnzgGWovR/TeIGgUUw3tKZdJXDRZslo+S4R +FGjxVJgIrCaSD96JntT6s3kr0qN51OyLrIdTaEJMUVF0HhsnLuP1Hyl0Te2v9+GS +mYHovjrHF1D2t8b8m7CKa9aIA5GPBnc6hQLdmNVDeD/GMBWsm2vLV7eJUYs66MmE +DNuxUCAKGkq6ahq97BvIxYSazQ== +-----END CERTIFICATE----- + +# Issuer: CN=TWCA Root Certification Authority O=TAIWAN-CA OU=Root CA +# Subject: CN=TWCA Root Certification Authority O=TAIWAN-CA OU=Root CA +# Label: "TWCA Root Certification Authority" +# Serial: 1 +# MD5 Fingerprint: aa:08:8f:f6:f9:7b:b7:f2:b1:a7:1e:9b:ea:ea:bd:79 +# SHA1 Fingerprint: cf:9e:87:6d:d3:eb:fc:42:26:97:a3:b5:a3:7a:a0:76:a9:06:23:48 +# SHA256 Fingerprint: bf:d8:8f:e1:10:1c:41:ae:3e:80:1b:f8:be:56:35:0e:e9:ba:d1:a6:b9:bd:51:5e:dc:5c:6d:5b:87:11:ac:44 +-----BEGIN CERTIFICATE----- +MIIDezCCAmOgAwIBAgIBATANBgkqhkiG9w0BAQUFADBfMQswCQYDVQQGEwJUVzES +MBAGA1UECgwJVEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFU +V0NBIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDgwODI4MDcyNDMz +WhcNMzAxMjMxMTU1OTU5WjBfMQswCQYDVQQGEwJUVzESMBAGA1UECgwJVEFJV0FO +LUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFUV0NBIFJvb3QgQ2VydGlm +aWNhdGlvbiBBdXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB +AQCwfnK4pAOU5qfeCTiRShFAh6d8WWQUe7UREN3+v9XAu1bihSX0NXIP+FPQQeFE +AcK0HMMxQhZHhTMidrIKbw/lJVBPhYa+v5guEGcevhEFhgWQxFnQfHgQsIBct+HH +K3XLfJ+utdGdIzdjp9xCoi2SBBtQwXu4PhvJVgSLL1KbralW6cH/ralYhzC2gfeX +RfwZVzsrb+RH9JlF/h3x+JejiB03HFyP4HYlmlD4oFT/RJB2I9IyxsOrBr/8+7/z +rX2SYgJbKdM1o5OaQ2RgXbL6Mv87BK9NQGr5x+PvI/1ry+UPizgN7gr8/g+YnzAx +3WxSZfmLgb4i4RxYA7qRG4kHAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqOFsmjd6LWvJPelSDGRjjCDWmujANBgkq +hkiG9w0BAQUFAAOCAQEAPNV3PdrfibqHDAhUaiBQkr6wQT25JmSDCi/oQMCXKCeC +MErJk/9q56YAf4lCmtYR5VPOL8zy2gXE/uJQxDqGfczafhAJO5I1KlOy/usrBdls +XebQ79NqZp4VKIV66IIArB6nCWlWQtNoURi+VJq/REG6Sb4gumlc7rh3zc5sH62D +lhh9DrUUOYTxKOkto557HnpyWoOzeW/vtPzQCqVYT0bf+215WfKEIlKuD8z7fDvn +aspHYcN6+NOSBB+4IIThNlQWx0DeO4pz3N/GCUzf7Nr/1FNCocnyYh0igzyXxfkZ +YiesZSLX0zzG5Y6yU8xJzrww/nsOM5D77dIUkR8Hrw== +-----END CERTIFICATE----- + +# Issuer: O=SECOM Trust Systems CO.,LTD. OU=Security Communication RootCA2 +# Subject: O=SECOM Trust Systems CO.,LTD. OU=Security Communication RootCA2 +# Label: "Security Communication RootCA2" +# Serial: 0 +# MD5 Fingerprint: 6c:39:7d:a4:0e:55:59:b2:3f:d6:41:b1:12:50:de:43 +# SHA1 Fingerprint: 5f:3b:8c:f2:f8:10:b3:7d:78:b4:ce:ec:19:19:c3:73:34:b9:c7:74 +# SHA256 Fingerprint: 51:3b:2c:ec:b8:10:d4:cd:e5:dd:85:39:1a:df:c6:c2:dd:60:d8:7b:b7:36:d2:b5:21:48:4a:a4:7a:0e:be:f6 +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIBADANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJKUDEl +MCMGA1UEChMcU0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEnMCUGA1UECxMe +U2VjdXJpdHkgQ29tbXVuaWNhdGlvbiBSb290Q0EyMB4XDTA5MDUyOTA1MDAzOVoX +DTI5MDUyOTA1MDAzOVowXTELMAkGA1UEBhMCSlAxJTAjBgNVBAoTHFNFQ09NIFRy +dXN0IFN5c3RlbXMgQ08uLExURC4xJzAlBgNVBAsTHlNlY3VyaXR5IENvbW11bmlj +YXRpb24gUm9vdENBMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANAV +OVKxUrO6xVmCxF1SrjpDZYBLx/KWvNs2l9amZIyoXvDjChz335c9S672XewhtUGr +zbl+dp+++T42NKA7wfYxEUV0kz1XgMX5iZnK5atq1LXaQZAQwdbWQonCv/Q4EpVM +VAX3NuRFg3sUZdbcDE3R3n4MqzvEFb46VqZab3ZpUql6ucjrappdUtAtCms1FgkQ +hNBqyjoGADdH5H5XTz+L62e4iKrFvlNVspHEfbmwhRkGeC7bYRr6hfVKkaHnFtWO +ojnflLhwHyg/i/xAXmODPIMqGplrz95Zajv8bxbXH/1KEOtOghY6rCcMU/Gt1SSw +awNQwS08Ft1ENCcadfsCAwEAAaNCMEAwHQYDVR0OBBYEFAqFqXdlBZh8QIH4D5cs +OPEK7DzPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3 +DQEBCwUAA4IBAQBMOqNErLlFsceTfsgLCkLfZOoc7llsCLqJX2rKSpWeeo8HxdpF +coJxDjrSzG+ntKEju/Ykn8sX/oymzsLS28yN/HH8AynBbF0zX2S2ZTuJbxh2ePXc +okgfGT+Ok+vx+hfuzU7jBBJV1uXk3fs+BXziHV7Gp7yXT2g69ekuCkO2r1dcYmh8 +t/2jioSgrGK+KwmHNPBqAbubKVY8/gA3zyNs8U6qtnRGEmyR7jTV7JqR50S+kDFy +1UkC9gLl9B/rfNmWVan/7Ir5mUf/NVoCqgTLiluHcSmRvaS0eg29mvVXIwAHIRc/ +SjnRBUkLp7Y3gaVdjKozXoEofKd9J+sAro03 +-----END CERTIFICATE----- + +# Issuer: CN=Hellenic Academic and Research Institutions RootCA 2011 O=Hellenic Academic and Research Institutions Cert. Authority +# Subject: CN=Hellenic Academic and Research Institutions RootCA 2011 O=Hellenic Academic and Research Institutions Cert. Authority +# Label: "Hellenic Academic and Research Institutions RootCA 2011" +# Serial: 0 +# MD5 Fingerprint: 73:9f:4c:4b:73:5b:79:e9:fa:ba:1c:ef:6e:cb:d5:c9 +# SHA1 Fingerprint: fe:45:65:9b:79:03:5b:98:a1:61:b5:51:2e:ac:da:58:09:48:22:4d +# SHA256 Fingerprint: bc:10:4f:15:a4:8b:e7:09:dc:a5:42:a7:e1:d4:b9:df:6f:05:45:27:e8:02:ea:a9:2d:59:54:44:25:8a:fe:71 +-----BEGIN CERTIFICATE----- +MIIEMTCCAxmgAwIBAgIBADANBgkqhkiG9w0BAQUFADCBlTELMAkGA1UEBhMCR1Ix +RDBCBgNVBAoTO0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1 +dGlvbnMgQ2VydC4gQXV0aG9yaXR5MUAwPgYDVQQDEzdIZWxsZW5pYyBBY2FkZW1p +YyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIFJvb3RDQSAyMDExMB4XDTExMTIw +NjEzNDk1MloXDTMxMTIwMTEzNDk1MlowgZUxCzAJBgNVBAYTAkdSMUQwQgYDVQQK +EztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIENl +cnQuIEF1dGhvcml0eTFAMD4GA1UEAxM3SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJl +c2VhcmNoIEluc3RpdHV0aW9ucyBSb290Q0EgMjAxMTCCASIwDQYJKoZIhvcNAQEB +BQADggEPADCCAQoCggEBAKlTAOMupvaO+mDYLZU++CwqVE7NuYRhlFhPjz2L5EPz +dYmNUeTDN9KKiE15HrcS3UN4SoqS5tdI1Q+kOilENbgH9mgdVc04UfCMJDGFr4PJ +fel3r+0ae50X+bOdOFAPplp5kYCvN66m0zH7tSYJnTxa71HFK9+WXesyHgLacEns +bgzImjeN9/E2YEsmLIKe0HjzDQ9jpFEw4fkrJxIH2Oq9GGKYsFk3fb7u8yBRQlqD +75O6aRXxYp2fmTmCobd0LovUxQt7L/DICto9eQqakxylKHJzkUOap9FNhYS5qXSP +FEDH3N6sQWRstBmbAmNtJGSPRLIl6s5ddAxjMlyNh+UCAwEAAaOBiTCBhjAPBgNV +HRMBAf8EBTADAQH/MAsGA1UdDwQEAwIBBjAdBgNVHQ4EFgQUppFC/RNhSiOeCKQp +5dgTBCPuQSUwRwYDVR0eBEAwPqA8MAWCAy5ncjAFggMuZXUwBoIELmVkdTAGggQu +b3JnMAWBAy5ncjAFgQMuZXUwBoEELmVkdTAGgQQub3JnMA0GCSqGSIb3DQEBBQUA +A4IBAQAf73lB4XtuP7KMhjdCSk4cNx6NZrokgclPEg8hwAOXhiVtXdMiKahsog2p +6z0GW5k6x8zDmjR/qw7IThzh+uTczQ2+vyT+bOdrwg3IBp5OjWEopmr95fZi6hg8 +TqBTnbI6nOulnJEWtk2C4AwFSKls9cz4y51JtPACpf1wA+2KIaWuE4ZJwzNzvoc7 +dIsXRSZMFpGD/md9zU1jZ/rzAxKWeAaNsWftjj++n08C9bMJL/NMh98qy5V8Acys +Nnq/onN694/BtZqhFLKPM58N7yLcZnuEvUUXBj08yrl3NI/K6s8/MT7jiOOASSXI +l7WdmplNsDz4SgCbZN2fOUvRJ9e4 +-----END CERTIFICATE----- + +# Issuer: CN=Actalis Authentication Root CA O=Actalis S.p.A./03358520967 +# Subject: CN=Actalis Authentication Root CA O=Actalis S.p.A./03358520967 +# Label: "Actalis Authentication Root CA" +# Serial: 6271844772424770508 +# MD5 Fingerprint: 69:c1:0d:4f:07:a3:1b:c3:fe:56:3d:04:bc:11:f6:a6 +# SHA1 Fingerprint: f3:73:b3:87:06:5a:28:84:8a:f2:f3:4a:ce:19:2b:dd:c7:8e:9c:ac +# SHA256 Fingerprint: 55:92:60:84:ec:96:3a:64:b9:6e:2a:be:01:ce:0b:a8:6a:64:fb:fe:bc:c7:aa:b5:af:c1:55:b3:7f:d7:60:66 +-----BEGIN CERTIFICATE----- +MIIFuzCCA6OgAwIBAgIIVwoRl0LE48wwDQYJKoZIhvcNAQELBQAwazELMAkGA1UE +BhMCSVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8w +MzM1ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290 +IENBMB4XDTExMDkyMjExMjIwMloXDTMwMDkyMjExMjIwMlowazELMAkGA1UEBhMC +SVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8wMzM1 +ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290IENB +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAp8bEpSmkLO/lGMWwUKNv +UTufClrJwkg4CsIcoBh/kbWHuUA/3R1oHwiD1S0eiKD4j1aPbZkCkpAW1V8IbInX +4ay8IMKx4INRimlNAJZaby/ARH6jDuSRzVju3PvHHkVH3Se5CAGfpiEd9UEtL0z9 +KK3giq0itFZljoZUj5NDKd45RnijMCO6zfB9E1fAXdKDa0hMxKufgFpbOr3JpyI/ +gCczWw63igxdBzcIy2zSekciRDXFzMwujt0q7bd9Zg1fYVEiVRvjRuPjPdA1Yprb +rxTIW6HMiRvhMCb8oJsfgadHHwTrozmSBp+Z07/T6k9QnBn+locePGX2oxgkg4YQ +51Q+qDp2JE+BIcXjDwL4k5RHILv+1A7TaLndxHqEguNTVHnd25zS8gebLra8Pu2F +be8lEfKXGkJh90qX6IuxEAf6ZYGyojnP9zz/GPvG8VqLWeICrHuS0E4UT1lF9gxe +KF+w6D9Fz8+vm2/7hNN3WpVvrJSEnu68wEqPSpP4RCHiMUVhUE4Q2OM1fEwZtN4F +v6MGn8i1zeQf1xcGDXqVdFUNaBr8EBtiZJ1t4JWgw5QHVw0U5r0F+7if5t+L4sbn +fpb2U8WANFAoWPASUHEXMLrmeGO89LKtmyuy/uE5jF66CyCU3nuDuP/jVo23Eek7 +jPKxwV2dpAtMK9myGPW1n0sCAwEAAaNjMGEwHQYDVR0OBBYEFFLYiDrIn3hm7Ynz +ezhwlMkCAjbQMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUUtiIOsifeGbt +ifN7OHCUyQICNtAwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQAL +e3KHwGCmSUyIWOYdiPcUZEim2FgKDk8TNd81HdTtBjHIgT5q1d07GjLukD0R0i70 +jsNjLiNmsGe+b7bAEzlgqqI0JZN1Ut6nna0Oh4lScWoWPBkdg/iaKWW+9D+a2fDz +WochcYBNy+A4mz+7+uAwTc+G02UQGRjRlwKxK3JCaKygvU5a2hi/a5iB0P2avl4V +SM0RFbnAKVy06Ij3Pjaut2L9HmLecHgQHEhb2rykOLpn7VU+Xlff1ANATIGk0k9j +pwlCCRT8AKnCgHNPLsBA2RF7SOp6AsDT6ygBJlh0wcBzIm2Tlf05fbsq4/aC4yyX +X04fkZT6/iyj2HYauE2yOE+b+h1IYHkm4vP9qdCa6HCPSXrW5b0KDtst842/6+Ok +fcvHlXHo2qN8xcL4dJIEG4aspCJTQLas/kx2z/uUMsA1n3Y/buWQbqCmJqK4LL7R +K4X9p2jIugErsWx0Hbhzlefut8cl8ABMALJ+tguLHPPAUJ4lueAI3jZm/zel0btU +ZCzJJ7VLkn5l/9Mt4blOvH+kQSGQQXemOR/qnuOf0GZvBeyqdn6/axag67XH/JJU +LysRJyU3eExRarDzzFhdFPFqSBX/wge2sY0PjlxQRrM9vwGYT7JZVEc+NHt4bVaT +LnPqZih4zR0Uv6CPLy64Lo7yFIrM6bV8+2ydDKXhlg== +-----END CERTIFICATE----- + +# Issuer: O=Trustis Limited OU=Trustis FPS Root CA +# Subject: O=Trustis Limited OU=Trustis FPS Root CA +# Label: "Trustis FPS Root CA" +# Serial: 36053640375399034304724988975563710553 +# MD5 Fingerprint: 30:c9:e7:1e:6b:e6:14:eb:65:b2:16:69:20:31:67:4d +# SHA1 Fingerprint: 3b:c0:38:0b:33:c3:f6:a6:0c:86:15:22:93:d9:df:f5:4b:81:c0:04 +# SHA256 Fingerprint: c1:b4:82:99:ab:a5:20:8f:e9:63:0a:ce:55:ca:68:a0:3e:da:5a:51:9c:88:02:a0:d3:a6:73:be:8f:8e:55:7d +-----BEGIN CERTIFICATE----- +MIIDZzCCAk+gAwIBAgIQGx+ttiD5JNM2a/fH8YygWTANBgkqhkiG9w0BAQUFADBF +MQswCQYDVQQGEwJHQjEYMBYGA1UEChMPVHJ1c3RpcyBMaW1pdGVkMRwwGgYDVQQL +ExNUcnVzdGlzIEZQUyBSb290IENBMB4XDTAzMTIyMzEyMTQwNloXDTI0MDEyMTEx +MzY1NFowRTELMAkGA1UEBhMCR0IxGDAWBgNVBAoTD1RydXN0aXMgTGltaXRlZDEc +MBoGA1UECxMTVHJ1c3RpcyBGUFMgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQAD +ggEPADCCAQoCggEBAMVQe547NdDfxIzNjpvto8A2mfRC6qc+gIMPpqdZh8mQRUN+ +AOqGeSoDvT03mYlmt+WKVoaTnGhLaASMk5MCPjDSNzoiYYkchU59j9WvezX2fihH +iTHcDnlkH5nSW7r+f2C/revnPDgpai/lkQtV/+xvWNUtyd5MZnGPDNcE2gfmHhjj +vSkCqPoc4Vu5g6hBSLwacY3nYuUtsuvffM/bq1rKMfFMIvMFE/eC+XN5DL7XSxzA +0RU8k0Fk0ea+IxciAIleH2ulrG6nS4zto3Lmr2NNL4XSFDWaLk6M6jKYKIahkQlB +OrTh4/L68MkKokHdqeMDx4gVOxzUGpTXn2RZEm0CAwEAAaNTMFEwDwYDVR0TAQH/ +BAUwAwEB/zAfBgNVHSMEGDAWgBS6+nEleYtXQSUhhgtx67JkDoshZzAdBgNVHQ4E +FgQUuvpxJXmLV0ElIYYLceuyZA6LIWcwDQYJKoZIhvcNAQEFBQADggEBAH5Y//01 +GX2cGE+esCu8jowU/yyg2kdbw++BLa8F6nRIW/M+TgfHbcWzk88iNVy2P3UnXwmW +zaD+vkAMXBJV+JOCyinpXj9WV4s4NvdFGkwozZ5BuO1WTISkQMi4sKUraXAEasP4 +1BIy+Q7DsdwyhEQsb8tGD+pmQQ9P8Vilpg0ND2HepZ5dfWWhPBfnqFVO76DH7cZE +f1T1o+CP8HxVIo8ptoGj4W1OLBuAZ+ytIJ8MYmHVl/9D7S3B2l0pKoU/rGXuhg8F +jZBf3+6f9L/uHfuY5H+QK4R4EA5sSVPvFVtlRkpdr7r7OnIdzfYliB6XzCGcKQEN +ZetX2fNXlrtIzYE= +-----END CERTIFICATE----- + +# Issuer: CN=StartCom Certification Authority O=StartCom Ltd. OU=Secure Digital Certificate Signing +# Subject: CN=StartCom Certification Authority O=StartCom Ltd. OU=Secure Digital Certificate Signing +# Label: "StartCom Certification Authority" +# Serial: 45 +# MD5 Fingerprint: c9:3b:0d:84:41:fc:a4:76:79:23:08:57:de:10:19:16 +# SHA1 Fingerprint: a3:f1:33:3f:e2:42:bf:cf:c5:d1:4e:8f:39:42:98:40:68:10:d1:a0 +# SHA256 Fingerprint: e1:78:90:ee:09:a3:fb:f4:f4:8b:9c:41:4a:17:d6:37:b7:a5:06:47:e9:bc:75:23:22:72:7f:cc:17:42:a9:11 +-----BEGIN CERTIFICATE----- +MIIHhzCCBW+gAwIBAgIBLTANBgkqhkiG9w0BAQsFADB9MQswCQYDVQQGEwJJTDEW +MBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwg +Q2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkwHhcNMDYwOTE3MTk0NjM3WhcNMzYwOTE3MTk0NjM2WjB9 +MQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMi +U2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3Rh +cnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUA +A4ICDwAwggIKAoICAQDBiNsJvGxGfHiflXu1M5DycmLWwTYgIiRezul38kMKogZk +pMyONvg45iPwbm2xPN1yo4UcodM9tDMr0y+v/uqwQVlntsQGfQqedIXWeUyAN3rf +OQVSWff0G0ZDpNKFhdLDcfN1YjS6LIp/Ho/u7TTQEceWzVI9ujPW3U3eCztKS5/C +Ji/6tRYccjV3yjxd5srhJosaNnZcAdt0FCX+7bWgiA/deMotHweXMAEtcnn6RtYT +Kqi5pquDSR3l8u/d5AGOGAqPY1MWhWKpDhk6zLVmpsJrdAfkK+F2PrRt2PZE4XNi +HzvEvqBTViVsUQn3qqvKv3b9bZvzndu/PWa8DFaqr5hIlTpL36dYUNk4dalb6kMM +Av+Z6+hsTXBbKWWc3apdzK8BMewM69KN6Oqce+Zu9ydmDBpI125C4z/eIT574Q1w ++2OqqGwaVLRcJXrJosmLFqa7LH4XXgVNWG4SHQHuEhANxjJ/GP/89PrNbpHoNkm+ +Gkhpi8KWTRoSsmkXwQqQ1vp5Iki/untp+HDH+no32NgN0nZPV/+Qt+OR0t3vwmC3 +Zzrd/qqc8NSLf3Iizsafl7b4r4qgEKjZ+xjGtrVcUjyJthkqcwEKDwOzEmDyei+B +26Nu/yYwl/WL3YlXtq09s68rxbd2AvCl1iuahhQqcvbjM4xdCUsT37uMdBNSSwID +AQABo4ICEDCCAgwwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYD +VR0OBBYEFE4L7xqkQFulF2mHMMo0aEPQQa7yMB8GA1UdIwQYMBaAFE4L7xqkQFul +F2mHMMo0aEPQQa7yMIIBWgYDVR0gBIIBUTCCAU0wggFJBgsrBgEEAYG1NwEBATCC +ATgwLgYIKwYBBQUHAgEWImh0dHA6Ly93d3cuc3RhcnRzc2wuY29tL3BvbGljeS5w +ZGYwNAYIKwYBBQUHAgEWKGh0dHA6Ly93d3cuc3RhcnRzc2wuY29tL2ludGVybWVk +aWF0ZS5wZGYwgc8GCCsGAQUFBwICMIHCMCcWIFN0YXJ0IENvbW1lcmNpYWwgKFN0 +YXJ0Q29tKSBMdGQuMAMCAQEagZZMaW1pdGVkIExpYWJpbGl0eSwgcmVhZCB0aGUg +c2VjdGlvbiAqTGVnYWwgTGltaXRhdGlvbnMqIG9mIHRoZSBTdGFydENvbSBDZXJ0 +aWZpY2F0aW9uIEF1dGhvcml0eSBQb2xpY3kgYXZhaWxhYmxlIGF0IGh0dHA6Ly93 +d3cuc3RhcnRzc2wuY29tL3BvbGljeS5wZGYwEQYJYIZIAYb4QgEBBAQDAgAHMDgG +CWCGSAGG+EIBDQQrFilTdGFydENvbSBGcmVlIFNTTCBDZXJ0aWZpY2F0aW9uIEF1 +dGhvcml0eTANBgkqhkiG9w0BAQsFAAOCAgEAjo/n3JR5fPGFf59Jb2vKXfuM/gTF +wWLRfUKKvFO3lANmMD+x5wqnUCBVJX92ehQN6wQOQOY+2IirByeDqXWmN3PH/UvS +Ta0XQMhGvjt/UfzDtgUx3M2FIk5xt/JxXrAaxrqTi3iSSoX4eA+D/i+tLPfkpLst +0OcNOrg+zvZ49q5HJMqjNTbOx8aHmNrs++myziebiMMEofYLWWivydsQD032ZGNc +pRJvkrKTlMeIFw6Ttn5ii5B/q06f/ON1FE8qMt9bDeD1e5MNq6HPh+GlBEXoPBKl +CcWw0bdT82AUuoVpaiF8H3VhFyAXe2w7QSlc4axa0c2Mm+tgHRns9+Ww2vl5GKVF +P0lDV9LdJNUso/2RjSe15esUBppMeyG7Oq0wBhjA2MFrLH9ZXF2RsXAiV+uKa0hK +1Q8p7MZAwC+ITGgBF3f0JBlPvfrhsiAhS90a2Cl9qrjeVOwhVYBsHvUwyKMQ5bLm +KhQxw4UtjJixhlpPiVktucf3HMiKf8CdBUrmQk9io20ppB+Fq9vlgcitKj1MXVuE +JnHEhV5xJMqlG2zYYdMa4FTbzrqpMrUi9nNBCV24F10OD5mQ1kfabwo6YigUZ4LZ +8dCAWZvLMdibD4x3TrVoivJs9iQOLWxwxXPR3hTQcY+203sC9uO41Alua551hDnm +fyWl8kgAwKQB2j8= +-----END CERTIFICATE----- + +# Issuer: CN=StartCom Certification Authority G2 O=StartCom Ltd. +# Subject: CN=StartCom Certification Authority G2 O=StartCom Ltd. +# Label: "StartCom Certification Authority G2" +# Serial: 59 +# MD5 Fingerprint: 78:4b:fb:9e:64:82:0a:d3:b8:4c:62:f3:64:f2:90:64 +# SHA1 Fingerprint: 31:f1:fd:68:22:63:20:ee:c6:3b:3f:9d:ea:4a:3e:53:7c:7c:39:17 +# SHA256 Fingerprint: c7:ba:65:67:de:93:a7:98:ae:1f:aa:79:1e:71:2d:37:8f:ae:1f:93:c4:39:7f:ea:44:1b:b7:cb:e6:fd:59:95 +-----BEGIN CERTIFICATE----- +MIIFYzCCA0ugAwIBAgIBOzANBgkqhkiG9w0BAQsFADBTMQswCQYDVQQGEwJJTDEW +MBQGA1UEChMNU3RhcnRDb20gTHRkLjEsMCoGA1UEAxMjU3RhcnRDb20gQ2VydGlm +aWNhdGlvbiBBdXRob3JpdHkgRzIwHhcNMTAwMTAxMDEwMDAxWhcNMzkxMjMxMjM1 +OTAxWjBTMQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRkLjEsMCoG +A1UEAxMjU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgRzIwggIiMA0G +CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2iTZbB7cgNr2Cu+EWIAOVeq8Oo1XJ +JZlKxdBWQYeQTSFgpBSHO839sj60ZwNq7eEPS8CRhXBF4EKe3ikj1AENoBB5uNsD +vfOpL9HG4A/LnooUCri99lZi8cVytjIl2bLzvWXFDSxu1ZJvGIsAQRSCb0AgJnoo +D/Uefyf3lLE3PbfHkffiAez9lInhzG7TNtYKGXmu1zSCZf98Qru23QumNK9LYP5/ +Q0kGi4xDuFby2X8hQxfqp0iVAXV16iulQ5XqFYSdCI0mblWbq9zSOdIxHWDirMxW +RST1HFSr7obdljKF+ExP6JV2tgXdNiNnvP8V4so75qbsO+wmETRIjfaAKxojAuuK +HDp2KntWFhxyKrOq42ClAJ8Em+JvHhRYW6Vsi1g8w7pOOlz34ZYrPu8HvKTlXcxN +nw3h3Kq74W4a7I/htkxNeXJdFzULHdfBR9qWJODQcqhaX2YtENwvKhOuJv4KHBnM +0D4LnMgJLvlblnpHnOl68wVQdJVznjAJ85eCXuaPOQgeWeU1FEIT/wCc976qUM/i +UUjXuG+v+E5+M5iSFGI6dWPPe/regjupuznixL0sAA7IF6wT700ljtizkC+p2il9 +Ha90OrInwMEePnWjFqmveiJdnxMaz6eg6+OGCtP95paV1yPIN93EfKo2rJgaErHg +TuixO/XWb/Ew1wIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQE +AwIBBjAdBgNVHQ4EFgQUS8W0QGutHLOlHGVuRjaJhwUMDrYwDQYJKoZIhvcNAQEL +BQADggIBAHNXPyzVlTJ+N9uWkusZXn5T50HsEbZH77Xe7XRcxfGOSeD8bpkTzZ+K +2s06Ctg6Wgk/XzTQLwPSZh0avZyQN8gMjgdalEVGKua+etqhqaRpEpKwfTbURIfX +UfEpY9Z1zRbkJ4kd+MIySP3bmdCPX1R0zKxnNBFi2QwKN4fRoxdIjtIXHfbX/dtl +6/2o1PXWT6RbdejF0mCy2wl+JYt7ulKSnj7oxXehPOBKc2thz4bcQ///If4jXSRK +9dNtD2IEBVeC2m6kMyV5Sy5UGYvMLD0w6dEG/+gyRr61M3Z3qAFdlsHB1b6uJcDJ +HgoJIIihDsnzb02CVAAgp9KP5DlUFy6NHrgbuxu9mk47EDTcnIhT76IxW1hPkWLI +wpqazRVdOKnWvvgTtZ8SafJQYqz7Fzf07rh1Z2AQ+4NQ+US1dZxAF7L+/XldblhY +XzD8AK6vM8EOTmy6p6ahfzLbOOCxchcKK5HsamMm7YnUeMx0HgX4a/6ManY5Ka5l +IxKVCCIcl85bBu4M4ru8H0ST9tg4RQUh7eStqxK2A6RCLi3ECToDZ2mEmuFZkIoo +hdVddLHRDiBYmxOlsGOm7XtH/UVVMKTumtTm4ofvmMkyghEpIrwACjFeLQ/Ajulr +so8uBtjRkcfGEvRM/TAXw8HaOFvjqermobp573PYtlNXLfbQ4ddI +-----END CERTIFICATE----- + +# Issuer: CN=Buypass Class 2 Root CA O=Buypass AS-983163327 +# Subject: CN=Buypass Class 2 Root CA O=Buypass AS-983163327 +# Label: "Buypass Class 2 Root CA" +# Serial: 2 +# MD5 Fingerprint: 46:a7:d2:fe:45:fb:64:5a:a8:59:90:9b:78:44:9b:29 +# SHA1 Fingerprint: 49:0a:75:74:de:87:0a:47:fe:58:ee:f6:c7:6b:eb:c6:0b:12:40:99 +# SHA256 Fingerprint: 9a:11:40:25:19:7c:5b:b9:5d:94:e6:3d:55:cd:43:79:08:47:b6:46:b2:3c:df:11:ad:a4:a0:0e:ff:15:fb:48 +-----BEGIN CERTIFICATE----- +MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEd +MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3Mg +Q2xhc3MgMiBSb290IENBMB4XDTEwMTAyNjA4MzgwM1oXDTQwMTAyNjA4MzgwM1ow +TjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MSAw +HgYDVQQDDBdCdXlwYXNzIENsYXNzIDIgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEB +BQADggIPADCCAgoCggIBANfHXvfBB9R3+0Mh9PT1aeTuMgHbo4Yf5FkNuud1g1Lr +6hxhFUi7HQfKjK6w3Jad6sNgkoaCKHOcVgb/S2TwDCo3SbXlzwx87vFKu3MwZfPV +L4O2fuPn9Z6rYPnT8Z2SdIrkHJasW4DptfQxh6NR/Md+oW+OU3fUl8FVM5I+GC91 +1K2GScuVr1QGbNgGE41b/+EmGVnAJLqBcXmQRFBoJJRfuLMR8SlBYaNByyM21cHx +MlAQTn/0hpPshNOOvEu/XAFOBz3cFIqUCqTqc/sLUegTBxj6DvEr0VQVfTzh97QZ +QmdiXnfgolXsttlpF9U6r0TtSsWe5HonfOV116rLJeffawrbD02TTqigzXsu8lkB +arcNuAeBfos4GzjmCleZPe4h6KP1DBbdi+w0jpwqHAAVF41og9JwnxgIzRFo1clr +Us3ERo/ctfPYV3Me6ZQ5BL/T3jjetFPsaRyifsSP5BtwrfKi+fv3FmRmaZ9JUaLi +FRhnBkp/1Wy1TbMz4GHrXb7pmA8y1x1LPC5aAVKRCfLf6o3YBkBjqhHk/sM3nhRS +P/TizPJhk9H9Z2vXUq6/aKtAQ6BXNVN48FP4YUIHZMbXb5tMOA1jrGKvNouicwoN +9SG9dKpN6nIDSdvHXx1iY8f93ZHsM+71bbRuMGjeyNYmsHVee7QHIJihdjK4TWxP +AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFMmAd+BikoL1Rpzz +uvdMw964o605MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAU18h +9bqwOlI5LJKwbADJ784g7wbylp7ppHR/ehb8t/W2+xUbP6umwHJdELFx7rxP462s +A20ucS6vxOOto70MEae0/0qyexAQH6dXQbLArvQsWdZHEIjzIVEpMMpghq9Gqx3t +OluwlN5E40EIosHsHdb9T7bWR9AUC8rmyrV7d35BH16Dx7aMOZawP5aBQW9gkOLo ++fsicdl9sz1Gv7SEr5AcD48Saq/v7h56rgJKihcrdv6sVIkkLE8/trKnToyokZf7 +KcZ7XC25y2a2t6hbElGFtQl+Ynhw/qlqYLYdDnkM/crqJIByw5c/8nerQyIKx+u2 +DISCLIBrQYoIwOula9+ZEsuK1V6ADJHgJgg2SMX6OBE1/yWDLfJ6v9r9jv6ly0Us +H8SIU653DtmadsWOLB2jutXsMq7Aqqz30XpN69QH4kj3Io6wpJ9qzo6ysmD0oyLQ +I+uUWnpp3Q+/QFesa1lQ2aOZ4W7+jQF5JyMV3pKdewlNWudLSDBaGOYKbeaP4NK7 +5t98biGCwWg5TbSYWGZizEqQXsP6JwSxeRV0mcy+rSDeJmAc61ZRpqPq5KM/p/9h +3PFaTWwyI0PurKju7koSCTxdccK+efrCh2gdC/1cacwG0Jp9VJkqyTkaGa9LKkPz +Y11aWOIv4x3kqdbQCtCev9eBCfHJxyYNrJgWVqA= +-----END CERTIFICATE----- + +# Issuer: CN=Buypass Class 3 Root CA O=Buypass AS-983163327 +# Subject: CN=Buypass Class 3 Root CA O=Buypass AS-983163327 +# Label: "Buypass Class 3 Root CA" +# Serial: 2 +# MD5 Fingerprint: 3d:3b:18:9e:2c:64:5a:e8:d5:88:ce:0e:f9:37:c2:ec +# SHA1 Fingerprint: da:fa:f7:fa:66:84:ec:06:8f:14:50:bd:c7:c2:81:a5:bc:a9:64:57 +# SHA256 Fingerprint: ed:f7:eb:bc:a2:7a:2a:38:4d:38:7b:7d:40:10:c6:66:e2:ed:b4:84:3e:4c:29:b4:ae:1d:5b:93:32:e6:b2:4d +-----BEGIN CERTIFICATE----- +MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEd +MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3Mg +Q2xhc3MgMyBSb290IENBMB4XDTEwMTAyNjA4Mjg1OFoXDTQwMTAyNjA4Mjg1OFow +TjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MSAw +HgYDVQQDDBdCdXlwYXNzIENsYXNzIDMgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEB +BQADggIPADCCAgoCggIBAKXaCpUWUOOV8l6ddjEGMnqb8RB2uACatVI2zSRHsJ8Y +ZLya9vrVediQYkwiL944PdbgqOkcLNt4EemOaFEVcsfzM4fkoF0LXOBXByow9c3E +N3coTRiR5r/VUv1xLXA+58bEiuPwKAv0dpihi4dVsjoT/Lc+JzeOIuOoTyrvYLs9 +tznDDgFHmV0ST9tD+leh7fmdvhFHJlsTmKtdFoqwNxxXnUX/iJY2v7vKB3tvh2PX +0DJq1l1sDPGzbjniazEuOQAnFN44wOwZZoYS6J1yFhNkUsepNxz9gjDthBgd9K5c +/3ATAOux9TN6S9ZV+AWNS2mw9bMoNlwUxFFzTWsL8TQH2xc519woe2v1n/MuwU8X +KhDzzMro6/1rqy6any2CbgTUUgGTLT2G/H783+9CHaZr77kgxve9oKeV/afmiSTY +zIw0bOIjL9kSGiG5VZFvC5F5GQytQIgLcOJ60g7YaEi7ghM5EFjp2CoHxhLbWNvS +O1UQRwUVZ2J+GGOmRj8JDlQyXr8NYnon74Do29lLBlo3WiXQCBJ31G8JUJc9yB3D +34xFMFbG02SrZvPAXpacw8Tvw3xrizp5f7NJzz3iiZ+gMEuFuZyUJHmPfWupRWgP +K9Dx2hzLabjKSWJtyNBjYt1gD1iqj6G8BaVmos8bdrKEZLFMOVLAMLrwjEsCsLa3 +AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFEe4zf/lb+74suwv +Tg75JbCOPGvDMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAACAj +QTUEkMJAYmDv4jVM1z+s4jSQuKFvdvoWFqRINyzpkMLyPPgKn9iB5btb2iUspKdV +cSQy9sgL8rxq+JOssgfCX5/bzMiKqr5qb+FJEMwx14C7u8jYog5kV+qi9cKpMRXS +IGrs/CIBKM+GuIAeqcwRpTzyFrNHnfzSgCHEy9BHcEGhyoMZCCxt8l13nIoUE9Q2 +HJLw5QY33KbmkJs4j1xrG0aGQ0JfPgEHU1RdZX33inOhmlRaHylDFCfChQ+1iHsa +O5S3HWCntZznKWlXWpuTekMwGwPXYshApqr8ZORK15FTAaggiG6cX0S5y2CBNOxv +033aSF/rtJC8LakcC6wc1aJoIIAE1vyxjy+7SjENSoYc6+I2KSb12tjE8nVhz36u +dmNKekBlk4f4HoCMhuWG1o8O/FMsYOgWYRqiPkN7zTlgVGr18okmAWiDSKIz6MkE +kbIRNBE+6tBDGR8Dk5AM/1E9V/RBbuHLoL7ryWPNbczk+DaqaJ3tvV2XcEQNtg41 +3OEMXbugUZTLfhbrES+jkkXITHHZvMmZUldGL1DPvTVp9D0VzgalLA8+9oG6lLvD +u79leNKGef9JOxqDDPDeeOzI8k1MGt6CKfjBWtrt7uYnXuhF0J0cUahoq0Tj0Itq +4/g7u9xN12TyUb7mqqta6THuBrxzvxNiCp/HuZc= +-----END CERTIFICATE----- + +# Issuer: CN=T-TeleSec GlobalRoot Class 3 O=T-Systems Enterprise Services GmbH OU=T-Systems Trust Center +# Subject: CN=T-TeleSec GlobalRoot Class 3 O=T-Systems Enterprise Services GmbH OU=T-Systems Trust Center +# Label: "T-TeleSec GlobalRoot Class 3" +# Serial: 1 +# MD5 Fingerprint: ca:fb:40:a8:4e:39:92:8a:1d:fe:8e:2f:c4:27:ea:ef +# SHA1 Fingerprint: 55:a6:72:3e:cb:f2:ec:cd:c3:23:74:70:19:9d:2a:be:11:e3:81:d1 +# SHA256 Fingerprint: fd:73:da:d3:1c:64:4f:f1:b4:3b:ef:0c:cd:da:96:71:0b:9c:d9:87:5e:ca:7e:31:70:7a:f3:e9:6d:52:2b:bd +-----BEGIN CERTIFICATE----- +MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUx +KzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAd +BgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNl +YyBHbG9iYWxSb290IENsYXNzIDMwHhcNMDgxMDAxMTAyOTU2WhcNMzMxMDAxMjM1 +OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnBy +aXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50 +ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC9dZPwYiJvJK7genasfb3ZJNW4t/zN +8ELg63iIVl6bmlQdTQyK9tPPcPRStdiTBONGhnFBSivwKixVA9ZIw+A5OO3yXDw/ +RLyTPWGrTs0NvvAgJ1gORH8EGoel15YUNpDQSXuhdfsaa3Ox+M6pCSzyU9XDFES4 +hqX2iys52qMzVNn6chr3IhUciJFrf2blw2qAsCTz34ZFiP0Zf3WHHx+xGwpzJFu5 +ZeAsVMhg02YXP+HMVDNzkQI6pn97djmiH5a2OK61yJN0HZ65tOVgnS9W0eDrXltM +EnAMbEQgqxHY9Bn20pxSN+f6tsIxO0rUFJmtxxr1XV/6B7h8DR/Wgx6zAgMBAAGj +QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS1 +A/d2O2GCahKqGFPrAyGUv/7OyjANBgkqhkiG9w0BAQsFAAOCAQEAVj3vlNW92nOy +WL6ukK2YJ5f+AbGwUgC4TeQbIXQbfsDuXmkqJa9c1h3a0nnJ85cp4IaH3gRZD/FZ +1GSFS5mvJQQeyUapl96Cshtwn5z2r3Ex3XsFpSzTucpH9sry9uetuUg/vBa3wW30 +6gmv7PO15wWeph6KU1HWk4HMdJP2udqmJQV0eVp+QD6CSyYRMG7hP0HHRwA11fXT +91Q+gT3aSWqas+8QPebrb9HIIkfLzM8BMZLZGOMivgkeGj5asuRrDFR6fUNOuIml +e9eiPZaGzPImNC1qkp2aGtAw4l1OBLBfiyB+d8E9lYLRRpo7PHi4b6HQDWSieB4p +TpPDpFQUWw== +-----END CERTIFICATE----- + +# Issuer: CN=EE Certification Centre Root CA O=AS Sertifitseerimiskeskus +# Subject: CN=EE Certification Centre Root CA O=AS Sertifitseerimiskeskus +# Label: "EE Certification Centre Root CA" +# Serial: 112324828676200291871926431888494945866 +# MD5 Fingerprint: 43:5e:88:d4:7d:1a:4a:7e:fd:84:2e:52:eb:01:d4:6f +# SHA1 Fingerprint: c9:a8:b9:e7:55:80:5e:58:e3:53:77:a7:25:eb:af:c3:7b:27:cc:d7 +# SHA256 Fingerprint: 3e:84:ba:43:42:90:85:16:e7:75:73:c0:99:2f:09:79:ca:08:4e:46:85:68:1f:f1:95:cc:ba:8a:22:9b:8a:76 +-----BEGIN CERTIFICATE----- +MIIEAzCCAuugAwIBAgIQVID5oHPtPwBMyonY43HmSjANBgkqhkiG9w0BAQUFADB1 +MQswCQYDVQQGEwJFRTEiMCAGA1UECgwZQVMgU2VydGlmaXRzZWVyaW1pc2tlc2t1 +czEoMCYGA1UEAwwfRUUgQ2VydGlmaWNhdGlvbiBDZW50cmUgUm9vdCBDQTEYMBYG +CSqGSIb3DQEJARYJcGtpQHNrLmVlMCIYDzIwMTAxMDMwMTAxMDMwWhgPMjAzMDEy +MTcyMzU5NTlaMHUxCzAJBgNVBAYTAkVFMSIwIAYDVQQKDBlBUyBTZXJ0aWZpdHNl +ZXJpbWlza2Vza3VzMSgwJgYDVQQDDB9FRSBDZXJ0aWZpY2F0aW9uIENlbnRyZSBS +b290IENBMRgwFgYJKoZIhvcNAQkBFglwa2lAc2suZWUwggEiMA0GCSqGSIb3DQEB +AQUAA4IBDwAwggEKAoIBAQDIIMDs4MVLqwd4lfNE7vsLDP90jmG7sWLqI9iroWUy +euuOF0+W2Ap7kaJjbMeMTC55v6kF/GlclY1i+blw7cNRfdCT5mzrMEvhvH2/UpvO +bntl8jixwKIy72KyaOBhU8E2lf/slLo2rpwcpzIP5Xy0xm90/XsY6KxX7QYgSzIw +WFv9zajmofxwvI6Sc9uXp3whrj3B9UiHbCe9nyV0gVWw93X2PaRka9ZP585ArQ/d +MtO8ihJTmMmJ+xAdTX7Nfh9WDSFwhfYggx/2uh8Ej+p3iDXE/+pOoYtNP2MbRMNE +1CV2yreN1x5KZmTNXMWcg+HCCIia7E6j8T4cLNlsHaFLAgMBAAGjgYowgYcwDwYD +VR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFBLyWj7qVhy/ +zQas8fElyalL1BSZMEUGA1UdJQQ+MDwGCCsGAQUFBwMCBggrBgEFBQcDAQYIKwYB +BQUHAwMGCCsGAQUFBwMEBggrBgEFBQcDCAYIKwYBBQUHAwkwDQYJKoZIhvcNAQEF +BQADggEBAHv25MANqhlHt01Xo/6tu7Fq1Q+e2+RjxY6hUFaTlrg4wCQiZrxTFGGV +v9DHKpY5P30osxBAIWrEr7BSdxjhlthWXePdNl4dp1BUoMUq5KqMlIpPnTX/dqQG +E5Gion0ARD9V04I8GtVbvFZMIi5GQ4okQC3zErg7cBqklrkar4dBGmoYDQZPxz5u +uSlNDUmJEYcyW+ZLBMjkXOZ0c5RdFpgTlf7727FE5TpwrDdr5rMzcijJs1eg9gIW +iAYLtqZLICjU3j2LrTcFU3T+bsy8QxdxXvnFzBqpYe73dgzzcvRyrc9yAjYHR8/v +GVCJYMzpJJUPwssd8m92kMfMdcGWxZ0= +-----END CERTIFICATE----- + +# Issuer: CN=TÜRKTRUST Elektronik Sertifika Hizmet Sağlayıcısı O=TÜRKTRUST Bilgi İletişim ve Bilişim Güvenliği Hizmetleri A.Ş. (c) Aralık 2007 +# Subject: CN=TÜRKTRUST Elektronik Sertifika Hizmet Sağlayıcısı O=TÜRKTRUST Bilgi İletişim ve Bilişim Güvenliği Hizmetleri A.Ş. (c) Aralık 2007 +# Label: "TURKTRUST Certificate Services Provider Root 2007" +# Serial: 1 +# MD5 Fingerprint: 2b:70:20:56:86:82:a0:18:c8:07:53:12:28:70:21:72 +# SHA1 Fingerprint: f1:7f:6f:b6:31:dc:99:e3:a3:c8:7f:fe:1c:f1:81:10:88:d9:60:33 +# SHA256 Fingerprint: 97:8c:d9:66:f2:fa:a0:7b:a7:aa:95:00:d9:c0:2e:9d:77:f2:cd:ad:a6:ad:6b:a7:4a:f4:b9:1c:66:59:3c:50 +-----BEGIN CERTIFICATE----- +MIIEPTCCAyWgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBvzE/MD0GA1UEAww2VMOc +UktUUlVTVCBFbGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sx +c8SxMQswCQYDVQQGEwJUUjEPMA0GA1UEBwwGQW5rYXJhMV4wXAYDVQQKDFVUw5xS +S1RSVVNUIEJpbGdpIMSwbGV0acWfaW0gdmUgQmlsacWfaW0gR8O8dmVubGnEn2kg +SGl6bWV0bGVyaSBBLsWeLiAoYykgQXJhbMSxayAyMDA3MB4XDTA3MTIyNTE4Mzcx +OVoXDTE3MTIyMjE4MzcxOVowgb8xPzA9BgNVBAMMNlTDnFJLVFJVU1QgRWxla3Ry +b25payBTZXJ0aWZpa2EgSGl6bWV0IFNhxJ9sYXnEsWPEsXPEsTELMAkGA1UEBhMC +VFIxDzANBgNVBAcMBkFua2FyYTFeMFwGA1UECgxVVMOcUktUUlVTVCBCaWxnaSDE +sGxldGnFn2ltIHZlIEJpbGnFn2ltIEfDvHZlbmxpxJ9pIEhpem1ldGxlcmkgQS7F +ni4gKGMpIEFyYWzEsWsgMjAwNzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAKu3PgqMyKVYFeaK7yc9SrToJdPNM8Ig3BnuiD9NYvDdE3ePYakqtdTyuTFY +KTsvP2qcb3N2Je40IIDu6rfwxArNK4aUyeNgsURSsloptJGXg9i3phQvKUmi8wUG ++7RP2qFsmmaf8EMJyupyj+sA1zU511YXRxcw9L6/P8JorzZAwan0qafoEGsIiveG +HtyaKhUG9qPw9ODHFNRRf8+0222vR5YXm3dx2KdxnSQM9pQ/hTEST7ruToK4uT6P +IzdezKKqdfcYbwnTrqdUKDT74eA7YH2gvnmJhsifLfkKS8RQouf9eRbHegsYz85M +733WB2+Y8a+xwXrXgTW4qhe04MsCAwEAAaNCMEAwHQYDVR0OBBYEFCnFkKslrxHk +Yb+j/4hhkeYO/pyBMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0G +CSqGSIb3DQEBBQUAA4IBAQAQDdr4Ouwo0RSVgrESLFF6QSU2TJ/sPx+EnWVUXKgW +AkD6bho3hO9ynYYKVZ1WKKxmLNA6VpM0ByWtCLCPyA8JWcqdmBzlVPi5RX9ql2+I +aE1KBiY3iAIOtsbWcpnOa3faYjGkVh+uX4132l32iPwa2Z61gfAyuOOI0JzzaqC5 +mxRZNTZPz/OOXl0XrRWV2N2y1RVuAE6zS89mlOTgzbUF2mNXi+WzqtvALhyQRNsa +XRik7r4EW5nVcV9VZWRi1aKbBFmGyGJ353yCRWo9F7/snXUMrqNvWtMvmDb08PUZ +qxFdyKbjKlhqQgnDvZImZjINXQhVdP+MmNAKpoRq0Tl9 +-----END CERTIFICATE----- + +# Issuer: CN=D-TRUST Root Class 3 CA 2 2009 O=D-Trust GmbH +# Subject: CN=D-TRUST Root Class 3 CA 2 2009 O=D-Trust GmbH +# Label: "D-TRUST Root Class 3 CA 2 2009" +# Serial: 623603 +# MD5 Fingerprint: cd:e0:25:69:8d:47:ac:9c:89:35:90:f7:fd:51:3d:2f +# SHA1 Fingerprint: 58:e8:ab:b0:36:15:33:fb:80:f7:9b:1b:6d:29:d3:ff:8d:5f:00:f0 +# SHA256 Fingerprint: 49:e7:a4:42:ac:f0:ea:62:87:05:00:54:b5:25:64:b6:50:e4:f4:9e:42:e3:48:d6:aa:38:e0:39:e9:57:b1:c1 +-----BEGIN CERTIFICATE----- +MIIEMzCCAxugAwIBAgIDCYPzMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNVBAYTAkRF +MRUwEwYDVQQKDAxELVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBD +bGFzcyAzIENBIDIgMjAwOTAeFw0wOTExMDUwODM1NThaFw0yOTExMDUwODM1NTha +ME0xCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxELVRydXN0IEdtYkgxJzAlBgNVBAMM +HkQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgMjAwOTCCASIwDQYJKoZIhvcNAQEB +BQADggEPADCCAQoCggEBANOySs96R+91myP6Oi/WUEWJNTrGa9v+2wBoqOADER03 +UAifTUpolDWzU9GUY6cgVq/eUXjsKj3zSEhQPgrfRlWLJ23DEE0NkVJD2IfgXU42 +tSHKXzlABF9bfsyjxiupQB7ZNoTWSPOSHjRGICTBpFGOShrvUD9pXRl/RcPHAY9R +ySPocq60vFYJfxLLHLGvKZAKyVXMD9O0Gu1HNVpK7ZxzBCHQqr0ME7UAyiZsxGsM +lFqVlNpQmvH/pStmMaTJOKDfHR+4CS7zp+hnUquVH+BGPtikw8paxTGA6Eian5Rp +/hnd2HN8gcqW3o7tszIFZYQ05ub9VxC1X3a/L7AQDcUCAwEAAaOCARowggEWMA8G +A1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFP3aFMSfMN4hvR5COfyrYyNJ4PGEMA4G +A1UdDwEB/wQEAwIBBjCB0wYDVR0fBIHLMIHIMIGAoH6gfIZ6bGRhcDovL2RpcmVj +dG9yeS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwUm9vdCUyMENsYXNzJTIwMyUy +MENBJTIwMiUyMDIwMDksTz1ELVRydXN0JTIwR21iSCxDPURFP2NlcnRpZmljYXRl +cmV2b2NhdGlvbmxpc3QwQ6BBoD+GPWh0dHA6Ly93d3cuZC10cnVzdC5uZXQvY3Js +L2QtdHJ1c3Rfcm9vdF9jbGFzc18zX2NhXzJfMjAwOS5jcmwwDQYJKoZIhvcNAQEL +BQADggEBAH+X2zDI36ScfSF6gHDOFBJpiBSVYEQBrLLpME+bUMJm2H6NMLVwMeni +acfzcNsgFYbQDfC+rAF1hM5+n02/t2A7nPPKHeJeaNijnZflQGDSNiH+0LS4F9p0 +o3/U37CYAqxva2ssJSRyoWXuJVrl5jLn8t+rSfrzkGkj2wTZ51xY/GXUl77M/C4K +zCUqNQT4YJEVdT1B/yMfGchs64JTBKbkTCJNjYy6zltz7GRUUG3RnFX7acM2w4y8 +PIWmawomDeCTmGCufsYkl4phX5GOZpIJhzbNi5stPvZR1FDUWSi9g/LMKHtThm3Y +Johw1+qRzT65ysCQblrGXnRl11z+o+I= +-----END CERTIFICATE----- + +# Issuer: CN=D-TRUST Root Class 3 CA 2 EV 2009 O=D-Trust GmbH +# Subject: CN=D-TRUST Root Class 3 CA 2 EV 2009 O=D-Trust GmbH +# Label: "D-TRUST Root Class 3 CA 2 EV 2009" +# Serial: 623604 +# MD5 Fingerprint: aa:c6:43:2c:5e:2d:cd:c4:34:c0:50:4f:11:02:4f:b6 +# SHA1 Fingerprint: 96:c9:1b:0b:95:b4:10:98:42:fa:d0:d8:22:79:fe:60:fa:b9:16:83 +# SHA256 Fingerprint: ee:c5:49:6b:98:8c:e9:86:25:b9:34:09:2e:ec:29:08:be:d0:b0:f3:16:c2:d4:73:0c:84:ea:f1:f3:d3:48:81 +-----BEGIN CERTIFICATE----- +MIIEQzCCAyugAwIBAgIDCYP0MA0GCSqGSIb3DQEBCwUAMFAxCzAJBgNVBAYTAkRF +MRUwEwYDVQQKDAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBD +bGFzcyAzIENBIDIgRVYgMjAwOTAeFw0wOTExMDUwODUwNDZaFw0yOTExMDUwODUw +NDZaMFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxELVRydXN0IEdtYkgxKjAoBgNV +BAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAwOTCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAJnxhDRwui+3MKCOvXwEz75ivJn9gpfSegpn +ljgJ9hBOlSJzmY3aFS3nBfwZcyK3jpgAvDw9rKFs+9Z5JUut8Mxk2og+KbgPCdM0 +3TP1YtHhzRnp7hhPTFiu4h7WDFsVWtg6uMQYZB7jM7K1iXdODL/ZlGsTl28So/6Z +qQTMFexgaDbtCHu39b+T7WYxg4zGcTSHThfqr4uRjRxWQa4iN1438h3Z0S0NL2lR +p75mpoo6Kr3HGrHhFPC+Oh25z1uxav60sUYgovseO3Dvk5h9jHOW8sXvhXCtKSb8 +HgQ+HKDYD8tSg2J87otTlZCpV6LqYQXY+U3EJ/pure3511H3a6UCAwEAAaOCASQw +ggEgMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNOUikxiEyoZLsyvcop9Ntea +HNxnMA4GA1UdDwEB/wQEAwIBBjCB3QYDVR0fBIHVMIHSMIGHoIGEoIGBhn9sZGFw +Oi8vZGlyZWN0b3J5LmQtdHJ1c3QubmV0L0NOPUQtVFJVU1QlMjBSb290JTIwQ2xh +c3MlMjAzJTIwQ0ElMjAyJTIwRVYlMjAyMDA5LE89RC1UcnVzdCUyMEdtYkgsQz1E +RT9jZXJ0aWZpY2F0ZXJldm9jYXRpb25saXN0MEagRKBChkBodHRwOi8vd3d3LmQt +dHJ1c3QubmV0L2NybC9kLXRydXN0X3Jvb3RfY2xhc3NfM19jYV8yX2V2XzIwMDku +Y3JsMA0GCSqGSIb3DQEBCwUAA4IBAQA07XtaPKSUiO8aEXUHL7P+PPoeUSbrh/Yp +3uDx1MYkCenBz1UbtDDZzhr+BlGmFaQt77JLvyAoJUnRpjZ3NOhk31KxEcdzes05 +nsKtjHEh8lprr988TlWvsoRlFIm5d8sqMb7Po23Pb0iUMkZv53GMoKaEGTcH8gNF +CSuGdXzfX2lXANtu2KZyIktQ1HWYVt+3GP9DQ1CuekR78HlR10M9p9OB0/DJT7na +xpeG0ILD5EJt/rDiZE4OJudANCa1CInXCGNjOCd1HjPqbqjdn5lPdE2BiYBL3ZqX +KVwvvoFBuYz/6n1gBp7N1z3TLqMVvKjmJuVvw9y4AyHqnxbxLFS1 +-----END CERTIFICATE----- + +# Issuer: CN=Autoridad de Certificacion Raiz del Estado Venezolano O=Sistema Nacional de Certificacion Electronica OU=Superintendencia de Servicios de Certificacion Electronica +# Subject: CN=PSCProcert O=Sistema Nacional de Certificacion Electronica OU=Proveedor de Certificados PROCERT +# Label: "PSCProcert" +# Serial: 11 +# MD5 Fingerprint: e6:24:e9:12:01:ae:0c:de:8e:85:c4:ce:a3:12:dd:ec +# SHA1 Fingerprint: 70:c1:8d:74:b4:28:81:0a:e4:fd:a5:75:d7:01:9f:99:b0:3d:50:74 +# SHA256 Fingerprint: 3c:fc:3c:14:d1:f6:84:ff:17:e3:8c:43:ca:44:0c:00:b9:67:ec:93:3e:8b:fe:06:4c:a1:d7:2c:90:f2:ad:b0 +-----BEGIN CERTIFICATE----- +MIIJhjCCB26gAwIBAgIBCzANBgkqhkiG9w0BAQsFADCCAR4xPjA8BgNVBAMTNUF1 +dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIFJhaXogZGVsIEVzdGFkbyBWZW5lem9s +YW5vMQswCQYDVQQGEwJWRTEQMA4GA1UEBxMHQ2FyYWNhczEZMBcGA1UECBMQRGlz +dHJpdG8gQ2FwaXRhbDE2MDQGA1UEChMtU2lzdGVtYSBOYWNpb25hbCBkZSBDZXJ0 +aWZpY2FjaW9uIEVsZWN0cm9uaWNhMUMwQQYDVQQLEzpTdXBlcmludGVuZGVuY2lh +IGRlIFNlcnZpY2lvcyBkZSBDZXJ0aWZpY2FjaW9uIEVsZWN0cm9uaWNhMSUwIwYJ +KoZIhvcNAQkBFhZhY3JhaXpAc3VzY2VydGUuZ29iLnZlMB4XDTEwMTIyODE2NTEw +MFoXDTIwMTIyNTIzNTk1OVowgdExJjAkBgkqhkiG9w0BCQEWF2NvbnRhY3RvQHBy +b2NlcnQubmV0LnZlMQ8wDQYDVQQHEwZDaGFjYW8xEDAOBgNVBAgTB01pcmFuZGEx +KjAoBgNVBAsTIVByb3ZlZWRvciBkZSBDZXJ0aWZpY2Fkb3MgUFJPQ0VSVDE2MDQG +A1UEChMtU2lzdGVtYSBOYWNpb25hbCBkZSBDZXJ0aWZpY2FjaW9uIEVsZWN0cm9u +aWNhMQswCQYDVQQGEwJWRTETMBEGA1UEAxMKUFNDUHJvY2VydDCCAiIwDQYJKoZI +hvcNAQEBBQADggIPADCCAgoCggIBANW39KOUM6FGqVVhSQ2oh3NekS1wwQYalNo9 +7BVCwfWMrmoX8Yqt/ICV6oNEolt6Vc5Pp6XVurgfoCfAUFM+jbnADrgV3NZs+J74 +BCXfgI8Qhd19L3uA3VcAZCP4bsm+lU/hdezgfl6VzbHvvnpC2Mks0+saGiKLt38G +ieU89RLAu9MLmV+QfI4tL3czkkohRqipCKzx9hEC2ZUWno0vluYC3XXCFCpa1sl9 +JcLB/KpnheLsvtF8PPqv1W7/U0HU9TI4seJfxPmOEO8GqQKJ/+MMbpfg353bIdD0 +PghpbNjU5Db4g7ayNo+c7zo3Fn2/omnXO1ty0K+qP1xmk6wKImG20qCZyFSTXai2 +0b1dCl53lKItwIKOvMoDKjSuc/HUtQy9vmebVOvh+qBa7Dh+PsHMosdEMXXqP+UH +0quhJZb25uSgXTcYOWEAM11G1ADEtMo88aKjPvM6/2kwLkDd9p+cJsmWN63nOaK/ +6mnbVSKVUyqUtd+tFjiBdWbjxywbk5yqjKPK2Ww8F22c3HxT4CAnQzb5EuE8XL1m +v6JpIzi4mWCZDlZTOpx+FIywBm/xhnaQr/2v/pDGj59/i5IjnOcVdo/Vi5QTcmn7 +K2FjiO/mpF7moxdqWEfLcU8UC17IAggmosvpr2uKGcfLFFb14dq12fy/czja+eev +bqQ34gcnAgMBAAGjggMXMIIDEzASBgNVHRMBAf8ECDAGAQH/AgEBMDcGA1UdEgQw +MC6CD3N1c2NlcnRlLmdvYi52ZaAbBgVghl4CAqASDBBSSUYtRy0yMDAwNDAzNi0w +MB0GA1UdDgQWBBRBDxk4qpl/Qguk1yeYVKIXTC1RVDCCAVAGA1UdIwSCAUcwggFD +gBStuyIdxuDSAaj9dlBSk+2YwU2u06GCASakggEiMIIBHjE+MDwGA1UEAxM1QXV0 +b3JpZGFkIGRlIENlcnRpZmljYWNpb24gUmFpeiBkZWwgRXN0YWRvIFZlbmV6b2xh +bm8xCzAJBgNVBAYTAlZFMRAwDgYDVQQHEwdDYXJhY2FzMRkwFwYDVQQIExBEaXN0 +cml0byBDYXBpdGFsMTYwNAYDVQQKEy1TaXN0ZW1hIE5hY2lvbmFsIGRlIENlcnRp +ZmljYWNpb24gRWxlY3Ryb25pY2ExQzBBBgNVBAsTOlN1cGVyaW50ZW5kZW5jaWEg +ZGUgU2VydmljaW9zIGRlIENlcnRpZmljYWNpb24gRWxlY3Ryb25pY2ExJTAjBgkq +hkiG9w0BCQEWFmFjcmFpekBzdXNjZXJ0ZS5nb2IudmWCAQowDgYDVR0PAQH/BAQD +AgEGME0GA1UdEQRGMESCDnByb2NlcnQubmV0LnZloBUGBWCGXgIBoAwMClBTQy0w +MDAwMDKgGwYFYIZeAgKgEgwQUklGLUotMzE2MzUzNzMtNzB2BgNVHR8EbzBtMEag +RKBChkBodHRwOi8vd3d3LnN1c2NlcnRlLmdvYi52ZS9sY3IvQ0VSVElGSUNBRE8t +UkFJWi1TSEEzODRDUkxERVIuY3JsMCOgIaAfhh1sZGFwOi8vYWNyYWl6LnN1c2Nl +cnRlLmdvYi52ZTA3BggrBgEFBQcBAQQrMCkwJwYIKwYBBQUHMAGGG2h0dHA6Ly9v +Y3NwLnN1c2NlcnRlLmdvYi52ZTBBBgNVHSAEOjA4MDYGBmCGXgMBAjAsMCoGCCsG +AQUFBwIBFh5odHRwOi8vd3d3LnN1c2NlcnRlLmdvYi52ZS9kcGMwDQYJKoZIhvcN +AQELBQADggIBACtZ6yKZu4SqT96QxtGGcSOeSwORR3C7wJJg7ODU523G0+1ng3dS +1fLld6c2suNUvtm7CpsR72H0xpkzmfWvADmNg7+mvTV+LFwxNG9s2/NkAZiqlCxB +3RWGymspThbASfzXg0gTB1GEMVKIu4YXx2sviiCtxQuPcD4quxtxj7mkoP3Yldmv +Wb8lK5jpY5MvYB7Eqvh39YtsL+1+LrVPQA3uvFd359m21D+VJzog1eWuq2w1n8Gh +HVnchIHuTQfiSLaeS5UtQbHh6N5+LwUeaO6/u5BlOsju6rEYNxxik6SgMexxbJHm +pHmJWhSnFFAFTKQAVzAswbVhltw+HoSvOULP5dAssSS830DD7X9jSr3hTxJkhpXz +sOfIt+FTvZLm8wyWuevo5pLtp4EJFAv8lXrPj9Y0TzYS3F7RNHXGRoAvlQSMx4bE +qCaJqD8Zm4G7UaRKhqsLEQ+xrmNTbSjq3TNWOByyrYDT13K9mmyZY+gAu0F2Bbdb +mRiKw7gSXFbPVgx96OLP7bx0R/vu0xdOIk9W/1DzLuY5poLWccret9W6aAjtmcz9 +opLLabid+Qqkpj5PkygqYWwHJgD/ll9ohri4zspV4KuxPX+Y1zMOWj3YeMLEYC/H +YvBhkdI4sPaeVdtAgAUSM84dkpvRabP/v/GSCmE1P93+hvS84Bpxs2Km +-----END CERTIFICATE----- + +# Issuer: CN=China Internet Network Information Center EV Certificates Root O=China Internet Network Information Center +# Subject: CN=China Internet Network Information Center EV Certificates Root O=China Internet Network Information Center +# Label: "China Internet Network Information Center EV Certificates Root" +# Serial: 1218379777 +# MD5 Fingerprint: 55:5d:63:00:97:bd:6a:97:f5:67:ab:4b:fb:6e:63:15 +# SHA1 Fingerprint: 4f:99:aa:93:fb:2b:d1:37:26:a1:99:4a:ce:7f:f0:05:f2:93:5d:1e +# SHA256 Fingerprint: 1c:01:c6:f4:db:b2:fe:fc:22:55:8b:2b:ca:32:56:3f:49:84:4a:cf:c3:2b:7b:e4:b0:ff:59:9f:9e:8c:7a:f7 +-----BEGIN CERTIFICATE----- +MIID9zCCAt+gAwIBAgIESJ8AATANBgkqhkiG9w0BAQUFADCBijELMAkGA1UEBhMC +Q04xMjAwBgNVBAoMKUNoaW5hIEludGVybmV0IE5ldHdvcmsgSW5mb3JtYXRpb24g +Q2VudGVyMUcwRQYDVQQDDD5DaGluYSBJbnRlcm5ldCBOZXR3b3JrIEluZm9ybWF0 +aW9uIENlbnRlciBFViBDZXJ0aWZpY2F0ZXMgUm9vdDAeFw0xMDA4MzEwNzExMjVa +Fw0zMDA4MzEwNzExMjVaMIGKMQswCQYDVQQGEwJDTjEyMDAGA1UECgwpQ2hpbmEg +SW50ZXJuZXQgTmV0d29yayBJbmZvcm1hdGlvbiBDZW50ZXIxRzBFBgNVBAMMPkNo +aW5hIEludGVybmV0IE5ldHdvcmsgSW5mb3JtYXRpb24gQ2VudGVyIEVWIENlcnRp +ZmljYXRlcyBSb290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAm35z +7r07eKpkQ0H1UN+U8i6yjUqORlTSIRLIOTJCBumD1Z9S7eVnAztUwYyZmczpwA// +DdmEEbK40ctb3B75aDFk4Zv6dOtouSCV98YPjUesWgbdYavi7NifFy2cyjw1l1Vx +zUOFsUcW9SxTgHbP0wBkvUCZ3czY28Sf1hNfQYOL+Q2HklY0bBoQCxfVWhyXWIQ8 +hBouXJE0bhlffxdpxWXvayHG1VA6v2G5BY3vbzQ6sm8UY78WO5upKv23KzhmBsUs +4qpnHkWnjQRmQvaPK++IIGmPMowUc9orhpFjIpryp9vOiYurXccUwVswah+xt54u +gQEC7c+WXmPbqOY4twIDAQABo2MwYTAfBgNVHSMEGDAWgBR8cks5x8DbYqVPm6oY +NJKiyoOCWTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4E +FgQUfHJLOcfA22KlT5uqGDSSosqDglkwDQYJKoZIhvcNAQEFBQADggEBACrDx0M3 +j92tpLIM7twUbY8opJhJywyA6vPtI2Z1fcXTIWd50XPFtQO3WKwMVC/GVhMPMdoG +52U7HW8228gd+f2ABsqjPWYWqJ1MFn3AlUa1UeTiH9fqBk1jjZaM7+czV0I664zB +echNdn3e9rG3geCg+aF4RhcaVpjwTj2rHO3sOdwHSPdj/gauwqRcalsyiMXHM4Ws +ZkJHwlgkmeHlPuV1LI5D1l08eB6olYIpUNHRFrrvwb562bTYzB5MRuF3sTGrvSrI +zo9uoV1/A3U05K2JRVRevq4opbs/eHnrc7MKDf2+yfdWrPa37S+bISnHOLaVxATy +wy39FCqQmbkHzJ8= +-----END CERTIFICATE----- + +# Issuer: CN=Swisscom Root CA 2 O=Swisscom OU=Digital Certificate Services +# Subject: CN=Swisscom Root CA 2 O=Swisscom OU=Digital Certificate Services +# Label: "Swisscom Root CA 2" +# Serial: 40698052477090394928831521023204026294 +# MD5 Fingerprint: 5b:04:69:ec:a5:83:94:63:18:a7:86:d0:e4:f2:6e:19 +# SHA1 Fingerprint: 77:47:4f:c6:30:e4:0f:4c:47:64:3f:84:ba:b8:c6:95:4a:8a:41:ec +# SHA256 Fingerprint: f0:9b:12:2c:71:14:f4:a0:9b:d4:ea:4f:4a:99:d5:58:b4:6e:4c:25:cd:81:14:0d:29:c0:56:13:91:4c:38:41 +-----BEGIN CERTIFICATE----- +MIIF2TCCA8GgAwIBAgIQHp4o6Ejy5e/DfEoeWhhntjANBgkqhkiG9w0BAQsFADBk +MQswCQYDVQQGEwJjaDERMA8GA1UEChMIU3dpc3Njb20xJTAjBgNVBAsTHERpZ2l0 +YWwgQ2VydGlmaWNhdGUgU2VydmljZXMxGzAZBgNVBAMTElN3aXNzY29tIFJvb3Qg +Q0EgMjAeFw0xMTA2MjQwODM4MTRaFw0zMTA2MjUwNzM4MTRaMGQxCzAJBgNVBAYT +AmNoMREwDwYDVQQKEwhTd2lzc2NvbTElMCMGA1UECxMcRGlnaXRhbCBDZXJ0aWZp +Y2F0ZSBTZXJ2aWNlczEbMBkGA1UEAxMSU3dpc3Njb20gUm9vdCBDQSAyMIICIjAN +BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAlUJOhJ1R5tMJ6HJaI2nbeHCOFvEr +jw0DzpPMLgAIe6szjPTpQOYXTKueuEcUMncy3SgM3hhLX3af+Dk7/E6J2HzFZ++r +0rk0X2s682Q2zsKwzxNoysjL67XiPS4h3+os1OD5cJZM/2pYmLcX5BtS5X4HAB1f +2uY+lQS3aYg5oUFgJWFLlTloYhyxCwWJwDaCFCE/rtuh/bxvHGCGtlOUSbkrRsVP +ACu/obvLP+DHVxxX6NZp+MEkUp2IVd3Chy50I9AU/SpHWrumnf2U5NGKpV+GY3aF +y6//SSj8gO1MedK75MDvAe5QQQg1I3ArqRa0jG6F6bYRzzHdUyYb3y1aSgJA/MTA +tukxGggo5WDDH8SQjhBiYEQN7Aq+VRhxLKX0srwVYv8c474d2h5Xszx+zYIdkeNL +6yxSNLCK/RJOlrDrcH+eOfdmQrGrrFLadkBXeyq96G4DsguAhYidDMfCd7Camlf0 +uPoTXGiTOmekl9AbmbeGMktg2M7v0Ax/lZ9vh0+Hio5fCHyqW/xavqGRn1V9TrAL +acywlKinh/LTSlDcX3KwFnUey7QYYpqwpzmqm59m2I2mbJYV4+by+PGDYmy7Velh +k6M99bFXi08jsJvllGov34zflVEpYKELKeRcVVi3qPyZ7iVNTA6z00yPhOgpD/0Q +VAKFyPnlw4vP5w8CAwEAAaOBhjCBgzAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0hBBYw +FDASBgdghXQBUwIBBgdghXQBUwIBMBIGA1UdEwEB/wQIMAYBAf8CAQcwHQYDVR0O +BBYEFE0mICKJS9PVpAqhb97iEoHF8TwuMB8GA1UdIwQYMBaAFE0mICKJS9PVpAqh +b97iEoHF8TwuMA0GCSqGSIb3DQEBCwUAA4ICAQAyCrKkG8t9voJXiblqf/P0wS4R +fbgZPnm3qKhyN2abGu2sEzsOv2LwnN+ee6FTSA5BesogpxcbtnjsQJHzQq0Qw1zv +/2BZf82Fo4s9SBwlAjxnffUy6S8w5X2lejjQ82YqZh6NM4OKb3xuqFp1mrjX2lhI +REeoTPpMSQpKwhI3qEAMw8jh0FcNlzKVxzqfl9NX+Ave5XLzo9v/tdhZsnPdTSpx +srpJ9csc1fV5yJmz/MFMdOO0vSk3FQQoHt5FRnDsr7p4DooqzgB53MBfGWcsa0vv +aGgLQ+OswWIJ76bdZWGgr4RVSJFSHMYlkSrQwSIjYVmvRRGFHQEkNI/Ps/8XciAT +woCqISxxOQ7Qj1zB09GOInJGTB2Wrk9xseEFKZZZ9LuedT3PDTcNYtsmjGOpI99n +Bjx8Oto0QuFmtEYE3saWmA9LSHokMnWRn6z3aOkquVVlzl1h0ydw2Df+n7mvoC5W +t6NlUe07qxS/TFED6F+KBZvuim6c779o+sjaC+NCydAXFJy3SuCvkychVSa1ZC+N +8f+mQAWFBVzKBxlcCxMoTFh/wqXvRdpg065lYZ1Tg3TCrvJcwhbtkj6EPnNgiLx2 +9CzP0H1907he0ZESEOnN3col49XtmS++dYFLJPlFRpTJKSFTnCZFqhMX5OfNeOI5 +wSsSnqaeG8XmDtkx2Q== +-----END CERTIFICATE----- + +# Issuer: CN=Swisscom Root EV CA 2 O=Swisscom OU=Digital Certificate Services +# Subject: CN=Swisscom Root EV CA 2 O=Swisscom OU=Digital Certificate Services +# Label: "Swisscom Root EV CA 2" +# Serial: 322973295377129385374608406479535262296 +# MD5 Fingerprint: 7b:30:34:9f:dd:0a:4b:6b:35:ca:31:51:28:5d:ae:ec +# SHA1 Fingerprint: e7:a1:90:29:d3:d5:52:dc:0d:0f:c6:92:d3:ea:88:0d:15:2e:1a:6b +# SHA256 Fingerprint: d9:5f:ea:3c:a4:ee:dc:e7:4c:d7:6e:75:fc:6d:1f:f6:2c:44:1f:0f:a8:bc:77:f0:34:b1:9e:5d:b2:58:01:5d +-----BEGIN CERTIFICATE----- +MIIF4DCCA8igAwIBAgIRAPL6ZOJ0Y9ON/RAdBB92ylgwDQYJKoZIhvcNAQELBQAw +ZzELMAkGA1UEBhMCY2gxETAPBgNVBAoTCFN3aXNzY29tMSUwIwYDVQQLExxEaWdp +dGFsIENlcnRpZmljYXRlIFNlcnZpY2VzMR4wHAYDVQQDExVTd2lzc2NvbSBSb290 +IEVWIENBIDIwHhcNMTEwNjI0MDk0NTA4WhcNMzEwNjI1MDg0NTA4WjBnMQswCQYD +VQQGEwJjaDERMA8GA1UEChMIU3dpc3Njb20xJTAjBgNVBAsTHERpZ2l0YWwgQ2Vy +dGlmaWNhdGUgU2VydmljZXMxHjAcBgNVBAMTFVN3aXNzY29tIFJvb3QgRVYgQ0Eg +MjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMT3HS9X6lds93BdY7Bx +UglgRCgzo3pOCvrY6myLURYaVa5UJsTMRQdBTxB5f3HSek4/OE6zAMaVylvNwSqD +1ycfMQ4jFrclyxy0uYAyXhqdk/HoPGAsp15XGVhRXrwsVgu42O+LgrQ8uMIkqBPH +oCE2G3pXKSinLr9xJZDzRINpUKTk4RtiGZQJo/PDvO/0vezbE53PnUgJUmfANykR +HvvSEaeFGHR55E+FFOtSN+KxRdjMDUN/rhPSays/p8LiqG12W0OfvrSdsyaGOx9/ +5fLoZigWJdBLlzin5M8J0TbDC77aO0RYjb7xnglrPvMyxyuHxuxenPaHZa0zKcQv +idm5y8kDnftslFGXEBuGCxobP/YCfnvUxVFkKJ3106yDgYjTdLRZncHrYTNaRdHL +OdAGalNgHa/2+2m8atwBz735j9m9W8E6X47aD0upm50qKGsaCnw8qyIL5XctcfaC +NYGu+HuB5ur+rPQam3Rc6I8k9l2dRsQs0h4rIWqDJ2dVSqTjyDKXZpBy2uPUZC5f +46Fq9mDU5zXNysRojddxyNMkM3OxbPlq4SjbX8Y96L5V5jcb7STZDxmPX2MYWFCB +UWVv8p9+agTnNCRxunZLWB4ZvRVgRaoMEkABnRDixzgHcgplwLa7JSnaFp6LNYth +7eVxV4O1PHGf40+/fh6Bn0GXAgMBAAGjgYYwgYMwDgYDVR0PAQH/BAQDAgGGMB0G +A1UdIQQWMBQwEgYHYIV0AVMCAgYHYIV0AVMCAjASBgNVHRMBAf8ECDAGAQH/AgED +MB0GA1UdDgQWBBRF2aWBbj2ITY1x0kbBbkUe88SAnTAfBgNVHSMEGDAWgBRF2aWB +bj2ITY1x0kbBbkUe88SAnTANBgkqhkiG9w0BAQsFAAOCAgEAlDpzBp9SSzBc1P6x +XCX5145v9Ydkn+0UjrgEjihLj6p7jjm02Vj2e6E1CqGdivdj5eu9OYLU43otb98T +PLr+flaYC/NUn81ETm484T4VvwYmneTwkLbUwp4wLh/vx3rEUMfqe9pQy3omywC0 +Wqu1kx+AiYQElY2NfwmTv9SoqORjbdlk5LgpWgi/UOGED1V7XwgiG/W9mR4U9s70 +WBCCswo9GcG/W6uqmdjyMb3lOGbcWAXH7WMaLgqXfIeTK7KK4/HsGOV1timH59yL +Gn602MnTihdsfSlEvoqq9X46Lmgxk7lq2prg2+kupYTNHAq4Sgj5nPFhJpiTt3tm +7JFe3VE/23MPrQRYCd0EApUKPtN236YQHoA96M2kZNEzx5LH4k5E4wnJTsJdhw4S +nr8PyQUQ3nqjsTzyP6WqJ3mtMX0f/fwZacXduT98zca0wjAefm6S139hdlqP65VN +vBFuIXxZN5nQBrz5Bm0yFqXZaajh3DyAHmBR3NdUIR7KYndP+tiPsys6DXhyyWhB +WkdKwqPrGtcKqzwyVcgKEZzfdNbwQBUdyLmPtTbFr/giuMod89a2GQ+fYWVq6nTI +fI/DT11lgh/ZDYnadXL77/FHZxOzyNEZiCcmmpl5fx7kLD977vHeTYuWl8PVP3wb +I+2ksx0WckNLIOFZfsLorSa/ovc= +-----END CERTIFICATE----- + +# Issuer: CN=CA Disig Root R1 O=Disig a.s. +# Subject: CN=CA Disig Root R1 O=Disig a.s. +# Label: "CA Disig Root R1" +# Serial: 14052245610670616104 +# MD5 Fingerprint: be:ec:11:93:9a:f5:69:21:bc:d7:c1:c0:67:89:cc:2a +# SHA1 Fingerprint: 8e:1c:74:f8:a6:20:b9:e5:8a:f4:61:fa:ec:2b:47:56:51:1a:52:c6 +# SHA256 Fingerprint: f9:6f:23:f4:c3:e7:9c:07:7a:46:98:8d:5a:f5:90:06:76:a0:f0:39:cb:64:5d:d1:75:49:b2:16:c8:24:40:ce +-----BEGIN CERTIFICATE----- +MIIFaTCCA1GgAwIBAgIJAMMDmu5QkG4oMA0GCSqGSIb3DQEBBQUAMFIxCzAJBgNV +BAYTAlNLMRMwEQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMu +MRkwFwYDVQQDExBDQSBEaXNpZyBSb290IFIxMB4XDTEyMDcxOTA5MDY1NloXDTQy +MDcxOTA5MDY1NlowUjELMAkGA1UEBhMCU0sxEzARBgNVBAcTCkJyYXRpc2xhdmEx +EzARBgNVBAoTCkRpc2lnIGEucy4xGTAXBgNVBAMTEENBIERpc2lnIFJvb3QgUjEw +ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCqw3j33Jijp1pedxiy3QRk +D2P9m5YJgNXoqqXinCaUOuiZc4yd39ffg/N4T0Dhf9Kn0uXKE5Pn7cZ3Xza1lK/o +OI7bm+V8u8yN63Vz4STN5qctGS7Y1oprFOsIYgrY3LMATcMjfF9DCCMyEtztDK3A +fQ+lekLZWnDZv6fXARz2m6uOt0qGeKAeVjGu74IKgEH3G8muqzIm1Cxr7X1r5OJe +IgpFy4QxTaz+29FHuvlglzmxZcfe+5nkCiKxLU3lSCZpq+Kq8/v8kiky6bM+TR8n +oc2OuRf7JT7JbvN32g0S9l3HuzYQ1VTW8+DiR0jm3hTaYVKvJrT1cU/J19IG32PK +/yHoWQbgCNWEFVP3Q+V8xaCJmGtzxmjOZd69fwX3se72V6FglcXM6pM6vpmumwKj +rckWtc7dXpl4fho5frLABaTAgqWjR56M6ly2vGfb5ipN0gTco65F97yLnByn1tUD +3AjLLhbKXEAz6GfDLuemROoRRRw1ZS0eRWEkG4IupZ0zXWX4Qfkuy5Q/H6MMMSRE +7cderVC6xkGbrPAXZcD4XW9boAo0PO7X6oifmPmvTiT6l7Jkdtqr9O3jw2Dv1fkC +yC2fg69naQanMVXVz0tv/wQFx1isXxYb5dKj6zHbHzMVTdDypVP1y+E9Tmgt2BLd +qvLmTZtJ5cUoobqwWsagtQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1Ud +DwEB/wQEAwIBBjAdBgNVHQ4EFgQUiQq0OJMa5qvum5EY+fU8PjXQ04IwDQYJKoZI +hvcNAQEFBQADggIBADKL9p1Kyb4U5YysOMo6CdQbzoaz3evUuii+Eq5FLAR0rBNR +xVgYZk2C2tXck8An4b58n1KeElb21Zyp9HWc+jcSjxyT7Ff+Bw+r1RL3D65hXlaA +SfX8MPWbTx9BLxyE04nH4toCdu0Jz2zBuByDHBb6lM19oMgY0sidbvW9adRtPTXo +HqJPYNcHKfyyo6SdbhWSVhlMCrDpfNIZTUJG7L399ldb3Zh+pE3McgODWF3vkzpB +emOqfDqo9ayk0d2iLbYq/J8BjuIQscTK5GfbVSUZP/3oNn6z4eGBrxEWi1CXYBmC +AMBrTXO40RMHPuq2MU/wQppt4hF05ZSsjYSVPCGvxdpHyN85YmLLW1AL14FABZyb +7bq2ix4Eb5YgOe2kfSnbSM6C3NQCjR0EMVrHS/BsYVLXtFHCgWzN4funodKSds+x +DzdYpPJScWc/DIh4gInByLUfkmO+p3qKViwaqKactV2zY9ATIKHrkWzQjX2v3wvk +F7mGnjixlAxYjOBVqjtjbZqJYLhkKpLGN/R+Q0O3c+gB53+XD9fyexn9GtePyfqF +a3qdnom2piiZk4hA9z7NUaPK6u95RyG1/jLix8NRb76AdPCkwzryT+lf3xkK8jsT +Q6wxpLPn6/wY1gGp8yqPNg7rtLG8t0zJa7+h89n07eLw4+1knj0vllJPgFOL +-----END CERTIFICATE----- + +# Issuer: CN=CA Disig Root R2 O=Disig a.s. +# Subject: CN=CA Disig Root R2 O=Disig a.s. +# Label: "CA Disig Root R2" +# Serial: 10572350602393338211 +# MD5 Fingerprint: 26:01:fb:d8:27:a7:17:9a:45:54:38:1a:43:01:3b:03 +# SHA1 Fingerprint: b5:61:eb:ea:a4:de:e4:25:4b:69:1a:98:a5:57:47:c2:34:c7:d9:71 +# SHA256 Fingerprint: e2:3d:4a:03:6d:7b:70:e9:f5:95:b1:42:20:79:d2:b9:1e:df:bb:1f:b6:51:a0:63:3e:aa:8a:9d:c5:f8:07:03 +-----BEGIN CERTIFICATE----- +MIIFaTCCA1GgAwIBAgIJAJK4iNuwisFjMA0GCSqGSIb3DQEBCwUAMFIxCzAJBgNV +BAYTAlNLMRMwEQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMu +MRkwFwYDVQQDExBDQSBEaXNpZyBSb290IFIyMB4XDTEyMDcxOTA5MTUzMFoXDTQy +MDcxOTA5MTUzMFowUjELMAkGA1UEBhMCU0sxEzARBgNVBAcTCkJyYXRpc2xhdmEx +EzARBgNVBAoTCkRpc2lnIGEucy4xGTAXBgNVBAMTEENBIERpc2lnIFJvb3QgUjIw +ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCio8QACdaFXS1tFPbCw3Oe +NcJxVX6B+6tGUODBfEl45qt5WDza/3wcn9iXAng+a0EE6UG9vgMsRfYvZNSrXaNH +PWSb6WiaxswbP7q+sos0Ai6YVRn8jG+qX9pMzk0DIaPY0jSTVpbLTAwAFjxfGs3I +x2ymrdMxp7zo5eFm1tL7A7RBZckQrg4FY8aAamkw/dLukO8NJ9+flXP04SXabBbe +QTg06ov80egEFGEtQX6sx3dOy1FU+16SGBsEWmjGycT6txOgmLcRK7fWV8x8nhfR +yyX+hk4kLlYMeE2eARKmK6cBZW58Yh2EhN/qwGu1pSqVg8NTEQxzHQuyRpDRQjrO +QG6Vrf/GlK1ul4SOfW+eioANSW1z4nuSHsPzwfPrLgVv2RvPN3YEyLRa5Beny912 +H9AZdugsBbPWnDTYltxhh5EF5EQIM8HauQhl1K6yNg3ruji6DOWbnuuNZt2Zz9aJ +QfYEkoopKW1rOhzndX0CcQ7zwOe9yxndnWCywmZgtrEE7snmhrmaZkCo5xHtgUUD +i/ZnWejBBhG93c+AAk9lQHhcR1DIm+YfgXvkRKhbhZri3lrVx/k6RGZL5DJUfORs +nLMOPReisjQS1n6yqEm70XooQL6iFh/f5DcfEXP7kAplQ6INfPgGAVUzfbANuPT1 +rqVCV3w2EYx7XsQDnYx5nQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1Ud +DwEB/wQEAwIBBjAdBgNVHQ4EFgQUtZn4r7CU9eMg1gqtzk5WpC5uQu0wDQYJKoZI +hvcNAQELBQADggIBACYGXnDnZTPIgm7ZnBc6G3pmsgH2eDtpXi/q/075KMOYKmFM +tCQSin1tERT3nLXK5ryeJ45MGcipvXrA1zYObYVybqjGom32+nNjf7xueQgcnYqf +GopTpti72TVVsRHFqQOzVju5hJMiXn7B9hJSi+osZ7z+Nkz1uM/Rs0mSO9MpDpkb +lvdhuDvEK7Z4bLQjb/D907JedR+Zlais9trhxTF7+9FGs9K8Z7RiVLoJ92Owk6Ka ++elSLotgEqv89WBW7xBci8QaQtyDW2QOy7W81k/BfDxujRNt+3vrMNDcTa/F1bal +TFtxyegxvug4BkihGuLq0t4SOVga/4AOgnXmt8kHbA7v/zjxmHHEt38OFdAlab0i +nSvtBfZGR6ztwPDUO+Ls7pZbkBNOHlY667DvlruWIxG68kOGdGSVyCh13x01utI3 +gzhTODY7z2zp+WsO0PsE6E9312UBeIYMej4hYvF/Y3EMyZ9E26gnonW+boE+18Dr +G5gPcFw0sorMwIUY6256s/daoQe/qUKS82Ail+QUoQebTnbAjn39pCXHR+3/H3Os +zMOl6W8KjptlwlCFtaOgUxLMVYdh84GuEEZhvUQhuMI9dM9+JDX6HAcOmz0iyu8x +L4ysEr3vQCj8KWefshNPZiTEUxnpHikV7+ZtsH8tZ/3zbBt1RqPlShfppNcL +-----END CERTIFICATE----- + +# Issuer: CN=ACCVRAIZ1 O=ACCV OU=PKIACCV +# Subject: CN=ACCVRAIZ1 O=ACCV OU=PKIACCV +# Label: "ACCVRAIZ1" +# Serial: 6828503384748696800 +# MD5 Fingerprint: d0:a0:5a:ee:05:b6:09:94:21:a1:7d:f1:b2:29:82:02 +# SHA1 Fingerprint: 93:05:7a:88:15:c6:4f:ce:88:2f:fa:91:16:52:28:78:bc:53:64:17 +# SHA256 Fingerprint: 9a:6e:c0:12:e1:a7:da:9d:be:34:19:4d:47:8a:d7:c0:db:18:22:fb:07:1d:f1:29:81:49:6e:d1:04:38:41:13 +-----BEGIN CERTIFICATE----- +MIIH0zCCBbugAwIBAgIIXsO3pkN/pOAwDQYJKoZIhvcNAQEFBQAwQjESMBAGA1UE +AwwJQUNDVlJBSVoxMRAwDgYDVQQLDAdQS0lBQ0NWMQ0wCwYDVQQKDARBQ0NWMQsw +CQYDVQQGEwJFUzAeFw0xMTA1MDUwOTM3MzdaFw0zMDEyMzEwOTM3MzdaMEIxEjAQ +BgNVBAMMCUFDQ1ZSQUlaMTEQMA4GA1UECwwHUEtJQUNDVjENMAsGA1UECgwEQUND +VjELMAkGA1UEBhMCRVMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCb +qau/YUqXry+XZpp0X9DZlv3P4uRm7x8fRzPCRKPfmt4ftVTdFXxpNRFvu8gMjmoY +HtiP2Ra8EEg2XPBjs5BaXCQ316PWywlxufEBcoSwfdtNgM3802/J+Nq2DoLSRYWo +G2ioPej0RGy9ocLLA76MPhMAhN9KSMDjIgro6TenGEyxCQ0jVn8ETdkXhBilyNpA +lHPrzg5XPAOBOp0KoVdDaaxXbXmQeOW1tDvYvEyNKKGno6e6Ak4l0Squ7a4DIrhr +IA8wKFSVf+DuzgpmndFALW4ir50awQUZ0m/A8p/4e7MCQvtQqR0tkw8jq8bBD5L/ +0KIV9VMJcRz/RROE5iZe+OCIHAr8Fraocwa48GOEAqDGWuzndN9wrqODJerWx5eH +k6fGioozl2A3ED6XPm4pFdahD9GILBKfb6qkxkLrQaLjlUPTAYVtjrs78yM2x/47 +4KElB0iryYl0/wiPgL/AlmXz7uxLaL2diMMxs0Dx6M/2OLuc5NF/1OVYm3z61PMO +m3WR5LpSLhl+0fXNWhn8ugb2+1KoS5kE3fj5tItQo05iifCHJPqDQsGH+tUtKSpa +cXpkatcnYGMN285J9Y0fkIkyF/hzQ7jSWpOGYdbhdQrqeWZ2iE9x6wQl1gpaepPl +uUsXQA+xtrn13k/c4LOsOxFwYIRKQ26ZIMApcQrAZQIDAQABo4ICyzCCAscwfQYI +KwYBBQUHAQEEcTBvMEwGCCsGAQUFBzAChkBodHRwOi8vd3d3LmFjY3YuZXMvZmls +ZWFkbWluL0FyY2hpdm9zL2NlcnRpZmljYWRvcy9yYWl6YWNjdjEuY3J0MB8GCCsG +AQUFBzABhhNodHRwOi8vb2NzcC5hY2N2LmVzMB0GA1UdDgQWBBTSh7Tj3zcnk1X2 +VuqB5TbMjB4/vTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNKHtOPfNyeT +VfZW6oHlNsyMHj+9MIIBcwYDVR0gBIIBajCCAWYwggFiBgRVHSAAMIIBWDCCASIG +CCsGAQUFBwICMIIBFB6CARAAQQB1AHQAbwByAGkAZABhAGQAIABkAGUAIABDAGUA +cgB0AGkAZgBpAGMAYQBjAGkA8wBuACAAUgBhAO0AegAgAGQAZQAgAGwAYQAgAEEA +QwBDAFYAIAAoAEEAZwBlAG4AYwBpAGEAIABkAGUAIABUAGUAYwBuAG8AbABvAGcA +7QBhACAAeQAgAEMAZQByAHQAaQBmAGkAYwBhAGMAaQDzAG4AIABFAGwAZQBjAHQA +cgDzAG4AaQBjAGEALAAgAEMASQBGACAAUQA0ADYAMAAxADEANQA2AEUAKQAuACAA +QwBQAFMAIABlAG4AIABoAHQAdABwADoALwAvAHcAdwB3AC4AYQBjAGMAdgAuAGUA +czAwBggrBgEFBQcCARYkaHR0cDovL3d3dy5hY2N2LmVzL2xlZ2lzbGFjaW9uX2Mu +aHRtMFUGA1UdHwROMEwwSqBIoEaGRGh0dHA6Ly93d3cuYWNjdi5lcy9maWxlYWRt +aW4vQXJjaGl2b3MvY2VydGlmaWNhZG9zL3JhaXphY2N2MV9kZXIuY3JsMA4GA1Ud +DwEB/wQEAwIBBjAXBgNVHREEEDAOgQxhY2N2QGFjY3YuZXMwDQYJKoZIhvcNAQEF +BQADggIBAJcxAp/n/UNnSEQU5CmH7UwoZtCPNdpNYbdKl02125DgBS4OxnnQ8pdp +D70ER9m+27Up2pvZrqmZ1dM8MJP1jaGo/AaNRPTKFpV8M9xii6g3+CfYCS0b78gU +JyCpZET/LtZ1qmxNYEAZSUNUY9rizLpm5U9EelvZaoErQNV/+QEnWCzI7UiRfD+m +AM/EKXMRNt6GGT6d7hmKG9Ww7Y49nCrADdg9ZuM8Db3VlFzi4qc1GwQA9j9ajepD +vV+JHanBsMyZ4k0ACtrJJ1vnE5Bc5PUzolVt3OAJTS+xJlsndQAJxGJ3KQhfnlms +tn6tn1QwIgPBHnFk/vk4CpYY3QIUrCPLBhwepH2NDd4nQeit2hW3sCPdK6jT2iWH +7ehVRE2I9DZ+hJp4rPcOVkkO1jMl1oRQQmwgEh0q1b688nCBpHBgvgW1m54ERL5h +I6zppSSMEYCUWqKiuUnSwdzRp+0xESyeGabu4VXhwOrPDYTkF7eifKXeVSUG7szA +h1xA2syVP1XgNce4hL60Xc16gwFy7ofmXx2utYXGJt/mwZrpHgJHnyqobalbz+xF +d3+YJ5oyXSrjhO7FmGYvliAd3djDJ9ew+f7Zfc3Qn48LFFhRny+Lwzgt3uiP1o2H +pPVWQxaZLPSkVrQ0uGE3ycJYgBugl6H8WY3pEfbRD0tVNEYqi4Y7 +-----END CERTIFICATE----- + +# Issuer: CN=TWCA Global Root CA O=TAIWAN-CA OU=Root CA +# Subject: CN=TWCA Global Root CA O=TAIWAN-CA OU=Root CA +# Label: "TWCA Global Root CA" +# Serial: 3262 +# MD5 Fingerprint: f9:03:7e:cf:e6:9e:3c:73:7a:2a:90:07:69:ff:2b:96 +# SHA1 Fingerprint: 9c:bb:48:53:f6:a4:f6:d3:52:a4:e8:32:52:55:60:13:f5:ad:af:65 +# SHA256 Fingerprint: 59:76:90:07:f7:68:5d:0f:cd:50:87:2f:9f:95:d5:75:5a:5b:2b:45:7d:81:f3:69:2b:61:0a:98:67:2f:0e:1b +-----BEGIN CERTIFICATE----- +MIIFQTCCAymgAwIBAgICDL4wDQYJKoZIhvcNAQELBQAwUTELMAkGA1UEBhMCVFcx +EjAQBgNVBAoTCVRBSVdBTi1DQTEQMA4GA1UECxMHUm9vdCBDQTEcMBoGA1UEAxMT +VFdDQSBHbG9iYWwgUm9vdCBDQTAeFw0xMjA2MjcwNjI4MzNaFw0zMDEyMzExNTU5 +NTlaMFExCzAJBgNVBAYTAlRXMRIwEAYDVQQKEwlUQUlXQU4tQ0ExEDAOBgNVBAsT +B1Jvb3QgQ0ExHDAaBgNVBAMTE1RXQ0EgR2xvYmFsIFJvb3QgQ0EwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQCwBdvI64zEbooh745NnHEKH1Jw7W2CnJfF +10xORUnLQEK1EjRsGcJ0pDFfhQKX7EMzClPSnIyOt7h52yvVavKOZsTuKwEHktSz +0ALfUPZVr2YOy+BHYC8rMjk1Ujoog/h7FsYYuGLWRyWRzvAZEk2tY/XTP3VfKfCh +MBwqoJimFb3u/Rk28OKRQ4/6ytYQJ0lM793B8YVwm8rqqFpD/G2Gb3PpN0Wp8DbH +zIh1HrtsBv+baz4X7GGqcXzGHaL3SekVtTzWoWH1EfcFbx39Eb7QMAfCKbAJTibc +46KokWofwpFFiFzlmLhxpRUZyXx1EcxwdE8tmx2RRP1WKKD+u4ZqyPpcC1jcxkt2 +yKsi2XMPpfRaAok/T54igu6idFMqPVMnaR1sjjIsZAAmY2E2TqNGtz99sy2sbZCi +laLOz9qC5wc0GZbpuCGqKX6mOL6OKUohZnkfs8O1CWfe1tQHRvMq2uYiN2DLgbYP +oA/pyJV/v1WRBXrPPRXAb94JlAGD1zQbzECl8LibZ9WYkTunhHiVJqRaCPgrdLQA +BDzfuBSO6N+pjWxnkjMdwLfS7JLIvgm/LCkFbwJrnu+8vyq8W8BQj0FwcYeyTbcE +qYSjMq+u7msXi7Kx/mzhkIyIqJdIzshNy/MGz19qCkKxHh53L46g5pIOBvwFItIm +4TFRfTLcDwIDAQABoyMwITAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB +/zANBgkqhkiG9w0BAQsFAAOCAgEAXzSBdu+WHdXltdkCY4QWwa6gcFGn90xHNcgL +1yg9iXHZqjNB6hQbbCEAwGxCGX6faVsgQt+i0trEfJdLjbDorMjupWkEmQqSpqsn +LhpNgb+E1HAerUf+/UqdM+DyucRFCCEK2mlpc3INvjT+lIutwx4116KD7+U4x6WF +H6vPNOw/KP4M8VeGTslV9xzU2KV9Bnpv1d8Q34FOIWWxtuEXeZVFBs5fzNxGiWNo +RI2T9GRwoD2dKAXDOXC4Ynsg/eTb6QihuJ49CcdP+yz4k3ZB3lLg4VfSnQO8d57+ +nile98FRYB/e2guyLXW3Q0iT5/Z5xoRdgFlglPx4mI88k1HtQJAH32RjJMtOcQWh +15QaiDLxInQirqWm2BJpTGCjAu4r7NRjkgtevi92a6O2JryPA9gK8kxkRr05YuWW +6zRjESjMlfGt7+/cgFhI6Uu46mWs6fyAtbXIRfmswZ/ZuepiiI7E8UuDEq3mi4TW +nsLrgxifarsbJGAzcMzs9zLzXNl5fe+epP7JI8Mk7hWSsT2RTyaGvWZzJBPqpK5j +wa19hAM8EHiGG3njxPPyBJUgriOCxLM6AGK/5jYk4Ve6xx6QddVfP5VhK8E7zeWz +aGHQRiapIVJpLesux+t3zqY6tQMzT3bR51xUAV3LePTJDL/PEo4XLSNolOer/qmy +KwbQBM0= +-----END CERTIFICATE----- + +# Issuer: CN=TeliaSonera Root CA v1 O=TeliaSonera +# Subject: CN=TeliaSonera Root CA v1 O=TeliaSonera +# Label: "TeliaSonera Root CA v1" +# Serial: 199041966741090107964904287217786801558 +# MD5 Fingerprint: 37:41:49:1b:18:56:9a:26:f5:ad:c2:66:fb:40:a5:4c +# SHA1 Fingerprint: 43:13:bb:96:f1:d5:86:9b:c1:4e:6a:92:f6:cf:f6:34:69:87:82:37 +# SHA256 Fingerprint: dd:69:36:fe:21:f8:f0:77:c1:23:a1:a5:21:c1:22:24:f7:22:55:b7:3e:03:a7:26:06:93:e8:a2:4b:0f:a3:89 +-----BEGIN CERTIFICATE----- +MIIFODCCAyCgAwIBAgIRAJW+FqD3LkbxezmCcvqLzZYwDQYJKoZIhvcNAQEFBQAw +NzEUMBIGA1UECgwLVGVsaWFTb25lcmExHzAdBgNVBAMMFlRlbGlhU29uZXJhIFJv +b3QgQ0EgdjEwHhcNMDcxMDE4MTIwMDUwWhcNMzIxMDE4MTIwMDUwWjA3MRQwEgYD +VQQKDAtUZWxpYVNvbmVyYTEfMB0GA1UEAwwWVGVsaWFTb25lcmEgUm9vdCBDQSB2 +MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMK+6yfwIaPzaSZVfp3F +VRaRXP3vIb9TgHot0pGMYzHw7CTww6XScnwQbfQ3t+XmfHnqjLWCi65ItqwA3GV1 +7CpNX8GH9SBlK4GoRz6JI5UwFpB/6FcHSOcZrr9FZ7E3GwYq/t75rH2D+1665I+X +Z75Ljo1kB1c4VWk0Nj0TSO9P4tNmHqTPGrdeNjPUtAa9GAH9d4RQAEX1jF3oI7x+ +/jXh7VB7qTCNGdMJjmhnXb88lxhTuylixcpecsHHltTbLaC0H2kD7OriUPEMPPCs +81Mt8Bz17Ww5OXOAFshSsCPN4D7c3TxHoLs1iuKYaIu+5b9y7tL6pe0S7fyYGKkm +dtwoSxAgHNN/Fnct7W+A90m7UwW7XWjH1Mh1Fj+JWov3F0fUTPHSiXk+TT2YqGHe +Oh7S+F4D4MHJHIzTjU3TlTazN19jY5szFPAtJmtTfImMMsJu7D0hADnJoWjiUIMu +sDor8zagrC/kb2HCUQk5PotTubtn2txTuXZZNp1D5SDgPTJghSJRt8czu90VL6R4 +pgd7gUY2BIbdeTXHlSw7sKMXNeVzH7RcWe/a6hBle3rQf5+ztCo3O3CLm1u5K7fs +slESl1MpWtTwEhDcTwK7EpIvYtQ/aUN8Ddb8WHUBiJ1YFkveupD/RwGJBmr2X7KQ +arMCpgKIv7NHfirZ1fpoeDVNAgMBAAGjPzA9MA8GA1UdEwEB/wQFMAMBAf8wCwYD +VR0PBAQDAgEGMB0GA1UdDgQWBBTwj1k4ALP1j5qWDNXr+nuqF+gTEjANBgkqhkiG +9w0BAQUFAAOCAgEAvuRcYk4k9AwI//DTDGjkk0kiP0Qnb7tt3oNmzqjMDfz1mgbl +dxSR651Be5kqhOX//CHBXfDkH1e3damhXwIm/9fH907eT/j3HEbAek9ALCI18Bmx +0GtnLLCo4MBANzX2hFxc469CeP6nyQ1Q6g2EdvZR74NTxnr/DlZJLo961gzmJ1Tj +TQpgcmLNkQfWpb/ImWvtxBnmq0wROMVvMeJuScg/doAmAyYp4Db29iBT4xdwNBed +Y2gea+zDTYa4EzAvXUYNR0PVG6pZDrlcjQZIrXSHX8f8MVRBE+LHIQ6e4B4N4cB7 +Q4WQxYpYxmUKeFfyxiMPAdkgS94P+5KFdSpcc41teyWRyu5FrgZLAMzTsVlQ2jqI +OylDRl6XK1TOU2+NSueW+r9xDkKLfP0ooNBIytrEgUy7onOTJsjrDNYmiLbAJM+7 +vVvrdX3pCI6GMyx5dwlppYn8s3CQh3aP0yK7Qs69cwsgJirQmz1wHiRszYd2qReW +t88NkvuOGKmYSdGe/mBEciG5Ge3C9THxOUiIkCR1VBatzvT4aRRkOfujuLpwQMcn +HL/EVlP6Y2XQ8xwOFvVrhlhNGNTkDY6lnVuR3HYkUD/GKvvZt5y11ubQ2egZixVx +SK236thZiNSQvxaz2emsWWFUyBy6ysHK4bkgTI86k4mloMy/0/Z1pHWWbVY= +-----END CERTIFICATE----- + +# Issuer: CN=E-Tugra Certification Authority O=E-Tuğra EBG Bilişim Teknolojileri ve Hizmetleri A.Ş. OU=E-Tugra Sertifikasyon Merkezi +# Subject: CN=E-Tugra Certification Authority O=E-Tuğra EBG Bilişim Teknolojileri ve Hizmetleri A.Ş. OU=E-Tugra Sertifikasyon Merkezi +# Label: "E-Tugra Certification Authority" +# Serial: 7667447206703254355 +# MD5 Fingerprint: b8:a1:03:63:b0:bd:21:71:70:8a:6f:13:3a:bb:79:49 +# SHA1 Fingerprint: 51:c6:e7:08:49:06:6e:f3:92:d4:5c:a0:0d:6d:a3:62:8f:c3:52:39 +# SHA256 Fingerprint: b0:bf:d5:2b:b0:d7:d9:bd:92:bf:5d:4d:c1:3d:a2:55:c0:2c:54:2f:37:83:65:ea:89:39:11:f5:5e:55:f2:3c +-----BEGIN CERTIFICATE----- +MIIGSzCCBDOgAwIBAgIIamg+nFGby1MwDQYJKoZIhvcNAQELBQAwgbIxCzAJBgNV +BAYTAlRSMQ8wDQYDVQQHDAZBbmthcmExQDA+BgNVBAoMN0UtVHXEn3JhIEVCRyBC +aWxpxZ9pbSBUZWtub2xvamlsZXJpIHZlIEhpem1ldGxlcmkgQS7Fni4xJjAkBgNV +BAsMHUUtVHVncmEgU2VydGlmaWthc3lvbiBNZXJrZXppMSgwJgYDVQQDDB9FLVR1 +Z3JhIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTEzMDMwNTEyMDk0OFoXDTIz +MDMwMzEyMDk0OFowgbIxCzAJBgNVBAYTAlRSMQ8wDQYDVQQHDAZBbmthcmExQDA+ +BgNVBAoMN0UtVHXEn3JhIEVCRyBCaWxpxZ9pbSBUZWtub2xvamlsZXJpIHZlIEhp +em1ldGxlcmkgQS7Fni4xJjAkBgNVBAsMHUUtVHVncmEgU2VydGlmaWthc3lvbiBN +ZXJrZXppMSgwJgYDVQQDDB9FLVR1Z3JhIENlcnRpZmljYXRpb24gQXV0aG9yaXR5 +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA4vU/kwVRHoViVF56C/UY +B4Oufq9899SKa6VjQzm5S/fDxmSJPZQuVIBSOTkHS0vdhQd2h8y/L5VMzH2nPbxH +D5hw+IyFHnSOkm0bQNGZDbt1bsipa5rAhDGvykPL6ys06I+XawGb1Q5KCKpbknSF +Q9OArqGIW66z6l7LFpp3RMih9lRozt6Plyu6W0ACDGQXwLWTzeHxE2bODHnv0ZEo +q1+gElIwcxmOj+GMB6LDu0rw6h8VqO4lzKRG+Bsi77MOQ7osJLjFLFzUHPhdZL3D +k14opz8n8Y4e0ypQBaNV2cvnOVPAmJ6MVGKLJrD3fY185MaeZkJVgkfnsliNZvcH +fC425lAcP9tDJMW/hkd5s3kc91r0E+xs+D/iWR+V7kI+ua2oMoVJl0b+SzGPWsut +dEcf6ZG33ygEIqDUD13ieU/qbIWGvaimzuT6w+Gzrt48Ue7LE3wBf4QOXVGUnhMM +ti6lTPk5cDZvlsouDERVxcr6XQKj39ZkjFqzAQqptQpHF//vkUAqjqFGOjGY5RH8 +zLtJVor8udBhmm9lbObDyz51Sf6Pp+KJxWfXnUYTTjF2OySznhFlhqt/7x3U+Lzn +rFpct1pHXFXOVbQicVtbC/DP3KBhZOqp12gKY6fgDT+gr9Oq0n7vUaDmUStVkhUX +U8u3Zg5mTPj5dUyQ5xJwx0UCAwEAAaNjMGEwHQYDVR0OBBYEFC7j27JJ0JxUeVz6 +Jyr+zE7S6E5UMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAULuPbsknQnFR5 +XPonKv7MTtLoTlQwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQAF +Nzr0TbdF4kV1JI+2d1LoHNgQk2Xz8lkGpD4eKexd0dCrfOAKkEh47U6YA5n+KGCR +HTAduGN8qOY1tfrTYXbm1gdLymmasoR6d5NFFxWfJNCYExL/u6Au/U5Mh/jOXKqY +GwXgAEZKgoClM4so3O0409/lPun++1ndYYRP0lSWE2ETPo+Aab6TR7U1Q9Jauz1c +77NCR807VRMGsAnb/WP2OogKmW9+4c4bU2pEZiNRCHu8W1Ki/QY3OEBhj0qWuJA3 ++GbHeJAAFS6LrVE1Uweoa2iu+U48BybNCAVwzDk/dr2l02cmAYamU9JgO3xDf1WK +vJUawSg5TB9D0pH0clmKuVb8P7Sd2nCcdlqMQ1DujjByTd//SffGqWfZbawCEeI6 +FiWnWAjLb1NBnEg4R2gz0dfHj9R0IdTDBZB6/86WiLEVKV0jq9BgoRJP3vQXzTLl +yb/IQ639Lo7xr+L0mPoSHyDYwKcMhcWQ9DstliaxLL5Mq+ux0orJ23gTDx4JnW2P +AJ8C2sH6H3p6CcRK5ogql5+Ji/03X186zjhZhkuvcQu02PJwT58yE+Owp1fl2tpD +y4Q08ijE6m30Ku/Ba3ba+367hTzSU8JNvnHhRdH9I2cNE3X7z2VnIp2usAnRCf8d +NL/+I5c30jn6PQ0GC7TbO6Orb1wdtn7os4I07QZcJA== +-----END CERTIFICATE----- + +# Issuer: CN=T-TeleSec GlobalRoot Class 2 O=T-Systems Enterprise Services GmbH OU=T-Systems Trust Center +# Subject: CN=T-TeleSec GlobalRoot Class 2 O=T-Systems Enterprise Services GmbH OU=T-Systems Trust Center +# Label: "T-TeleSec GlobalRoot Class 2" +# Serial: 1 +# MD5 Fingerprint: 2b:9b:9e:e4:7b:6c:1f:00:72:1a:cc:c1:77:79:df:6a +# SHA1 Fingerprint: 59:0d:2d:7d:88:4f:40:2e:61:7e:a5:62:32:17:65:cf:17:d8:94:e9 +# SHA256 Fingerprint: 91:e2:f5:78:8d:58:10:eb:a7:ba:58:73:7d:e1:54:8a:8e:ca:cd:01:45:98:bc:0b:14:3e:04:1b:17:05:25:52 +-----BEGIN CERTIFICATE----- +MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUx +KzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAd +BgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNl +YyBHbG9iYWxSb290IENsYXNzIDIwHhcNMDgxMDAxMTA0MDE0WhcNMzMxMDAxMjM1 +OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnBy +aXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50 +ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCqX9obX+hzkeXaXPSi5kfl82hVYAUd +AqSzm1nzHoqvNK38DcLZSBnuaY/JIPwhqgcZ7bBcrGXHX+0CfHt8LRvWurmAwhiC +FoT6ZrAIxlQjgeTNuUk/9k9uN0goOA/FvudocP05l03Sx5iRUKrERLMjfTlH6VJi +1hKTXrcxlkIF+3anHqP1wvzpesVsqXFP6st4vGCvx9702cu+fjOlbpSD8DT6Iavq +jnKgP6TeMFvvhk1qlVtDRKgQFRzlAVfFmPHmBiiRqiDFt1MmUUOyCxGVWOHAD3bZ +wI18gfNycJ5v/hqO2V81xrJvNHy+SE/iWjnX2J14np+GPgNeGYtEotXHAgMBAAGj +QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS/ +WSA2AHmgoCJrjNXyYdK4LMuCSjANBgkqhkiG9w0BAQsFAAOCAQEAMQOiYQsfdOhy +NsZt+U2e+iKo4YFWz827n+qrkRk4r6p8FU3ztqONpfSO9kSpp+ghla0+AGIWiPAC +uvxhI+YzmzB6azZie60EI4RYZeLbK4rnJVM3YlNfvNoBYimipidx5joifsFvHZVw +IEoHNN/q/xWA5brXethbdXwFeilHfkCoMRN3zUA7tFFHei4R40cR3p1m0IvVVGb6 +g1XqfMIpiRvpb7PO4gWEyS8+eIVibslfwXhjdFjASBgMmTnrpMwatXlajRWc2BQN +9noHV8cigwUtPJslJj0Ys6lDfMjIq2SPDqO/nBudMNva0Bkuqjzx+zOAduTNrRlP +BSeOE6Fuwg== +-----END CERTIFICATE----- + +# Issuer: CN=Atos TrustedRoot 2011 O=Atos +# Subject: CN=Atos TrustedRoot 2011 O=Atos +# Label: "Atos TrustedRoot 2011" +# Serial: 6643877497813316402 +# MD5 Fingerprint: ae:b9:c4:32:4b:ac:7f:5d:66:cc:77:94:bb:2a:77:56 +# SHA1 Fingerprint: 2b:b1:f5:3e:55:0c:1d:c5:f1:d4:e6:b7:6a:46:4b:55:06:02:ac:21 +# SHA256 Fingerprint: f3:56:be:a2:44:b7:a9:1e:b3:5d:53:ca:9a:d7:86:4a:ce:01:8e:2d:35:d5:f8:f9:6d:df:68:a6:f4:1a:a4:74 +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIIXDPLYixfszIwDQYJKoZIhvcNAQELBQAwPDEeMBwGA1UE +AwwVQXRvcyBUcnVzdGVkUm9vdCAyMDExMQ0wCwYDVQQKDARBdG9zMQswCQYDVQQG +EwJERTAeFw0xMTA3MDcxNDU4MzBaFw0zMDEyMzEyMzU5NTlaMDwxHjAcBgNVBAMM +FUF0b3MgVHJ1c3RlZFJvb3QgMjAxMTENMAsGA1UECgwEQXRvczELMAkGA1UEBhMC +REUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCVhTuXbyo7LjvPpvMp +Nb7PGKw+qtn4TaA+Gke5vJrf8v7MPkfoepbCJI419KkM/IL9bcFyYie96mvr54rM +VD6QUM+A1JX76LWC1BTFtqlVJVfbsVD2sGBkWXppzwO3bw2+yj5vdHLqqjAqc2K+ +SZFhyBH+DgMq92og3AIVDV4VavzjgsG1xZ1kCWyjWZgHJ8cblithdHFsQ/H3NYkQ +4J7sVaE3IqKHBAUsR320HLliKWYoyrfhk/WklAOZuXCFteZI6o1Q/NnezG8HDt0L +cp2AMBYHlT8oDv3FdU9T1nSatCQujgKRz3bFmx5VdJx4IbHwLfELn8LVlhgf8FQi +eowHAgMBAAGjfTB7MB0GA1UdDgQWBBSnpQaxLKYJYO7Rl+lwrrw7GWzbITAPBgNV +HRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKelBrEspglg7tGX6XCuvDsZbNshMBgG +A1UdIAQRMA8wDQYLKwYBBAGwLQMEAQEwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3 +DQEBCwUAA4IBAQAmdzTblEiGKkGdLD4GkGDEjKwLVLgfuXvTBznk+j57sj1O7Z8j +vZfza1zv7v1Apt+hk6EKhqzvINB5Ab149xnYJDE0BAGmuhWawyfc2E8PzBhj/5kP +DpFrdRbhIfzYJsdHt6bPWHJxfrrhTZVHO8mvbaG0weyJ9rQPOLXiZNwlz6bb65pc +maHFCN795trV1lpFDMS3wrUU77QR/w4VtfX128a961qn8FYiqTxlVMYVqL2Gns2D +lmh6cYGJ4Qvh6hEbaAjMaZ7snkGeRDImeuKHCnE96+RapNLbxc3G3mB/ufNPRJLv +KrcYPqcZ2Qt9sTdBQrC6YB3y/gkRsPCHe6ed +-----END CERTIFICATE----- + +# Issuer: CN=QuoVadis Root CA 1 G3 O=QuoVadis Limited +# Subject: CN=QuoVadis Root CA 1 G3 O=QuoVadis Limited +# Label: "QuoVadis Root CA 1 G3" +# Serial: 687049649626669250736271037606554624078720034195 +# MD5 Fingerprint: a4:bc:5b:3f:fe:37:9a:fa:64:f0:e2:fa:05:3d:0b:ab +# SHA1 Fingerprint: 1b:8e:ea:57:96:29:1a:c9:39:ea:b8:0a:81:1a:73:73:c0:93:79:67 +# SHA256 Fingerprint: 8a:86:6f:d1:b2:76:b5:7e:57:8e:92:1c:65:82:8a:2b:ed:58:e9:f2:f2:88:05:41:34:b7:f1:f4:bf:c9:cc:74 +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIUeFhfLq0sGUvjNwc1NBMotZbUZZMwDQYJKoZIhvcNAQEL +BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc +BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMSBHMzAeFw0xMjAxMTIxNzI3NDRaFw00 +MjAxMTIxNzI3NDRaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDEgRzMwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQCgvlAQjunybEC0BJyFuTHK3C3kEakEPBtV +wedYMB0ktMPvhd6MLOHBPd+C5k+tR4ds7FtJwUrVu4/sh6x/gpqG7D0DmVIB0jWe +rNrwU8lmPNSsAgHaJNM7qAJGr6Qc4/hzWHa39g6QDbXwz8z6+cZM5cOGMAqNF341 +68Xfuw6cwI2H44g4hWf6Pser4BOcBRiYz5P1sZK0/CPTz9XEJ0ngnjybCKOLXSoh +4Pw5qlPafX7PGglTvF0FBM+hSo+LdoINofjSxxR3W5A2B4GbPgb6Ul5jxaYA/qXp +UhtStZI5cgMJYr2wYBZupt0lwgNm3fME0UDiTouG9G/lg6AnhF4EwfWQvTA9xO+o +abw4m6SkltFi2mnAAZauy8RRNOoMqv8hjlmPSlzkYZqn0ukqeI1RPToV7qJZjqlc +3sX5kCLliEVx3ZGZbHqfPT2YfF72vhZooF6uCyP8Wg+qInYtyaEQHeTTRCOQiJ/G +KubX9ZqzWB4vMIkIG1SitZgj7Ah3HJVdYdHLiZxfokqRmu8hqkkWCKi9YSgxyXSt +hfbZxbGL0eUQMk1fiyA6PEkfM4VZDdvLCXVDaXP7a3F98N/ETH3Goy7IlXnLc6KO +Tk0k+17kBL5yG6YnLUlamXrXXAkgt3+UuU/xDRxeiEIbEbfnkduebPRq34wGmAOt +zCjvpUfzUwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +BjAdBgNVHQ4EFgQUo5fW816iEOGrRZ88F2Q87gFwnMwwDQYJKoZIhvcNAQELBQAD +ggIBABj6W3X8PnrHX3fHyt/PX8MSxEBd1DKquGrX1RUVRpgjpeaQWxiZTOOtQqOC +MTaIzen7xASWSIsBx40Bz1szBpZGZnQdT+3Btrm0DWHMY37XLneMlhwqI2hrhVd2 +cDMT/uFPpiN3GPoajOi9ZcnPP/TJF9zrx7zABC4tRi9pZsMbj/7sPtPKlL92CiUN +qXsCHKnQO18LwIE6PWThv6ctTr1NxNgpxiIY0MWscgKCP6o6ojoilzHdCGPDdRS5 +YCgtW2jgFqlmgiNR9etT2DGbe+m3nUvriBbP+V04ikkwj+3x6xn0dxoxGE1nVGwv +b2X52z3sIexe9PSLymBlVNFxZPT5pqOBMzYzcfCkeF9OrYMh3jRJjehZrJ3ydlo2 +8hP0r+AJx2EqbPfgna67hkooby7utHnNkDPDs3b69fBsnQGQ+p6Q9pxyz0fawx/k +NSBT8lTR32GDpgLiJTjehTItXnOQUl1CxM49S+H5GYQd1aJQzEH7QRTDvdbJWqNj +ZgKAvQU6O0ec7AAmTPWIUb+oI38YB7AL7YsmoWTTYUrrXJ/es69nA7Mf3W1daWhp +q1467HxpvMc7hU6eFbm0FU/DlXpY18ls6Wy58yljXrQs8C097Vpl4KlbQMJImYFt +nh8GKjwStIsPm6Ik8KaN1nrgS7ZklmOVhMJKzRwuJIczYOXD +-----END CERTIFICATE----- + +# Issuer: CN=QuoVadis Root CA 2 G3 O=QuoVadis Limited +# Subject: CN=QuoVadis Root CA 2 G3 O=QuoVadis Limited +# Label: "QuoVadis Root CA 2 G3" +# Serial: 390156079458959257446133169266079962026824725800 +# MD5 Fingerprint: af:0c:86:6e:bf:40:2d:7f:0b:3e:12:50:ba:12:3d:06 +# SHA1 Fingerprint: 09:3c:61:f3:8b:8b:dc:7d:55:df:75:38:02:05:00:e1:25:f5:c8:36 +# SHA256 Fingerprint: 8f:e4:fb:0a:f9:3a:4d:0d:67:db:0b:eb:b2:3e:37:c7:1b:f3:25:dc:bc:dd:24:0e:a0:4d:af:58:b4:7e:18:40 +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIURFc0JFuBiZs18s64KztbpybwdSgwDQYJKoZIhvcNAQEL +BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc +BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMiBHMzAeFw0xMjAxMTIxODU5MzJaFw00 +MjAxMTIxODU5MzJaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDIgRzMwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQChriWyARjcV4g/Ruv5r+LrI3HimtFhZiFf +qq8nUeVuGxbULX1QsFN3vXg6YOJkApt8hpvWGo6t/x8Vf9WVHhLL5hSEBMHfNrMW +n4rjyduYNM7YMxcoRvynyfDStNVNCXJJ+fKH46nafaF9a7I6JaltUkSs+L5u+9ym +c5GQYaYDFCDy54ejiK2toIz/pgslUiXnFgHVy7g1gQyjO/Dh4fxaXc6AcW34Sas+ +O7q414AB+6XrW7PFXmAqMaCvN+ggOp+oMiwMzAkd056OXbxMmO7FGmh77FOm6RQ1 +o9/NgJ8MSPsc9PG/Srj61YxxSscfrf5BmrODXfKEVu+lV0POKa2Mq1W/xPtbAd0j +IaFYAI7D0GoT7RPjEiuA3GfmlbLNHiJuKvhB1PLKFAeNilUSxmn1uIZoL1NesNKq +IcGY5jDjZ1XHm26sGahVpkUG0CM62+tlXSoREfA7T8pt9DTEceT/AFr2XK4jYIVz +8eQQsSWu1ZK7E8EM4DnatDlXtas1qnIhO4M15zHfeiFuuDIIfR0ykRVKYnLP43eh +vNURG3YBZwjgQQvD6xVu+KQZ2aKrr+InUlYrAoosFCT5v0ICvybIxo/gbjh9Uy3l +7ZizlWNof/k19N+IxWA1ksB8aRxhlRbQ694Lrz4EEEVlWFA4r0jyWbYW8jwNkALG +cC4BrTwV1wIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +BjAdBgNVHQ4EFgQU7edvdlq/YOxJW8ald7tyFnGbxD0wDQYJKoZIhvcNAQELBQAD +ggIBAJHfgD9DCX5xwvfrs4iP4VGyvD11+ShdyLyZm3tdquXK4Qr36LLTn91nMX66 +AarHakE7kNQIXLJgapDwyM4DYvmL7ftuKtwGTTwpD4kWilhMSA/ohGHqPHKmd+RC +roijQ1h5fq7KpVMNqT1wvSAZYaRsOPxDMuHBR//47PERIjKWnML2W2mWeyAMQ0Ga +W/ZZGYjeVYg3UQt4XAoeo0L9x52ID8DyeAIkVJOviYeIyUqAHerQbj5hLja7NQ4n +lv1mNDthcnPxFlxHBlRJAHpYErAK74X9sbgzdWqTHBLmYF5vHX/JHyPLhGGfHoJE ++V+tYlUkmlKY7VHnoX6XOuYvHxHaU4AshZ6rNRDbIl9qxV6XU/IyAgkwo1jwDQHV +csaxfGl7w/U2Rcxhbl5MlMVerugOXou/983g7aEOGzPuVBj+D77vfoRrQ+NwmNtd +dbINWQeFFSM51vHfqSYP1kjHs6Yi9TM3WpVHn3u6GBVv/9YUZINJ0gpnIdsPNWNg +KCLjsZWDzYWm3S8P52dSbrsvhXz1SnPnxT7AvSESBT/8twNJAlvIJebiVDj1eYeM +HVOyToV7BjjHLPj4sHKNJeV3UvQDHEimUF+IIDBu8oJDqz2XhOdT+yHBTw8imoa4 +WSr2Rz0ZiC3oheGe7IUIarFsNMkd7EgrO3jtZsSOeWmD3n+M +-----END CERTIFICATE----- + +# Issuer: CN=QuoVadis Root CA 3 G3 O=QuoVadis Limited +# Subject: CN=QuoVadis Root CA 3 G3 O=QuoVadis Limited +# Label: "QuoVadis Root CA 3 G3" +# Serial: 268090761170461462463995952157327242137089239581 +# MD5 Fingerprint: df:7d:b9:ad:54:6f:68:a1:df:89:57:03:97:43:b0:d7 +# SHA1 Fingerprint: 48:12:bd:92:3c:a8:c4:39:06:e7:30:6d:27:96:e6:a4:cf:22:2e:7d +# SHA256 Fingerprint: 88:ef:81:de:20:2e:b0:18:45:2e:43:f8:64:72:5c:ea:5f:bd:1f:c2:d9:d2:05:73:07:09:c5:d8:b8:69:0f:46 +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIULvWbAiin23r/1aOp7r0DoM8Sah0wDQYJKoZIhvcNAQEL +BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc +BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMyBHMzAeFw0xMjAxMTIyMDI2MzJaFw00 +MjAxMTIyMDI2MzJaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDMgRzMwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQCzyw4QZ47qFJenMioKVjZ/aEzHs286IxSR +/xl/pcqs7rN2nXrpixurazHb+gtTTK/FpRp5PIpM/6zfJd5O2YIyC0TeytuMrKNu +FoM7pmRLMon7FhY4futD4tN0SsJiCnMK3UmzV9KwCoWdcTzeo8vAMvMBOSBDGzXR +U7Ox7sWTaYI+FrUoRqHe6okJ7UO4BUaKhvVZR74bbwEhELn9qdIoyhA5CcoTNs+c +ra1AdHkrAj80//ogaX3T7mH1urPnMNA3I4ZyYUUpSFlob3emLoG+B01vr87ERROR +FHAGjx+f+IdpsQ7vw4kZ6+ocYfx6bIrc1gMLnia6Et3UVDmrJqMz6nWB2i3ND0/k +A9HvFZcba5DFApCTZgIhsUfei5pKgLlVj7WiL8DWM2fafsSntARE60f75li59wzw +eyuxwHApw0BiLTtIadwjPEjrewl5qW3aqDCYz4ByA4imW0aucnl8CAMhZa634Ryl +sSqiMd5mBPfAdOhx3v89WcyWJhKLhZVXGqtrdQtEPREoPHtht+KPZ0/l7DxMYIBp +VzgeAVuNVejH38DMdyM0SXV89pgR6y3e7UEuFAUCf+D+IOs15xGsIs5XPd7JMG0Q +A4XN8f+MFrXBsj6IbGB/kE+V9/YtrQE5BwT6dYB9v0lQ7e/JxHwc64B+27bQ3RP+ +ydOc17KXqQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +BjAdBgNVHQ4EFgQUxhfQvKjqAkPyGwaZXSuQILnXnOQwDQYJKoZIhvcNAQELBQAD +ggIBADRh2Va1EodVTd2jNTFGu6QHcrxfYWLopfsLN7E8trP6KZ1/AvWkyaiTt3px +KGmPc+FSkNrVvjrlt3ZqVoAh313m6Tqe5T72omnHKgqwGEfcIHB9UqM+WXzBusnI +FUBhynLWcKzSt/Ac5IYp8M7vaGPQtSCKFWGafoaYtMnCdvvMujAWzKNhxnQT5Wvv +oxXqA/4Ti2Tk08HS6IT7SdEQTXlm66r99I0xHnAUrdzeZxNMgRVhvLfZkXdxGYFg +u/BYpbWcC/ePIlUnwEsBbTuZDdQdm2NnL9DuDcpmvJRPpq3t/O5jrFc/ZSXPsoaP +0Aj/uHYUbt7lJ+yreLVTubY/6CD50qi+YUbKh4yE8/nxoGibIh6BJpsQBJFxwAYf +3KDTuVan45gtf4Od34wrnDKOMpTwATwiKp9Dwi7DmDkHOHv8XgBCH/MyJnmDhPbl +8MFREsALHgQjDFSlTC9JxUrRtm5gDWv8a4uFJGS3iQ6rJUdbPM9+Sb3H6QrG2vd+ +DhcI00iX0HGS8A85PjRqHH3Y8iKuu2n0M7SmSFXRDw4m6Oy2Cy2nhTXN/VnIn9HN +PlopNLk9hM6xZdRZkZFWdSHBd575euFgndOtBBj0fOtek49TSiIp+EgrPk2GrFt/ +ywaZWWDYWGWVjUTR939+J399roD1B0y2PpxxVJkES/1Y+Zj0 +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Assured ID Root G2 O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Assured ID Root G2 O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Assured ID Root G2" +# Serial: 15385348160840213938643033620894905419 +# MD5 Fingerprint: 92:38:b9:f8:63:24:82:65:2c:57:33:e6:fe:81:8f:9d +# SHA1 Fingerprint: a1:4b:48:d9:43:ee:0a:0e:40:90:4f:3c:e0:a4:c0:91:93:51:5d:3f +# SHA256 Fingerprint: 7d:05:eb:b6:82:33:9f:8c:94:51:ee:09:4e:eb:fe:fa:79:53:a1:14:ed:b2:f4:49:49:45:2f:ab:7d:2f:c1:85 +-----BEGIN CERTIFICATE----- +MIIDljCCAn6gAwIBAgIQC5McOtY5Z+pnI7/Dr5r0SzANBgkqhkiG9w0BAQsFADBl +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv +b3QgRzIwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBlMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl +cnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzIwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZ5ygvUj82ckmIkzTz+GoeMVSA +n61UQbVH35ao1K+ALbkKz3X9iaV9JPrjIgwrvJUXCzO/GU1BBpAAvQxNEP4Htecc +biJVMWWXvdMX0h5i89vqbFCMP4QMls+3ywPgym2hFEwbid3tALBSfK+RbLE4E9Hp +EgjAALAcKxHad3A2m67OeYfcgnDmCXRwVWmvo2ifv922ebPynXApVfSr/5Vh88lA +bx3RvpO704gqu52/clpWcTs/1PPRCv4o76Pu2ZmvA9OPYLfykqGxvYmJHzDNw6Yu +YjOuFgJ3RFrngQo8p0Quebg/BLxcoIfhG69Rjs3sLPr4/m3wOnyqi+RnlTGNAgMB +AAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQW +BBTOw0q5mVXyuNtgv6l+vVa1lzan1jANBgkqhkiG9w0BAQsFAAOCAQEAyqVVjOPI +QW5pJ6d1Ee88hjZv0p3GeDgdaZaikmkuOGybfQTUiaWxMTeKySHMq2zNixya1r9I +0jJmwYrA8y8678Dj1JGG0VDjA9tzd29KOVPt3ibHtX2vK0LRdWLjSisCx1BL4Gni +lmwORGYQRI+tBev4eaymG+g3NJ1TyWGqolKvSnAWhsI6yLETcDbYz+70CjTVW0z9 +B5yiutkBclzzTcHdDrEcDcRjvq30FPuJ7KJBDkzMyFdA0G4Dqs0MjomZmWzwPDCv +ON9vvKO+KSAnq3T/EyJ43pdSVR6DtVQgA+6uwE9W3jfMw3+qBCe703e4YtsXfJwo +IhNzbM8m9Yop5w== +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Assured ID Root G3 O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Assured ID Root G3 O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Assured ID Root G3" +# Serial: 15459312981008553731928384953135426796 +# MD5 Fingerprint: 7c:7f:65:31:0c:81:df:8d:ba:3e:99:e2:5c:ad:6e:fb +# SHA1 Fingerprint: f5:17:a2:4f:9a:48:c6:c9:f8:a2:00:26:9f:dc:0f:48:2c:ab:30:89 +# SHA256 Fingerprint: 7e:37:cb:8b:4c:47:09:0c:ab:36:55:1b:a6:f4:5d:b8:40:68:0f:ba:16:6a:95:2d:b1:00:71:7f:43:05:3f:c2 +-----BEGIN CERTIFICATE----- +MIICRjCCAc2gAwIBAgIQC6Fa+h3foLVJRK/NJKBs7DAKBggqhkjOPQQDAzBlMQsw +CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu +ZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3Qg +RzMwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBlMQswCQYDVQQGEwJV +UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu +Y29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwdjAQBgcq +hkjOPQIBBgUrgQQAIgNiAAQZ57ysRGXtzbg/WPuNsVepRC0FFfLvC/8QdJ+1YlJf +Zn4f5dwbRXkLzMZTCp2NXQLZqVneAlr2lSoOjThKiknGvMYDOAdfVdp+CW7if17Q +RSAPWXYQ1qAk8C3eNvJsKTmjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/ +BAQDAgGGMB0GA1UdDgQWBBTL0L2p4ZgFUaFNN6KDec6NHSrkhDAKBggqhkjOPQQD +AwNnADBkAjAlpIFFAmsSS3V0T8gj43DydXLefInwz5FyYZ5eEJJZVrmDxxDnOOlY +JjZ91eQ0hjkCMHw2U/Aw5WJjOpnitqM7mzT6HtoQknFekROn3aRukswy1vUhZscv +6pZjamVFkpUBtA== +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Global Root G2 O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Global Root G2 O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Global Root G2" +# Serial: 4293743540046975378534879503202253541 +# MD5 Fingerprint: e4:a6:8a:c8:54:ac:52:42:46:0a:fd:72:48:1b:2a:44 +# SHA1 Fingerprint: df:3c:24:f9:bf:d6:66:76:1b:26:80:73:fe:06:d1:cc:8d:4f:82:a4 +# SHA256 Fingerprint: cb:3c:cb:b7:60:31:e5:e0:13:8f:8d:d3:9a:23:f9:de:47:ff:c3:5e:43:c1:14:4c:ea:27:d4:6a:5a:b1:cb:5f +-----BEGIN CERTIFICATE----- +MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH +MjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT +MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j +b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI +2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx +1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ +q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz +tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ +vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP +BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV +5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY +1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4 +NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG +Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91 +8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe +pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl +MrY= +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Global Root G3 O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Global Root G3 O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Global Root G3" +# Serial: 7089244469030293291760083333884364146 +# MD5 Fingerprint: f5:5d:a4:50:a5:fb:28:7e:1e:0f:0d:cc:96:57:56:ca +# SHA1 Fingerprint: 7e:04:de:89:6a:3e:66:6d:00:e6:87:d3:3f:fa:d9:3b:e8:3d:34:9e +# SHA256 Fingerprint: 31:ad:66:48:f8:10:41:38:c7:38:f3:9e:a4:32:01:33:39:3e:3a:18:cc:02:29:6e:f9:7c:2a:c9:ef:67:31:d0 +-----BEGIN CERTIFICATE----- +MIICPzCCAcWgAwIBAgIQBVVWvPJepDU1w6QP1atFcjAKBggqhkjOPQQDAzBhMQsw +CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu +ZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMzAe +Fw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVTMRUw +EwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20x +IDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEczMHYwEAYHKoZIzj0CAQYF +K4EEACIDYgAE3afZu4q4C/sLfyHS8L6+c/MzXRq8NOrexpu80JX28MzQC7phW1FG +fp4tn+6OYwwX7Adw9c+ELkCDnOg/QW07rdOkFFk2eJ0DQ+4QE2xy3q6Ip6FrtUPO +Z9wj/wMco+I+o0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAd +BgNVHQ4EFgQUs9tIpPmhxdiuNkHMEWNpYim8S8YwCgYIKoZIzj0EAwMDaAAwZQIx +AK288mw/EkrRLTnDCgmXc/SINoyIJ7vmiI1Qhadj+Z4y3maTD/HMsQmP3Wyr+mt/ +oAIwOWZbwmSNuJ5Q3KjVSaLtx9zRSX8XAbjIho9OjIgrqJqpisXRAL34VOKa5Vt8 +sycX +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Trusted Root G4 O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Trusted Root G4 O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Trusted Root G4" +# Serial: 7451500558977370777930084869016614236 +# MD5 Fingerprint: 78:f2:fc:aa:60:1f:2f:b4:eb:c9:37:ba:53:2e:75:49 +# SHA1 Fingerprint: dd:fb:16:cd:49:31:c9:73:a2:03:7d:3f:c8:3a:4d:7d:77:5d:05:e4 +# SHA256 Fingerprint: 55:2f:7b:dc:f1:a7:af:9e:6c:e6:72:01:7f:4f:12:ab:f7:72:40:c7:8e:76:1a:c2:03:d1:d9:d2:0a:c8:99:88 +-----BEGIN CERTIFICATE----- +MIIFkDCCA3igAwIBAgIQBZsbV56OITLiOQe9p3d1XDANBgkqhkiG9w0BAQwFADBi +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3Qg +RzQwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBiMQswCQYDVQQGEwJV +UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu +Y29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQC/5pBzaN675F1KPDAiMGkz7MKnJS7JIT3y +ithZwuEppz1Yq3aaza57G4QNxDAf8xukOBbrVsaXbR2rsnnyyhHS5F/WBTxSD1If +xp4VpX6+n6lXFllVcq9ok3DCsrp1mWpzMpTREEQQLt+C8weE5nQ7bXHiLQwb7iDV +ySAdYyktzuxeTsiT+CFhmzTrBcZe7FsavOvJz82sNEBfsXpm7nfISKhmV1efVFiO +DCu3T6cw2Vbuyntd463JT17lNecxy9qTXtyOj4DatpGYQJB5w3jHtrHEtWoYOAMQ +jdjUN6QuBX2I9YI+EJFwq1WCQTLX2wRzKm6RAXwhTNS8rhsDdV14Ztk6MUSaM0C/ +CNdaSaTC5qmgZ92kJ7yhTzm1EVgX9yRcRo9k98FpiHaYdj1ZXUJ2h4mXaXpI8OCi +EhtmmnTK3kse5w5jrubU75KSOp493ADkRSWJtppEGSt+wJS00mFt6zPZxd9LBADM +fRyVw4/3IbKyEbe7f/LVjHAsQWCqsWMYRJUadmJ+9oCw++hkpjPRiQfhvbfmQ6QY +uKZ3AeEPlAwhHbJUKSWJbOUOUlFHdL4mrLZBdd56rF+NP8m800ERElvlEFDrMcXK +chYiCd98THU/Y+whX8QgUWtvsauGi0/C1kVfnSD8oR7FwI+isX4KJpn15GkvmB0t +9dmpsh3lGwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +hjAdBgNVHQ4EFgQU7NfjgtJxXWRM3y5nP+e6mK4cD08wDQYJKoZIhvcNAQEMBQAD +ggIBALth2X2pbL4XxJEbw6GiAI3jZGgPVs93rnD5/ZpKmbnJeFwMDF/k5hQpVgs2 +SV1EY+CtnJYYZhsjDT156W1r1lT40jzBQ0CuHVD1UvyQO7uYmWlrx8GnqGikJ9yd ++SeuMIW59mdNOj6PWTkiU0TryF0Dyu1Qen1iIQqAyHNm0aAFYF/opbSnr6j3bTWc +fFqK1qI4mfN4i/RN0iAL3gTujJtHgXINwBQy7zBZLq7gcfJW5GqXb5JQbZaNaHqa +sjYUegbyJLkJEVDXCLG4iXqEI2FCKeWjzaIgQdfRnGTZ6iahixTXTBmyUEFxPT9N +cCOGDErcgdLMMpSEDQgJlxxPwO5rIHQw0uA5NBCFIRUBCOhVMt5xSdkoF1BN5r5N +0XWs0Mr7QbhDparTwwVETyw2m+L64kW4I1NsBm9nVX9GtUw/bihaeSbSpKhil9Ie +4u1Ki7wb/UdKDd9nZn6yW0HQO+T0O/QEY+nvwlQAUaCKKsnOeMzV6ocEGLPOr0mI +r/OSmbaz5mEP0oUA51Aa5BuVnRmhuZyxm7EAHu/QD09CbMkKvO5D+jpxpchNJqU1 +/YldvIViHTLSoCtU7ZpXwdv6EM8Zt4tKG48BtieVU+i2iW1bvGjUI+iLUaJW+fCm +gKDWHrO8Dw9TdSmq6hN35N6MgSGtBxBHEa2HPQfRdbzP82Z+ +-----END CERTIFICATE----- + +# Issuer: CN=Certification Authority of WoSign O=WoSign CA Limited +# Subject: CN=Certification Authority of WoSign O=WoSign CA Limited +# Label: "WoSign" +# Serial: 125491772294754854453622855443212256657 +# MD5 Fingerprint: a1:f2:f9:b5:d2:c8:7a:74:b8:f3:05:f1:d7:e1:84:8d +# SHA1 Fingerprint: b9:42:94:bf:91:ea:8f:b6:4b:e6:10:97:c7:fb:00:13:59:b6:76:cb +# SHA256 Fingerprint: 4b:22:d5:a6:ae:c9:9f:3c:db:79:aa:5e:c0:68:38:47:9c:d5:ec:ba:71:64:f7:f2:2d:c1:d6:5f:63:d8:57:08 +-----BEGIN CERTIFICATE----- +MIIFdjCCA16gAwIBAgIQXmjWEXGUY1BWAGjzPsnFkTANBgkqhkiG9w0BAQUFADBV +MQswCQYDVQQGEwJDTjEaMBgGA1UEChMRV29TaWduIENBIExpbWl0ZWQxKjAoBgNV +BAMTIUNlcnRpZmljYXRpb24gQXV0aG9yaXR5IG9mIFdvU2lnbjAeFw0wOTA4MDgw +MTAwMDFaFw0zOTA4MDgwMTAwMDFaMFUxCzAJBgNVBAYTAkNOMRowGAYDVQQKExFX +b1NpZ24gQ0EgTGltaXRlZDEqMCgGA1UEAxMhQ2VydGlmaWNhdGlvbiBBdXRob3Jp +dHkgb2YgV29TaWduMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAvcqN +rLiRFVaXe2tcesLea9mhsMMQI/qnobLMMfo+2aYpbxY94Gv4uEBf2zmoAHqLoE1U +fcIiePyOCbiohdfMlZdLdNiefvAA5A6JrkkoRBoQmTIPJYhTpA2zDxIIFgsDcScc +f+Hb0v1naMQFXQoOXXDX2JegvFNBmpGN9J42Znp+VsGQX+axaCA2pIwkLCxHC1l2 +ZjC1vt7tj/id07sBMOby8w7gLJKA84X5KIq0VC6a7fd2/BVoFutKbOsuEo/Uz/4M +x1wdC34FMr5esAkqQtXJTpCzWQ27en7N1QhatH/YHGkR+ScPewavVIMYe+HdVHpR +aG53/Ma/UkpmRqGyZxq7o093oL5d//xWC0Nyd5DKnvnyOfUNqfTq1+ezEC8wQjch +zDBwyYaYD8xYTYO7feUapTeNtqwylwA6Y3EkHp43xP901DfA4v6IRmAR3Qg/UDar +uHqklWJqbrDKaiFaafPz+x1wOZXzp26mgYmhiMU7ccqjUu6Du/2gd/Tkb+dC221K +mYo0SLwX3OSACCK28jHAPwQ+658geda4BmRkAjHXqc1S+4RFaQkAKtxVi8QGRkvA +Sh0JWzko/amrzgD5LkhLJuYwTKVYyrREgk/nkR4zw7CT/xH8gdLKH3Ep3XZPkiWv +HYG3Dy+MwwbMLyejSuQOmbp8HkUff6oZRZb9/D0CAwEAAaNCMEAwDgYDVR0PAQH/ +BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFOFmzw7R8bNLtwYgFP6H +EtX2/vs+MA0GCSqGSIb3DQEBBQUAA4ICAQCoy3JAsnbBfnv8rWTjMnvMPLZdRtP1 +LOJwXcgu2AZ9mNELIaCJWSQBnfmvCX0KI4I01fx8cpm5o9dU9OpScA7F9dY74ToJ +MuYhOZO9sxXqT2r09Ys/L3yNWC7F4TmgPsc9SnOeQHrAK2GpZ8nzJLmzbVUsWh2e +JXLOC62qx1ViC777Y7NhRCOjy+EaDveaBk3e1CNOIZZbOVtXHS9dCF4Jef98l7VN +g64N1uajeeAz0JmWAjCnPv/So0M/BVoG6kQC2nz4SNAzqfkHx5Xh9T71XXG68pWp +dIhhWeO/yloTunK0jF02h+mmxTwTv97QRCbut+wucPrXnbes5cVAWubXbHssw1ab +R80LzvobtCHXt2a49CUwi1wNuepnsvRtrtWhnk/Yn+knArAdBtaP4/tIEp9/EaEQ +PkxROpaw0RPxx9gmrjrKkcRpnd8BKWRRb2jaFOwIQZeQjdCygPLPwj2/kWjFgGce +xGATVdVhmVd8upUPYUk6ynW8yQqTP2cOEvIo4jEbwFcW3wh8GcF+Dx+FHgo2fFt+ +J7x6v+Db9NpSvd4MVHAxkUOVyLzwPt0JfjBkUO1/AaQzZ01oT74V77D2AhGiGxMl +OtzCWfHjXEa7ZywCRuoeSKbmW9m1vFGikpbbqsY3Iqb+zCB0oy2pLmvLwIIRIbWT +ee5Ehr7XHuQe+w== +-----END CERTIFICATE----- + +# Issuer: CN=CA 沃通根证书 O=WoSign CA Limited +# Subject: CN=CA 沃通根证书 O=WoSign CA Limited +# Label: "WoSign China" +# Serial: 106921963437422998931660691310149453965 +# MD5 Fingerprint: 78:83:5b:52:16:76:c4:24:3b:83:78:e8:ac:da:9a:93 +# SHA1 Fingerprint: 16:32:47:8d:89:f9:21:3a:92:00:85:63:f5:a4:a7:d3:12:40:8a:d6 +# SHA256 Fingerprint: d6:f0:34:bd:94:aa:23:3f:02:97:ec:a4:24:5b:28:39:73:e4:47:aa:59:0f:31:0c:77:f4:8f:df:83:11:22:54 +-----BEGIN CERTIFICATE----- +MIIFWDCCA0CgAwIBAgIQUHBrzdgT/BtOOzNy0hFIjTANBgkqhkiG9w0BAQsFADBG +MQswCQYDVQQGEwJDTjEaMBgGA1UEChMRV29TaWduIENBIExpbWl0ZWQxGzAZBgNV +BAMMEkNBIOayg+mAmuagueivgeS5pjAeFw0wOTA4MDgwMTAwMDFaFw0zOTA4MDgw +MTAwMDFaMEYxCzAJBgNVBAYTAkNOMRowGAYDVQQKExFXb1NpZ24gQ0EgTGltaXRl +ZDEbMBkGA1UEAwwSQ0Eg5rKD6YCa5qC56K+B5LmmMIICIjANBgkqhkiG9w0BAQEF +AAOCAg8AMIICCgKCAgEA0EkhHiX8h8EqwqzbdoYGTufQdDTc7WU1/FDWiD+k8H/r +D195L4mx/bxjWDeTmzj4t1up+thxx7S8gJeNbEvxUNUqKaqoGXqW5pWOdO2XCld1 +9AXbbQs5uQF/qvbW2mzmBeCkTVL829B0txGMe41P/4eDrv8FAxNXUDf+jJZSEExf +v5RxadmWPgxDT74wwJ85dE8GRV2j1lY5aAfMh09Qd5Nx2UQIsYo06Yms25tO4dnk +UkWMLhQfkWsZHWgpLFbE4h4TV2TwYeO5Ed+w4VegG63XX9Gv2ystP9Bojg/qnw+L +NVgbExz03jWhCl3W6t8Sb8D7aQdGctyB9gQjF+BNdeFyb7Ao65vh4YOhn0pdr8yb ++gIgthhid5E7o9Vlrdx8kHccREGkSovrlXLp9glk3Kgtn3R46MGiCWOc76DbT52V +qyBPt7D3h1ymoOQ3OMdc4zUPLK2jgKLsLl3Az+2LBcLmc272idX10kaO6m1jGx6K +yX2m+Jzr5dVjhU1zZmkR/sgO9MHHZklTfuQZa/HpelmjbX7FF+Ynxu8b22/8DU0G +AbQOXDBGVWCvOGU6yke6rCzMRh+yRpY/8+0mBe53oWprfi1tWFxK1I5nuPHa1UaK +J/kR8slC/k7e3x9cxKSGhxYzoacXGKUN5AXlK8IrC6KVkLn9YDxOiT7nnO4fuwEC +AwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O +BBYEFOBNv9ybQV0T6GTwp+kVpOGBwboxMA0GCSqGSIb3DQEBCwUAA4ICAQBqinA4 +WbbaixjIvirTthnVZil6Xc1bL3McJk6jfW+rtylNpumlEYOnOXOvEESS5iVdT2H6 +yAa+Tkvv/vMx/sZ8cApBWNromUuWyXi8mHwCKe0JgOYKOoICKuLJL8hWGSbueBwj +/feTZU7n85iYr83d2Z5AiDEoOqsuC7CsDCT6eiaY8xJhEPRdF/d+4niXVOKM6Cm6 +jBAyvd0zaziGfjk9DgNyp115j0WKWa5bIW4xRtVZjc8VX90xJc/bYNaBRHIpAlf2 +ltTW/+op2znFuCyKGo3Oy+dCMYYFaA6eFN0AkLppRQjbbpCBhqcqBT/mhDn4t/lX +X0ykeVoQDF7Va/81XwVRHmyjdanPUIPTfPRm94KNPQx96N97qA4bLJyuQHCH2u2n +FoJavjVsIE4iYdm8UXrNemHcSxH5/mc0zy4EZmFcV5cjjPOGG0jfKq+nwf/Yjj4D +u9gqsPoUJbJRa4ZDhS4HIxaAjUz7tGM7zMN07RujHv41D198HRaG9Q7DlfEvr10l +O1Hm13ZBONFLAzkopR6RctR9q5czxNM+4Gm2KHmgCY0c0f9BckgG/Jou5yD5m6Le +ie2uPAmvylezkolwQOQvT8Jwg0DXJCxr5wkf09XHwQj02w47HAcLQxGEIYbpgNR1 +2KvxAmLBsX5VYc8T1yaw15zLKYs4SgsOkI26oQ== +-----END CERTIFICATE----- + +# Issuer: CN=COMODO RSA Certification Authority O=COMODO CA Limited +# Subject: CN=COMODO RSA Certification Authority O=COMODO CA Limited +# Label: "COMODO RSA Certification Authority" +# Serial: 101909084537582093308941363524873193117 +# MD5 Fingerprint: 1b:31:b0:71:40:36:cc:14:36:91:ad:c4:3e:fd:ec:18 +# SHA1 Fingerprint: af:e5:d2:44:a8:d1:19:42:30:ff:47:9f:e2:f8:97:bb:cd:7a:8c:b4 +# SHA256 Fingerprint: 52:f0:e1:c4:e5:8e:c6:29:29:1b:60:31:7f:07:46:71:b8:5d:7e:a8:0d:5b:07:27:34:63:53:4b:32:b4:02:34 +-----BEGIN CERTIFICATE----- +MIIF2DCCA8CgAwIBAgIQTKr5yttjb+Af907YWwOGnTANBgkqhkiG9w0BAQwFADCB +hTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G +A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNV +BAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMTE5 +MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgT +EkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR +Q09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCR +6FSS0gpWsawNJN3Fz0RndJkrN6N9I3AAcbxT38T6KhKPS38QVr2fcHK3YX/JSw8X +pz3jsARh7v8Rl8f0hj4K+j5c+ZPmNHrZFGvnnLOFoIJ6dq9xkNfs/Q36nGz637CC +9BR++b7Epi9Pf5l/tfxnQ3K9DADWietrLNPtj5gcFKt+5eNu/Nio5JIk2kNrYrhV +/erBvGy2i/MOjZrkm2xpmfh4SDBF1a3hDTxFYPwyllEnvGfDyi62a+pGx8cgoLEf +Zd5ICLqkTqnyg0Y3hOvozIFIQ2dOciqbXL1MGyiKXCJ7tKuY2e7gUYPDCUZObT6Z ++pUX2nwzV0E8jVHtC7ZcryxjGt9XyD+86V3Em69FmeKjWiS0uqlWPc9vqv9JWL7w +qP/0uK3pN/u6uPQLOvnoQ0IeidiEyxPx2bvhiWC4jChWrBQdnArncevPDt09qZah +SL0896+1DSJMwBGB7FY79tOi4lu3sgQiUpWAk2nojkxl8ZEDLXB0AuqLZxUpaVIC +u9ffUGpVRr+goyhhf3DQw6KqLCGqR84onAZFdr+CGCe01a60y1Dma/RMhnEw6abf +Fobg2P9A3fvQQoh/ozM6LlweQRGBY84YcWsr7KaKtzFcOmpH4MN5WdYgGq/yapiq +crxXStJLnbsQ/LBMQeXtHT1eKJ2czL+zUdqnR+WEUwIDAQABo0IwQDAdBgNVHQ4E +FgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB +/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAArx1UaEt65Ru2yyTUEUAJNMnMvl +wFTPoCWOAvn9sKIN9SCYPBMtrFaisNZ+EZLpLrqeLppysb0ZRGxhNaKatBYSaVqM +4dc+pBroLwP0rmEdEBsqpIt6xf4FpuHA1sj+nq6PK7o9mfjYcwlYRm6mnPTXJ9OV +2jeDchzTc+CiR5kDOF3VSXkAKRzH7JsgHAckaVd4sjn8OoSgtZx8jb8uk2Intzna +FxiuvTwJaP+EmzzV1gsD41eeFPfR60/IvYcjt7ZJQ3mFXLrrkguhxuhoqEwWsRqZ +CuhTLJK7oQkYdQxlqHvLI7cawiiFwxv/0Cti76R7CZGYZ4wUAc1oBmpjIXUDgIiK +boHGhfKppC3n9KUkEEeDys30jXlYsQab5xoq2Z0B15R97QNKyvDb6KkBPvVWmcke +jkk9u+UJueBPSZI9FoJAzMxZxuY67RIuaTxslbH9qh17f4a+Hg4yRvv7E491f0yL +S0Zj/gA0QHDBw7mh3aZw4gSzQbzpgJHqZJx64SIDqZxubw5lT2yHh17zbqD5daWb +QOhTsiedSrnAdyGN/4fy3ryM7xfft0kL0fJuMAsaDk527RH89elWsn2/x20Kk4yl +0MC2Hb46TpSi125sC8KKfPog88Tk5c0NqMuRkrF8hey1FGlmDoLnzc7ILaZRfyHB +NVOFBkpdn627G190 +-----END CERTIFICATE----- + +# Issuer: CN=USERTrust RSA Certification Authority O=The USERTRUST Network +# Subject: CN=USERTrust RSA Certification Authority O=The USERTRUST Network +# Label: "USERTrust RSA Certification Authority" +# Serial: 2645093764781058787591871645665788717 +# MD5 Fingerprint: 1b:fe:69:d1:91:b7:19:33:a3:72:a8:0f:e1:55:e5:b5 +# SHA1 Fingerprint: 2b:8f:1b:57:33:0d:bb:a2:d0:7a:6c:51:f7:0e:e9:0d:da:b9:ad:8e +# SHA256 Fingerprint: e7:93:c9:b0:2f:d8:aa:13:e2:1c:31:22:8a:cc:b0:81:19:64:3b:74:9c:89:89:64:b1:74:6d:46:c3:d4:cb:d2 +-----BEGIN CERTIFICATE----- +MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCB +iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0pl +cnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNV +BAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAw +MjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNV +BAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU +aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2Vy +dGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK +AoICAQCAEmUXNg7D2wiz0KxXDXbtzSfTTK1Qg2HiqiBNCS1kCdzOiZ/MPans9s/B +3PHTsdZ7NygRK0faOca8Ohm0X6a9fZ2jY0K2dvKpOyuR+OJv0OwWIJAJPuLodMkY +tJHUYmTbf6MG8YgYapAiPLz+E/CHFHv25B+O1ORRxhFnRghRy4YUVD+8M/5+bJz/ +Fp0YvVGONaanZshyZ9shZrHUm3gDwFA66Mzw3LyeTP6vBZY1H1dat//O+T23LLb2 +VN3I5xI6Ta5MirdcmrS3ID3KfyI0rn47aGYBROcBTkZTmzNg95S+UzeQc0PzMsNT +79uq/nROacdrjGCT3sTHDN/hMq7MkztReJVni+49Vv4M0GkPGw/zJSZrM233bkf6 +c0Plfg6lZrEpfDKEY1WJxA3Bk1QwGROs0303p+tdOmw1XNtB1xLaqUkL39iAigmT +Yo61Zs8liM2EuLE/pDkP2QKe6xJMlXzzawWpXhaDzLhn4ugTncxbgtNMs+1b/97l +c6wjOy0AvzVVdAlJ2ElYGn+SNuZRkg7zJn0cTRe8yexDJtC/QV9AqURE9JnnV4ee +UB9XVKg+/XRjL7FQZQnmWEIuQxpMtPAlR1n6BB6T1CZGSlCBst6+eLf8ZxXhyVeE +Hg9j1uliutZfVS7qXMYoCAQlObgOK6nyTJccBz8NUvXt7y+CDwIDAQABo0IwQDAd +BgNVHQ4EFgQUU3m/WqorSs9UgOHYm8Cd8rIDZsswDgYDVR0PAQH/BAQDAgEGMA8G +A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAFzUfA3P9wF9QZllDHPF +Up/L+M+ZBn8b2kMVn54CVVeWFPFSPCeHlCjtHzoBN6J2/FNQwISbxmtOuowhT6KO +VWKR82kV2LyI48SqC/3vqOlLVSoGIG1VeCkZ7l8wXEskEVX/JJpuXior7gtNn3/3 +ATiUFJVDBwn7YKnuHKsSjKCaXqeYalltiz8I+8jRRa8YFWSQEg9zKC7F4iRO/Fjs +8PRF/iKz6y+O0tlFYQXBl2+odnKPi4w2r78NBc5xjeambx9spnFixdjQg3IM8WcR +iQycE0xyNN+81XHfqnHd4blsjDwSXWXavVcStkNr/+XeTWYRUc+ZruwXtuhxkYze +Sf7dNXGiFSeUHM9h4ya7b6NnJSFd5t0dCy5oGzuCr+yDZ4XUmFF0sbmZgIn/f3gZ +XHlKYC6SQK5MNyosycdiyA5d9zZbyuAlJQG03RoHnHcAP9Dc1ew91Pq7P8yF1m9/ +qS3fuQL39ZeatTXaw2ewh0qpKJ4jjv9cJ2vhsE/zB+4ALtRZh8tSQZXq9EfX7mRB +VXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGwsAvgnEzDHNb842m1R0aB +L6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gxQ+6IHdfG +jjxDah2nGN59PRbxYvnKkKj9 +-----END CERTIFICATE----- + +# Issuer: CN=USERTrust ECC Certification Authority O=The USERTRUST Network +# Subject: CN=USERTrust ECC Certification Authority O=The USERTRUST Network +# Label: "USERTrust ECC Certification Authority" +# Serial: 123013823720199481456569720443997572134 +# MD5 Fingerprint: fa:68:bc:d9:b5:7f:ad:fd:c9:1d:06:83:28:cc:24:c1 +# SHA1 Fingerprint: d1:cb:ca:5d:b2:d5:2a:7f:69:3b:67:4d:e5:f0:5a:1d:0c:95:7d:f0 +# SHA256 Fingerprint: 4f:f4:60:d5:4b:9c:86:da:bf:bc:fc:57:12:e0:40:0d:2b:ed:3f:bc:4d:4f:bd:aa:86:e0:6a:dc:d2:a9:ad:7a +-----BEGIN CERTIFICATE----- +MIICjzCCAhWgAwIBAgIQXIuZxVqUxdJxVt7NiYDMJjAKBggqhkjOPQQDAzCBiDEL +MAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNl +eSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMT +JVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMjAx +MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgT +Ck5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVUaGUg +VVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlm +aWNhdGlvbiBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQarFRaqflo +I+d61SRvU8Za2EurxtW20eZzca7dnNYMYf3boIkDuAUU7FfO7l0/4iGzzvfUinng +o4N+LZfQYcTxmdwlkWOrfzCjtHDix6EznPO/LlxTsV+zfTJ/ijTjeXmjQjBAMB0G +A1UdDgQWBBQ64QmG1M8ZwpZ2dEl23OA1xmNjmjAOBgNVHQ8BAf8EBAMCAQYwDwYD +VR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjA2Z6EWCNzklwBBHU6+4WMB +zzuqQhFkoJ2UOQIReVx7Hfpkue4WQrO/isIJxOzksU0CMQDpKmFHjFJKS04YcPbW +RNZu9YO6bVi9JNlWSOrvxKJGgYhqOkbRqZtNyWHa0V1Xahg= +-----END CERTIFICATE----- + +# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R4 +# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R4 +# Label: "GlobalSign ECC Root CA - R4" +# Serial: 14367148294922964480859022125800977897474 +# MD5 Fingerprint: 20:f0:27:68:d1:7e:a0:9d:0e:e6:2a:ca:df:5c:89:8e +# SHA1 Fingerprint: 69:69:56:2e:40:80:f4:24:a1:e7:19:9f:14:ba:f3:ee:58:ab:6a:bb +# SHA256 Fingerprint: be:c9:49:11:c2:95:56:76:db:6c:0a:55:09:86:d7:6e:3b:a0:05:66:7c:44:2c:97:62:b4:fb:b7:73:de:22:8c +-----BEGIN CERTIFICATE----- +MIIB4TCCAYegAwIBAgIRKjikHJYKBN5CsiilC+g0mAIwCgYIKoZIzj0EAwIwUDEk +MCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI0MRMwEQYDVQQKEwpH +bG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoX +DTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBD +QSAtIFI0MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEuMZ5049sJQ6fLjkZHAOkrprlOQcJ +FspjsbmG+IpXwVfOQvpzofdlQv8ewQCybnMO/8ch5RikqtlxP6jUuc6MHaNCMEAw +DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFFSwe61F +uOJAf/sKbvu+M8k8o4TVMAoGCCqGSM49BAMCA0gAMEUCIQDckqGgE6bPA7DmxCGX +kPoUVy0D7O48027KqGx2vKLeuwIgJ6iFJzWbVsaj8kfSt24bAgAXqmemFZHe+pTs +ewv4n4Q= +-----END CERTIFICATE----- + +# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R5 +# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R5 +# Label: "GlobalSign ECC Root CA - R5" +# Serial: 32785792099990507226680698011560947931244 +# MD5 Fingerprint: 9f:ad:3b:1c:02:1e:8a:ba:17:74:38:81:0c:a2:bc:08 +# SHA1 Fingerprint: 1f:24:c6:30:cd:a4:18:ef:20:69:ff:ad:4f:dd:5f:46:3a:1b:69:aa +# SHA256 Fingerprint: 17:9f:bc:14:8a:3d:d0:0f:d2:4e:a1:34:58:cc:43:bf:a7:f5:9c:81:82:d7:83:a5:13:f6:eb:ec:10:0c:89:24 +-----BEGIN CERTIFICATE----- +MIICHjCCAaSgAwIBAgIRYFlJ4CYuu1X5CneKcflK2GwwCgYIKoZIzj0EAwMwUDEk +MCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpH +bG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoX +DTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBD +QSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAER0UOlvt9Xb/pOdEh+J8LttV7HpI6SFkc +8GIxLcB6KP4ap1yztsyX50XUWPrRd21DosCHZTQKH3rd6zwzocWdTaRvQZU4f8ke +hOvRnkmSh5SHDDqFSmafnVmTTZdhBoZKo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYD +VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUPeYpSJvqB8ohREom3m7e0oPQn1kwCgYI +KoZIzj0EAwMDaAAwZQIxAOVpEslu28YxuglB4Zf4+/2a4n0Sye18ZNPLBSWLVtmg +515dTguDnFt2KaAJJiFqYgIwcdK1j1zqO+F4CYWodZI7yFz9SO8NdCKoCOJuxUnO +xwy8p2Fp8fc74SrL+SvzZpA3 +-----END CERTIFICATE----- + +# Issuer: CN=Staat der Nederlanden Root CA - G3 O=Staat der Nederlanden +# Subject: CN=Staat der Nederlanden Root CA - G3 O=Staat der Nederlanden +# Label: "Staat der Nederlanden Root CA - G3" +# Serial: 10003001 +# MD5 Fingerprint: 0b:46:67:07:db:10:2f:19:8c:35:50:60:d1:0b:f4:37 +# SHA1 Fingerprint: d8:eb:6b:41:51:92:59:e0:f3:e7:85:00:c0:3d:b6:88:97:c9:ee:fc +# SHA256 Fingerprint: 3c:4f:b0:b9:5a:b8:b3:00:32:f4:32:b8:6f:53:5f:e1:72:c1:85:d0:fd:39:86:58:37:cf:36:18:7f:a6:f4:28 +-----BEGIN CERTIFICATE----- +MIIFdDCCA1ygAwIBAgIEAJiiOTANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJO +TDEeMBwGA1UECgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSswKQYDVQQDDCJTdGFh +dCBkZXIgTmVkZXJsYW5kZW4gUm9vdCBDQSAtIEczMB4XDTEzMTExNDExMjg0MloX +DTI4MTExMzIzMDAwMFowWjELMAkGA1UEBhMCTkwxHjAcBgNVBAoMFVN0YWF0IGRl +ciBOZWRlcmxhbmRlbjErMCkGA1UEAwwiU3RhYXQgZGVyIE5lZGVybGFuZGVuIFJv +b3QgQ0EgLSBHMzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAL4yolQP +cPssXFnrbMSkUeiFKrPMSjTysF/zDsccPVMeiAho2G89rcKezIJnByeHaHE6n3WW +IkYFsO2tx1ueKt6c/DrGlaf1F2cY5y9JCAxcz+bMNO14+1Cx3Gsy8KL+tjzk7FqX +xz8ecAgwoNzFs21v0IJyEavSgWhZghe3eJJg+szeP4TrjTgzkApyI/o1zCZxMdFy +KJLZWyNtZrVtB0LrpjPOktvA9mxjeM3KTj215VKb8b475lRgsGYeCasH/lSJEULR +9yS6YHgamPfJEf0WwTUaVHXvQ9Plrk7O53vDxk5hUUurmkVLoR9BvUhTFXFkC4az +5S6+zqQbwSmEorXLCCN2QyIkHxcE1G6cxvx/K2Ya7Irl1s9N9WMJtxU51nus6+N8 +6U78dULI7ViVDAZCopz35HCz33JvWjdAidiFpNfxC95DGdRKWCyMijmev4SH8RY7 +Ngzp07TKbBlBUgmhHbBqv4LvcFEhMtwFdozL92TkA1CvjJFnq8Xy7ljY3r735zHP +bMk7ccHViLVlvMDoFxcHErVc0qsgk7TmgoNwNsXNo42ti+yjwUOH5kPiNL6VizXt +BznaqB16nzaeErAMZRKQFWDZJkBE41ZgpRDUajz9QdwOWke275dhdU/Z/seyHdTt +XUmzqWrLZoQT1Vyg3N9udwbRcXXIV2+vD3dbAgMBAAGjQjBAMA8GA1UdEwEB/wQF +MAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRUrfrHkleuyjWcLhL75Lpd +INyUVzANBgkqhkiG9w0BAQsFAAOCAgEAMJmdBTLIXg47mAE6iqTnB/d6+Oea31BD +U5cqPco8R5gu4RV78ZLzYdqQJRZlwJ9UXQ4DO1t3ApyEtg2YXzTdO2PCwyiBwpwp +LiniyMMB8jPqKqrMCQj3ZWfGzd/TtiunvczRDnBfuCPRy5FOCvTIeuXZYzbB1N/8 +Ipf3YF3qKS9Ysr1YvY2WTxB1v0h7PVGHoTx0IsL8B3+A3MSs/mrBcDCw6Y5p4ixp +gZQJut3+TcCDjJRYwEYgr5wfAvg1VUkvRtTA8KCWAg8zxXHzniN9lLf9OtMJgwYh +/WA9rjLA0u6NpvDntIJ8CsxwyXmA+P5M9zWEGYox+wrZ13+b8KKaa8MFSu1BYBQw +0aoRQm7TIwIEC8Zl3d1Sd9qBa7Ko+gE4uZbqKmxnl4mUnrzhVNXkanjvSr0rmj1A +fsbAddJu+2gw7OyLnflJNZoaLNmzlTnVHpL3prllL+U9bTpITAjc5CgSKL59NVzq +4BZ+Extq1z7XnvwtdbLBFNUjA9tbbws+eC8N3jONFrdI54OagQ97wUNNVQQXOEpR +1VmiiXTTn74eS9fGbbeIJG9gkaSChVtWQbzQRKtqE77RLFi3EjNYsjdj3BP1lB0/ +QFH1T/U67cjF68IeHRaVesd+QnGTbksVtzDfqu1XhUisHWrdOWnk4Xl4vs4Fv6EM +94B7IWcnMFk= +-----END CERTIFICATE----- + +# Issuer: CN=Staat der Nederlanden EV Root CA O=Staat der Nederlanden +# Subject: CN=Staat der Nederlanden EV Root CA O=Staat der Nederlanden +# Label: "Staat der Nederlanden EV Root CA" +# Serial: 10000013 +# MD5 Fingerprint: fc:06:af:7b:e8:1a:f1:9a:b4:e8:d2:70:1f:c0:f5:ba +# SHA1 Fingerprint: 76:e2:7e:c1:4f:db:82:c1:c0:a6:75:b5:05:be:3d:29:b4:ed:db:bb +# SHA256 Fingerprint: 4d:24:91:41:4c:fe:95:67:46:ec:4c:ef:a6:cf:6f:72:e2:8a:13:29:43:2f:9d:8a:90:7a:c4:cb:5d:ad:c1:5a +-----BEGIN CERTIFICATE----- +MIIFcDCCA1igAwIBAgIEAJiWjTANBgkqhkiG9w0BAQsFADBYMQswCQYDVQQGEwJO +TDEeMBwGA1UECgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSkwJwYDVQQDDCBTdGFh +dCBkZXIgTmVkZXJsYW5kZW4gRVYgUm9vdCBDQTAeFw0xMDEyMDgxMTE5MjlaFw0y +MjEyMDgxMTEwMjhaMFgxCzAJBgNVBAYTAk5MMR4wHAYDVQQKDBVTdGFhdCBkZXIg +TmVkZXJsYW5kZW4xKTAnBgNVBAMMIFN0YWF0IGRlciBOZWRlcmxhbmRlbiBFViBS +b290IENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA48d+ifkkSzrS +M4M1LGns3Amk41GoJSt5uAg94JG6hIXGhaTK5skuU6TJJB79VWZxXSzFYGgEt9nC +UiY4iKTWO0Cmws0/zZiTs1QUWJZV1VD+hq2kY39ch/aO5ieSZxeSAgMs3NZmdO3d +Z//BYY1jTw+bbRcwJu+r0h8QoPnFfxZpgQNH7R5ojXKhTbImxrpsX23Wr9GxE46p +rfNeaXUmGD5BKyF/7otdBwadQ8QpCiv8Kj6GyzyDOvnJDdrFmeK8eEEzduG/L13l +pJhQDBXd4Pqcfzho0LKmeqfRMb1+ilgnQ7O6M5HTp5gVXJrm0w912fxBmJc+qiXb +j5IusHsMX/FjqTf5m3VpTCgmJdrV8hJwRVXj33NeN/UhbJCONVrJ0yPr08C+eKxC +KFhmpUZtcALXEPlLVPxdhkqHz3/KRawRWrUgUY0viEeXOcDPusBCAUCZSCELa6fS +/ZbV0b5GnUngC6agIk440ME8MLxwjyx1zNDFjFE7PZQIZCZhfbnDZY8UnCHQqv0X +cgOPvZuM5l5Tnrmd74K74bzickFbIZTTRTeU0d8JOV3nI6qaHcptqAqGhYqCvkIH +1vI4gnPah1vlPNOePqc7nvQDs/nxfRN0Av+7oeX6AHkcpmZBiFxgV6YuCcS6/ZrP +px9Aw7vMWgpVSzs4dlG4Y4uElBbmVvMCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB +/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFP6rAJCYniT8qcwaivsnuL8wbqg7 +MA0GCSqGSIb3DQEBCwUAA4ICAQDPdyxuVr5Os7aEAJSrR8kN0nbHhp8dB9O2tLsI +eK9p0gtJ3jPFrK3CiAJ9Brc1AsFgyb/E6JTe1NOpEyVa/m6irn0F3H3zbPB+po3u +2dfOWBfoqSmuc0iH55vKbimhZF8ZE/euBhD/UcabTVUlT5OZEAFTdfETzsemQUHS +v4ilf0X8rLiltTMMgsT7B/Zq5SWEXwbKwYY5EdtYzXc7LMJMD16a4/CrPmEbUCTC +wPTxGfARKbalGAKb12NMcIxHowNDXLldRqANb/9Zjr7dn3LDWyvfjFvO5QxGbJKy +CqNMVEIYFRIYvdr8unRu/8G2oGTYqV9Vrp9canaW2HNnh/tNf1zuacpzEPuKqf2e +vTY4SUmH9A4U8OmHuD+nT3pajnnUk+S7aFKErGzp85hwVXIy+TSrK0m1zSBi5Dp6 +Z2Orltxtrpfs/J92VoguZs9btsmksNcFuuEnL5O7Jiqik7Ab846+HUCjuTaPPoIa +Gl6I6lD4WeKDRikL40Rc4ZW2aZCaFG+XroHPaO+Zmr615+F/+PoTRxZMzG0IQOeL +eG9QgkRQP2YGiqtDhFZKDyAthg710tvSeopLzaXoTvFeJiUBWSOgftL2fiFX1ye8 +FVdMpEbB4IMeDExNH08GGeL5qPQ6gqGyeUN51q1veieQA6TqJIc/2b3Z6fJfUEkc +7uzXLg== +-----END CERTIFICATE----- + +# Issuer: CN=IdenTrust Commercial Root CA 1 O=IdenTrust +# Subject: CN=IdenTrust Commercial Root CA 1 O=IdenTrust +# Label: "IdenTrust Commercial Root CA 1" +# Serial: 13298821034946342390520003877796839426 +# MD5 Fingerprint: b3:3e:77:73:75:ee:a0:d3:e3:7e:49:63:49:59:bb:c7 +# SHA1 Fingerprint: df:71:7e:aa:4a:d9:4e:c9:55:84:99:60:2d:48:de:5f:bc:f0:3a:25 +# SHA256 Fingerprint: 5d:56:49:9b:e4:d2:e0:8b:cf:ca:d0:8a:3e:38:72:3d:50:50:3b:de:70:69:48:e4:2f:55:60:30:19:e5:28:ae +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIQCgFCgAAAAUUjyES1AAAAAjANBgkqhkiG9w0BAQsFADBK +MQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MScwJQYDVQQDEx5JZGVu +VHJ1c3QgQ29tbWVyY2lhbCBSb290IENBIDEwHhcNMTQwMTE2MTgxMjIzWhcNMzQw +MTE2MTgxMjIzWjBKMQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MScw +JQYDVQQDEx5JZGVuVHJ1c3QgQ29tbWVyY2lhbCBSb290IENBIDEwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQCnUBneP5k91DNG8W9RYYKyqU+PZ4ldhNlT +3Qwo2dfw/66VQ3KZ+bVdfIrBQuExUHTRgQ18zZshq0PirK1ehm7zCYofWjK9ouuU ++ehcCuz/mNKvcbO0U59Oh++SvL3sTzIwiEsXXlfEU8L2ApeN2WIrvyQfYo3fw7gp +S0l4PJNgiCL8mdo2yMKi1CxUAGc1bnO/AljwpN3lsKImesrgNqUZFvX9t++uP0D1 +bVoE/c40yiTcdCMbXTMTEl3EASX2MN0CXZ/g1Ue9tOsbobtJSdifWwLziuQkkORi +T0/Br4sOdBeo0XKIanoBScy0RnnGF7HamB4HWfp1IYVl3ZBWzvurpWCdxJ35UrCL +vYf5jysjCiN2O/cz4ckA82n5S6LgTrx+kzmEB/dEcH7+B1rlsazRGMzyNeVJSQjK +Vsk9+w8YfYs7wRPCTY/JTw436R+hDmrfYi7LNQZReSzIJTj0+kuniVyc0uMNOYZK +dHzVWYfCP04MXFL0PfdSgvHqo6z9STQaKPNBiDoT7uje/5kdX7rL6B7yuVBgwDHT +c+XvvqDtMwt0viAgxGds8AgDelWAf0ZOlqf0Hj7h9tgJ4TNkK2PXMl6f+cB7D3hv +l7yTmvmcEpB4eoCHFddydJxVdHixuuFucAS6T6C6aMN7/zHwcz09lCqxC0EOoP5N +iGVreTO01wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB +/zAdBgNVHQ4EFgQU7UQZwNPwBovupHu+QucmVMiONnYwDQYJKoZIhvcNAQELBQAD +ggIBAA2ukDL2pkt8RHYZYR4nKM1eVO8lvOMIkPkp165oCOGUAFjvLi5+U1KMtlwH +6oi6mYtQlNeCgN9hCQCTrQ0U5s7B8jeUeLBfnLOic7iPBZM4zY0+sLj7wM+x8uwt +LRvM7Kqas6pgghstO8OEPVeKlh6cdbjTMM1gCIOQ045U8U1mwF10A0Cj7oV+wh93 +nAbowacYXVKV7cndJZ5t+qntozo00Fl72u1Q8zW/7esUTTHHYPTa8Yec4kjixsU3 ++wYQ+nVZZjFHKdp2mhzpgq7vmrlR94gjmmmVYjzlVYA211QC//G5Xc7UI2/YRYRK +W2XviQzdFKcgyxilJbQN+QHwotL0AMh0jqEqSI5l2xPE4iUXfeu+h1sXIFRRk0pT +AwvsXcoz7WL9RccvW9xYoIA55vrX/hMUpu09lEpCdNTDd1lzzY9GvlU47/rokTLq +l1gEIt44w8y8bckzOmoKaT+gyOpyj4xjhiO9bTyWnpXgSUyqorkqG5w2gXjtw+hG +4iZZRHUe2XWJUc0QhJ1hYMtd+ZciTY6Y5uN/9lu7rs3KSoFrXgvzUeF0K+l+J6fZ +mUlO+KWA2yUPHGNiiskzZ2s8EIPGrd6ozRaOjfAHN3Gf8qv8QfXBi+wAN10J5U6A +7/qxXDgGpRtK4dw4LTzcqx+QGtVKnO7RcGzM7vRX+Bi6hG6H +-----END CERTIFICATE----- + +# Issuer: CN=IdenTrust Public Sector Root CA 1 O=IdenTrust +# Subject: CN=IdenTrust Public Sector Root CA 1 O=IdenTrust +# Label: "IdenTrust Public Sector Root CA 1" +# Serial: 13298821034946342390521976156843933698 +# MD5 Fingerprint: 37:06:a5:b0:fc:89:9d:ba:f4:6b:8c:1a:64:cd:d5:ba +# SHA1 Fingerprint: ba:29:41:60:77:98:3f:f4:f3:ef:f2:31:05:3b:2e:ea:6d:4d:45:fd +# SHA256 Fingerprint: 30:d0:89:5a:9a:44:8a:26:20:91:63:55:22:d1:f5:20:10:b5:86:7a:ca:e1:2c:78:ef:95:8f:d4:f4:38:9f:2f +-----BEGIN CERTIFICATE----- +MIIFZjCCA06gAwIBAgIQCgFCgAAAAUUjz0Z8AAAAAjANBgkqhkiG9w0BAQsFADBN +MQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MSowKAYDVQQDEyFJZGVu +VHJ1c3QgUHVibGljIFNlY3RvciBSb290IENBIDEwHhcNMTQwMTE2MTc1MzMyWhcN +MzQwMTE2MTc1MzMyWjBNMQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0 +MSowKAYDVQQDEyFJZGVuVHJ1c3QgUHVibGljIFNlY3RvciBSb290IENBIDEwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2IpT8pEiv6EdrCvsnduTyP4o7 +ekosMSqMjbCpwzFrqHd2hCa2rIFCDQjrVVi7evi8ZX3yoG2LqEfpYnYeEe4IFNGy +RBb06tD6Hi9e28tzQa68ALBKK0CyrOE7S8ItneShm+waOh7wCLPQ5CQ1B5+ctMlS +bdsHyo+1W/CD80/HLaXIrcuVIKQxKFdYWuSNG5qrng0M8gozOSI5Cpcu81N3uURF +/YTLNiCBWS2ab21ISGHKTN9T0a9SvESfqy9rg3LvdYDaBjMbXcjaY8ZNzaxmMc3R +3j6HEDbhuaR672BQssvKplbgN6+rNBM5Jeg5ZuSYeqoSmJxZZoY+rfGwyj4GD3vw +EUs3oERte8uojHH01bWRNszwFcYr3lEXsZdMUD2xlVl8BX0tIdUAvwFnol57plzy +9yLxkA2T26pEUWbMfXYD62qoKjgZl3YNa4ph+bz27nb9cCvdKTz4Ch5bQhyLVi9V +GxyhLrXHFub4qjySjmm2AcG1hp2JDws4lFTo6tyePSW8Uybt1as5qsVATFSrsrTZ +2fjXctscvG29ZV/viDUqZi/u9rNl8DONfJhBaUYPQxxp+pu10GFqzcpL2UyQRqsV +WaFHVCkugyhfHMKiq3IXAAaOReyL4jM9f9oZRORicsPfIsbyVtTdX5Vy7W1f90gD +W/3FKqD2cyOEEBsB5wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/ +BAUwAwEB/zAdBgNVHQ4EFgQU43HgntinQtnbcZFrlJPrw6PRFKMwDQYJKoZIhvcN +AQELBQADggIBAEf63QqwEZE4rU1d9+UOl1QZgkiHVIyqZJnYWv6IAcVYpZmxI1Qj +t2odIFflAWJBF9MJ23XLblSQdf4an4EKwt3X9wnQW3IV5B4Jaj0z8yGa5hV+rVHV +DRDtfULAj+7AmgjVQdZcDiFpboBhDhXAuM/FSRJSzL46zNQuOAXeNf0fb7iAaJg9 +TaDKQGXSc3z1i9kKlT/YPyNtGtEqJBnZhbMX73huqVjRI9PHE+1yJX9dsXNw0H8G +lwmEKYBhHfpe/3OsoOOJuBxxFcbeMX8S3OFtm6/n6J91eEyrRjuazr8FGF1NFTwW +mhlQBJqymm9li1JfPFgEKCXAZmExfrngdbkaqIHWchezxQMxNRF4eKLg6TCMf4Df +WN88uieW4oA0beOY02QnrEh+KHdcxiVhJfiFDGX6xDIvpZgF5PgLZxYWxoK4Mhn5 ++bl53B/N66+rDt0b20XkeucC4pVd/GnwU2lhlXV5C15V5jgclKlZM57IcXR5f1GJ +tshquDDIajjDbp7hNxbqBWJMWxJH7ae0s1hWx0nzfxJoCTFx8G34Tkf71oXuxVhA +GaQdp/lLQzfcaFpPz+vCZHTetBXZ9FRUGi8c15dxVJCO2SCdUyt/q4/i6jC8UDfv +8Ue1fXwsBOxonbRJRBD0ckscZOf85muQ3Wl9af0AVqW3rLatt8o+Ae+c +-----END CERTIFICATE----- + +# Issuer: CN=Entrust Root Certification Authority - G2 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2009 Entrust, Inc. - for authorized use only +# Subject: CN=Entrust Root Certification Authority - G2 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2009 Entrust, Inc. - for authorized use only +# Label: "Entrust Root Certification Authority - G2" +# Serial: 1246989352 +# MD5 Fingerprint: 4b:e2:c9:91:96:65:0c:f4:0e:5a:93:92:a0:0a:fe:b2 +# SHA1 Fingerprint: 8c:f4:27:fd:79:0c:3a:d1:66:06:8d:e8:1e:57:ef:bb:93:22:72:d4 +# SHA256 Fingerprint: 43:df:57:74:b0:3e:7f:ef:5f:e4:0d:93:1a:7b:ed:f1:bb:2e:6b:42:73:8c:4e:6d:38:41:10:3d:3a:a7:f3:39 +-----BEGIN CERTIFICATE----- +MIIEPjCCAyagAwIBAgIESlOMKDANBgkqhkiG9w0BAQsFADCBvjELMAkGA1UEBhMC +VVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50 +cnVzdC5uZXQvbGVnYWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3Qs +IEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ugb25seTEyMDAGA1UEAxMpRW50cnVz +dCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzIwHhcNMDkwNzA3MTcy +NTU0WhcNMzAxMjA3MTc1NTU0WjCBvjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUVu +dHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVnYWwt +dGVybXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0 +aG9yaXplZCB1c2Ugb25seTEyMDAGA1UEAxMpRW50cnVzdCBSb290IENlcnRpZmlj +YXRpb24gQXV0aG9yaXR5IC0gRzIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQC6hLZy254Ma+KZ6TABp3bqMriVQRrJ2mFOWHLP/vaCeb9zYQYKpSfYs1/T +RU4cctZOMvJyig/3gxnQaoCAAEUesMfnmr8SVycco2gvCoe9amsOXmXzHHfV1IWN +cCG0szLni6LVhjkCsbjSR87kyUnEO6fe+1R9V77w6G7CebI6C1XiUJgWMhNcL3hW +wcKUs/Ja5CeanyTXxuzQmyWC48zCxEXFjJd6BmsqEZ+pCm5IO2/b1BEZQvePB7/1 +U1+cPvQXLOZprE4yTGJ36rfo5bs0vBmLrpxR57d+tVOxMyLlbc9wPBr64ptntoP0 +jaWvYkxN4FisZDQSA/i2jZRjJKRxAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAP +BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqciZ60B7vfec7aVHUbI2fkBJmqzAN +BgkqhkiG9w0BAQsFAAOCAQEAeZ8dlsa2eT8ijYfThwMEYGprmi5ZiXMRrEPR9RP/ +jTkrwPK9T3CMqS/qF8QLVJ7UG5aYMzyorWKiAHarWWluBh1+xLlEjZivEtRh2woZ +Rkfz6/djwUAFQKXSt/S1mja/qYh2iARVBCuch38aNzx+LaUa2NSJXsq9rD1s2G2v +1fN2D807iDginWyTmsQ9v4IbZT+mD12q/OWyFcq1rca8PdCE6OoGcrBNOTJ4vz4R +nAuknZoh8/CbCzB428Hch0P+vGOaysXCHMnHjf87ElgI5rY97HosTvuDls4MPGmH +VHOkc8KT/1EQrBVUAdj8BbGJoX90g5pJ19xOe4pIb4tF9g== +-----END CERTIFICATE----- + +# Issuer: CN=Entrust Root Certification Authority - EC1 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2012 Entrust, Inc. - for authorized use only +# Subject: CN=Entrust Root Certification Authority - EC1 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2012 Entrust, Inc. - for authorized use only +# Label: "Entrust Root Certification Authority - EC1" +# Serial: 51543124481930649114116133369 +# MD5 Fingerprint: b6:7e:1d:f0:58:c5:49:6c:24:3b:3d:ed:98:18:ed:bc +# SHA1 Fingerprint: 20:d8:06:40:df:9b:25:f5:12:25:3a:11:ea:f7:59:8a:eb:14:b5:47 +# SHA256 Fingerprint: 02:ed:0e:b2:8c:14:da:45:16:5c:56:67:91:70:0d:64:51:d7:fb:56:f0:b2:ab:1d:3b:8e:b0:70:e5:6e:df:f5 +-----BEGIN CERTIFICATE----- +MIIC+TCCAoCgAwIBAgINAKaLeSkAAAAAUNCR+TAKBggqhkjOPQQDAzCBvzELMAkG +A1UEBhMCVVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3 +d3cuZW50cnVzdC5uZXQvbGVnYWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDEyIEVu +dHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ugb25seTEzMDEGA1UEAxMq +RW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRUMxMB4XDTEy +MTIxODE1MjUzNloXDTM3MTIxODE1NTUzNlowgb8xCzAJBgNVBAYTAlVTMRYwFAYD +VQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1c3QubmV0 +L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxMiBFbnRydXN0LCBJbmMuIC0g +Zm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxMzAxBgNVBAMTKkVudHJ1c3QgUm9vdCBD +ZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEVDMTB2MBAGByqGSM49AgEGBSuBBAAi +A2IABIQTydC6bUF74mzQ61VfZgIaJPRbiWlH47jCffHyAsWfoPZb1YsGGYZPUxBt +ByQnoaD41UcZYUx9ypMn6nQM72+WCf5j7HBdNq1nd67JnXxVRDqiY1Ef9eNi1KlH +Bz7MIKNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O +BBYEFLdj5xrdjekIplWDpOBqUEFlEUJJMAoGCCqGSM49BAMDA2cAMGQCMGF52OVC +R98crlOZF7ZvHH3hvxGU0QOIdeSNiaSKd0bebWHvAvX7td/M/k7//qnmpwIwW5nX +hTcGtXsI/esni0qU+eH6p44mCOh8kmhtc9hvJqwhAriZtyZBWyVgrtBIGu4G +-----END CERTIFICATE----- + +# Issuer: CN=CFCA EV ROOT O=China Financial Certification Authority +# Subject: CN=CFCA EV ROOT O=China Financial Certification Authority +# Label: "CFCA EV ROOT" +# Serial: 407555286 +# MD5 Fingerprint: 74:e1:b6:ed:26:7a:7a:44:30:33:94:ab:7b:27:81:30 +# SHA1 Fingerprint: e2:b8:29:4b:55:84:ab:6b:58:c2:90:46:6c:ac:3f:b8:39:8f:84:83 +# SHA256 Fingerprint: 5c:c3:d7:8e:4e:1d:5e:45:54:7a:04:e6:87:3e:64:f9:0c:f9:53:6d:1c:cc:2e:f8:00:f3:55:c4:c5:fd:70:fd +-----BEGIN CERTIFICATE----- +MIIFjTCCA3WgAwIBAgIEGErM1jANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJD +TjEwMC4GA1UECgwnQ2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9y +aXR5MRUwEwYDVQQDDAxDRkNBIEVWIFJPT1QwHhcNMTIwODA4MDMwNzAxWhcNMjkx +MjMxMDMwNzAxWjBWMQswCQYDVQQGEwJDTjEwMC4GA1UECgwnQ2hpbmEgRmluYW5j +aWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRUwEwYDVQQDDAxDRkNBIEVWIFJP +T1QwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDXXWvNED8fBVnVBU03 +sQ7smCuOFR36k0sXgiFxEFLXUWRwFsJVaU2OFW2fvwwbwuCjZ9YMrM8irq93VCpL +TIpTUnrD7i7es3ElweldPe6hL6P3KjzJIx1qqx2hp/Hz7KDVRM8Vz3IvHWOX6Jn5 +/ZOkVIBMUtRSqy5J35DNuF++P96hyk0g1CXohClTt7GIH//62pCfCqktQT+x8Rgp +7hZZLDRJGqgG16iI0gNyejLi6mhNbiyWZXvKWfry4t3uMCz7zEasxGPrb382KzRz +EpR/38wmnvFyXVBlWY9ps4deMm/DGIq1lY+wejfeWkU7xzbh72fROdOXW3NiGUgt +hxwG+3SYIElz8AXSG7Ggo7cbcNOIabla1jj0Ytwli3i/+Oh+uFzJlU9fpy25IGvP +a931DfSCt/SyZi4QKPaXWnuWFo8BGS1sbn85WAZkgwGDg8NNkt0yxoekN+kWzqot +aK8KgWU6cMGbrU1tVMoqLUuFG7OA5nBFDWteNfB/O7ic5ARwiRIlk9oKmSJgamNg +TnYGmE69g60dWIolhdLHZR4tjsbftsbhf4oEIRUpdPA+nJCdDC7xij5aqgwJHsfV +PKPtl8MeNPo4+QgO48BdK4PRVmrJtqhUUy54Mmc9gn900PvhtgVguXDbjgv5E1hv +cWAQUhC5wUEJ73IfZzF4/5YFjQIDAQABo2MwYTAfBgNVHSMEGDAWgBTj/i39KNAL +tbq2osS/BqoFjJP7LzAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAd +BgNVHQ4EFgQU4/4t/SjQC7W6tqLEvwaqBYyT+y8wDQYJKoZIhvcNAQELBQADggIB +ACXGumvrh8vegjmWPfBEp2uEcwPenStPuiB/vHiyz5ewG5zz13ku9Ui20vsXiObT +ej/tUxPQ4i9qecsAIyjmHjdXNYmEwnZPNDatZ8POQQaIxffu2Bq41gt/UP+TqhdL +jOztUmCypAbqTuv0axn96/Ua4CUqmtzHQTb3yHQFhDmVOdYLO6Qn+gjYXB74BGBS +ESgoA//vU2YApUo0FmZ8/Qmkrp5nGm9BC2sGE5uPhnEFtC+NiWYzKXZUmhH4J/qy +P5Hgzg0b8zAarb8iXRvTvyUFTeGSGn+ZnzxEk8rUQElsgIfXBDrDMlI1Dlb4pd19 +xIsNER9Tyx6yF7Zod1rg1MvIB671Oi6ON7fQAUtDKXeMOZePglr4UeWJoBjnaH9d +Ci77o0cOPaYjesYBx4/IXr9tgFa+iiS6M+qf4TIRnvHST4D2G0CvOJ4RUHlzEhLN +5mydLIhyPDCBBpEi6lmt2hkuIsKNuYyH4Ga8cyNfIWRjgEj1oDwYPZTISEEdQLpe +/v5WOaHIz16eGWRGENoXkbcFgKyLmZJ956LYBws2J+dIeWCKw9cTXPhyQN9Ky8+Z +AAoACxGV2lZFA4gKn2fQ1XmxqI1AbQ3CekD6819kR5LLU7m7Wc5P/dAVUwHY3+vZ +5nbv0CO7O6l5s9UCKc2Jo5YPSjXnTkLAdc0Hz+Ys63su +-----END CERTIFICATE----- + +# Issuer: CN=TÜRKTRUST Elektronik Sertifika Hizmet Sağlayıcısı H5 O=TÜRKTRUST Bilgi İletişim ve Bilişim Güvenliği Hizmetleri A.Ş. +# Subject: CN=TÜRKTRUST Elektronik Sertifika Hizmet Sağlayıcısı H5 O=TÜRKTRUST Bilgi İletişim ve Bilişim Güvenliği Hizmetleri A.Ş. +# Label: "TÜRKTRUST Elektronik Sertifika Hizmet Sağlayıcısı H5" +# Serial: 156233699172481 +# MD5 Fingerprint: da:70:8e:f0:22:df:93:26:f6:5f:9f:d3:15:06:52:4e +# SHA1 Fingerprint: c4:18:f6:4d:46:d1:df:00:3d:27:30:13:72:43:a9:12:11:c6:75:fb +# SHA256 Fingerprint: 49:35:1b:90:34:44:c1:85:cc:dc:5c:69:3d:24:d8:55:5c:b2:08:d6:a8:14:13:07:69:9f:4a:f0:63:19:9d:78 +-----BEGIN CERTIFICATE----- +MIIEJzCCAw+gAwIBAgIHAI4X/iQggTANBgkqhkiG9w0BAQsFADCBsTELMAkGA1UE +BhMCVFIxDzANBgNVBAcMBkFua2FyYTFNMEsGA1UECgxEVMOcUktUUlVTVCBCaWxn +aSDEsGxldGnFn2ltIHZlIEJpbGnFn2ltIEfDvHZlbmxpxJ9pIEhpem1ldGxlcmkg +QS7Fni4xQjBABgNVBAMMOVTDnFJLVFJVU1QgRWxla3Ryb25payBTZXJ0aWZpa2Eg +SGl6bWV0IFNhxJ9sYXnEsWPEsXPEsSBINTAeFw0xMzA0MzAwODA3MDFaFw0yMzA0 +MjgwODA3MDFaMIGxMQswCQYDVQQGEwJUUjEPMA0GA1UEBwwGQW5rYXJhMU0wSwYD +VQQKDERUw5xSS1RSVVNUIEJpbGdpIMSwbGV0acWfaW0gdmUgQmlsacWfaW0gR8O8 +dmVubGnEn2kgSGl6bWV0bGVyaSBBLsWeLjFCMEAGA1UEAww5VMOcUktUUlVTVCBF +bGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxIEg1MIIB +IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApCUZ4WWe60ghUEoI5RHwWrom +/4NZzkQqL/7hzmAD/I0Dpe3/a6i6zDQGn1k19uwsu537jVJp45wnEFPzpALFp/kR +Gml1bsMdi9GYjZOHp3GXDSHHmflS0yxjXVW86B8BSLlg/kJK9siArs1mep5Fimh3 +4khon6La8eHBEJ/rPCmBp+EyCNSgBbGM+42WAA4+Jd9ThiI7/PS98wl+d+yG6w8z +5UNP9FR1bSmZLmZaQ9/LXMrI5Tjxfjs1nQ/0xVqhzPMggCTTV+wVunUlm+hkS7M0 +hO8EuPbJbKoCPrZV4jI3X/xml1/N1p7HIL9Nxqw/dV8c7TKcfGkAaZHjIxhT6QID +AQABo0IwQDAdBgNVHQ4EFgQUVpkHHtOsDGlktAxQR95DLL4gwPswDgYDVR0PAQH/ +BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAJ5FdnsX +SDLyOIspve6WSk6BGLFRRyDN0GSxDsnZAdkJzsiZ3GglE9Rc8qPoBP5yCccLqh0l +VX6Wmle3usURehnmp349hQ71+S4pL+f5bFgWV1Al9j4uPqrtd3GqqpmWRgqujuwq +URawXs3qZwQcWDD1YIq9pr1N5Za0/EKJAWv2cMhQOQwt1WbZyNKzMrcbGW3LM/nf +peYVhDfwwvJllpKQd/Ct9JDpEXjXk4nAPQu6KfTomZ1yju2dL+6SfaHx/126M2CF +Yv4HAqGEVka+lgqaE9chTLd8B59OTj+RdPsnnRHM3eaxynFNExc5JsUpISuTKWqW ++qtB4Uu2NQvAmxU= +-----END CERTIFICATE----- + +# Issuer: CN=TÜRKTRUST Elektronik Sertifika Hizmet Sağlayıcısı H6 O=TÜRKTRUST Bilgi İletişim ve Bilişim Güvenliği Hizmetleri A.Ş. +# Subject: CN=TÜRKTRUST Elektronik Sertifika Hizmet Sağlayıcısı H6 O=TÜRKTRUST Bilgi İletişim ve Bilişim Güvenliği Hizmetleri A.Ş. +# Label: "TÜRKTRUST Elektronik Sertifika Hizmet Sağlayıcısı H6" +# Serial: 138134509972618 +# MD5 Fingerprint: f8:c5:ee:2a:6b:be:95:8d:08:f7:25:4a:ea:71:3e:46 +# SHA1 Fingerprint: 8a:5c:8c:ee:a5:03:e6:05:56:ba:d8:1b:d4:f6:c9:b0:ed:e5:2f:e0 +# SHA256 Fingerprint: 8d:e7:86:55:e1:be:7f:78:47:80:0b:93:f6:94:d2:1d:36:8c:c0:6e:03:3e:7f:ab:04:bb:5e:b9:9d:a6:b7:00 +-----BEGIN CERTIFICATE----- +MIIEJjCCAw6gAwIBAgIGfaHyZeyKMA0GCSqGSIb3DQEBCwUAMIGxMQswCQYDVQQG +EwJUUjEPMA0GA1UEBwwGQW5rYXJhMU0wSwYDVQQKDERUw5xSS1RSVVNUIEJpbGdp +IMSwbGV0acWfaW0gdmUgQmlsacWfaW0gR8O8dmVubGnEn2kgSGl6bWV0bGVyaSBB +LsWeLjFCMEAGA1UEAww5VMOcUktUUlVTVCBFbGVrdHJvbmlrIFNlcnRpZmlrYSBI +aXptZXQgU2HEn2xhecSxY8Sxc8SxIEg2MB4XDTEzMTIxODA5MDQxMFoXDTIzMTIx +NjA5MDQxMFowgbExCzAJBgNVBAYTAlRSMQ8wDQYDVQQHDAZBbmthcmExTTBLBgNV +BAoMRFTDnFJLVFJVU1QgQmlsZ2kgxLBsZXRpxZ9pbSB2ZSBCaWxpxZ9pbSBHw7x2 +ZW5sacSfaSBIaXptZXRsZXJpIEEuxZ4uMUIwQAYDVQQDDDlUw5xSS1RSVVNUIEVs +ZWt0cm9uaWsgU2VydGlmaWthIEhpem1ldCBTYcSfbGF5xLFjxLFzxLEgSDYwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCdsGjW6L0UlqMACprx9MfMkU1x +eHe59yEmFXNRFpQJRwXiM/VomjX/3EsvMsew7eKC5W/a2uqsxgbPJQ1BgfbBOCK9 ++bGlprMBvD9QFyv26WZV1DOzXPhDIHiTVRZwGTLmiddk671IUP320EEDwnS3/faA +z1vFq6TWlRKb55cTMgPp1KtDWxbtMyJkKbbSk60vbNg9tvYdDjTu0n2pVQ8g9P0p +u5FbHH3GQjhtQiht1AH7zYiXSX6484P4tZgvsycLSF5W506jM7NE1qXyGJTtHB6p +lVxiSvgNZ1GpryHV+DKdeboaX+UEVU0TRv/yz3THGmNtwx8XEsMeED5gCLMxAgMB +AAGjQjBAMB0GA1UdDgQWBBTdVRcT9qzoSCHK77Wv0QAy7Z6MtTAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAb1gNl0Oq +FlQ+v6nfkkU/hQu7VtMMUszIv3ZnXuaqs6fvuay0EBQNdH49ba3RfdCaqaXKGDsC +QC4qnFAUi/5XfldcEQlLNkVS9z2sFP1E34uXI9TDwe7UU5X+LEr+DXCqu4svLcsy +o4LyVN/Y8t3XSHLuSqMplsNEzm61kod2pLv0kmzOLBQJZo6NrRa1xxsJYTvjIKID +gI6tflEATseWhvtDmHd9KMeP2Cpu54Rvl0EpABZeTeIT6lnAY2c6RPuY/ATTMHKm +9ocJV612ph1jmv3XZch4gyt1O6VbuA1df74jrlZVlFjvH4GMKrLN5ptjnhi85WsG +tAuYSyher4hYyw== +-----END CERTIFICATE----- + +# Issuer: CN=Certinomis - Root CA O=Certinomis OU=0002 433998903 +# Subject: CN=Certinomis - Root CA O=Certinomis OU=0002 433998903 +# Label: "Certinomis - Root CA" +# Serial: 1 +# MD5 Fingerprint: 14:0a:fd:8d:a8:28:b5:38:69:db:56:7e:61:22:03:3f +# SHA1 Fingerprint: 9d:70:bb:01:a5:a4:a0:18:11:2e:f7:1c:01:b9:32:c5:34:e7:88:a8 +# SHA256 Fingerprint: 2a:99:f5:bc:11:74:b7:3c:bb:1d:62:08:84:e0:1c:34:e5:1c:cb:39:78:da:12:5f:0e:33:26:88:83:bf:41:58 +-----BEGIN CERTIFICATE----- +MIIFkjCCA3qgAwIBAgIBATANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJGUjET +MBEGA1UEChMKQ2VydGlub21pczEXMBUGA1UECxMOMDAwMiA0MzM5OTg5MDMxHTAb +BgNVBAMTFENlcnRpbm9taXMgLSBSb290IENBMB4XDTEzMTAyMTA5MTcxOFoXDTMz +MTAyMTA5MTcxOFowWjELMAkGA1UEBhMCRlIxEzARBgNVBAoTCkNlcnRpbm9taXMx +FzAVBgNVBAsTDjAwMDIgNDMzOTk4OTAzMR0wGwYDVQQDExRDZXJ0aW5vbWlzIC0g +Um9vdCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANTMCQosP5L2 +fxSeC5yaah1AMGT9qt8OHgZbn1CF6s2Nq0Nn3rD6foCWnoR4kkjW4znuzuRZWJfl +LieY6pOod5tK8O90gC3rMB+12ceAnGInkYjwSond3IjmFPnVAy//ldu9n+ws+hQV +WZUKxkd8aRi5pwP5ynapz8dvtF4F/u7BUrJ1Mofs7SlmO/NKFoL21prbcpjp3vDF +TKWrteoB4owuZH9kb/2jJZOLyKIOSY008B/sWEUuNKqEUL3nskoTuLAPrjhdsKkb +5nPJWqHZZkCqqU2mNAKthH6yI8H7KsZn9DS2sJVqM09xRLWtwHkziOC/7aOgFLSc +CbAK42C++PhmiM1b8XcF4LVzbsF9Ri6OSyemzTUK/eVNfaoqoynHWmgE6OXWk6Ri +wsXm9E/G+Z8ajYJJGYrKWUM66A0ywfRMEwNvbqY/kXPLynNvEiCL7sCCeN5LLsJJ +wx3tFvYk9CcbXFcx3FXuqB5vbKziRcxXV4p1VxngtViZSTYxPDMBbRZKzbgqg4SG +m/lg0h9tkQPTYKbVPZrdd5A9NaSfD171UkRpucC63M9933zZxKyGIjK8e2uR73r4 +F2iw4lNVYC2vPsKD2NkJK/DAZNuHi5HMkesE/Xa0lZrmFAYb1TQdvtj/dBxThZng +WVJKYe2InmtJiUZ+IFrZ50rlau7SZRFDAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIB +BjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTvkUz1pcMw6C8I6tNxIqSSaHh0 +2TAfBgNVHSMEGDAWgBTvkUz1pcMw6C8I6tNxIqSSaHh02TANBgkqhkiG9w0BAQsF +AAOCAgEAfj1U2iJdGlg+O1QnurrMyOMaauo++RLrVl89UM7g6kgmJs95Vn6RHJk/ +0KGRHCwPT5iVWVO90CLYiF2cN/z7ZMF4jIuaYAnq1fohX9B0ZedQxb8uuQsLrbWw +F6YSjNRieOpWauwK0kDDPAUwPk2Ut59KA9N9J0u2/kTO+hkzGm2kQtHdzMjI1xZS +g081lLMSVX3l4kLr5JyTCcBMWwerx20RoFAXlCOotQqSD7J6wWAsOMwaplv/8gzj +qh8c3LigkyfeY+N/IZ865Z764BNqdeuWXGKRlI5nU7aJ+BIJy29SWwNyhlCVCNSN +h4YVH5Uk2KRvms6knZtt0rJ2BobGVgjF6wnaNsIbW0G+YSrjcOa4pvi2WsS9Iff/ +ql+hbHY5ZtbqTFXhADObE5hjyW/QASAJN1LnDE8+zbz1X5YnpyACleAu6AdBBR8V +btaw5BngDwKTACdyxYvRVB9dSsNAl35VpnzBMwQUAR1JIGkLGZOdblgi90AMRgwj +Y/M50n92Uaf0yKHxDHYiI0ZSKS3io0EHVmmY0gUJvGnHWmHNj4FgFU2A3ZDifcRQ +8ow7bkrHxuaAKzyBvBGAFhAn1/DNP3nMcyrDflOR1m749fPH0FFNjkulW+YZFzvW +gQncItzujrnEj1PhZ7szuIgVRs/taTX/dQ1G885x4cVrhkIGuUE= +-----END CERTIFICATE----- +# Issuer: CN=Entrust.net Secure Server Certification Authority O=Entrust.net OU=www.entrust.net/CPS incorp. by ref. (limits liab.)/(c) 1999 Entrust.net Limited +# Subject: CN=Entrust.net Secure Server Certification Authority O=Entrust.net OU=www.entrust.net/CPS incorp. by ref. (limits liab.)/(c) 1999 Entrust.net Limited +# Label: "Entrust.net Secure Server CA" +# Serial: 927650371 +# MD5 Fingerprint: df:f2:80:73:cc:f1:e6:61:73:fc:f5:42:e9:c5:7c:ee +# SHA1 Fingerprint: 99:a6:9b:e6:1a:fe:88:6b:4d:2b:82:00:7c:b8:54:fc:31:7e:15:39 +# SHA256 Fingerprint: 62:f2:40:27:8c:56:4c:4d:d8:bf:7d:9d:4f:6f:36:6e:a8:94:d2:2f:5f:34:d9:89:a9:83:ac:ec:2f:ff:ed:50 +-----BEGIN CERTIFICATE----- +MIIE2DCCBEGgAwIBAgIEN0rSQzANBgkqhkiG9w0BAQUFADCBwzELMAkGA1UEBhMC +VVMxFDASBgNVBAoTC0VudHJ1c3QubmV0MTswOQYDVQQLEzJ3d3cuZW50cnVzdC5u +ZXQvQ1BTIGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTElMCMGA1UECxMc +KGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDE6MDgGA1UEAxMxRW50cnVzdC5u +ZXQgU2VjdXJlIFNlcnZlciBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw05OTA1 +MjUxNjA5NDBaFw0xOTA1MjUxNjM5NDBaMIHDMQswCQYDVQQGEwJVUzEUMBIGA1UE +ChMLRW50cnVzdC5uZXQxOzA5BgNVBAsTMnd3dy5lbnRydXN0Lm5ldC9DUFMgaW5j +b3JwLiBieSByZWYuIChsaW1pdHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBF +bnRydXN0Lm5ldCBMaW1pdGVkMTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUg +U2VydmVyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGdMA0GCSqGSIb3DQEBAQUA +A4GLADCBhwKBgQDNKIM0VBuJ8w+vN5Ex/68xYMmo6LIQaO2f55M28Qpku0f1BBc/ +I0dNxScZgSYMVHINiC3ZH5oSn7yzcdOAGT9HZnuMNSjSuQrfJNqc1lB5gXpa0zf3 +wkrYKZImZNHkmGw6AIr1NJtl+O3jEP/9uElY3KDegjlrgbEWGWG5VLbmQwIBA6OC +AdcwggHTMBEGCWCGSAGG+EIBAQQEAwIABzCCARkGA1UdHwSCARAwggEMMIHeoIHb +oIHYpIHVMIHSMQswCQYDVQQGEwJVUzEUMBIGA1UEChMLRW50cnVzdC5uZXQxOzA5 +BgNVBAsTMnd3dy5lbnRydXN0Lm5ldC9DUFMgaW5jb3JwLiBieSByZWYuIChsaW1p +dHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBFbnRydXN0Lm5ldCBMaW1pdGVk +MTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUgU2VydmVyIENlcnRpZmljYXRp +b24gQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMCmgJ6AlhiNodHRwOi8vd3d3LmVu +dHJ1c3QubmV0L0NSTC9uZXQxLmNybDArBgNVHRAEJDAigA8xOTk5MDUyNTE2MDk0 +MFqBDzIwMTkwNTI1MTYwOTQwWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAU8Bdi +E1U9s/8KAGv7UISX8+1i0BowHQYDVR0OBBYEFPAXYhNVPbP/CgBr+1CEl/PtYtAa +MAwGA1UdEwQFMAMBAf8wGQYJKoZIhvZ9B0EABAwwChsEVjQuMAMCBJAwDQYJKoZI +hvcNAQEFBQADgYEAkNwwAvpkdMKnCqV8IY00F6j7Rw7/JXyNEwr75Ji174z4xRAN +95K+8cPV1ZVqBLssziY2ZcgxxufuP+NXdYR6Ee9GTxj005i7qIcyunL2POI9n9cd +2cNgQ4xYDiKWL2KjLB+6rQXvqzJ4h6BUcxm1XAX5Uj5tLUUL9wqT6u0G+bI= +-----END CERTIFICATE----- + +# Issuer: CN=http://www.valicert.com/ O=ValiCert, Inc. OU=ValiCert Class 2 Policy Validation Authority +# Subject: CN=http://www.valicert.com/ O=ValiCert, Inc. OU=ValiCert Class 2 Policy Validation Authority +# Label: "ValiCert Class 2 VA" +# Serial: 1 +# MD5 Fingerprint: a9:23:75:9b:ba:49:36:6e:31:c2:db:f2:e7:66:ba:87 +# SHA1 Fingerprint: 31:7a:2a:d0:7f:2b:33:5e:f5:a1:c3:4e:4b:57:e8:b7:d8:f1:fc:a6 +# SHA256 Fingerprint: 58:d0:17:27:9c:d4:dc:63:ab:dd:b1:96:a6:c9:90:6c:30:c4:e0:87:83:ea:e8:c1:60:99:54:d6:93:55:59:6b +-----BEGIN CERTIFICATE----- +MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0 +IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAz +BgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9y +aXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG +9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAwMTk1NFoXDTE5MDYy +NjAwMTk1NFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29y +azEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs +YXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRw +Oi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNl +cnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDOOnHK5avIWZJV16vY +dA757tn2VUdZZUcOBVXc65g2PFxTXdMwzzjsvUGJ7SVCCSRrCl6zfN1SLUzm1NZ9 +WlmpZdRJEy0kTRxQb7XBhVQ7/nHk01xC+YDgkRoKWzk2Z/M/VXwbP7RfZHM047QS +v4dk+NoS/zcnwbNDu+97bi5p9wIDAQABMA0GCSqGSIb3DQEBBQUAA4GBADt/UG9v +UJSZSWI4OB9L+KXIPqeCgfYrx+jFzug6EILLGACOTb2oWH+heQC1u+mNr0HZDzTu +IYEZoDJJKPTEjlbVUjP9UNV+mWwD5MlM/Mtsq2azSiGM5bUMMj4QssxsodyamEwC +W/POuZ6lcg5Ktz885hZo+L7tdEy8W9ViH0Pd +-----END CERTIFICATE----- + +# Issuer: CN=NetLock Expressz (Class C) Tanusitvanykiado O=NetLock Halozatbiztonsagi Kft. OU=Tanusitvanykiadok +# Subject: CN=NetLock Expressz (Class C) Tanusitvanykiado O=NetLock Halozatbiztonsagi Kft. OU=Tanusitvanykiadok +# Label: "NetLock Express (Class C) Root" +# Serial: 104 +# MD5 Fingerprint: 4f:eb:f1:f0:70:c2:80:63:5d:58:9f:da:12:3c:a9:c4 +# SHA1 Fingerprint: e3:92:51:2f:0a:cf:f5:05:df:f6:de:06:7f:75:37:e1:65:ea:57:4b +# SHA256 Fingerprint: 0b:5e:ed:4e:84:64:03:cf:55:e0:65:84:84:40:ed:2a:82:75:8b:f5:b9:aa:1f:25:3d:46:13:cf:a0:80:ff:3f +-----BEGIN CERTIFICATE----- +MIIFTzCCBLigAwIBAgIBaDANBgkqhkiG9w0BAQQFADCBmzELMAkGA1UEBhMCSFUx +ETAPBgNVBAcTCEJ1ZGFwZXN0MScwJQYDVQQKEx5OZXRMb2NrIEhhbG96YXRiaXp0 +b25zYWdpIEtmdC4xGjAYBgNVBAsTEVRhbnVzaXR2YW55a2lhZG9rMTQwMgYDVQQD +EytOZXRMb2NrIEV4cHJlc3N6IChDbGFzcyBDKSBUYW51c2l0dmFueWtpYWRvMB4X +DTk5MDIyNTE0MDgxMVoXDTE5MDIyMDE0MDgxMVowgZsxCzAJBgNVBAYTAkhVMREw +DwYDVQQHEwhCdWRhcGVzdDEnMCUGA1UEChMeTmV0TG9jayBIYWxvemF0Yml6dG9u +c2FnaSBLZnQuMRowGAYDVQQLExFUYW51c2l0dmFueWtpYWRvazE0MDIGA1UEAxMr +TmV0TG9jayBFeHByZXNzeiAoQ2xhc3MgQykgVGFudXNpdHZhbnlraWFkbzCBnzAN +BgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA6+ywbGGKIyWvYCDj2Z/8kwvbXY2wobNA +OoLO/XXgeDIDhlqGlZHtU/qdQPzm6N3ZW3oDvV3zOwzDUXmbrVWg6dADEK8KuhRC +2VImESLH0iDMgqSaqf64gXadarfSNnU+sYYJ9m5tfk63euyucYT2BDMIJTLrdKwW +RMbkQJMdf60CAwEAAaOCAp8wggKbMBIGA1UdEwEB/wQIMAYBAf8CAQQwDgYDVR0P +AQH/BAQDAgAGMBEGCWCGSAGG+EIBAQQEAwIABzCCAmAGCWCGSAGG+EIBDQSCAlEW +ggJNRklHWUVMRU0hIEV6ZW4gdGFudXNpdHZhbnkgYSBOZXRMb2NrIEtmdC4gQWx0 +YWxhbm9zIFN6b2xnYWx0YXRhc2kgRmVsdGV0ZWxlaWJlbiBsZWlydCBlbGphcmFz +b2sgYWxhcGphbiBrZXN6dWx0LiBBIGhpdGVsZXNpdGVzIGZvbHlhbWF0YXQgYSBO +ZXRMb2NrIEtmdC4gdGVybWVrZmVsZWxvc3NlZy1iaXp0b3NpdGFzYSB2ZWRpLiBB +IGRpZ2l0YWxpcyBhbGFpcmFzIGVsZm9nYWRhc2FuYWsgZmVsdGV0ZWxlIGF6IGVs +b2lydCBlbGxlbm9yemVzaSBlbGphcmFzIG1lZ3RldGVsZS4gQXogZWxqYXJhcyBs +ZWlyYXNhIG1lZ3RhbGFsaGF0byBhIE5ldExvY2sgS2Z0LiBJbnRlcm5ldCBob25s +YXBqYW4gYSBodHRwczovL3d3dy5uZXRsb2NrLm5ldC9kb2NzIGNpbWVuIHZhZ3kg +a2VyaGV0byBheiBlbGxlbm9yemVzQG5ldGxvY2submV0IGUtbWFpbCBjaW1lbi4g +SU1QT1JUQU5UISBUaGUgaXNzdWFuY2UgYW5kIHRoZSB1c2Ugb2YgdGhpcyBjZXJ0 +aWZpY2F0ZSBpcyBzdWJqZWN0IHRvIHRoZSBOZXRMb2NrIENQUyBhdmFpbGFibGUg +YXQgaHR0cHM6Ly93d3cubmV0bG9jay5uZXQvZG9jcyBvciBieSBlLW1haWwgYXQg +Y3BzQG5ldGxvY2submV0LjANBgkqhkiG9w0BAQQFAAOBgQAQrX/XDDKACtiG8XmY +ta3UzbM2xJZIwVzNmtkFLp++UOv0JhQQLdRmF/iewSf98e3ke0ugbLWrmldwpu2g +pO0u9f38vf5NNwgMvOOWgyL1SRt/Syu0VMGAfJlOHdCM7tCs5ZL6dVb+ZKATj7i4 +Fp1hBWeAyNDYpQcCNJgEjTME1A== +-----END CERTIFICATE----- + +# Issuer: CN=NetLock Uzleti (Class B) Tanusitvanykiado O=NetLock Halozatbiztonsagi Kft. OU=Tanusitvanykiadok +# Subject: CN=NetLock Uzleti (Class B) Tanusitvanykiado O=NetLock Halozatbiztonsagi Kft. OU=Tanusitvanykiadok +# Label: "NetLock Business (Class B) Root" +# Serial: 105 +# MD5 Fingerprint: 39:16:aa:b9:6a:41:e1:14:69:df:9e:6c:3b:72:dc:b6 +# SHA1 Fingerprint: 87:9f:4b:ee:05:df:98:58:3b:e3:60:d6:33:e7:0d:3f:fe:98:71:af +# SHA256 Fingerprint: 39:df:7b:68:2b:7b:93:8f:84:71:54:81:cc:de:8d:60:d8:f2:2e:c5:98:87:7d:0a:aa:c1:2b:59:18:2b:03:12 +-----BEGIN CERTIFICATE----- +MIIFSzCCBLSgAwIBAgIBaTANBgkqhkiG9w0BAQQFADCBmTELMAkGA1UEBhMCSFUx +ETAPBgNVBAcTCEJ1ZGFwZXN0MScwJQYDVQQKEx5OZXRMb2NrIEhhbG96YXRiaXp0 +b25zYWdpIEtmdC4xGjAYBgNVBAsTEVRhbnVzaXR2YW55a2lhZG9rMTIwMAYDVQQD +EylOZXRMb2NrIFV6bGV0aSAoQ2xhc3MgQikgVGFudXNpdHZhbnlraWFkbzAeFw05 +OTAyMjUxNDEwMjJaFw0xOTAyMjAxNDEwMjJaMIGZMQswCQYDVQQGEwJIVTERMA8G +A1UEBxMIQnVkYXBlc3QxJzAlBgNVBAoTHk5ldExvY2sgSGFsb3phdGJpenRvbnNh +Z2kgS2Z0LjEaMBgGA1UECxMRVGFudXNpdHZhbnlraWFkb2sxMjAwBgNVBAMTKU5l +dExvY2sgVXpsZXRpIChDbGFzcyBCKSBUYW51c2l0dmFueWtpYWRvMIGfMA0GCSqG +SIb3DQEBAQUAA4GNADCBiQKBgQCx6gTsIKAjwo84YM/HRrPVG/77uZmeBNwcf4xK +gZjupNTKihe5In+DCnVMm8Bp2GQ5o+2So/1bXHQawEfKOml2mrriRBf8TKPV/riX +iK+IA4kfpPIEPsgHC+b5sy96YhQJRhTKZPWLgLViqNhr1nGTLbO/CVRY7QbrqHvc +Q7GhaQIDAQABo4ICnzCCApswEgYDVR0TAQH/BAgwBgEB/wIBBDAOBgNVHQ8BAf8E +BAMCAAYwEQYJYIZIAYb4QgEBBAQDAgAHMIICYAYJYIZIAYb4QgENBIICURaCAk1G +SUdZRUxFTSEgRXplbiB0YW51c2l0dmFueSBhIE5ldExvY2sgS2Z0LiBBbHRhbGFu +b3MgU3pvbGdhbHRhdGFzaSBGZWx0ZXRlbGVpYmVuIGxlaXJ0IGVsamFyYXNvayBh +bGFwamFuIGtlc3p1bHQuIEEgaGl0ZWxlc2l0ZXMgZm9seWFtYXRhdCBhIE5ldExv +Y2sgS2Z0LiB0ZXJtZWtmZWxlbG9zc2VnLWJpenRvc2l0YXNhIHZlZGkuIEEgZGln +aXRhbGlzIGFsYWlyYXMgZWxmb2dhZGFzYW5hayBmZWx0ZXRlbGUgYXogZWxvaXJ0 +IGVsbGVub3J6ZXNpIGVsamFyYXMgbWVndGV0ZWxlLiBBeiBlbGphcmFzIGxlaXJh +c2EgbWVndGFsYWxoYXRvIGEgTmV0TG9jayBLZnQuIEludGVybmV0IGhvbmxhcGph +biBhIGh0dHBzOi8vd3d3Lm5ldGxvY2submV0L2RvY3MgY2ltZW4gdmFneSBrZXJo +ZXRvIGF6IGVsbGVub3J6ZXNAbmV0bG9jay5uZXQgZS1tYWlsIGNpbWVuLiBJTVBP +UlRBTlQhIFRoZSBpc3N1YW5jZSBhbmQgdGhlIHVzZSBvZiB0aGlzIGNlcnRpZmlj +YXRlIGlzIHN1YmplY3QgdG8gdGhlIE5ldExvY2sgQ1BTIGF2YWlsYWJsZSBhdCBo +dHRwczovL3d3dy5uZXRsb2NrLm5ldC9kb2NzIG9yIGJ5IGUtbWFpbCBhdCBjcHNA +bmV0bG9jay5uZXQuMA0GCSqGSIb3DQEBBAUAA4GBAATbrowXr/gOkDFOzT4JwG06 +sPgzTEdM43WIEJessDgVkcYplswhwG08pXTP2IKlOcNl40JwuyKQ433bNXbhoLXa +n3BukxowOR0w2y7jfLKRstE3Kfq51hdcR0/jHTjrn9V7lagonhVK0dHQKwCXoOKS +NitjrFgBazMpUIaD8QFI +-----END CERTIFICATE----- + +# Issuer: CN=http://www.valicert.com/ O=ValiCert, Inc. OU=ValiCert Class 3 Policy Validation Authority +# Subject: CN=http://www.valicert.com/ O=ValiCert, Inc. OU=ValiCert Class 3 Policy Validation Authority +# Label: "RSA Root Certificate 1" +# Serial: 1 +# MD5 Fingerprint: a2:6f:53:b7:ee:40:db:4a:68:e7:fa:18:d9:10:4b:72 +# SHA1 Fingerprint: 69:bd:8c:f4:9c:d3:00:fb:59:2e:17:93:ca:55:6a:f3:ec:aa:35:fb +# SHA256 Fingerprint: bc:23:f9:8a:31:3c:b9:2d:e3:bb:fc:3a:5a:9f:44:61:ac:39:49:4c:4a:e1:5a:9e:9d:f1:31:e9:9b:73:01:9a +-----BEGIN CERTIFICATE----- +MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0 +IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAz +BgNVBAsTLFZhbGlDZXJ0IENsYXNzIDMgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9y +aXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG +9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAwMjIzM1oXDTE5MDYy +NjAwMjIzM1owgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29y +azEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs +YXNzIDMgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRw +Oi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNl +cnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDjmFGWHOjVsQaBalfD +cnWTq8+epvzzFlLWLU2fNUSoLgRNB0mKOCn1dzfnt6td3zZxFJmP3MKS8edgkpfs +2Ejcv8ECIMYkpChMMFp2bbFc893enhBxoYjHW5tBbcqwuI4V7q0zK89HBFx1cQqY +JJgpp0lZpd34t0NiYfPT4tBVPwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAFa7AliE +Zwgs3x/be0kz9dNnnfS0ChCzycUs4pJqcXgn8nCDQtM+z6lU9PHYkhaM0QTLS6vJ +n0WuPIqpsHEzXcjFV9+vqDWzf4mH6eglkrh/hXqu1rweN1gqZ8mRzyqBPu3GOd/A +PhmcGcwTTYJBtYze4D1gCCAPRX5ron+jjBXu +-----END CERTIFICATE----- + +# Issuer: CN=http://www.valicert.com/ O=ValiCert, Inc. OU=ValiCert Class 1 Policy Validation Authority +# Subject: CN=http://www.valicert.com/ O=ValiCert, Inc. OU=ValiCert Class 1 Policy Validation Authority +# Label: "ValiCert Class 1 VA" +# Serial: 1 +# MD5 Fingerprint: 65:58:ab:15:ad:57:6c:1e:a8:a7:b5:69:ac:bf:ff:eb +# SHA1 Fingerprint: e5:df:74:3c:b6:01:c4:9b:98:43:dc:ab:8c:e8:6a:81:10:9f:e4:8e +# SHA256 Fingerprint: f4:c1:49:55:1a:30:13:a3:5b:c7:bf:fe:17:a7:f3:44:9b:c1:ab:5b:5a:0a:e7:4b:06:c2:3b:90:00:4c:01:04 +-----BEGIN CERTIFICATE----- +MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0 +IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAz +BgNVBAsTLFZhbGlDZXJ0IENsYXNzIDEgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9y +aXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG +9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNTIyMjM0OFoXDTE5MDYy +NTIyMjM0OFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29y +azEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs +YXNzIDEgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRw +Oi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNl +cnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDYWYJ6ibiWuqYvaG9Y +LqdUHAZu9OqNSLwxlBfw8068srg1knaw0KWlAdcAAxIiGQj4/xEjm84H9b9pGib+ +TunRf50sQB1ZaG6m+FiwnRqP0z/x3BkGgagO4DrdyFNFCQbmD3DD+kCmDuJWBQ8Y +TfwggtFzVXSNdnKgHZ0dwN0/cQIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAFBoPUn0 +LBwGlN+VYH+Wexf+T3GtZMjdd9LvWVXoP+iOBSoh8gfStadS/pyxtuJbdxdA6nLW +I8sogTLDAHkY7FkXicnGah5xyf23dKUlRWnFSKsZ4UWKJWsZ7uW7EvV/96aNUcPw +nXS3qT6gpf+2SQMT2iLM7XGCK5nPOrf1LXLI +-----END CERTIFICATE----- + +# Issuer: CN=Equifax Secure eBusiness CA-1 O=Equifax Secure Inc. +# Subject: CN=Equifax Secure eBusiness CA-1 O=Equifax Secure Inc. +# Label: "Equifax Secure eBusiness CA 1" +# Serial: 4 +# MD5 Fingerprint: 64:9c:ef:2e:44:fc:c6:8f:52:07:d0:51:73:8f:cb:3d +# SHA1 Fingerprint: da:40:18:8b:91:89:a3:ed:ee:ae:da:97:fe:2f:9d:f5:b7:d1:8a:41 +# SHA256 Fingerprint: cf:56:ff:46:a4:a1:86:10:9d:d9:65:84:b5:ee:b5:8a:51:0c:42:75:b0:e5:f9:4f:40:bb:ae:86:5e:19:f6:73 +-----BEGIN CERTIFICATE----- +MIICgjCCAeugAwIBAgIBBDANBgkqhkiG9w0BAQQFADBTMQswCQYDVQQGEwJVUzEc +MBoGA1UEChMTRXF1aWZheCBTZWN1cmUgSW5jLjEmMCQGA1UEAxMdRXF1aWZheCBT +ZWN1cmUgZUJ1c2luZXNzIENBLTEwHhcNOTkwNjIxMDQwMDAwWhcNMjAwNjIxMDQw +MDAwWjBTMQswCQYDVQQGEwJVUzEcMBoGA1UEChMTRXF1aWZheCBTZWN1cmUgSW5j +LjEmMCQGA1UEAxMdRXF1aWZheCBTZWN1cmUgZUJ1c2luZXNzIENBLTEwgZ8wDQYJ +KoZIhvcNAQEBBQADgY0AMIGJAoGBAM4vGbwXt3fek6lfWg0XTzQaDJj0ItlZ1MRo +RvC0NcWFAyDGr0WlIVFFQesWWDYyb+JQYmT5/VGcqiTZ9J2DKocKIdMSODRsjQBu +WqDZQu4aIZX5UkxVWsUPOE9G+m34LjXWHXzr4vCwdYDIqROsvojvOm6rXyo4YgKw +Env+j6YDAgMBAAGjZjBkMBEGCWCGSAGG+EIBAQQEAwIABzAPBgNVHRMBAf8EBTAD +AQH/MB8GA1UdIwQYMBaAFEp4MlIR21kWNl7fwRQ2QGpHfEyhMB0GA1UdDgQWBBRK +eDJSEdtZFjZe38EUNkBqR3xMoTANBgkqhkiG9w0BAQQFAAOBgQB1W6ibAxHm6VZM +zfmpTMANmvPMZWnmJXbMWbfWVMMdzZmsGd20hdXgPfxiIKeES1hl8eL5lSE/9dR+ +WB5Hh1Q+WKG1tfgq73HnvMP2sUlG4tega+VWeponmHxGYhTnyfxuAxJ5gDgdSIKN +/Bf+KpYrtWKmpj29f5JZzVoqgrI3eQ== +-----END CERTIFICATE----- + +# Issuer: CN=Equifax Secure Global eBusiness CA-1 O=Equifax Secure Inc. +# Subject: CN=Equifax Secure Global eBusiness CA-1 O=Equifax Secure Inc. +# Label: "Equifax Secure Global eBusiness CA" +# Serial: 1 +# MD5 Fingerprint: 8f:5d:77:06:27:c4:98:3c:5b:93:78:e7:d7:7d:9b:cc +# SHA1 Fingerprint: 7e:78:4a:10:1c:82:65:cc:2d:e1:f1:6d:47:b4:40:ca:d9:0a:19:45 +# SHA256 Fingerprint: 5f:0b:62:ea:b5:e3:53:ea:65:21:65:16:58:fb:b6:53:59:f4:43:28:0a:4a:fb:d1:04:d7:7d:10:f9:f0:4c:07 +-----BEGIN CERTIFICATE----- +MIICkDCCAfmgAwIBAgIBATANBgkqhkiG9w0BAQQFADBaMQswCQYDVQQGEwJVUzEc +MBoGA1UEChMTRXF1aWZheCBTZWN1cmUgSW5jLjEtMCsGA1UEAxMkRXF1aWZheCBT +ZWN1cmUgR2xvYmFsIGVCdXNpbmVzcyBDQS0xMB4XDTk5MDYyMTA0MDAwMFoXDTIw +MDYyMTA0MDAwMFowWjELMAkGA1UEBhMCVVMxHDAaBgNVBAoTE0VxdWlmYXggU2Vj +dXJlIEluYy4xLTArBgNVBAMTJEVxdWlmYXggU2VjdXJlIEdsb2JhbCBlQnVzaW5l +c3MgQ0EtMTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAuucXkAJlsTRVPEnC +UdXfp9E3j9HngXNBUmCbnaEXJnitx7HoJpQytd4zjTov2/KaelpzmKNc6fuKcxtc +58O/gGzNqfTWK8D3+ZmqY6KxRwIP1ORROhI8bIpaVIRw28HFkM9yRcuoWcDNM50/ +o5brhTMhHD4ePmBudpxnhcXIw2ECAwEAAaNmMGQwEQYJYIZIAYb4QgEBBAQDAgAH +MA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUvqigdHJQa0S3ySPY+6j/s1dr +aGwwHQYDVR0OBBYEFL6ooHRyUGtEt8kj2Puo/7NXa2hsMA0GCSqGSIb3DQEBBAUA +A4GBADDiAVGqx+pf2rnQZQ8w1j7aDRRJbpGTJxQx78T3LUX47Me/okENI7SS+RkA +Z70Br83gcfxaz2TE4JaY0KNA4gGK7ycH8WUBikQtBmV1UsCGECAhX2xrD2yuCRyv +8qIYNMR1pHMc8Y3c7635s3a0kr/clRAevsvIO1qEYBlWlKlV +-----END CERTIFICATE----- + +# Issuer: CN=Thawte Premium Server CA O=Thawte Consulting cc OU=Certification Services Division +# Subject: CN=Thawte Premium Server CA O=Thawte Consulting cc OU=Certification Services Division +# Label: "Thawte Premium Server CA" +# Serial: 1 +# MD5 Fingerprint: 06:9f:69:79:16:66:90:02:1b:8c:8c:a2:c3:07:6f:3a +# SHA1 Fingerprint: 62:7f:8d:78:27:65:63:99:d2:7d:7f:90:44:c9:fe:b3:f3:3e:fa:9a +# SHA256 Fingerprint: ab:70:36:36:5c:71:54:aa:29:c2:c2:9f:5d:41:91:16:3b:16:2a:22:25:01:13:57:d5:6d:07:ff:a7:bc:1f:72 +-----BEGIN CERTIFICATE----- +MIIDJzCCApCgAwIBAgIBATANBgkqhkiG9w0BAQQFADCBzjELMAkGA1UEBhMCWkEx +FTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYD +VQQKExRUaGF3dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlv +biBTZXJ2aWNlcyBEaXZpc2lvbjEhMB8GA1UEAxMYVGhhd3RlIFByZW1pdW0gU2Vy +dmVyIENBMSgwJgYJKoZIhvcNAQkBFhlwcmVtaXVtLXNlcnZlckB0aGF3dGUuY29t +MB4XDTk2MDgwMTAwMDAwMFoXDTIwMTIzMTIzNTk1OVowgc4xCzAJBgNVBAYTAlpB +MRUwEwYDVQQIEwxXZXN0ZXJuIENhcGUxEjAQBgNVBAcTCUNhcGUgVG93bjEdMBsG +A1UEChMUVGhhd3RlIENvbnN1bHRpbmcgY2MxKDAmBgNVBAsTH0NlcnRpZmljYXRp +b24gU2VydmljZXMgRGl2aXNpb24xITAfBgNVBAMTGFRoYXd0ZSBQcmVtaXVtIFNl +cnZlciBDQTEoMCYGCSqGSIb3DQEJARYZcHJlbWl1bS1zZXJ2ZXJAdGhhd3RlLmNv +bTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA0jY2aovXwlue2oFBYo847kkE +VdbQ7xwblRZH7xhINTpS9CtqBo87L+pW46+GjZ4X9560ZXUCTe/LCaIhUdib0GfQ +ug2SBhRz1JPLlyoAnFxODLz6FVL88kRu2hFKbgifLy3j+ao6hnO2RlNYyIkFvYMR +uHM/qgeN9EJN50CdHDcCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG +9w0BAQQFAAOBgQAmSCwWwlj66BZ0DKqqX1Q/8tfJeGBeXm43YyJ3Nn6yF8Q0ufUI +hfzJATj/Tb7yFkJD57taRvvBxhEf8UqwKEbJw8RCfbz6q1lu1bdRiBHjpIUZa4JM +pAwSremkrj/xw0llmozFyD4lt5SZu5IycQfwhl7tUCemDaYj+bvLpgcUQg== +-----END CERTIFICATE----- + +# Issuer: CN=Thawte Server CA O=Thawte Consulting cc OU=Certification Services Division +# Subject: CN=Thawte Server CA O=Thawte Consulting cc OU=Certification Services Division +# Label: "Thawte Server CA" +# Serial: 1 +# MD5 Fingerprint: c5:70:c4:a2:ed:53:78:0c:c8:10:53:81:64:cb:d0:1d +# SHA1 Fingerprint: 23:e5:94:94:51:95:f2:41:48:03:b4:d5:64:d2:a3:a3:f5:d8:8b:8c +# SHA256 Fingerprint: b4:41:0b:73:e2:e6:ea:ca:47:fb:c4:2f:8f:a4:01:8a:f4:38:1d:c5:4c:fa:a8:44:50:46:1e:ed:09:45:4d:e9 +-----BEGIN CERTIFICATE----- +MIIDEzCCAnygAwIBAgIBATANBgkqhkiG9w0BAQQFADCBxDELMAkGA1UEBhMCWkEx +FTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYD +VQQKExRUaGF3dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlv +biBTZXJ2aWNlcyBEaXZpc2lvbjEZMBcGA1UEAxMQVGhhd3RlIFNlcnZlciBDQTEm +MCQGCSqGSIb3DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0ZS5jb20wHhcNOTYwODAx +MDAwMDAwWhcNMjAxMjMxMjM1OTU5WjCBxDELMAkGA1UEBhMCWkExFTATBgNVBAgT +DFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYDVQQKExRUaGF3 +dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNl +cyBEaXZpc2lvbjEZMBcGA1UEAxMQVGhhd3RlIFNlcnZlciBDQTEmMCQGCSqGSIb3 +DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0ZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQAD +gY0AMIGJAoGBANOkUG7I/1Zr5s9dtuoMaHVHoqrC2oQl/Kj0R1HahbUgdJSGHg91 +yekIYfUGbTBuFRkC6VLAYttNmZ7iagxEOM3+vuNkCXDF/rFrKbYvScg71CcEJRCX +L+eQbcAoQpnXTEPew/UhbVSfXcNY4cDk2VuwuNy0e982OsK1ZiIS1ocNAgMBAAGj +EzARMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAB/pMaVz7lcxG +7oWDTSEwjsrZqG9JGubaUeNgcGyEYRGhGshIPllDfU+VPaGLtwtimHp1it2ITk6e +QNuozDJ0uW8NxuOzRAvZim+aKZuZGCg70eNAKJpaPNW15yAbi8qkq43pUdniTCxZ +qdq5snUb9kLy78fyGPmJvKP/iiMucEc= +-----END CERTIFICATE----- + +# Issuer: O=VeriSign, Inc. OU=Class 3 Public Primary Certification Authority +# Subject: O=VeriSign, Inc. OU=Class 3 Public Primary Certification Authority +# Label: "Verisign Class 3 Public Primary Certification Authority" +# Serial: 149843929435818692848040365716851702463 +# MD5 Fingerprint: 10:fc:63:5d:f6:26:3e:0d:f3:25:be:5f:79:cd:67:67 +# SHA1 Fingerprint: 74:2c:31:92:e6:07:e4:24:eb:45:49:54:2b:e1:bb:c5:3e:61:74:e2 +# SHA256 Fingerprint: e7:68:56:34:ef:ac:f6:9a:ce:93:9a:6b:25:5b:7b:4f:ab:ef:42:93:5b:50:a2:65:ac:b5:cb:60:27:e4:4e:70 +-----BEGIN CERTIFICATE----- +MIICPDCCAaUCEHC65B0Q2Sk0tjjKewPMur8wDQYJKoZIhvcNAQECBQAwXzELMAkG +A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz +cyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2 +MDEyOTAwMDAwMFoXDTI4MDgwMTIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV +BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmlt +YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN +ADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhE +BarsAx94f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/is +I19wKTakyYbnsZogy1Olhec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0G +CSqGSIb3DQEBAgUAA4GBALtMEivPLCYATxQT3ab7/AoRhIzzKBxnki98tsX63/Do +lbwdj2wsqFHMc9ikwFPwTtYmwHYBV4GSXiHx0bH/59AhWM1pF+NEHJwZRDmJXNyc +AA9WjQKZ7aKQRUzkuxCkPfAyAw7xzvjoyVGM5mKf5p/AfbdynMk2OmufTqj/ZA1k +-----END CERTIFICATE----- + +# Issuer: O=VeriSign, Inc. OU=Class 3 Public Primary Certification Authority +# Subject: O=VeriSign, Inc. OU=Class 3 Public Primary Certification Authority +# Label: "Verisign Class 3 Public Primary Certification Authority" +# Serial: 80507572722862485515306429940691309246 +# MD5 Fingerprint: ef:5a:f1:33:ef:f1:cd:bb:51:02:ee:12:14:4b:96:c4 +# SHA1 Fingerprint: a1:db:63:93:91:6f:17:e4:18:55:09:40:04:15:c7:02:40:b0:ae:6b +# SHA256 Fingerprint: a4:b6:b3:99:6f:c2:f3:06:b3:fd:86:81:bd:63:41:3d:8c:50:09:cc:4f:a3:29:c2:cc:f0:e2:fa:1b:14:03:05 +-----BEGIN CERTIFICATE----- +MIICPDCCAaUCEDyRMcsf9tAbDpq40ES/Er4wDQYJKoZIhvcNAQEFBQAwXzELMAkG +A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz +cyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2 +MDEyOTAwMDAwMFoXDTI4MDgwMjIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV +BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmlt +YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN +ADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhE +BarsAx94f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/is +I19wKTakyYbnsZogy1Olhec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0G +CSqGSIb3DQEBBQUAA4GBABByUqkFFBkyCEHwxWsKzH4PIRnN5GfcX6kb5sroc50i +2JhucwNhkcV8sEVAbkSdjbCxlnRhLQ2pRdKkkirWmnWXbj9T/UWZYB2oK0z5XqcJ +2HUw19JlYD1n1khVdWk/kfVIC0dpImmClr7JyDiGSnoscxlIaU5rfGW/D/xwzoiQ +-----END CERTIFICATE----- + +# Issuer: O=VeriSign, Inc. OU=Class 3 Public Primary Certification Authority - G2/(c) 1998 VeriSign, Inc. - For authorized use only/VeriSign Trust Network +# Subject: O=VeriSign, Inc. OU=Class 3 Public Primary Certification Authority - G2/(c) 1998 VeriSign, Inc. - For authorized use only/VeriSign Trust Network +# Label: "Verisign Class 3 Public Primary Certification Authority - G2" +# Serial: 167285380242319648451154478808036881606 +# MD5 Fingerprint: a2:33:9b:4c:74:78:73:d4:6c:e7:c1:f3:8d:cb:5c:e9 +# SHA1 Fingerprint: 85:37:1c:a6:e5:50:14:3d:ce:28:03:47:1b:de:3a:09:e8:f8:77:0f +# SHA256 Fingerprint: 83:ce:3c:12:29:68:8a:59:3d:48:5f:81:97:3c:0f:91:95:43:1e:da:37:cc:5e:36:43:0e:79:c7:a8:88:63:8b +-----BEGIN CERTIFICATE----- +MIIDAjCCAmsCEH3Z/gfPqB63EHln+6eJNMYwDQYJKoZIhvcNAQEFBQAwgcExCzAJ +BgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xh +c3MgMyBQdWJsaWMgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcy +MTowOAYDVQQLEzEoYykgMTk5OCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3Jp +emVkIHVzZSBvbmx5MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMB4X +DTk4MDUxODAwMDAwMFoXDTI4MDgwMTIzNTk1OVowgcExCzAJBgNVBAYTAlVTMRcw +FQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xhc3MgMyBQdWJsaWMg +UHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMTowOAYDVQQLEzEo +YykgMTk5OCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5 +MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMIGfMA0GCSqGSIb3DQEB +AQUAA4GNADCBiQKBgQDMXtERXVxp0KvTuWpMmR9ZmDCOFoUgRm1HP9SFIIThbbP4 +pO0M8RcPO/mn+SXXwc+EY/J8Y8+iR/LGWzOOZEAEaMGAuWQcRXfH2G71lSk8UOg0 +13gfqLptQ5GVj0VXXn7F+8qkBOvqlzdUMG+7AUcyM83cV5tkaWH4mx0ciU9cZwID +AQABMA0GCSqGSIb3DQEBBQUAA4GBAFFNzb5cy5gZnBWyATl4Lk0PZ3BwmcYQWpSk +U01UbSuvDV1Ai2TT1+7eVmGSX6bEHRBhNtMsJzzoKQm5EWR0zLVznxxIqbxhAe7i +F6YM40AIOw7n60RzKprxaZLvcRTDOaxxp5EJb+RxBrO6WVcmeQD2+A2iMzAo1KpY +oJ2daZH9 +-----END CERTIFICATE----- + +# Issuer: CN=GTE CyberTrust Global Root O=GTE Corporation OU=GTE CyberTrust Solutions, Inc. +# Subject: CN=GTE CyberTrust Global Root O=GTE Corporation OU=GTE CyberTrust Solutions, Inc. +# Label: "GTE CyberTrust Global Root" +# Serial: 421 +# MD5 Fingerprint: ca:3d:d3:68:f1:03:5c:d0:32:fa:b8:2b:59:e8:5a:db +# SHA1 Fingerprint: 97:81:79:50:d8:1c:96:70:cc:34:d8:09:cf:79:44:31:36:7e:f4:74 +# SHA256 Fingerprint: a5:31:25:18:8d:21:10:aa:96:4b:02:c7:b7:c6:da:32:03:17:08:94:e5:fb:71:ff:fb:66:67:d5:e6:81:0a:36 +-----BEGIN CERTIFICATE----- +MIICWjCCAcMCAgGlMA0GCSqGSIb3DQEBBAUAMHUxCzAJBgNVBAYTAlVTMRgwFgYD +VQQKEw9HVEUgQ29ycG9yYXRpb24xJzAlBgNVBAsTHkdURSBDeWJlclRydXN0IFNv +bHV0aW9ucywgSW5jLjEjMCEGA1UEAxMaR1RFIEN5YmVyVHJ1c3QgR2xvYmFsIFJv +b3QwHhcNOTgwODEzMDAyOTAwWhcNMTgwODEzMjM1OTAwWjB1MQswCQYDVQQGEwJV +UzEYMBYGA1UEChMPR1RFIENvcnBvcmF0aW9uMScwJQYDVQQLEx5HVEUgQ3liZXJU +cnVzdCBTb2x1dGlvbnMsIEluYy4xIzAhBgNVBAMTGkdURSBDeWJlclRydXN0IEds +b2JhbCBSb290MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCVD6C28FCc6HrH +iM3dFw4usJTQGz0O9pTAipTHBsiQl8i4ZBp6fmw8U+E3KHNgf7KXUwefU/ltWJTS +r41tiGeA5u2ylc9yMcqlHHK6XALnZELn+aks1joNrI1CqiQBOeacPwGFVw1Yh0X4 +04Wqk2kmhXBIgD8SFcd5tB8FLztimQIDAQABMA0GCSqGSIb3DQEBBAUAA4GBAG3r +GwnpXtlR22ciYaQqPEh346B8pt5zohQDhT37qw4wxYMWM4ETCJ57NE7fQMh017l9 +3PR2VX2bY1QY6fDq81yx2YtCHrnAlU66+tXifPVoYb+O7AWXX1uw16OFNMQkpw0P +lZPvy5TYnh+dXIVtx6quTx8itc2VrbqnzPmrC3p/ +-----END CERTIFICATE----- diff --git a/resources/lib/libraries/requests/certs.py b/resources/lib/libraries/requests/certs.py new file mode 100644 index 00000000..07e64750 --- /dev/null +++ b/resources/lib/libraries/requests/certs.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +certs.py +~~~~~~~~ + +This module returns the preferred default CA certificate bundle. + +If you are packaging Requests, e.g., for a Linux distribution or a managed +environment, you can change the definition of where() to return a separately +packaged CA bundle. +""" +import os.path + +try: + from certifi import where +except ImportError: + def where(): + """Return the preferred certificate bundle.""" + # vendored bundle inside Requests + return os.path.join(os.path.dirname(__file__), 'cacert.pem') + +if __name__ == '__main__': + print(where()) diff --git a/resources/lib/libraries/requests/compat.py b/resources/lib/libraries/requests/compat.py new file mode 100644 index 00000000..70edff78 --- /dev/null +++ b/resources/lib/libraries/requests/compat.py @@ -0,0 +1,62 @@ +# -*- coding: utf-8 -*- + +""" +pythoncompat +""" + +from .packages import chardet + +import sys + +# ------- +# Pythons +# ------- + +# Syntax sugar. +_ver = sys.version_info + +#: Python 2.x? +is_py2 = (_ver[0] == 2) + +#: Python 3.x? +is_py3 = (_ver[0] == 3) + +try: + import simplejson as json +except (ImportError, SyntaxError): + # simplejson does not support Python 3.2, it throws a SyntaxError + # because of u'...' Unicode literals. + import json + +# --------- +# Specifics +# --------- + +if is_py2: + from urllib import quote, unquote, quote_plus, unquote_plus, urlencode, getproxies, proxy_bypass + from urlparse import urlparse, urlunparse, urljoin, urlsplit, urldefrag + from urllib2 import parse_http_list + import cookielib + from Cookie import Morsel + from StringIO import StringIO + from .packages.urllib3.packages.ordered_dict import OrderedDict + + builtin_str = str + bytes = str + str = unicode + basestring = basestring + numeric_types = (int, long, float) + +elif is_py3: + from urllib.parse import urlparse, urlunparse, urljoin, urlsplit, urlencode, quote, unquote, quote_plus, unquote_plus, urldefrag + from urllib.request import parse_http_list, getproxies, proxy_bypass + from http import cookiejar as cookielib + from http.cookies import Morsel + from io import StringIO + from collections import OrderedDict + + builtin_str = str + str = str + bytes = bytes + basestring = (str, bytes) + numeric_types = (int, float) diff --git a/resources/lib/libraries/requests/cookies.py b/resources/lib/libraries/requests/cookies.py new file mode 100644 index 00000000..b85fd2b6 --- /dev/null +++ b/resources/lib/libraries/requests/cookies.py @@ -0,0 +1,487 @@ +# -*- coding: utf-8 -*- + +""" +Compatibility code to be able to use `cookielib.CookieJar` with requests. + +requests.utils imports from here, so be careful with imports. +""" + +import copy +import time +import calendar +import collections +from .compat import cookielib, urlparse, urlunparse, Morsel + +try: + import threading + # grr, pyflakes: this fixes "redefinition of unused 'threading'" + threading +except ImportError: + import dummy_threading as threading + + +class MockRequest(object): + """Wraps a `requests.Request` to mimic a `urllib2.Request`. + + The code in `cookielib.CookieJar` expects this interface in order to correctly + manage cookie policies, i.e., determine whether a cookie can be set, given the + domains of the request and the cookie. + + The original request object is read-only. The client is responsible for collecting + the new headers via `get_new_headers()` and interpreting them appropriately. You + probably want `get_cookie_header`, defined below. + """ + + def __init__(self, request): + self._r = request + self._new_headers = {} + self.type = urlparse(self._r.url).scheme + + def get_type(self): + return self.type + + def get_host(self): + return urlparse(self._r.url).netloc + + def get_origin_req_host(self): + return self.get_host() + + def get_full_url(self): + # Only return the response's URL if the user hadn't set the Host + # header + if not self._r.headers.get('Host'): + return self._r.url + # If they did set it, retrieve it and reconstruct the expected domain + host = self._r.headers['Host'] + parsed = urlparse(self._r.url) + # Reconstruct the URL as we expect it + return urlunparse([ + parsed.scheme, host, parsed.path, parsed.params, parsed.query, + parsed.fragment + ]) + + def is_unverifiable(self): + return True + + def has_header(self, name): + return name in self._r.headers or name in self._new_headers + + def get_header(self, name, default=None): + return self._r.headers.get(name, self._new_headers.get(name, default)) + + def add_header(self, key, val): + """cookielib has no legitimate use for this method; add it back if you find one.""" + raise NotImplementedError("Cookie headers should be added with add_unredirected_header()") + + def add_unredirected_header(self, name, value): + self._new_headers[name] = value + + def get_new_headers(self): + return self._new_headers + + @property + def unverifiable(self): + return self.is_unverifiable() + + @property + def origin_req_host(self): + return self.get_origin_req_host() + + @property + def host(self): + return self.get_host() + + +class MockResponse(object): + """Wraps a `httplib.HTTPMessage` to mimic a `urllib.addinfourl`. + + ...what? Basically, expose the parsed HTTP headers from the server response + the way `cookielib` expects to see them. + """ + + def __init__(self, headers): + """Make a MockResponse for `cookielib` to read. + + :param headers: a httplib.HTTPMessage or analogous carrying the headers + """ + self._headers = headers + + def info(self): + return self._headers + + def getheaders(self, name): + self._headers.getheaders(name) + + +def extract_cookies_to_jar(jar, request, response): + """Extract the cookies from the response into a CookieJar. + + :param jar: cookielib.CookieJar (not necessarily a RequestsCookieJar) + :param request: our own requests.Request object + :param response: urllib3.HTTPResponse object + """ + if not (hasattr(response, '_original_response') and + response._original_response): + return + # the _original_response field is the wrapped httplib.HTTPResponse object, + req = MockRequest(request) + # pull out the HTTPMessage with the headers and put it in the mock: + res = MockResponse(response._original_response.msg) + jar.extract_cookies(res, req) + + +def get_cookie_header(jar, request): + """Produce an appropriate Cookie header string to be sent with `request`, or None.""" + r = MockRequest(request) + jar.add_cookie_header(r) + return r.get_new_headers().get('Cookie') + + +def remove_cookie_by_name(cookiejar, name, domain=None, path=None): + """Unsets a cookie by name, by default over all domains and paths. + + Wraps CookieJar.clear(), is O(n). + """ + clearables = [] + for cookie in cookiejar: + if cookie.name != name: + continue + if domain is not None and domain != cookie.domain: + continue + if path is not None and path != cookie.path: + continue + clearables.append((cookie.domain, cookie.path, cookie.name)) + + for domain, path, name in clearables: + cookiejar.clear(domain, path, name) + + +class CookieConflictError(RuntimeError): + """There are two cookies that meet the criteria specified in the cookie jar. + Use .get and .set and include domain and path args in order to be more specific.""" + + +class RequestsCookieJar(cookielib.CookieJar, collections.MutableMapping): + """Compatibility class; is a cookielib.CookieJar, but exposes a dict + interface. + + This is the CookieJar we create by default for requests and sessions that + don't specify one, since some clients may expect response.cookies and + session.cookies to support dict operations. + + Requests does not use the dict interface internally; it's just for + compatibility with external client code. All requests code should work + out of the box with externally provided instances of ``CookieJar``, e.g. + ``LWPCookieJar`` and ``FileCookieJar``. + + Unlike a regular CookieJar, this class is pickleable. + + .. warning:: dictionary operations that are normally O(1) may be O(n). + """ + def get(self, name, default=None, domain=None, path=None): + """Dict-like get() that also supports optional domain and path args in + order to resolve naming collisions from using one cookie jar over + multiple domains. + + .. warning:: operation is O(n), not O(1).""" + try: + return self._find_no_duplicates(name, domain, path) + except KeyError: + return default + + def set(self, name, value, **kwargs): + """Dict-like set() that also supports optional domain and path args in + order to resolve naming collisions from using one cookie jar over + multiple domains.""" + # support client code that unsets cookies by assignment of a None value: + if value is None: + remove_cookie_by_name(self, name, domain=kwargs.get('domain'), path=kwargs.get('path')) + return + + if isinstance(value, Morsel): + c = morsel_to_cookie(value) + else: + c = create_cookie(name, value, **kwargs) + self.set_cookie(c) + return c + + def iterkeys(self): + """Dict-like iterkeys() that returns an iterator of names of cookies + from the jar. See itervalues() and iteritems().""" + for cookie in iter(self): + yield cookie.name + + def keys(self): + """Dict-like keys() that returns a list of names of cookies from the + jar. See values() and items().""" + return list(self.iterkeys()) + + def itervalues(self): + """Dict-like itervalues() that returns an iterator of values of cookies + from the jar. See iterkeys() and iteritems().""" + for cookie in iter(self): + yield cookie.value + + def values(self): + """Dict-like values() that returns a list of values of cookies from the + jar. See keys() and items().""" + return list(self.itervalues()) + + def iteritems(self): + """Dict-like iteritems() that returns an iterator of name-value tuples + from the jar. See iterkeys() and itervalues().""" + for cookie in iter(self): + yield cookie.name, cookie.value + + def items(self): + """Dict-like items() that returns a list of name-value tuples from the + jar. See keys() and values(). Allows client-code to call + ``dict(RequestsCookieJar)`` and get a vanilla python dict of key value + pairs.""" + return list(self.iteritems()) + + def list_domains(self): + """Utility method to list all the domains in the jar.""" + domains = [] + for cookie in iter(self): + if cookie.domain not in domains: + domains.append(cookie.domain) + return domains + + def list_paths(self): + """Utility method to list all the paths in the jar.""" + paths = [] + for cookie in iter(self): + if cookie.path not in paths: + paths.append(cookie.path) + return paths + + def multiple_domains(self): + """Returns True if there are multiple domains in the jar. + Returns False otherwise.""" + domains = [] + for cookie in iter(self): + if cookie.domain is not None and cookie.domain in domains: + return True + domains.append(cookie.domain) + return False # there is only one domain in jar + + def get_dict(self, domain=None, path=None): + """Takes as an argument an optional domain and path and returns a plain + old Python dict of name-value pairs of cookies that meet the + requirements.""" + dictionary = {} + for cookie in iter(self): + if (domain is None or cookie.domain == domain) and (path is None + or cookie.path == path): + dictionary[cookie.name] = cookie.value + return dictionary + + def __getitem__(self, name): + """Dict-like __getitem__() for compatibility with client code. Throws + exception if there are more than one cookie with name. In that case, + use the more explicit get() method instead. + + .. warning:: operation is O(n), not O(1).""" + + return self._find_no_duplicates(name) + + def __setitem__(self, name, value): + """Dict-like __setitem__ for compatibility with client code. Throws + exception if there is already a cookie of that name in the jar. In that + case, use the more explicit set() method instead.""" + + self.set(name, value) + + def __delitem__(self, name): + """Deletes a cookie given a name. Wraps ``cookielib.CookieJar``'s + ``remove_cookie_by_name()``.""" + remove_cookie_by_name(self, name) + + def set_cookie(self, cookie, *args, **kwargs): + if hasattr(cookie.value, 'startswith') and cookie.value.startswith('"') and cookie.value.endswith('"'): + cookie.value = cookie.value.replace('\\"', '') + return super(RequestsCookieJar, self).set_cookie(cookie, *args, **kwargs) + + def update(self, other): + """Updates this jar with cookies from another CookieJar or dict-like""" + if isinstance(other, cookielib.CookieJar): + for cookie in other: + self.set_cookie(copy.copy(cookie)) + else: + super(RequestsCookieJar, self).update(other) + + def _find(self, name, domain=None, path=None): + """Requests uses this method internally to get cookie values. Takes as + args name and optional domain and path. Returns a cookie.value. If + there are conflicting cookies, _find arbitrarily chooses one. See + _find_no_duplicates if you want an exception thrown if there are + conflicting cookies.""" + for cookie in iter(self): + if cookie.name == name: + if domain is None or cookie.domain == domain: + if path is None or cookie.path == path: + return cookie.value + + raise KeyError('name=%r, domain=%r, path=%r' % (name, domain, path)) + + def _find_no_duplicates(self, name, domain=None, path=None): + """Both ``__get_item__`` and ``get`` call this function: it's never + used elsewhere in Requests. Takes as args name and optional domain and + path. Returns a cookie.value. Throws KeyError if cookie is not found + and CookieConflictError if there are multiple cookies that match name + and optionally domain and path.""" + toReturn = None + for cookie in iter(self): + if cookie.name == name: + if domain is None or cookie.domain == domain: + if path is None or cookie.path == path: + if toReturn is not None: # if there are multiple cookies that meet passed in criteria + raise CookieConflictError('There are multiple cookies with name, %r' % (name)) + toReturn = cookie.value # we will eventually return this as long as no cookie conflict + + if toReturn: + return toReturn + raise KeyError('name=%r, domain=%r, path=%r' % (name, domain, path)) + + def __getstate__(self): + """Unlike a normal CookieJar, this class is pickleable.""" + state = self.__dict__.copy() + # remove the unpickleable RLock object + state.pop('_cookies_lock') + return state + + def __setstate__(self, state): + """Unlike a normal CookieJar, this class is pickleable.""" + self.__dict__.update(state) + if '_cookies_lock' not in self.__dict__: + self._cookies_lock = threading.RLock() + + def copy(self): + """Return a copy of this RequestsCookieJar.""" + new_cj = RequestsCookieJar() + new_cj.update(self) + return new_cj + + +def _copy_cookie_jar(jar): + if jar is None: + return None + + if hasattr(jar, 'copy'): + # We're dealing with an instance of RequestsCookieJar + return jar.copy() + # We're dealing with a generic CookieJar instance + new_jar = copy.copy(jar) + new_jar.clear() + for cookie in jar: + new_jar.set_cookie(copy.copy(cookie)) + return new_jar + + +def create_cookie(name, value, **kwargs): + """Make a cookie from underspecified parameters. + + By default, the pair of `name` and `value` will be set for the domain '' + and sent on every request (this is sometimes called a "supercookie"). + """ + result = dict( + version=0, + name=name, + value=value, + port=None, + domain='', + path='/', + secure=False, + expires=None, + discard=True, + comment=None, + comment_url=None, + rest={'HttpOnly': None}, + rfc2109=False,) + + badargs = set(kwargs) - set(result) + if badargs: + err = 'create_cookie() got unexpected keyword arguments: %s' + raise TypeError(err % list(badargs)) + + result.update(kwargs) + result['port_specified'] = bool(result['port']) + result['domain_specified'] = bool(result['domain']) + result['domain_initial_dot'] = result['domain'].startswith('.') + result['path_specified'] = bool(result['path']) + + return cookielib.Cookie(**result) + + +def morsel_to_cookie(morsel): + """Convert a Morsel object into a Cookie containing the one k/v pair.""" + + expires = None + if morsel['max-age']: + try: + expires = int(time.time() + int(morsel['max-age'])) + except ValueError: + raise TypeError('max-age: %s must be integer' % morsel['max-age']) + elif morsel['expires']: + time_template = '%a, %d-%b-%Y %H:%M:%S GMT' + expires = calendar.timegm( + time.strptime(morsel['expires'], time_template) + ) + return create_cookie( + comment=morsel['comment'], + comment_url=bool(morsel['comment']), + discard=False, + domain=morsel['domain'], + expires=expires, + name=morsel.key, + path=morsel['path'], + port=None, + rest={'HttpOnly': morsel['httponly']}, + rfc2109=False, + secure=bool(morsel['secure']), + value=morsel.value, + version=morsel['version'] or 0, + ) + + +def cookiejar_from_dict(cookie_dict, cookiejar=None, overwrite=True): + """Returns a CookieJar from a key/value dictionary. + + :param cookie_dict: Dict of key/values to insert into CookieJar. + :param cookiejar: (optional) A cookiejar to add the cookies to. + :param overwrite: (optional) If False, will not replace cookies + already in the jar with new ones. + """ + if cookiejar is None: + cookiejar = RequestsCookieJar() + + if cookie_dict is not None: + names_from_jar = [cookie.name for cookie in cookiejar] + for name in cookie_dict: + if overwrite or (name not in names_from_jar): + cookiejar.set_cookie(create_cookie(name, cookie_dict[name])) + + return cookiejar + + +def merge_cookies(cookiejar, cookies): + """Add cookies to cookiejar and returns a merged CookieJar. + + :param cookiejar: CookieJar object to add the cookies to. + :param cookies: Dictionary or CookieJar object to be added. + """ + if not isinstance(cookiejar, cookielib.CookieJar): + raise ValueError('You can only merge into CookieJar') + + if isinstance(cookies, dict): + cookiejar = cookiejar_from_dict( + cookies, cookiejar=cookiejar, overwrite=False) + elif isinstance(cookies, cookielib.CookieJar): + try: + cookiejar.update(cookies) + except AttributeError: + for cookie_in_jar in cookies: + cookiejar.set_cookie(cookie_in_jar) + + return cookiejar diff --git a/resources/lib/libraries/requests/exceptions.py b/resources/lib/libraries/requests/exceptions.py new file mode 100644 index 00000000..ba0b910e --- /dev/null +++ b/resources/lib/libraries/requests/exceptions.py @@ -0,0 +1,114 @@ +# -*- coding: utf-8 -*- + +""" +requests.exceptions +~~~~~~~~~~~~~~~~~~~ + +This module contains the set of Requests' exceptions. + +""" +from .packages.urllib3.exceptions import HTTPError as BaseHTTPError + + +class RequestException(IOError): + """There was an ambiguous exception that occurred while handling your + request.""" + + def __init__(self, *args, **kwargs): + """ + Initialize RequestException with `request` and `response` objects. + """ + response = kwargs.pop('response', None) + self.response = response + self.request = kwargs.pop('request', None) + if (response is not None and not self.request and + hasattr(response, 'request')): + self.request = self.response.request + super(RequestException, self).__init__(*args, **kwargs) + + +class HTTPError(RequestException): + """An HTTP error occurred.""" + + +class ConnectionError(RequestException): + """A Connection error occurred.""" + + +class ProxyError(ConnectionError): + """A proxy error occurred.""" + + +class SSLError(ConnectionError): + """An SSL error occurred.""" + + +class Timeout(RequestException): + """The request timed out. + + Catching this error will catch both + :exc:`~requests.exceptions.ConnectTimeout` and + :exc:`~requests.exceptions.ReadTimeout` errors. + """ + + +class ConnectTimeout(ConnectionError, Timeout): + """The request timed out while trying to connect to the remote server. + + Requests that produced this error are safe to retry. + """ + + +class ReadTimeout(Timeout): + """The server did not send any data in the allotted amount of time.""" + + +class URLRequired(RequestException): + """A valid URL is required to make a request.""" + + +class TooManyRedirects(RequestException): + """Too many redirects.""" + + +class MissingSchema(RequestException, ValueError): + """The URL schema (e.g. http or https) is missing.""" + + +class InvalidSchema(RequestException, ValueError): + """See defaults.py for valid schemas.""" + + +class InvalidURL(RequestException, ValueError): + """ The URL provided was somehow invalid. """ + + +class ChunkedEncodingError(RequestException): + """The server declared chunked encoding but sent an invalid chunk.""" + + +class ContentDecodingError(RequestException, BaseHTTPError): + """Failed to decode response content""" + + +class StreamConsumedError(RequestException, TypeError): + """The content for this response was already consumed""" + + +class RetryError(RequestException): + """Custom retries logic failed""" + + +# Warnings + + +class RequestsWarning(Warning): + """Base warning for Requests.""" + pass + + +class FileModeWarning(RequestsWarning, DeprecationWarning): + """ + A file was opened in text mode, but Requests determined its binary length. + """ + pass diff --git a/resources/lib/libraries/requests/hooks.py b/resources/lib/libraries/requests/hooks.py new file mode 100644 index 00000000..9da94366 --- /dev/null +++ b/resources/lib/libraries/requests/hooks.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- + +""" +requests.hooks +~~~~~~~~~~~~~~ + +This module provides the capabilities for the Requests hooks system. + +Available hooks: + +``response``: + The response generated from a Request. + +""" +HOOKS = ['response'] + +def default_hooks(): + return dict((event, []) for event in HOOKS) + +# TODO: response is the only one + + +def dispatch_hook(key, hooks, hook_data, **kwargs): + """Dispatches a hook dictionary on a given piece of data.""" + hooks = hooks or dict() + hooks = hooks.get(key) + if hooks: + if hasattr(hooks, '__call__'): + hooks = [hooks] + for hook in hooks: + _hook_data = hook(hook_data, **kwargs) + if _hook_data is not None: + hook_data = _hook_data + return hook_data diff --git a/resources/lib/libraries/requests/models.py b/resources/lib/libraries/requests/models.py new file mode 100644 index 00000000..4bcbc548 --- /dev/null +++ b/resources/lib/libraries/requests/models.py @@ -0,0 +1,851 @@ +# -*- coding: utf-8 -*- + +""" +requests.models +~~~~~~~~~~~~~~~ + +This module contains the primary objects that power Requests. +""" + +import collections +import datetime + +from io import BytesIO, UnsupportedOperation +from .hooks import default_hooks +from .structures import CaseInsensitiveDict + +from .auth import HTTPBasicAuth +from .cookies import cookiejar_from_dict, get_cookie_header, _copy_cookie_jar +from .packages.urllib3.fields import RequestField +from .packages.urllib3.filepost import encode_multipart_formdata +from .packages.urllib3.util import parse_url +from .packages.urllib3.exceptions import ( + DecodeError, ReadTimeoutError, ProtocolError, LocationParseError) +from .exceptions import ( + HTTPError, MissingSchema, InvalidURL, ChunkedEncodingError, + ContentDecodingError, ConnectionError, StreamConsumedError) +from .utils import ( + guess_filename, get_auth_from_url, requote_uri, + stream_decode_response_unicode, to_key_val_list, parse_header_links, + iter_slices, guess_json_utf, super_len, to_native_string) +from .compat import ( + cookielib, urlunparse, urlsplit, urlencode, str, bytes, StringIO, + is_py2, chardet, builtin_str, basestring) +from .compat import json as complexjson +from .status_codes import codes + +#: The set of HTTP status codes that indicate an automatically +#: processable redirect. +REDIRECT_STATI = ( + codes.moved, # 301 + codes.found, # 302 + codes.other, # 303 + codes.temporary_redirect, # 307 + codes.permanent_redirect, # 308 +) + +DEFAULT_REDIRECT_LIMIT = 30 +CONTENT_CHUNK_SIZE = 10 * 1024 +ITER_CHUNK_SIZE = 512 + + +class RequestEncodingMixin(object): + @property + def path_url(self): + """Build the path URL to use.""" + + url = [] + + p = urlsplit(self.url) + + path = p.path + if not path: + path = '/' + + url.append(path) + + query = p.query + if query: + url.append('?') + url.append(query) + + return ''.join(url) + + @staticmethod + def _encode_params(data): + """Encode parameters in a piece of data. + + Will successfully encode parameters when passed as a dict or a list of + 2-tuples. Order is retained if data is a list of 2-tuples but arbitrary + if parameters are supplied as a dict. + """ + + if isinstance(data, (str, bytes)): + return data + elif hasattr(data, 'read'): + return data + elif hasattr(data, '__iter__'): + result = [] + for k, vs in to_key_val_list(data): + if isinstance(vs, basestring) or not hasattr(vs, '__iter__'): + vs = [vs] + for v in vs: + if v is not None: + result.append( + (k.encode('utf-8') if isinstance(k, str) else k, + v.encode('utf-8') if isinstance(v, str) else v)) + return urlencode(result, doseq=True) + else: + return data + + @staticmethod + def _encode_files(files, data): + """Build the body for a multipart/form-data request. + + Will successfully encode files when passed as a dict or a list of + 2-tuples. Order is retained if data is a list of 2-tuples but arbitrary + if parameters are supplied as a dict. + + """ + if (not files): + raise ValueError("Files must be provided.") + elif isinstance(data, basestring): + raise ValueError("Data must not be a string.") + + new_fields = [] + fields = to_key_val_list(data or {}) + files = to_key_val_list(files or {}) + + for field, val in fields: + if isinstance(val, basestring) or not hasattr(val, '__iter__'): + val = [val] + for v in val: + if v is not None: + # Don't call str() on bytestrings: in Py3 it all goes wrong. + if not isinstance(v, bytes): + v = str(v) + + new_fields.append( + (field.decode('utf-8') if isinstance(field, bytes) else field, + v.encode('utf-8') if isinstance(v, str) else v)) + + for (k, v) in files: + # support for explicit filename + ft = None + fh = None + if isinstance(v, (tuple, list)): + if len(v) == 2: + fn, fp = v + elif len(v) == 3: + fn, fp, ft = v + else: + fn, fp, ft, fh = v + else: + fn = guess_filename(v) or k + fp = v + + if isinstance(fp, (str, bytes, bytearray)): + fdata = fp + else: + fdata = fp.read() + + rf = RequestField(name=k, data=fdata, filename=fn, headers=fh) + rf.make_multipart(content_type=ft) + new_fields.append(rf) + + body, content_type = encode_multipart_formdata(new_fields) + + return body, content_type + + +class RequestHooksMixin(object): + def register_hook(self, event, hook): + """Properly register a hook.""" + + if event not in self.hooks: + raise ValueError('Unsupported event specified, with event name "%s"' % (event)) + + if isinstance(hook, collections.Callable): + self.hooks[event].append(hook) + elif hasattr(hook, '__iter__'): + self.hooks[event].extend(h for h in hook if isinstance(h, collections.Callable)) + + def deregister_hook(self, event, hook): + """Deregister a previously registered hook. + Returns True if the hook existed, False if not. + """ + + try: + self.hooks[event].remove(hook) + return True + except ValueError: + return False + + +class Request(RequestHooksMixin): + """A user-created :class:`Request <Request>` object. + + Used to prepare a :class:`PreparedRequest <PreparedRequest>`, which is sent to the server. + + :param method: HTTP method to use. + :param url: URL to send. + :param headers: dictionary of headers to send. + :param files: dictionary of {filename: fileobject} files to multipart upload. + :param data: the body to attach to the request. If a dictionary is provided, form-encoding will take place. + :param json: json for the body to attach to the request (if files or data is not specified). + :param params: dictionary of URL parameters to append to the URL. + :param auth: Auth handler or (user, pass) tuple. + :param cookies: dictionary or CookieJar of cookies to attach to this request. + :param hooks: dictionary of callback hooks, for internal usage. + + Usage:: + + >>> import requests + >>> req = requests.Request('GET', 'http://httpbin.org/get') + >>> req.prepare() + <PreparedRequest [GET]> + + """ + def __init__(self, method=None, url=None, headers=None, files=None, + data=None, params=None, auth=None, cookies=None, hooks=None, json=None): + + # Default empty dicts for dict params. + data = [] if data is None else data + files = [] if files is None else files + headers = {} if headers is None else headers + params = {} if params is None else params + hooks = {} if hooks is None else hooks + + self.hooks = default_hooks() + for (k, v) in list(hooks.items()): + self.register_hook(event=k, hook=v) + + self.method = method + self.url = url + self.headers = headers + self.files = files + self.data = data + self.json = json + self.params = params + self.auth = auth + self.cookies = cookies + + def __repr__(self): + return '<Request [%s]>' % (self.method) + + def prepare(self): + """Constructs a :class:`PreparedRequest <PreparedRequest>` for transmission and returns it.""" + p = PreparedRequest() + p.prepare( + method=self.method, + url=self.url, + headers=self.headers, + files=self.files, + data=self.data, + json=self.json, + params=self.params, + auth=self.auth, + cookies=self.cookies, + hooks=self.hooks, + ) + return p + + +class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): + """The fully mutable :class:`PreparedRequest <PreparedRequest>` object, + containing the exact bytes that will be sent to the server. + + Generated from either a :class:`Request <Request>` object or manually. + + Usage:: + + >>> import requests + >>> req = requests.Request('GET', 'http://httpbin.org/get') + >>> r = req.prepare() + <PreparedRequest [GET]> + + >>> s = requests.Session() + >>> s.send(r) + <Response [200]> + + """ + + def __init__(self): + #: HTTP verb to send to the server. + self.method = None + #: HTTP URL to send the request to. + self.url = None + #: dictionary of HTTP headers. + self.headers = None + # The `CookieJar` used to create the Cookie header will be stored here + # after prepare_cookies is called + self._cookies = None + #: request body to send to the server. + self.body = None + #: dictionary of callback hooks, for internal usage. + self.hooks = default_hooks() + + def prepare(self, method=None, url=None, headers=None, files=None, + data=None, params=None, auth=None, cookies=None, hooks=None, json=None): + """Prepares the entire request with the given parameters.""" + + self.prepare_method(method) + self.prepare_url(url, params) + self.prepare_headers(headers) + self.prepare_cookies(cookies) + self.prepare_body(data, files, json) + self.prepare_auth(auth, url) + + # Note that prepare_auth must be last to enable authentication schemes + # such as OAuth to work on a fully prepared request. + + # This MUST go after prepare_auth. Authenticators could add a hook + self.prepare_hooks(hooks) + + def __repr__(self): + return '<PreparedRequest [%s]>' % (self.method) + + def copy(self): + p = PreparedRequest() + p.method = self.method + p.url = self.url + p.headers = self.headers.copy() if self.headers is not None else None + p._cookies = _copy_cookie_jar(self._cookies) + p.body = self.body + p.hooks = self.hooks + return p + + def prepare_method(self, method): + """Prepares the given HTTP method.""" + self.method = method + if self.method is not None: + self.method = to_native_string(self.method.upper()) + + def prepare_url(self, url, params): + """Prepares the given HTTP URL.""" + #: Accept objects that have string representations. + #: We're unable to blindly call unicode/str functions + #: as this will include the bytestring indicator (b'') + #: on python 3.x. + #: https://github.com/kennethreitz/requests/pull/2238 + if isinstance(url, bytes): + url = url.decode('utf8') + else: + url = unicode(url) if is_py2 else str(url) + + # Don't do any URL preparation for non-HTTP schemes like `mailto`, + # `data` etc to work around exceptions from `url_parse`, which + # handles RFC 3986 only. + if ':' in url and not url.lower().startswith('http'): + self.url = url + return + + # Support for unicode domain names and paths. + try: + scheme, auth, host, port, path, query, fragment = parse_url(url) + except LocationParseError as e: + raise InvalidURL(*e.args) + + if not scheme: + error = ("Invalid URL {0!r}: No schema supplied. Perhaps you meant http://{0}?") + error = error.format(to_native_string(url, 'utf8')) + + raise MissingSchema(error) + + if not host: + raise InvalidURL("Invalid URL %r: No host supplied" % url) + + # Only want to apply IDNA to the hostname + try: + host = host.encode('idna').decode('utf-8') + except UnicodeError: + raise InvalidURL('URL has an invalid label.') + + # Carefully reconstruct the network location + netloc = auth or '' + if netloc: + netloc += '@' + netloc += host + if port: + netloc += ':' + str(port) + + # Bare domains aren't valid URLs. + if not path: + path = '/' + + if is_py2: + if isinstance(scheme, str): + scheme = scheme.encode('utf-8') + if isinstance(netloc, str): + netloc = netloc.encode('utf-8') + if isinstance(path, str): + path = path.encode('utf-8') + if isinstance(query, str): + query = query.encode('utf-8') + if isinstance(fragment, str): + fragment = fragment.encode('utf-8') + + if isinstance(params, (str, bytes)): + params = to_native_string(params) + + enc_params = self._encode_params(params) + if enc_params: + if query: + query = '%s&%s' % (query, enc_params) + else: + query = enc_params + + url = requote_uri(urlunparse([scheme, netloc, path, None, query, fragment])) + self.url = url + + def prepare_headers(self, headers): + """Prepares the given HTTP headers.""" + + if headers: + self.headers = CaseInsensitiveDict((to_native_string(name), value) for name, value in headers.items()) + else: + self.headers = CaseInsensitiveDict() + + def prepare_body(self, data, files, json=None): + """Prepares the given HTTP body data.""" + + # Check if file, fo, generator, iterator. + # If not, run through normal process. + + # Nottin' on you. + body = None + content_type = None + length = None + + if not data and json is not None: + content_type = 'application/json' + body = complexjson.dumps(json) + + is_stream = all([ + hasattr(data, '__iter__'), + not isinstance(data, (basestring, list, tuple, dict)) + ]) + + try: + length = super_len(data) + except (TypeError, AttributeError, UnsupportedOperation): + length = None + + if is_stream: + body = data + + if files: + raise NotImplementedError('Streamed bodies and files are mutually exclusive.') + + if length: + self.headers['Content-Length'] = builtin_str(length) + else: + self.headers['Transfer-Encoding'] = 'chunked' + else: + # Multi-part file uploads. + if files: + (body, content_type) = self._encode_files(files, data) + else: + if data: + body = self._encode_params(data) + if isinstance(data, basestring) or hasattr(data, 'read'): + content_type = None + else: + content_type = 'application/x-www-form-urlencoded' + + self.prepare_content_length(body) + + # Add content-type if it wasn't explicitly provided. + if content_type and ('content-type' not in self.headers): + self.headers['Content-Type'] = content_type + + self.body = body + + def prepare_content_length(self, body): + if hasattr(body, 'seek') and hasattr(body, 'tell'): + body.seek(0, 2) + self.headers['Content-Length'] = builtin_str(body.tell()) + body.seek(0, 0) + elif body is not None: + l = super_len(body) + if l: + self.headers['Content-Length'] = builtin_str(l) + elif (self.method not in ('GET', 'HEAD')) and (self.headers.get('Content-Length') is None): + self.headers['Content-Length'] = '0' + + def prepare_auth(self, auth, url=''): + """Prepares the given HTTP auth data.""" + + # If no Auth is explicitly provided, extract it from the URL first. + if auth is None: + url_auth = get_auth_from_url(self.url) + auth = url_auth if any(url_auth) else None + + if auth: + if isinstance(auth, tuple) and len(auth) == 2: + # special-case basic HTTP auth + auth = HTTPBasicAuth(*auth) + + # Allow auth to make its changes. + r = auth(self) + + # Update self to reflect the auth changes. + self.__dict__.update(r.__dict__) + + # Recompute Content-Length + self.prepare_content_length(self.body) + + def prepare_cookies(self, cookies): + """Prepares the given HTTP cookie data. + + This function eventually generates a ``Cookie`` header from the + given cookies using cookielib. Due to cookielib's design, the header + will not be regenerated if it already exists, meaning this function + can only be called once for the life of the + :class:`PreparedRequest <PreparedRequest>` object. Any subsequent calls + to ``prepare_cookies`` will have no actual effect, unless the "Cookie" + header is removed beforehand.""" + + if isinstance(cookies, cookielib.CookieJar): + self._cookies = cookies + else: + self._cookies = cookiejar_from_dict(cookies) + + cookie_header = get_cookie_header(self._cookies, self) + if cookie_header is not None: + self.headers['Cookie'] = cookie_header + + def prepare_hooks(self, hooks): + """Prepares the given hooks.""" + # hooks can be passed as None to the prepare method and to this + # method. To prevent iterating over None, simply use an empty list + # if hooks is False-y + hooks = hooks or [] + for event in hooks: + self.register_hook(event, hooks[event]) + + +class Response(object): + """The :class:`Response <Response>` object, which contains a + server's response to an HTTP request. + """ + + __attrs__ = [ + '_content', 'status_code', 'headers', 'url', 'history', + 'encoding', 'reason', 'cookies', 'elapsed', 'request' + ] + + def __init__(self): + super(Response, self).__init__() + + self._content = False + self._content_consumed = False + + #: Integer Code of responded HTTP Status, e.g. 404 or 200. + self.status_code = None + + #: Case-insensitive Dictionary of Response Headers. + #: For example, ``headers['content-encoding']`` will return the + #: value of a ``'Content-Encoding'`` response header. + self.headers = CaseInsensitiveDict() + + #: File-like object representation of response (for advanced usage). + #: Use of ``raw`` requires that ``stream=True`` be set on the request. + # This requirement does not apply for use internally to Requests. + self.raw = None + + #: Final URL location of Response. + self.url = None + + #: Encoding to decode with when accessing r.text. + self.encoding = None + + #: A list of :class:`Response <Response>` objects from + #: the history of the Request. Any redirect responses will end + #: up here. The list is sorted from the oldest to the most recent request. + self.history = [] + + #: Textual reason of responded HTTP Status, e.g. "Not Found" or "OK". + self.reason = None + + #: A CookieJar of Cookies the server sent back. + self.cookies = cookiejar_from_dict({}) + + #: The amount of time elapsed between sending the request + #: and the arrival of the response (as a timedelta). + #: This property specifically measures the time taken between sending + #: the first byte of the request and finishing parsing the headers. It + #: is therefore unaffected by consuming the response content or the + #: value of the ``stream`` keyword argument. + self.elapsed = datetime.timedelta(0) + + #: The :class:`PreparedRequest <PreparedRequest>` object to which this + #: is a response. + self.request = None + + def __getstate__(self): + # Consume everything; accessing the content attribute makes + # sure the content has been fully read. + if not self._content_consumed: + self.content + + return dict( + (attr, getattr(self, attr, None)) + for attr in self.__attrs__ + ) + + def __setstate__(self, state): + for name, value in state.items(): + setattr(self, name, value) + + # pickled objects do not have .raw + setattr(self, '_content_consumed', True) + setattr(self, 'raw', None) + + def __repr__(self): + return '<Response [%s]>' % (self.status_code) + + def __bool__(self): + """Returns true if :attr:`status_code` is 'OK'.""" + return self.ok + + def __nonzero__(self): + """Returns true if :attr:`status_code` is 'OK'.""" + return self.ok + + def __iter__(self): + """Allows you to use a response as an iterator.""" + return self.iter_content(128) + + @property + def ok(self): + try: + self.raise_for_status() + except HTTPError: + return False + return True + + @property + def is_redirect(self): + """True if this Response is a well-formed HTTP redirect that could have + been processed automatically (by :meth:`Session.resolve_redirects`). + """ + return ('location' in self.headers and self.status_code in REDIRECT_STATI) + + @property + def is_permanent_redirect(self): + """True if this Response one of the permanent versions of redirect""" + return ('location' in self.headers and self.status_code in (codes.moved_permanently, codes.permanent_redirect)) + + @property + def apparent_encoding(self): + """The apparent encoding, provided by the chardet library""" + return chardet.detect(self.content)['encoding'] + + def iter_content(self, chunk_size=1, decode_unicode=False): + """Iterates over the response data. When stream=True is set on the + request, this avoids reading the content at once into memory for + large responses. The chunk size is the number of bytes it should + read into memory. This is not necessarily the length of each item + returned as decoding can take place. + + If decode_unicode is True, content will be decoded using the best + available encoding based on the response. + """ + + def generate(): + # Special case for urllib3. + if hasattr(self.raw, 'stream'): + try: + for chunk in self.raw.stream(chunk_size, decode_content=True): + yield chunk + except ProtocolError as e: + raise ChunkedEncodingError(e) + except DecodeError as e: + raise ContentDecodingError(e) + except ReadTimeoutError as e: + raise ConnectionError(e) + else: + # Standard file-like object. + while True: + chunk = self.raw.read(chunk_size) + if not chunk: + break + yield chunk + + self._content_consumed = True + + if self._content_consumed and isinstance(self._content, bool): + raise StreamConsumedError() + # simulate reading small chunks of the content + reused_chunks = iter_slices(self._content, chunk_size) + + stream_chunks = generate() + + chunks = reused_chunks if self._content_consumed else stream_chunks + + if decode_unicode: + chunks = stream_decode_response_unicode(chunks, self) + + return chunks + + def iter_lines(self, chunk_size=ITER_CHUNK_SIZE, decode_unicode=None, delimiter=None): + """Iterates over the response data, one line at a time. When + stream=True is set on the request, this avoids reading the + content at once into memory for large responses. + + .. note:: This method is not reentrant safe. + """ + + pending = None + + for chunk in self.iter_content(chunk_size=chunk_size, decode_unicode=decode_unicode): + + if pending is not None: + chunk = pending + chunk + + if delimiter: + lines = chunk.split(delimiter) + else: + lines = chunk.splitlines() + + if lines and lines[-1] and chunk and lines[-1][-1] == chunk[-1]: + pending = lines.pop() + else: + pending = None + + for line in lines: + yield line + + if pending is not None: + yield pending + + @property + def content(self): + """Content of the response, in bytes.""" + + if self._content is False: + # Read the contents. + try: + if self._content_consumed: + raise RuntimeError( + 'The content for this response was already consumed') + + if self.status_code == 0: + self._content = None + else: + self._content = bytes().join(self.iter_content(CONTENT_CHUNK_SIZE)) or bytes() + + except AttributeError: + self._content = None + + self._content_consumed = True + # don't need to release the connection; that's been handled by urllib3 + # since we exhausted the data. + return self._content + + @property + def text(self): + """Content of the response, in unicode. + + If Response.encoding is None, encoding will be guessed using + ``chardet``. + + The encoding of the response content is determined based solely on HTTP + headers, following RFC 2616 to the letter. If you can take advantage of + non-HTTP knowledge to make a better guess at the encoding, you should + set ``r.encoding`` appropriately before accessing this property. + """ + + # Try charset from content-type + content = None + encoding = self.encoding + + if not self.content: + return str('') + + # Fallback to auto-detected encoding. + if self.encoding is None: + encoding = self.apparent_encoding + + # Decode unicode from given encoding. + try: + content = str(self.content, encoding, errors='replace') + except (LookupError, TypeError): + # A LookupError is raised if the encoding was not found which could + # indicate a misspelling or similar mistake. + # + # A TypeError can be raised if encoding is None + # + # So we try blindly encoding. + content = str(self.content, errors='replace') + + return content + + def json(self, **kwargs): + """Returns the json-encoded content of a response, if any. + + :param \*\*kwargs: Optional arguments that ``json.loads`` takes. + """ + + if not self.encoding and len(self.content) > 3: + # No encoding set. JSON RFC 4627 section 3 states we should expect + # UTF-8, -16 or -32. Detect which one to use; If the detection or + # decoding fails, fall back to `self.text` (using chardet to make + # a best guess). + encoding = guess_json_utf(self.content) + if encoding is not None: + try: + return complexjson.loads( + self.content.decode(encoding), **kwargs + ) + except UnicodeDecodeError: + # Wrong UTF codec detected; usually because it's not UTF-8 + # but some other 8-bit codec. This is an RFC violation, + # and the server didn't bother to tell us what codec *was* + # used. + pass + return complexjson.loads(self.text, **kwargs) + + @property + def links(self): + """Returns the parsed header links of the response, if any.""" + + header = self.headers.get('link') + + # l = MultiDict() + l = {} + + if header: + links = parse_header_links(header) + + for link in links: + key = link.get('rel') or link.get('url') + l[key] = link + + return l + + def raise_for_status(self): + """Raises stored :class:`HTTPError`, if one occurred.""" + + http_error_msg = '' + + if 400 <= self.status_code < 500: + http_error_msg = '%s Client Error: %s for url: %s' % (self.status_code, self.reason, self.url) + + elif 500 <= self.status_code < 600: + http_error_msg = '%s Server Error: %s for url: %s' % (self.status_code, self.reason, self.url) + + if http_error_msg: + raise HTTPError(http_error_msg, response=self) + + def close(self): + """Releases the connection back to the pool. Once this method has been + called the underlying ``raw`` object must not be accessed again. + + *Note: Should not normally need to be called explicitly.* + """ + if not self._content_consumed: + return self.raw.close() + + return self.raw.release_conn() diff --git a/resources/lib/libraries/requests/packages/README.rst b/resources/lib/libraries/requests/packages/README.rst new file mode 100644 index 00000000..83e0c625 --- /dev/null +++ b/resources/lib/libraries/requests/packages/README.rst @@ -0,0 +1,11 @@ +If you are planning to submit a pull request to requests with any changes in +this library do not go any further. These are independent libraries which we +vendor into requests. Any changes necessary to these libraries must be made in +them and submitted as separate pull requests to those libraries. + +urllib3 pull requests go here: https://github.com/shazow/urllib3 + +chardet pull requests go here: https://github.com/chardet/chardet + +See https://github.com/kennethreitz/requests/pull/1812#issuecomment-30854316 +for the reasoning behind this. diff --git a/resources/lib/libraries/requests/packages/__init__.py b/resources/lib/libraries/requests/packages/__init__.py new file mode 100644 index 00000000..971c2ad0 --- /dev/null +++ b/resources/lib/libraries/requests/packages/__init__.py @@ -0,0 +1,36 @@ +''' +Debian and other distributions "unbundle" requests' vendored dependencies, and +rewrite all imports to use the global versions of ``urllib3`` and ``chardet``. +The problem with this is that not only requests itself imports those +dependencies, but third-party code outside of the distros' control too. + +In reaction to these problems, the distro maintainers replaced +``requests.packages`` with a magical "stub module" that imports the correct +modules. The implementations were varying in quality and all had severe +problems. For example, a symlink (or hardlink) that links the correct modules +into place introduces problems regarding object identity, since you now have +two modules in `sys.modules` with the same API, but different identities:: + + requests.packages.urllib3 is not urllib3 + +With version ``2.5.2``, requests started to maintain its own stub, so that +distro-specific breakage would be reduced to a minimum, even though the whole +issue is not requests' fault in the first place. See +https://github.com/kennethreitz/requests/pull/2375 for the corresponding pull +request. +''' + +from __future__ import absolute_import +import sys + +try: + from . import urllib3 +except ImportError: + import urllib3 + sys.modules['%s.urllib3' % __name__] = urllib3 + +try: + from . import chardet +except ImportError: + import chardet + sys.modules['%s.chardet' % __name__] = chardet diff --git a/resources/lib/libraries/requests/packages/chardet/__init__.py b/resources/lib/libraries/requests/packages/chardet/__init__.py new file mode 100644 index 00000000..82c2a48d --- /dev/null +++ b/resources/lib/libraries/requests/packages/chardet/__init__.py @@ -0,0 +1,32 @@ +######################## BEGIN LICENSE BLOCK ######################## +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +__version__ = "2.3.0" +from sys import version_info + + +def detect(aBuf): + if ((version_info < (3, 0) and isinstance(aBuf, unicode)) or + (version_info >= (3, 0) and not isinstance(aBuf, bytes))): + raise ValueError('Expected a bytes object, not a unicode object') + + from . import universaldetector + u = universaldetector.UniversalDetector() + u.reset() + u.feed(aBuf) + u.close() + return u.result diff --git a/resources/lib/libraries/requests/packages/chardet/big5freq.py b/resources/lib/libraries/requests/packages/chardet/big5freq.py new file mode 100644 index 00000000..65bffc04 --- /dev/null +++ b/resources/lib/libraries/requests/packages/chardet/big5freq.py @@ -0,0 +1,925 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is Mozilla Communicator client code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +# Big5 frequency table +# by Taiwan's Mandarin Promotion Council +# <http://www.edu.tw:81/mandr/> +# +# 128 --> 0.42261 +# 256 --> 0.57851 +# 512 --> 0.74851 +# 1024 --> 0.89384 +# 2048 --> 0.97583 +# +# Ideal Distribution Ratio = 0.74851/(1-0.74851) =2.98 +# Random Distribution Ration = 512/(5401-512)=0.105 +# +# Typical Distribution Ratio about 25% of Ideal one, still much higher than RDR + +BIG5_TYPICAL_DISTRIBUTION_RATIO = 0.75 + +#Char to FreqOrder table +BIG5_TABLE_SIZE = 5376 + +Big5CharToFreqOrder = ( + 1,1801,1506, 255,1431, 198, 9, 82, 6,5008, 177, 202,3681,1256,2821, 110, # 16 +3814, 33,3274, 261, 76, 44,2114, 16,2946,2187,1176, 659,3971, 26,3451,2653, # 32 +1198,3972,3350,4202, 410,2215, 302, 590, 361,1964, 8, 204, 58,4510,5009,1932, # 48 + 63,5010,5011, 317,1614, 75, 222, 159,4203,2417,1480,5012,3555,3091, 224,2822, # 64 +3682, 3, 10,3973,1471, 29,2787,1135,2866,1940, 873, 130,3275,1123, 312,5013, # 80 +4511,2052, 507, 252, 682,5014, 142,1915, 124, 206,2947, 34,3556,3204, 64, 604, # 96 +5015,2501,1977,1978, 155,1991, 645, 641,1606,5016,3452, 337, 72, 406,5017, 80, # 112 + 630, 238,3205,1509, 263, 939,1092,2654, 756,1440,1094,3453, 449, 69,2987, 591, # 128 + 179,2096, 471, 115,2035,1844, 60, 50,2988, 134, 806,1869, 734,2036,3454, 180, # 144 + 995,1607, 156, 537,2907, 688,5018, 319,1305, 779,2145, 514,2379, 298,4512, 359, # 160 +2502, 90,2716,1338, 663, 11, 906,1099,2553, 20,2441, 182, 532,1716,5019, 732, # 176 +1376,4204,1311,1420,3206, 25,2317,1056, 113, 399, 382,1950, 242,3455,2474, 529, # 192 +3276, 475,1447,3683,5020, 117, 21, 656, 810,1297,2300,2334,3557,5021, 126,4205, # 208 + 706, 456, 150, 613,4513, 71,1118,2037,4206, 145,3092, 85, 835, 486,2115,1246, # 224 +1426, 428, 727,1285,1015, 800, 106, 623, 303,1281,5022,2128,2359, 347,3815, 221, # 240 +3558,3135,5023,1956,1153,4207, 83, 296,1199,3093, 192, 624, 93,5024, 822,1898, # 256 +2823,3136, 795,2065, 991,1554,1542,1592, 27, 43,2867, 859, 139,1456, 860,4514, # 272 + 437, 712,3974, 164,2397,3137, 695, 211,3037,2097, 195,3975,1608,3559,3560,3684, # 288 +3976, 234, 811,2989,2098,3977,2233,1441,3561,1615,2380, 668,2077,1638, 305, 228, # 304 +1664,4515, 467, 415,5025, 262,2099,1593, 239, 108, 300, 200,1033, 512,1247,2078, # 320 +5026,5027,2176,3207,3685,2682, 593, 845,1062,3277, 88,1723,2038,3978,1951, 212, # 336 + 266, 152, 149, 468,1899,4208,4516, 77, 187,5028,3038, 37, 5,2990,5029,3979, # 352 +5030,5031, 39,2524,4517,2908,3208,2079, 55, 148, 74,4518, 545, 483,1474,1029, # 368 +1665, 217,1870,1531,3138,1104,2655,4209, 24, 172,3562, 900,3980,3563,3564,4519, # 384 + 32,1408,2824,1312, 329, 487,2360,2251,2717, 784,2683, 4,3039,3351,1427,1789, # 400 + 188, 109, 499,5032,3686,1717,1790, 888,1217,3040,4520,5033,3565,5034,3352,1520, # 416 +3687,3981, 196,1034, 775,5035,5036, 929,1816, 249, 439, 38,5037,1063,5038, 794, # 432 +3982,1435,2301, 46, 178,3278,2066,5039,2381,5040, 214,1709,4521, 804, 35, 707, # 448 + 324,3688,1601,2554, 140, 459,4210,5041,5042,1365, 839, 272, 978,2262,2580,3456, # 464 +2129,1363,3689,1423, 697, 100,3094, 48, 70,1231, 495,3139,2196,5043,1294,5044, # 480 +2080, 462, 586,1042,3279, 853, 256, 988, 185,2382,3457,1698, 434,1084,5045,3458, # 496 + 314,2625,2788,4522,2335,2336, 569,2285, 637,1817,2525, 757,1162,1879,1616,3459, # 512 + 287,1577,2116, 768,4523,1671,2868,3566,2526,1321,3816, 909,2418,5046,4211, 933, # 528 +3817,4212,2053,2361,1222,4524, 765,2419,1322, 786,4525,5047,1920,1462,1677,2909, # 544 +1699,5048,4526,1424,2442,3140,3690,2600,3353,1775,1941,3460,3983,4213, 309,1369, # 560 +1130,2825, 364,2234,1653,1299,3984,3567,3985,3986,2656, 525,1085,3041, 902,2001, # 576 +1475, 964,4527, 421,1845,1415,1057,2286, 940,1364,3141, 376,4528,4529,1381, 7, # 592 +2527, 983,2383, 336,1710,2684,1846, 321,3461, 559,1131,3042,2752,1809,1132,1313, # 608 + 265,1481,1858,5049, 352,1203,2826,3280, 167,1089, 420,2827, 776, 792,1724,3568, # 624 +4214,2443,3281,5050,4215,5051, 446, 229, 333,2753, 901,3818,1200,1557,4530,2657, # 640 +1921, 395,2754,2685,3819,4216,1836, 125, 916,3209,2626,4531,5052,5053,3820,5054, # 656 +5055,5056,4532,3142,3691,1133,2555,1757,3462,1510,2318,1409,3569,5057,2146, 438, # 672 +2601,2910,2384,3354,1068, 958,3043, 461, 311,2869,2686,4217,1916,3210,4218,1979, # 688 + 383, 750,2755,2627,4219, 274, 539, 385,1278,1442,5058,1154,1965, 384, 561, 210, # 704 + 98,1295,2556,3570,5059,1711,2420,1482,3463,3987,2911,1257, 129,5060,3821, 642, # 720 + 523,2789,2790,2658,5061, 141,2235,1333, 68, 176, 441, 876, 907,4220, 603,2602, # 736 + 710, 171,3464, 404, 549, 18,3143,2398,1410,3692,1666,5062,3571,4533,2912,4534, # 752 +5063,2991, 368,5064, 146, 366, 99, 871,3693,1543, 748, 807,1586,1185, 22,2263, # 768 + 379,3822,3211,5065,3212, 505,1942,2628,1992,1382,2319,5066, 380,2362, 218, 702, # 784 +1818,1248,3465,3044,3572,3355,3282,5067,2992,3694, 930,3283,3823,5068, 59,5069, # 800 + 585, 601,4221, 497,3466,1112,1314,4535,1802,5070,1223,1472,2177,5071, 749,1837, # 816 + 690,1900,3824,1773,3988,1476, 429,1043,1791,2236,2117, 917,4222, 447,1086,1629, # 832 +5072, 556,5073,5074,2021,1654, 844,1090, 105, 550, 966,1758,2828,1008,1783, 686, # 848 +1095,5075,2287, 793,1602,5076,3573,2603,4536,4223,2948,2302,4537,3825, 980,2503, # 864 + 544, 353, 527,4538, 908,2687,2913,5077, 381,2629,1943,1348,5078,1341,1252, 560, # 880 +3095,5079,3467,2870,5080,2054, 973, 886,2081, 143,4539,5081,5082, 157,3989, 496, # 896 +4224, 57, 840, 540,2039,4540,4541,3468,2118,1445, 970,2264,1748,1966,2082,4225, # 912 +3144,1234,1776,3284,2829,3695, 773,1206,2130,1066,2040,1326,3990,1738,1725,4226, # 928 + 279,3145, 51,1544,2604, 423,1578,2131,2067, 173,4542,1880,5083,5084,1583, 264, # 944 + 610,3696,4543,2444, 280, 154,5085,5086,5087,1739, 338,1282,3096, 693,2871,1411, # 960 +1074,3826,2445,5088,4544,5089,5090,1240, 952,2399,5091,2914,1538,2688, 685,1483, # 976 +4227,2475,1436, 953,4228,2055,4545, 671,2400, 79,4229,2446,3285, 608, 567,2689, # 992 +3469,4230,4231,1691, 393,1261,1792,2401,5092,4546,5093,5094,5095,5096,1383,1672, # 1008 +3827,3213,1464, 522,1119, 661,1150, 216, 675,4547,3991,1432,3574, 609,4548,2690, # 1024 +2402,5097,5098,5099,4232,3045, 0,5100,2476, 315, 231,2447, 301,3356,4549,2385, # 1040 +5101, 233,4233,3697,1819,4550,4551,5102, 96,1777,1315,2083,5103, 257,5104,1810, # 1056 +3698,2718,1139,1820,4234,2022,1124,2164,2791,1778,2659,5105,3097, 363,1655,3214, # 1072 +5106,2993,5107,5108,5109,3992,1567,3993, 718, 103,3215, 849,1443, 341,3357,2949, # 1088 +1484,5110,1712, 127, 67, 339,4235,2403, 679,1412, 821,5111,5112, 834, 738, 351, # 1104 +2994,2147, 846, 235,1497,1881, 418,1993,3828,2719, 186,1100,2148,2756,3575,1545, # 1120 +1355,2950,2872,1377, 583,3994,4236,2581,2995,5113,1298,3699,1078,2557,3700,2363, # 1136 + 78,3829,3830, 267,1289,2100,2002,1594,4237, 348, 369,1274,2197,2178,1838,4552, # 1152 +1821,2830,3701,2757,2288,2003,4553,2951,2758, 144,3358, 882,4554,3995,2759,3470, # 1168 +4555,2915,5114,4238,1726, 320,5115,3996,3046, 788,2996,5116,2831,1774,1327,2873, # 1184 +3997,2832,5117,1306,4556,2004,1700,3831,3576,2364,2660, 787,2023, 506, 824,3702, # 1200 + 534, 323,4557,1044,3359,2024,1901, 946,3471,5118,1779,1500,1678,5119,1882,4558, # 1216 + 165, 243,4559,3703,2528, 123, 683,4239, 764,4560, 36,3998,1793, 589,2916, 816, # 1232 + 626,1667,3047,2237,1639,1555,1622,3832,3999,5120,4000,2874,1370,1228,1933, 891, # 1248 +2084,2917, 304,4240,5121, 292,2997,2720,3577, 691,2101,4241,1115,4561, 118, 662, # 1264 +5122, 611,1156, 854,2386,1316,2875, 2, 386, 515,2918,5123,5124,3286, 868,2238, # 1280 +1486, 855,2661, 785,2216,3048,5125,1040,3216,3578,5126,3146, 448,5127,1525,5128, # 1296 +2165,4562,5129,3833,5130,4242,2833,3579,3147, 503, 818,4001,3148,1568, 814, 676, # 1312 +1444, 306,1749,5131,3834,1416,1030, 197,1428, 805,2834,1501,4563,5132,5133,5134, # 1328 +1994,5135,4564,5136,5137,2198, 13,2792,3704,2998,3149,1229,1917,5138,3835,2132, # 1344 +5139,4243,4565,2404,3580,5140,2217,1511,1727,1120,5141,5142, 646,3836,2448, 307, # 1360 +5143,5144,1595,3217,5145,5146,5147,3705,1113,1356,4002,1465,2529,2530,5148, 519, # 1376 +5149, 128,2133, 92,2289,1980,5150,4003,1512, 342,3150,2199,5151,2793,2218,1981, # 1392 +3360,4244, 290,1656,1317, 789, 827,2365,5152,3837,4566, 562, 581,4004,5153, 401, # 1408 +4567,2252, 94,4568,5154,1399,2794,5155,1463,2025,4569,3218,1944,5156, 828,1105, # 1424 +4245,1262,1394,5157,4246, 605,4570,5158,1784,2876,5159,2835, 819,2102, 578,2200, # 1440 +2952,5160,1502, 436,3287,4247,3288,2836,4005,2919,3472,3473,5161,2721,2320,5162, # 1456 +5163,2337,2068, 23,4571, 193, 826,3838,2103, 699,1630,4248,3098, 390,1794,1064, # 1472 +3581,5164,1579,3099,3100,1400,5165,4249,1839,1640,2877,5166,4572,4573, 137,4250, # 1488 + 598,3101,1967, 780, 104, 974,2953,5167, 278, 899, 253, 402, 572, 504, 493,1339, # 1504 +5168,4006,1275,4574,2582,2558,5169,3706,3049,3102,2253, 565,1334,2722, 863, 41, # 1520 +5170,5171,4575,5172,1657,2338, 19, 463,2760,4251, 606,5173,2999,3289,1087,2085, # 1536 +1323,2662,3000,5174,1631,1623,1750,4252,2691,5175,2878, 791,2723,2663,2339, 232, # 1552 +2421,5176,3001,1498,5177,2664,2630, 755,1366,3707,3290,3151,2026,1609, 119,1918, # 1568 +3474, 862,1026,4253,5178,4007,3839,4576,4008,4577,2265,1952,2477,5179,1125, 817, # 1584 +4254,4255,4009,1513,1766,2041,1487,4256,3050,3291,2837,3840,3152,5180,5181,1507, # 1600 +5182,2692, 733, 40,1632,1106,2879, 345,4257, 841,2531, 230,4578,3002,1847,3292, # 1616 +3475,5183,1263, 986,3476,5184, 735, 879, 254,1137, 857, 622,1300,1180,1388,1562, # 1632 +4010,4011,2954, 967,2761,2665,1349, 592,2134,1692,3361,3003,1995,4258,1679,4012, # 1648 +1902,2188,5185, 739,3708,2724,1296,1290,5186,4259,2201,2202,1922,1563,2605,2559, # 1664 +1871,2762,3004,5187, 435,5188, 343,1108, 596, 17,1751,4579,2239,3477,3709,5189, # 1680 +4580, 294,3582,2955,1693, 477, 979, 281,2042,3583, 643,2043,3710,2631,2795,2266, # 1696 +1031,2340,2135,2303,3584,4581, 367,1249,2560,5190,3585,5191,4582,1283,3362,2005, # 1712 + 240,1762,3363,4583,4584, 836,1069,3153, 474,5192,2149,2532, 268,3586,5193,3219, # 1728 +1521,1284,5194,1658,1546,4260,5195,3587,3588,5196,4261,3364,2693,1685,4262, 961, # 1744 +1673,2632, 190,2006,2203,3841,4585,4586,5197, 570,2504,3711,1490,5198,4587,2633, # 1760 +3293,1957,4588, 584,1514, 396,1045,1945,5199,4589,1968,2449,5200,5201,4590,4013, # 1776 + 619,5202,3154,3294, 215,2007,2796,2561,3220,4591,3221,4592, 763,4263,3842,4593, # 1792 +5203,5204,1958,1767,2956,3365,3712,1174, 452,1477,4594,3366,3155,5205,2838,1253, # 1808 +2387,2189,1091,2290,4264, 492,5206, 638,1169,1825,2136,1752,4014, 648, 926,1021, # 1824 +1324,4595, 520,4596, 997, 847,1007, 892,4597,3843,2267,1872,3713,2405,1785,4598, # 1840 +1953,2957,3103,3222,1728,4265,2044,3714,4599,2008,1701,3156,1551, 30,2268,4266, # 1856 +5207,2027,4600,3589,5208, 501,5209,4267, 594,3478,2166,1822,3590,3479,3591,3223, # 1872 + 829,2839,4268,5210,1680,3157,1225,4269,5211,3295,4601,4270,3158,2341,5212,4602, # 1888 +4271,5213,4015,4016,5214,1848,2388,2606,3367,5215,4603, 374,4017, 652,4272,4273, # 1904 + 375,1140, 798,5216,5217,5218,2366,4604,2269, 546,1659, 138,3051,2450,4605,5219, # 1920 +2254, 612,1849, 910, 796,3844,1740,1371, 825,3845,3846,5220,2920,2562,5221, 692, # 1936 + 444,3052,2634, 801,4606,4274,5222,1491, 244,1053,3053,4275,4276, 340,5223,4018, # 1952 +1041,3005, 293,1168, 87,1357,5224,1539, 959,5225,2240, 721, 694,4277,3847, 219, # 1968 +1478, 644,1417,3368,2666,1413,1401,1335,1389,4019,5226,5227,3006,2367,3159,1826, # 1984 + 730,1515, 184,2840, 66,4607,5228,1660,2958, 246,3369, 378,1457, 226,3480, 975, # 2000 +4020,2959,1264,3592, 674, 696,5229, 163,5230,1141,2422,2167, 713,3593,3370,4608, # 2016 +4021,5231,5232,1186, 15,5233,1079,1070,5234,1522,3224,3594, 276,1050,2725, 758, # 2032 +1126, 653,2960,3296,5235,2342, 889,3595,4022,3104,3007, 903,1250,4609,4023,3481, # 2048 +3596,1342,1681,1718, 766,3297, 286, 89,2961,3715,5236,1713,5237,2607,3371,3008, # 2064 +5238,2962,2219,3225,2880,5239,4610,2505,2533, 181, 387,1075,4024, 731,2190,3372, # 2080 +5240,3298, 310, 313,3482,2304, 770,4278, 54,3054, 189,4611,3105,3848,4025,5241, # 2096 +1230,1617,1850, 355,3597,4279,4612,3373, 111,4280,3716,1350,3160,3483,3055,4281, # 2112 +2150,3299,3598,5242,2797,4026,4027,3009, 722,2009,5243,1071, 247,1207,2343,2478, # 2128 +1378,4613,2010, 864,1437,1214,4614, 373,3849,1142,2220, 667,4615, 442,2763,2563, # 2144 +3850,4028,1969,4282,3300,1840, 837, 170,1107, 934,1336,1883,5244,5245,2119,4283, # 2160 +2841, 743,1569,5246,4616,4284, 582,2389,1418,3484,5247,1803,5248, 357,1395,1729, # 2176 +3717,3301,2423,1564,2241,5249,3106,3851,1633,4617,1114,2086,4285,1532,5250, 482, # 2192 +2451,4618,5251,5252,1492, 833,1466,5253,2726,3599,1641,2842,5254,1526,1272,3718, # 2208 +4286,1686,1795, 416,2564,1903,1954,1804,5255,3852,2798,3853,1159,2321,5256,2881, # 2224 +4619,1610,1584,3056,2424,2764, 443,3302,1163,3161,5257,5258,4029,5259,4287,2506, # 2240 +3057,4620,4030,3162,2104,1647,3600,2011,1873,4288,5260,4289, 431,3485,5261, 250, # 2256 + 97, 81,4290,5262,1648,1851,1558, 160, 848,5263, 866, 740,1694,5264,2204,2843, # 2272 +3226,4291,4621,3719,1687, 950,2479, 426, 469,3227,3720,3721,4031,5265,5266,1188, # 2288 + 424,1996, 861,3601,4292,3854,2205,2694, 168,1235,3602,4293,5267,2087,1674,4622, # 2304 +3374,3303, 220,2565,1009,5268,3855, 670,3010, 332,1208, 717,5269,5270,3603,2452, # 2320 +4032,3375,5271, 513,5272,1209,2882,3376,3163,4623,1080,5273,5274,5275,5276,2534, # 2336 +3722,3604, 815,1587,4033,4034,5277,3605,3486,3856,1254,4624,1328,3058,1390,4035, # 2352 +1741,4036,3857,4037,5278, 236,3858,2453,3304,5279,5280,3723,3859,1273,3860,4625, # 2368 +5281, 308,5282,4626, 245,4627,1852,2480,1307,2583, 430, 715,2137,2454,5283, 270, # 2384 + 199,2883,4038,5284,3606,2727,1753, 761,1754, 725,1661,1841,4628,3487,3724,5285, # 2400 +5286, 587, 14,3305, 227,2608, 326, 480,2270, 943,2765,3607, 291, 650,1884,5287, # 2416 +1702,1226, 102,1547, 62,3488, 904,4629,3489,1164,4294,5288,5289,1224,1548,2766, # 2432 + 391, 498,1493,5290,1386,1419,5291,2056,1177,4630, 813, 880,1081,2368, 566,1145, # 2448 +4631,2291,1001,1035,2566,2609,2242, 394,1286,5292,5293,2069,5294, 86,1494,1730, # 2464 +4039, 491,1588, 745, 897,2963, 843,3377,4040,2767,2884,3306,1768, 998,2221,2070, # 2480 + 397,1827,1195,1970,3725,3011,3378, 284,5295,3861,2507,2138,2120,1904,5296,4041, # 2496 +2151,4042,4295,1036,3490,1905, 114,2567,4296, 209,1527,5297,5298,2964,2844,2635, # 2512 +2390,2728,3164, 812,2568,5299,3307,5300,1559, 737,1885,3726,1210, 885, 28,2695, # 2528 +3608,3862,5301,4297,1004,1780,4632,5302, 346,1982,2222,2696,4633,3863,1742, 797, # 2544 +1642,4043,1934,1072,1384,2152, 896,4044,3308,3727,3228,2885,3609,5303,2569,1959, # 2560 +4634,2455,1786,5304,5305,5306,4045,4298,1005,1308,3728,4299,2729,4635,4636,1528, # 2576 +2610, 161,1178,4300,1983, 987,4637,1101,4301, 631,4046,1157,3229,2425,1343,1241, # 2592 +1016,2243,2570, 372, 877,2344,2508,1160, 555,1935, 911,4047,5307, 466,1170, 169, # 2608 +1051,2921,2697,3729,2481,3012,1182,2012,2571,1251,2636,5308, 992,2345,3491,1540, # 2624 +2730,1201,2071,2406,1997,2482,5309,4638, 528,1923,2191,1503,1874,1570,2369,3379, # 2640 +3309,5310, 557,1073,5311,1828,3492,2088,2271,3165,3059,3107, 767,3108,2799,4639, # 2656 +1006,4302,4640,2346,1267,2179,3730,3230, 778,4048,3231,2731,1597,2667,5312,4641, # 2672 +5313,3493,5314,5315,5316,3310,2698,1433,3311, 131, 95,1504,4049, 723,4303,3166, # 2688 +1842,3610,2768,2192,4050,2028,2105,3731,5317,3013,4051,1218,5318,3380,3232,4052, # 2704 +4304,2584, 248,1634,3864, 912,5319,2845,3732,3060,3865, 654, 53,5320,3014,5321, # 2720 +1688,4642, 777,3494,1032,4053,1425,5322, 191, 820,2121,2846, 971,4643, 931,3233, # 2736 + 135, 664, 783,3866,1998, 772,2922,1936,4054,3867,4644,2923,3234, 282,2732, 640, # 2752 +1372,3495,1127, 922, 325,3381,5323,5324, 711,2045,5325,5326,4055,2223,2800,1937, # 2768 +4056,3382,2224,2255,3868,2305,5327,4645,3869,1258,3312,4057,3235,2139,2965,4058, # 2784 +4059,5328,2225, 258,3236,4646, 101,1227,5329,3313,1755,5330,1391,3314,5331,2924, # 2800 +2057, 893,5332,5333,5334,1402,4305,2347,5335,5336,3237,3611,5337,5338, 878,1325, # 2816 +1781,2801,4647, 259,1385,2585, 744,1183,2272,4648,5339,4060,2509,5340, 684,1024, # 2832 +4306,5341, 472,3612,3496,1165,3315,4061,4062, 322,2153, 881, 455,1695,1152,1340, # 2848 + 660, 554,2154,4649,1058,4650,4307, 830,1065,3383,4063,4651,1924,5342,1703,1919, # 2864 +5343, 932,2273, 122,5344,4652, 947, 677,5345,3870,2637, 297,1906,1925,2274,4653, # 2880 +2322,3316,5346,5347,4308,5348,4309, 84,4310, 112, 989,5349, 547,1059,4064, 701, # 2896 +3613,1019,5350,4311,5351,3497, 942, 639, 457,2306,2456, 993,2966, 407, 851, 494, # 2912 +4654,3384, 927,5352,1237,5353,2426,3385, 573,4312, 680, 921,2925,1279,1875, 285, # 2928 + 790,1448,1984, 719,2168,5354,5355,4655,4065,4066,1649,5356,1541, 563,5357,1077, # 2944 +5358,3386,3061,3498, 511,3015,4067,4068,3733,4069,1268,2572,3387,3238,4656,4657, # 2960 +5359, 535,1048,1276,1189,2926,2029,3167,1438,1373,2847,2967,1134,2013,5360,4313, # 2976 +1238,2586,3109,1259,5361, 700,5362,2968,3168,3734,4314,5363,4315,1146,1876,1907, # 2992 +4658,2611,4070, 781,2427, 132,1589, 203, 147, 273,2802,2407, 898,1787,2155,4071, # 3008 +4072,5364,3871,2803,5365,5366,4659,4660,5367,3239,5368,1635,3872, 965,5369,1805, # 3024 +2699,1516,3614,1121,1082,1329,3317,4073,1449,3873, 65,1128,2848,2927,2769,1590, # 3040 +3874,5370,5371, 12,2668, 45, 976,2587,3169,4661, 517,2535,1013,1037,3240,5372, # 3056 +3875,2849,5373,3876,5374,3499,5375,2612, 614,1999,2323,3877,3110,2733,2638,5376, # 3072 +2588,4316, 599,1269,5377,1811,3735,5378,2700,3111, 759,1060, 489,1806,3388,3318, # 3088 +1358,5379,5380,2391,1387,1215,2639,2256, 490,5381,5382,4317,1759,2392,2348,5383, # 3104 +4662,3878,1908,4074,2640,1807,3241,4663,3500,3319,2770,2349, 874,5384,5385,3501, # 3120 +3736,1859, 91,2928,3737,3062,3879,4664,5386,3170,4075,2669,5387,3502,1202,1403, # 3136 +3880,2969,2536,1517,2510,4665,3503,2511,5388,4666,5389,2701,1886,1495,1731,4076, # 3152 +2370,4667,5390,2030,5391,5392,4077,2702,1216, 237,2589,4318,2324,4078,3881,4668, # 3168 +4669,2703,3615,3504, 445,4670,5393,5394,5395,5396,2771, 61,4079,3738,1823,4080, # 3184 +5397, 687,2046, 935, 925, 405,2670, 703,1096,1860,2734,4671,4081,1877,1367,2704, # 3200 +3389, 918,2106,1782,2483, 334,3320,1611,1093,4672, 564,3171,3505,3739,3390, 945, # 3216 +2641,2058,4673,5398,1926, 872,4319,5399,3506,2705,3112, 349,4320,3740,4082,4674, # 3232 +3882,4321,3741,2156,4083,4675,4676,4322,4677,2408,2047, 782,4084, 400, 251,4323, # 3248 +1624,5400,5401, 277,3742, 299,1265, 476,1191,3883,2122,4324,4325,1109, 205,5402, # 3264 +2590,1000,2157,3616,1861,5403,5404,5405,4678,5406,4679,2573, 107,2484,2158,4085, # 3280 +3507,3172,5407,1533, 541,1301, 158, 753,4326,2886,3617,5408,1696, 370,1088,4327, # 3296 +4680,3618, 579, 327, 440, 162,2244, 269,1938,1374,3508, 968,3063, 56,1396,3113, # 3312 +2107,3321,3391,5409,1927,2159,4681,3016,5410,3619,5411,5412,3743,4682,2485,5413, # 3328 +2804,5414,1650,4683,5415,2613,5416,5417,4086,2671,3392,1149,3393,4087,3884,4088, # 3344 +5418,1076, 49,5419, 951,3242,3322,3323, 450,2850, 920,5420,1812,2805,2371,4328, # 3360 +1909,1138,2372,3885,3509,5421,3243,4684,1910,1147,1518,2428,4685,3886,5422,4686, # 3376 +2393,2614, 260,1796,3244,5423,5424,3887,3324, 708,5425,3620,1704,5426,3621,1351, # 3392 +1618,3394,3017,1887, 944,4329,3395,4330,3064,3396,4331,5427,3744, 422, 413,1714, # 3408 +3325, 500,2059,2350,4332,2486,5428,1344,1911, 954,5429,1668,5430,5431,4089,2409, # 3424 +4333,3622,3888,4334,5432,2307,1318,2512,3114, 133,3115,2887,4687, 629, 31,2851, # 3440 +2706,3889,4688, 850, 949,4689,4090,2970,1732,2089,4335,1496,1853,5433,4091, 620, # 3456 +3245, 981,1242,3745,3397,1619,3746,1643,3326,2140,2457,1971,1719,3510,2169,5434, # 3472 +3246,5435,5436,3398,1829,5437,1277,4690,1565,2048,5438,1636,3623,3116,5439, 869, # 3488 +2852, 655,3890,3891,3117,4092,3018,3892,1310,3624,4691,5440,5441,5442,1733, 558, # 3504 +4692,3747, 335,1549,3065,1756,4336,3748,1946,3511,1830,1291,1192, 470,2735,2108, # 3520 +2806, 913,1054,4093,5443,1027,5444,3066,4094,4693, 982,2672,3399,3173,3512,3247, # 3536 +3248,1947,2807,5445, 571,4694,5446,1831,5447,3625,2591,1523,2429,5448,2090, 984, # 3552 +4695,3749,1960,5449,3750, 852, 923,2808,3513,3751, 969,1519, 999,2049,2325,1705, # 3568 +5450,3118, 615,1662, 151, 597,4095,2410,2326,1049, 275,4696,3752,4337, 568,3753, # 3584 +3626,2487,4338,3754,5451,2430,2275, 409,3249,5452,1566,2888,3514,1002, 769,2853, # 3600 + 194,2091,3174,3755,2226,3327,4339, 628,1505,5453,5454,1763,2180,3019,4096, 521, # 3616 +1161,2592,1788,2206,2411,4697,4097,1625,4340,4341, 412, 42,3119, 464,5455,2642, # 3632 +4698,3400,1760,1571,2889,3515,2537,1219,2207,3893,2643,2141,2373,4699,4700,3328, # 3648 +1651,3401,3627,5456,5457,3628,2488,3516,5458,3756,5459,5460,2276,2092, 460,5461, # 3664 +4701,5462,3020, 962, 588,3629, 289,3250,2644,1116, 52,5463,3067,1797,5464,5465, # 3680 +5466,1467,5467,1598,1143,3757,4342,1985,1734,1067,4702,1280,3402, 465,4703,1572, # 3696 + 510,5468,1928,2245,1813,1644,3630,5469,4704,3758,5470,5471,2673,1573,1534,5472, # 3712 +5473, 536,1808,1761,3517,3894,3175,2645,5474,5475,5476,4705,3518,2929,1912,2809, # 3728 +5477,3329,1122, 377,3251,5478, 360,5479,5480,4343,1529, 551,5481,2060,3759,1769, # 3744 +2431,5482,2930,4344,3330,3120,2327,2109,2031,4706,1404, 136,1468,1479, 672,1171, # 3760 +3252,2308, 271,3176,5483,2772,5484,2050, 678,2736, 865,1948,4707,5485,2014,4098, # 3776 +2971,5486,2737,2227,1397,3068,3760,4708,4709,1735,2931,3403,3631,5487,3895, 509, # 3792 +2854,2458,2890,3896,5488,5489,3177,3178,4710,4345,2538,4711,2309,1166,1010, 552, # 3808 + 681,1888,5490,5491,2972,2973,4099,1287,1596,1862,3179, 358, 453, 736, 175, 478, # 3824 +1117, 905,1167,1097,5492,1854,1530,5493,1706,5494,2181,3519,2292,3761,3520,3632, # 3840 +4346,2093,4347,5495,3404,1193,2489,4348,1458,2193,2208,1863,1889,1421,3331,2932, # 3856 +3069,2182,3521, 595,2123,5496,4100,5497,5498,4349,1707,2646, 223,3762,1359, 751, # 3872 +3121, 183,3522,5499,2810,3021, 419,2374, 633, 704,3897,2394, 241,5500,5501,5502, # 3888 + 838,3022,3763,2277,2773,2459,3898,1939,2051,4101,1309,3122,2246,1181,5503,1136, # 3904 +2209,3899,2375,1446,4350,2310,4712,5504,5505,4351,1055,2615, 484,3764,5506,4102, # 3920 + 625,4352,2278,3405,1499,4353,4103,5507,4104,4354,3253,2279,2280,3523,5508,5509, # 3936 +2774, 808,2616,3765,3406,4105,4355,3123,2539, 526,3407,3900,4356, 955,5510,1620, # 3952 +4357,2647,2432,5511,1429,3766,1669,1832, 994, 928,5512,3633,1260,5513,5514,5515, # 3968 +1949,2293, 741,2933,1626,4358,2738,2460, 867,1184, 362,3408,1392,5516,5517,4106, # 3984 +4359,1770,1736,3254,2934,4713,4714,1929,2707,1459,1158,5518,3070,3409,2891,1292, # 4000 +1930,2513,2855,3767,1986,1187,2072,2015,2617,4360,5519,2574,2514,2170,3768,2490, # 4016 +3332,5520,3769,4715,5521,5522, 666,1003,3023,1022,3634,4361,5523,4716,1814,2257, # 4032 + 574,3901,1603, 295,1535, 705,3902,4362, 283, 858, 417,5524,5525,3255,4717,4718, # 4048 +3071,1220,1890,1046,2281,2461,4107,1393,1599, 689,2575, 388,4363,5526,2491, 802, # 4064 +5527,2811,3903,2061,1405,2258,5528,4719,3904,2110,1052,1345,3256,1585,5529, 809, # 4080 +5530,5531,5532, 575,2739,3524, 956,1552,1469,1144,2328,5533,2329,1560,2462,3635, # 4096 +3257,4108, 616,2210,4364,3180,2183,2294,5534,1833,5535,3525,4720,5536,1319,3770, # 4112 +3771,1211,3636,1023,3258,1293,2812,5537,5538,5539,3905, 607,2311,3906, 762,2892, # 4128 +1439,4365,1360,4721,1485,3072,5540,4722,1038,4366,1450,2062,2648,4367,1379,4723, # 4144 +2593,5541,5542,4368,1352,1414,2330,2935,1172,5543,5544,3907,3908,4724,1798,1451, # 4160 +5545,5546,5547,5548,2936,4109,4110,2492,2351, 411,4111,4112,3637,3333,3124,4725, # 4176 +1561,2674,1452,4113,1375,5549,5550, 47,2974, 316,5551,1406,1591,2937,3181,5552, # 4192 +1025,2142,3125,3182, 354,2740, 884,2228,4369,2412, 508,3772, 726,3638, 996,2433, # 4208 +3639, 729,5553, 392,2194,1453,4114,4726,3773,5554,5555,2463,3640,2618,1675,2813, # 4224 + 919,2352,2975,2353,1270,4727,4115, 73,5556,5557, 647,5558,3259,2856,2259,1550, # 4240 +1346,3024,5559,1332, 883,3526,5560,5561,5562,5563,3334,2775,5564,1212, 831,1347, # 4256 +4370,4728,2331,3909,1864,3073, 720,3910,4729,4730,3911,5565,4371,5566,5567,4731, # 4272 +5568,5569,1799,4732,3774,2619,4733,3641,1645,2376,4734,5570,2938, 669,2211,2675, # 4288 +2434,5571,2893,5572,5573,1028,3260,5574,4372,2413,5575,2260,1353,5576,5577,4735, # 4304 +3183, 518,5578,4116,5579,4373,1961,5580,2143,4374,5581,5582,3025,2354,2355,3912, # 4320 + 516,1834,1454,4117,2708,4375,4736,2229,2620,1972,1129,3642,5583,2776,5584,2976, # 4336 +1422, 577,1470,3026,1524,3410,5585,5586, 432,4376,3074,3527,5587,2594,1455,2515, # 4352 +2230,1973,1175,5588,1020,2741,4118,3528,4737,5589,2742,5590,1743,1361,3075,3529, # 4368 +2649,4119,4377,4738,2295, 895, 924,4378,2171, 331,2247,3076, 166,1627,3077,1098, # 4384 +5591,1232,2894,2231,3411,4739, 657, 403,1196,2377, 542,3775,3412,1600,4379,3530, # 4400 +5592,4740,2777,3261, 576, 530,1362,4741,4742,2540,2676,3776,4120,5593, 842,3913, # 4416 +5594,2814,2032,1014,4121, 213,2709,3413, 665, 621,4380,5595,3777,2939,2435,5596, # 4432 +2436,3335,3643,3414,4743,4381,2541,4382,4744,3644,1682,4383,3531,1380,5597, 724, # 4448 +2282, 600,1670,5598,1337,1233,4745,3126,2248,5599,1621,4746,5600, 651,4384,5601, # 4464 +1612,4385,2621,5602,2857,5603,2743,2312,3078,5604, 716,2464,3079, 174,1255,2710, # 4480 +4122,3645, 548,1320,1398, 728,4123,1574,5605,1891,1197,3080,4124,5606,3081,3082, # 4496 +3778,3646,3779, 747,5607, 635,4386,4747,5608,5609,5610,4387,5611,5612,4748,5613, # 4512 +3415,4749,2437, 451,5614,3780,2542,2073,4388,2744,4389,4125,5615,1764,4750,5616, # 4528 +4390, 350,4751,2283,2395,2493,5617,4391,4126,2249,1434,4127, 488,4752, 458,4392, # 4544 +4128,3781, 771,1330,2396,3914,2576,3184,2160,2414,1553,2677,3185,4393,5618,2494, # 4560 +2895,2622,1720,2711,4394,3416,4753,5619,2543,4395,5620,3262,4396,2778,5621,2016, # 4576 +2745,5622,1155,1017,3782,3915,5623,3336,2313, 201,1865,4397,1430,5624,4129,5625, # 4592 +5626,5627,5628,5629,4398,1604,5630, 414,1866, 371,2595,4754,4755,3532,2017,3127, # 4608 +4756,1708, 960,4399, 887, 389,2172,1536,1663,1721,5631,2232,4130,2356,2940,1580, # 4624 +5632,5633,1744,4757,2544,4758,4759,5634,4760,5635,2074,5636,4761,3647,3417,2896, # 4640 +4400,5637,4401,2650,3418,2815, 673,2712,2465, 709,3533,4131,3648,4402,5638,1148, # 4656 + 502, 634,5639,5640,1204,4762,3649,1575,4763,2623,3783,5641,3784,3128, 948,3263, # 4672 + 121,1745,3916,1110,5642,4403,3083,2516,3027,4132,3785,1151,1771,3917,1488,4133, # 4688 +1987,5643,2438,3534,5644,5645,2094,5646,4404,3918,1213,1407,2816, 531,2746,2545, # 4704 +3264,1011,1537,4764,2779,4405,3129,1061,5647,3786,3787,1867,2897,5648,2018, 120, # 4720 +4406,4407,2063,3650,3265,2314,3919,2678,3419,1955,4765,4134,5649,3535,1047,2713, # 4736 +1266,5650,1368,4766,2858, 649,3420,3920,2546,2747,1102,2859,2679,5651,5652,2000, # 4752 +5653,1111,3651,2977,5654,2495,3921,3652,2817,1855,3421,3788,5655,5656,3422,2415, # 4768 +2898,3337,3266,3653,5657,2577,5658,3654,2818,4135,1460, 856,5659,3655,5660,2899, # 4784 +2978,5661,2900,3922,5662,4408, 632,2517, 875,3923,1697,3924,2296,5663,5664,4767, # 4800 +3028,1239, 580,4768,4409,5665, 914, 936,2075,1190,4136,1039,2124,5666,5667,5668, # 4816 +5669,3423,1473,5670,1354,4410,3925,4769,2173,3084,4137, 915,3338,4411,4412,3339, # 4832 +1605,1835,5671,2748, 398,3656,4413,3926,4138, 328,1913,2860,4139,3927,1331,4414, # 4848 +3029, 937,4415,5672,3657,4140,4141,3424,2161,4770,3425, 524, 742, 538,3085,1012, # 4864 +5673,5674,3928,2466,5675, 658,1103, 225,3929,5676,5677,4771,5678,4772,5679,3267, # 4880 +1243,5680,4142, 963,2250,4773,5681,2714,3658,3186,5682,5683,2596,2332,5684,4774, # 4896 +5685,5686,5687,3536, 957,3426,2547,2033,1931,2941,2467, 870,2019,3659,1746,2780, # 4912 +2781,2439,2468,5688,3930,5689,3789,3130,3790,3537,3427,3791,5690,1179,3086,5691, # 4928 +3187,2378,4416,3792,2548,3188,3131,2749,4143,5692,3428,1556,2549,2297, 977,2901, # 4944 +2034,4144,1205,3429,5693,1765,3430,3189,2125,1271, 714,1689,4775,3538,5694,2333, # 4960 +3931, 533,4417,3660,2184, 617,5695,2469,3340,3539,2315,5696,5697,3190,5698,5699, # 4976 +3932,1988, 618, 427,2651,3540,3431,5700,5701,1244,1690,5702,2819,4418,4776,5703, # 4992 +3541,4777,5704,2284,1576, 473,3661,4419,3432, 972,5705,3662,5706,3087,5707,5708, # 5008 +4778,4779,5709,3793,4145,4146,5710, 153,4780, 356,5711,1892,2902,4420,2144, 408, # 5024 + 803,2357,5712,3933,5713,4421,1646,2578,2518,4781,4782,3934,5714,3935,4422,5715, # 5040 +2416,3433, 752,5716,5717,1962,3341,2979,5718, 746,3030,2470,4783,4423,3794, 698, # 5056 +4784,1893,4424,3663,2550,4785,3664,3936,5719,3191,3434,5720,1824,1302,4147,2715, # 5072 +3937,1974,4425,5721,4426,3192, 823,1303,1288,1236,2861,3542,4148,3435, 774,3938, # 5088 +5722,1581,4786,1304,2862,3939,4787,5723,2440,2162,1083,3268,4427,4149,4428, 344, # 5104 +1173, 288,2316, 454,1683,5724,5725,1461,4788,4150,2597,5726,5727,4789, 985, 894, # 5120 +5728,3436,3193,5729,1914,2942,3795,1989,5730,2111,1975,5731,4151,5732,2579,1194, # 5136 + 425,5733,4790,3194,1245,3796,4429,5734,5735,2863,5736, 636,4791,1856,3940, 760, # 5152 +1800,5737,4430,2212,1508,4792,4152,1894,1684,2298,5738,5739,4793,4431,4432,2213, # 5168 + 479,5740,5741, 832,5742,4153,2496,5743,2980,2497,3797, 990,3132, 627,1815,2652, # 5184 +4433,1582,4434,2126,2112,3543,4794,5744, 799,4435,3195,5745,4795,2113,1737,3031, # 5200 +1018, 543, 754,4436,3342,1676,4796,4797,4154,4798,1489,5746,3544,5747,2624,2903, # 5216 +4155,5748,5749,2981,5750,5751,5752,5753,3196,4799,4800,2185,1722,5754,3269,3270, # 5232 +1843,3665,1715, 481, 365,1976,1857,5755,5756,1963,2498,4801,5757,2127,3666,3271, # 5248 + 433,1895,2064,2076,5758, 602,2750,5759,5760,5761,5762,5763,3032,1628,3437,5764, # 5264 +3197,4802,4156,2904,4803,2519,5765,2551,2782,5766,5767,5768,3343,4804,2905,5769, # 5280 +4805,5770,2864,4806,4807,1221,2982,4157,2520,5771,5772,5773,1868,1990,5774,5775, # 5296 +5776,1896,5777,5778,4808,1897,4158, 318,5779,2095,4159,4437,5780,5781, 485,5782, # 5312 + 938,3941, 553,2680, 116,5783,3942,3667,5784,3545,2681,2783,3438,3344,2820,5785, # 5328 +3668,2943,4160,1747,2944,2983,5786,5787, 207,5788,4809,5789,4810,2521,5790,3033, # 5344 + 890,3669,3943,5791,1878,3798,3439,5792,2186,2358,3440,1652,5793,5794,5795, 941, # 5360 +2299, 208,3546,4161,2020, 330,4438,3944,2906,2499,3799,4439,4811,5796,5797,5798, # 5376 #last 512 +#Everything below is of no interest for detection purpose +2522,1613,4812,5799,3345,3945,2523,5800,4162,5801,1637,4163,2471,4813,3946,5802, # 5392 +2500,3034,3800,5803,5804,2195,4814,5805,2163,5806,5807,5808,5809,5810,5811,5812, # 5408 +5813,5814,5815,5816,5817,5818,5819,5820,5821,5822,5823,5824,5825,5826,5827,5828, # 5424 +5829,5830,5831,5832,5833,5834,5835,5836,5837,5838,5839,5840,5841,5842,5843,5844, # 5440 +5845,5846,5847,5848,5849,5850,5851,5852,5853,5854,5855,5856,5857,5858,5859,5860, # 5456 +5861,5862,5863,5864,5865,5866,5867,5868,5869,5870,5871,5872,5873,5874,5875,5876, # 5472 +5877,5878,5879,5880,5881,5882,5883,5884,5885,5886,5887,5888,5889,5890,5891,5892, # 5488 +5893,5894,5895,5896,5897,5898,5899,5900,5901,5902,5903,5904,5905,5906,5907,5908, # 5504 +5909,5910,5911,5912,5913,5914,5915,5916,5917,5918,5919,5920,5921,5922,5923,5924, # 5520 +5925,5926,5927,5928,5929,5930,5931,5932,5933,5934,5935,5936,5937,5938,5939,5940, # 5536 +5941,5942,5943,5944,5945,5946,5947,5948,5949,5950,5951,5952,5953,5954,5955,5956, # 5552 +5957,5958,5959,5960,5961,5962,5963,5964,5965,5966,5967,5968,5969,5970,5971,5972, # 5568 +5973,5974,5975,5976,5977,5978,5979,5980,5981,5982,5983,5984,5985,5986,5987,5988, # 5584 +5989,5990,5991,5992,5993,5994,5995,5996,5997,5998,5999,6000,6001,6002,6003,6004, # 5600 +6005,6006,6007,6008,6009,6010,6011,6012,6013,6014,6015,6016,6017,6018,6019,6020, # 5616 +6021,6022,6023,6024,6025,6026,6027,6028,6029,6030,6031,6032,6033,6034,6035,6036, # 5632 +6037,6038,6039,6040,6041,6042,6043,6044,6045,6046,6047,6048,6049,6050,6051,6052, # 5648 +6053,6054,6055,6056,6057,6058,6059,6060,6061,6062,6063,6064,6065,6066,6067,6068, # 5664 +6069,6070,6071,6072,6073,6074,6075,6076,6077,6078,6079,6080,6081,6082,6083,6084, # 5680 +6085,6086,6087,6088,6089,6090,6091,6092,6093,6094,6095,6096,6097,6098,6099,6100, # 5696 +6101,6102,6103,6104,6105,6106,6107,6108,6109,6110,6111,6112,6113,6114,6115,6116, # 5712 +6117,6118,6119,6120,6121,6122,6123,6124,6125,6126,6127,6128,6129,6130,6131,6132, # 5728 +6133,6134,6135,6136,6137,6138,6139,6140,6141,6142,6143,6144,6145,6146,6147,6148, # 5744 +6149,6150,6151,6152,6153,6154,6155,6156,6157,6158,6159,6160,6161,6162,6163,6164, # 5760 +6165,6166,6167,6168,6169,6170,6171,6172,6173,6174,6175,6176,6177,6178,6179,6180, # 5776 +6181,6182,6183,6184,6185,6186,6187,6188,6189,6190,6191,6192,6193,6194,6195,6196, # 5792 +6197,6198,6199,6200,6201,6202,6203,6204,6205,6206,6207,6208,6209,6210,6211,6212, # 5808 +6213,6214,6215,6216,6217,6218,6219,6220,6221,6222,6223,3670,6224,6225,6226,6227, # 5824 +6228,6229,6230,6231,6232,6233,6234,6235,6236,6237,6238,6239,6240,6241,6242,6243, # 5840 +6244,6245,6246,6247,6248,6249,6250,6251,6252,6253,6254,6255,6256,6257,6258,6259, # 5856 +6260,6261,6262,6263,6264,6265,6266,6267,6268,6269,6270,6271,6272,6273,6274,6275, # 5872 +6276,6277,6278,6279,6280,6281,6282,6283,6284,6285,4815,6286,6287,6288,6289,6290, # 5888 +6291,6292,4816,6293,6294,6295,6296,6297,6298,6299,6300,6301,6302,6303,6304,6305, # 5904 +6306,6307,6308,6309,6310,6311,4817,4818,6312,6313,6314,6315,6316,6317,6318,4819, # 5920 +6319,6320,6321,6322,6323,6324,6325,6326,6327,6328,6329,6330,6331,6332,6333,6334, # 5936 +6335,6336,6337,4820,6338,6339,6340,6341,6342,6343,6344,6345,6346,6347,6348,6349, # 5952 +6350,6351,6352,6353,6354,6355,6356,6357,6358,6359,6360,6361,6362,6363,6364,6365, # 5968 +6366,6367,6368,6369,6370,6371,6372,6373,6374,6375,6376,6377,6378,6379,6380,6381, # 5984 +6382,6383,6384,6385,6386,6387,6388,6389,6390,6391,6392,6393,6394,6395,6396,6397, # 6000 +6398,6399,6400,6401,6402,6403,6404,6405,6406,6407,6408,6409,6410,3441,6411,6412, # 6016 +6413,6414,6415,6416,6417,6418,6419,6420,6421,6422,6423,6424,6425,4440,6426,6427, # 6032 +6428,6429,6430,6431,6432,6433,6434,6435,6436,6437,6438,6439,6440,6441,6442,6443, # 6048 +6444,6445,6446,6447,6448,6449,6450,6451,6452,6453,6454,4821,6455,6456,6457,6458, # 6064 +6459,6460,6461,6462,6463,6464,6465,6466,6467,6468,6469,6470,6471,6472,6473,6474, # 6080 +6475,6476,6477,3947,3948,6478,6479,6480,6481,3272,4441,6482,6483,6484,6485,4442, # 6096 +6486,6487,6488,6489,6490,6491,6492,6493,6494,6495,6496,4822,6497,6498,6499,6500, # 6112 +6501,6502,6503,6504,6505,6506,6507,6508,6509,6510,6511,6512,6513,6514,6515,6516, # 6128 +6517,6518,6519,6520,6521,6522,6523,6524,6525,6526,6527,6528,6529,6530,6531,6532, # 6144 +6533,6534,6535,6536,6537,6538,6539,6540,6541,6542,6543,6544,6545,6546,6547,6548, # 6160 +6549,6550,6551,6552,6553,6554,6555,6556,2784,6557,4823,6558,6559,6560,6561,6562, # 6176 +6563,6564,6565,6566,6567,6568,6569,3949,6570,6571,6572,4824,6573,6574,6575,6576, # 6192 +6577,6578,6579,6580,6581,6582,6583,4825,6584,6585,6586,3950,2785,6587,6588,6589, # 6208 +6590,6591,6592,6593,6594,6595,6596,6597,6598,6599,6600,6601,6602,6603,6604,6605, # 6224 +6606,6607,6608,6609,6610,6611,6612,4826,6613,6614,6615,4827,6616,6617,6618,6619, # 6240 +6620,6621,6622,6623,6624,6625,4164,6626,6627,6628,6629,6630,6631,6632,6633,6634, # 6256 +3547,6635,4828,6636,6637,6638,6639,6640,6641,6642,3951,2984,6643,6644,6645,6646, # 6272 +6647,6648,6649,4165,6650,4829,6651,6652,4830,6653,6654,6655,6656,6657,6658,6659, # 6288 +6660,6661,6662,4831,6663,6664,6665,6666,6667,6668,6669,6670,6671,4166,6672,4832, # 6304 +3952,6673,6674,6675,6676,4833,6677,6678,6679,4167,6680,6681,6682,3198,6683,6684, # 6320 +6685,6686,6687,6688,6689,6690,6691,6692,6693,6694,6695,6696,6697,4834,6698,6699, # 6336 +6700,6701,6702,6703,6704,6705,6706,6707,6708,6709,6710,6711,6712,6713,6714,6715, # 6352 +6716,6717,6718,6719,6720,6721,6722,6723,6724,6725,6726,6727,6728,6729,6730,6731, # 6368 +6732,6733,6734,4443,6735,6736,6737,6738,6739,6740,6741,6742,6743,6744,6745,4444, # 6384 +6746,6747,6748,6749,6750,6751,6752,6753,6754,6755,6756,6757,6758,6759,6760,6761, # 6400 +6762,6763,6764,6765,6766,6767,6768,6769,6770,6771,6772,6773,6774,6775,6776,6777, # 6416 +6778,6779,6780,6781,4168,6782,6783,3442,6784,6785,6786,6787,6788,6789,6790,6791, # 6432 +4169,6792,6793,6794,6795,6796,6797,6798,6799,6800,6801,6802,6803,6804,6805,6806, # 6448 +6807,6808,6809,6810,6811,4835,6812,6813,6814,4445,6815,6816,4446,6817,6818,6819, # 6464 +6820,6821,6822,6823,6824,6825,6826,6827,6828,6829,6830,6831,6832,6833,6834,6835, # 6480 +3548,6836,6837,6838,6839,6840,6841,6842,6843,6844,6845,6846,4836,6847,6848,6849, # 6496 +6850,6851,6852,6853,6854,3953,6855,6856,6857,6858,6859,6860,6861,6862,6863,6864, # 6512 +6865,6866,6867,6868,6869,6870,6871,6872,6873,6874,6875,6876,6877,3199,6878,6879, # 6528 +6880,6881,6882,4447,6883,6884,6885,6886,6887,6888,6889,6890,6891,6892,6893,6894, # 6544 +6895,6896,6897,6898,6899,6900,6901,6902,6903,6904,4170,6905,6906,6907,6908,6909, # 6560 +6910,6911,6912,6913,6914,6915,6916,6917,6918,6919,6920,6921,6922,6923,6924,6925, # 6576 +6926,6927,4837,6928,6929,6930,6931,6932,6933,6934,6935,6936,3346,6937,6938,4838, # 6592 +6939,6940,6941,4448,6942,6943,6944,6945,6946,4449,6947,6948,6949,6950,6951,6952, # 6608 +6953,6954,6955,6956,6957,6958,6959,6960,6961,6962,6963,6964,6965,6966,6967,6968, # 6624 +6969,6970,6971,6972,6973,6974,6975,6976,6977,6978,6979,6980,6981,6982,6983,6984, # 6640 +6985,6986,6987,6988,6989,6990,6991,6992,6993,6994,3671,6995,6996,6997,6998,4839, # 6656 +6999,7000,7001,7002,3549,7003,7004,7005,7006,7007,7008,7009,7010,7011,7012,7013, # 6672 +7014,7015,7016,7017,7018,7019,7020,7021,7022,7023,7024,7025,7026,7027,7028,7029, # 6688 +7030,4840,7031,7032,7033,7034,7035,7036,7037,7038,4841,7039,7040,7041,7042,7043, # 6704 +7044,7045,7046,7047,7048,7049,7050,7051,7052,7053,7054,7055,7056,7057,7058,7059, # 6720 +7060,7061,7062,7063,7064,7065,7066,7067,7068,7069,7070,2985,7071,7072,7073,7074, # 6736 +7075,7076,7077,7078,7079,7080,4842,7081,7082,7083,7084,7085,7086,7087,7088,7089, # 6752 +7090,7091,7092,7093,7094,7095,7096,7097,7098,7099,7100,7101,7102,7103,7104,7105, # 6768 +7106,7107,7108,7109,7110,7111,7112,7113,7114,7115,7116,7117,7118,4450,7119,7120, # 6784 +7121,7122,7123,7124,7125,7126,7127,7128,7129,7130,7131,7132,7133,7134,7135,7136, # 6800 +7137,7138,7139,7140,7141,7142,7143,4843,7144,7145,7146,7147,7148,7149,7150,7151, # 6816 +7152,7153,7154,7155,7156,7157,7158,7159,7160,7161,7162,7163,7164,7165,7166,7167, # 6832 +7168,7169,7170,7171,7172,7173,7174,7175,7176,7177,7178,7179,7180,7181,7182,7183, # 6848 +7184,7185,7186,7187,7188,4171,4172,7189,7190,7191,7192,7193,7194,7195,7196,7197, # 6864 +7198,7199,7200,7201,7202,7203,7204,7205,7206,7207,7208,7209,7210,7211,7212,7213, # 6880 +7214,7215,7216,7217,7218,7219,7220,7221,7222,7223,7224,7225,7226,7227,7228,7229, # 6896 +7230,7231,7232,7233,7234,7235,7236,7237,7238,7239,7240,7241,7242,7243,7244,7245, # 6912 +7246,7247,7248,7249,7250,7251,7252,7253,7254,7255,7256,7257,7258,7259,7260,7261, # 6928 +7262,7263,7264,7265,7266,7267,7268,7269,7270,7271,7272,7273,7274,7275,7276,7277, # 6944 +7278,7279,7280,7281,7282,7283,7284,7285,7286,7287,7288,7289,7290,7291,7292,7293, # 6960 +7294,7295,7296,4844,7297,7298,7299,7300,7301,7302,7303,7304,7305,7306,7307,7308, # 6976 +7309,7310,7311,7312,7313,7314,7315,7316,4451,7317,7318,7319,7320,7321,7322,7323, # 6992 +7324,7325,7326,7327,7328,7329,7330,7331,7332,7333,7334,7335,7336,7337,7338,7339, # 7008 +7340,7341,7342,7343,7344,7345,7346,7347,7348,7349,7350,7351,7352,7353,4173,7354, # 7024 +7355,4845,7356,7357,7358,7359,7360,7361,7362,7363,7364,7365,7366,7367,7368,7369, # 7040 +7370,7371,7372,7373,7374,7375,7376,7377,7378,7379,7380,7381,7382,7383,7384,7385, # 7056 +7386,7387,7388,4846,7389,7390,7391,7392,7393,7394,7395,7396,7397,7398,7399,7400, # 7072 +7401,7402,7403,7404,7405,3672,7406,7407,7408,7409,7410,7411,7412,7413,7414,7415, # 7088 +7416,7417,7418,7419,7420,7421,7422,7423,7424,7425,7426,7427,7428,7429,7430,7431, # 7104 +7432,7433,7434,7435,7436,7437,7438,7439,7440,7441,7442,7443,7444,7445,7446,7447, # 7120 +7448,7449,7450,7451,7452,7453,4452,7454,3200,7455,7456,7457,7458,7459,7460,7461, # 7136 +7462,7463,7464,7465,7466,7467,7468,7469,7470,7471,7472,7473,7474,4847,7475,7476, # 7152 +7477,3133,7478,7479,7480,7481,7482,7483,7484,7485,7486,7487,7488,7489,7490,7491, # 7168 +7492,7493,7494,7495,7496,7497,7498,7499,7500,7501,7502,3347,7503,7504,7505,7506, # 7184 +7507,7508,7509,7510,7511,7512,7513,7514,7515,7516,7517,7518,7519,7520,7521,4848, # 7200 +7522,7523,7524,7525,7526,7527,7528,7529,7530,7531,7532,7533,7534,7535,7536,7537, # 7216 +7538,7539,7540,7541,7542,7543,7544,7545,7546,7547,7548,7549,3801,4849,7550,7551, # 7232 +7552,7553,7554,7555,7556,7557,7558,7559,7560,7561,7562,7563,7564,7565,7566,7567, # 7248 +7568,7569,3035,7570,7571,7572,7573,7574,7575,7576,7577,7578,7579,7580,7581,7582, # 7264 +7583,7584,7585,7586,7587,7588,7589,7590,7591,7592,7593,7594,7595,7596,7597,7598, # 7280 +7599,7600,7601,7602,7603,7604,7605,7606,7607,7608,7609,7610,7611,7612,7613,7614, # 7296 +7615,7616,4850,7617,7618,3802,7619,7620,7621,7622,7623,7624,7625,7626,7627,7628, # 7312 +7629,7630,7631,7632,4851,7633,7634,7635,7636,7637,7638,7639,7640,7641,7642,7643, # 7328 +7644,7645,7646,7647,7648,7649,7650,7651,7652,7653,7654,7655,7656,7657,7658,7659, # 7344 +7660,7661,7662,7663,7664,7665,7666,7667,7668,7669,7670,4453,7671,7672,7673,7674, # 7360 +7675,7676,7677,7678,7679,7680,7681,7682,7683,7684,7685,7686,7687,7688,7689,7690, # 7376 +7691,7692,7693,7694,7695,7696,7697,3443,7698,7699,7700,7701,7702,4454,7703,7704, # 7392 +7705,7706,7707,7708,7709,7710,7711,7712,7713,2472,7714,7715,7716,7717,7718,7719, # 7408 +7720,7721,7722,7723,7724,7725,7726,7727,7728,7729,7730,7731,3954,7732,7733,7734, # 7424 +7735,7736,7737,7738,7739,7740,7741,7742,7743,7744,7745,7746,7747,7748,7749,7750, # 7440 +3134,7751,7752,4852,7753,7754,7755,4853,7756,7757,7758,7759,7760,4174,7761,7762, # 7456 +7763,7764,7765,7766,7767,7768,7769,7770,7771,7772,7773,7774,7775,7776,7777,7778, # 7472 +7779,7780,7781,7782,7783,7784,7785,7786,7787,7788,7789,7790,7791,7792,7793,7794, # 7488 +7795,7796,7797,7798,7799,7800,7801,7802,7803,7804,7805,4854,7806,7807,7808,7809, # 7504 +7810,7811,7812,7813,7814,7815,7816,7817,7818,7819,7820,7821,7822,7823,7824,7825, # 7520 +4855,7826,7827,7828,7829,7830,7831,7832,7833,7834,7835,7836,7837,7838,7839,7840, # 7536 +7841,7842,7843,7844,7845,7846,7847,3955,7848,7849,7850,7851,7852,7853,7854,7855, # 7552 +7856,7857,7858,7859,7860,3444,7861,7862,7863,7864,7865,7866,7867,7868,7869,7870, # 7568 +7871,7872,7873,7874,7875,7876,7877,7878,7879,7880,7881,7882,7883,7884,7885,7886, # 7584 +7887,7888,7889,7890,7891,4175,7892,7893,7894,7895,7896,4856,4857,7897,7898,7899, # 7600 +7900,2598,7901,7902,7903,7904,7905,7906,7907,7908,4455,7909,7910,7911,7912,7913, # 7616 +7914,3201,7915,7916,7917,7918,7919,7920,7921,4858,7922,7923,7924,7925,7926,7927, # 7632 +7928,7929,7930,7931,7932,7933,7934,7935,7936,7937,7938,7939,7940,7941,7942,7943, # 7648 +7944,7945,7946,7947,7948,7949,7950,7951,7952,7953,7954,7955,7956,7957,7958,7959, # 7664 +7960,7961,7962,7963,7964,7965,7966,7967,7968,7969,7970,7971,7972,7973,7974,7975, # 7680 +7976,7977,7978,7979,7980,7981,4859,7982,7983,7984,7985,7986,7987,7988,7989,7990, # 7696 +7991,7992,7993,7994,7995,7996,4860,7997,7998,7999,8000,8001,8002,8003,8004,8005, # 7712 +8006,8007,8008,8009,8010,8011,8012,8013,8014,8015,8016,4176,8017,8018,8019,8020, # 7728 +8021,8022,8023,4861,8024,8025,8026,8027,8028,8029,8030,8031,8032,8033,8034,8035, # 7744 +8036,4862,4456,8037,8038,8039,8040,4863,8041,8042,8043,8044,8045,8046,8047,8048, # 7760 +8049,8050,8051,8052,8053,8054,8055,8056,8057,8058,8059,8060,8061,8062,8063,8064, # 7776 +8065,8066,8067,8068,8069,8070,8071,8072,8073,8074,8075,8076,8077,8078,8079,8080, # 7792 +8081,8082,8083,8084,8085,8086,8087,8088,8089,8090,8091,8092,8093,8094,8095,8096, # 7808 +8097,8098,8099,4864,4177,8100,8101,8102,8103,8104,8105,8106,8107,8108,8109,8110, # 7824 +8111,8112,8113,8114,8115,8116,8117,8118,8119,8120,4178,8121,8122,8123,8124,8125, # 7840 +8126,8127,8128,8129,8130,8131,8132,8133,8134,8135,8136,8137,8138,8139,8140,8141, # 7856 +8142,8143,8144,8145,4865,4866,8146,8147,8148,8149,8150,8151,8152,8153,8154,8155, # 7872 +8156,8157,8158,8159,8160,8161,8162,8163,8164,8165,4179,8166,8167,8168,8169,8170, # 7888 +8171,8172,8173,8174,8175,8176,8177,8178,8179,8180,8181,4457,8182,8183,8184,8185, # 7904 +8186,8187,8188,8189,8190,8191,8192,8193,8194,8195,8196,8197,8198,8199,8200,8201, # 7920 +8202,8203,8204,8205,8206,8207,8208,8209,8210,8211,8212,8213,8214,8215,8216,8217, # 7936 +8218,8219,8220,8221,8222,8223,8224,8225,8226,8227,8228,8229,8230,8231,8232,8233, # 7952 +8234,8235,8236,8237,8238,8239,8240,8241,8242,8243,8244,8245,8246,8247,8248,8249, # 7968 +8250,8251,8252,8253,8254,8255,8256,3445,8257,8258,8259,8260,8261,8262,4458,8263, # 7984 +8264,8265,8266,8267,8268,8269,8270,8271,8272,4459,8273,8274,8275,8276,3550,8277, # 8000 +8278,8279,8280,8281,8282,8283,8284,8285,8286,8287,8288,8289,4460,8290,8291,8292, # 8016 +8293,8294,8295,8296,8297,8298,8299,8300,8301,8302,8303,8304,8305,8306,8307,4867, # 8032 +8308,8309,8310,8311,8312,3551,8313,8314,8315,8316,8317,8318,8319,8320,8321,8322, # 8048 +8323,8324,8325,8326,4868,8327,8328,8329,8330,8331,8332,8333,8334,8335,8336,8337, # 8064 +8338,8339,8340,8341,8342,8343,8344,8345,8346,8347,8348,8349,8350,8351,8352,8353, # 8080 +8354,8355,8356,8357,8358,8359,8360,8361,8362,8363,4869,4461,8364,8365,8366,8367, # 8096 +8368,8369,8370,4870,8371,8372,8373,8374,8375,8376,8377,8378,8379,8380,8381,8382, # 8112 +8383,8384,8385,8386,8387,8388,8389,8390,8391,8392,8393,8394,8395,8396,8397,8398, # 8128 +8399,8400,8401,8402,8403,8404,8405,8406,8407,8408,8409,8410,4871,8411,8412,8413, # 8144 +8414,8415,8416,8417,8418,8419,8420,8421,8422,4462,8423,8424,8425,8426,8427,8428, # 8160 +8429,8430,8431,8432,8433,2986,8434,8435,8436,8437,8438,8439,8440,8441,8442,8443, # 8176 +8444,8445,8446,8447,8448,8449,8450,8451,8452,8453,8454,8455,8456,8457,8458,8459, # 8192 +8460,8461,8462,8463,8464,8465,8466,8467,8468,8469,8470,8471,8472,8473,8474,8475, # 8208 +8476,8477,8478,4180,8479,8480,8481,8482,8483,8484,8485,8486,8487,8488,8489,8490, # 8224 +8491,8492,8493,8494,8495,8496,8497,8498,8499,8500,8501,8502,8503,8504,8505,8506, # 8240 +8507,8508,8509,8510,8511,8512,8513,8514,8515,8516,8517,8518,8519,8520,8521,8522, # 8256 +8523,8524,8525,8526,8527,8528,8529,8530,8531,8532,8533,8534,8535,8536,8537,8538, # 8272 +8539,8540,8541,8542,8543,8544,8545,8546,8547,8548,8549,8550,8551,8552,8553,8554, # 8288 +8555,8556,8557,8558,8559,8560,8561,8562,8563,8564,4872,8565,8566,8567,8568,8569, # 8304 +8570,8571,8572,8573,4873,8574,8575,8576,8577,8578,8579,8580,8581,8582,8583,8584, # 8320 +8585,8586,8587,8588,8589,8590,8591,8592,8593,8594,8595,8596,8597,8598,8599,8600, # 8336 +8601,8602,8603,8604,8605,3803,8606,8607,8608,8609,8610,8611,8612,8613,4874,3804, # 8352 +8614,8615,8616,8617,8618,8619,8620,8621,3956,8622,8623,8624,8625,8626,8627,8628, # 8368 +8629,8630,8631,8632,8633,8634,8635,8636,8637,8638,2865,8639,8640,8641,8642,8643, # 8384 +8644,8645,8646,8647,8648,8649,8650,8651,8652,8653,8654,8655,8656,4463,8657,8658, # 8400 +8659,4875,4876,8660,8661,8662,8663,8664,8665,8666,8667,8668,8669,8670,8671,8672, # 8416 +8673,8674,8675,8676,8677,8678,8679,8680,8681,4464,8682,8683,8684,8685,8686,8687, # 8432 +8688,8689,8690,8691,8692,8693,8694,8695,8696,8697,8698,8699,8700,8701,8702,8703, # 8448 +8704,8705,8706,8707,8708,8709,2261,8710,8711,8712,8713,8714,8715,8716,8717,8718, # 8464 +8719,8720,8721,8722,8723,8724,8725,8726,8727,8728,8729,8730,8731,8732,8733,4181, # 8480 +8734,8735,8736,8737,8738,8739,8740,8741,8742,8743,8744,8745,8746,8747,8748,8749, # 8496 +8750,8751,8752,8753,8754,8755,8756,8757,8758,8759,8760,8761,8762,8763,4877,8764, # 8512 +8765,8766,8767,8768,8769,8770,8771,8772,8773,8774,8775,8776,8777,8778,8779,8780, # 8528 +8781,8782,8783,8784,8785,8786,8787,8788,4878,8789,4879,8790,8791,8792,4880,8793, # 8544 +8794,8795,8796,8797,8798,8799,8800,8801,4881,8802,8803,8804,8805,8806,8807,8808, # 8560 +8809,8810,8811,8812,8813,8814,8815,3957,8816,8817,8818,8819,8820,8821,8822,8823, # 8576 +8824,8825,8826,8827,8828,8829,8830,8831,8832,8833,8834,8835,8836,8837,8838,8839, # 8592 +8840,8841,8842,8843,8844,8845,8846,8847,4882,8848,8849,8850,8851,8852,8853,8854, # 8608 +8855,8856,8857,8858,8859,8860,8861,8862,8863,8864,8865,8866,8867,8868,8869,8870, # 8624 +8871,8872,8873,8874,8875,8876,8877,8878,8879,8880,8881,8882,8883,8884,3202,8885, # 8640 +8886,8887,8888,8889,8890,8891,8892,8893,8894,8895,8896,8897,8898,8899,8900,8901, # 8656 +8902,8903,8904,8905,8906,8907,8908,8909,8910,8911,8912,8913,8914,8915,8916,8917, # 8672 +8918,8919,8920,8921,8922,8923,8924,4465,8925,8926,8927,8928,8929,8930,8931,8932, # 8688 +4883,8933,8934,8935,8936,8937,8938,8939,8940,8941,8942,8943,2214,8944,8945,8946, # 8704 +8947,8948,8949,8950,8951,8952,8953,8954,8955,8956,8957,8958,8959,8960,8961,8962, # 8720 +8963,8964,8965,4884,8966,8967,8968,8969,8970,8971,8972,8973,8974,8975,8976,8977, # 8736 +8978,8979,8980,8981,8982,8983,8984,8985,8986,8987,8988,8989,8990,8991,8992,4885, # 8752 +8993,8994,8995,8996,8997,8998,8999,9000,9001,9002,9003,9004,9005,9006,9007,9008, # 8768 +9009,9010,9011,9012,9013,9014,9015,9016,9017,9018,9019,9020,9021,4182,9022,9023, # 8784 +9024,9025,9026,9027,9028,9029,9030,9031,9032,9033,9034,9035,9036,9037,9038,9039, # 8800 +9040,9041,9042,9043,9044,9045,9046,9047,9048,9049,9050,9051,9052,9053,9054,9055, # 8816 +9056,9057,9058,9059,9060,9061,9062,9063,4886,9064,9065,9066,9067,9068,9069,4887, # 8832 +9070,9071,9072,9073,9074,9075,9076,9077,9078,9079,9080,9081,9082,9083,9084,9085, # 8848 +9086,9087,9088,9089,9090,9091,9092,9093,9094,9095,9096,9097,9098,9099,9100,9101, # 8864 +9102,9103,9104,9105,9106,9107,9108,9109,9110,9111,9112,9113,9114,9115,9116,9117, # 8880 +9118,9119,9120,9121,9122,9123,9124,9125,9126,9127,9128,9129,9130,9131,9132,9133, # 8896 +9134,9135,9136,9137,9138,9139,9140,9141,3958,9142,9143,9144,9145,9146,9147,9148, # 8912 +9149,9150,9151,4888,9152,9153,9154,9155,9156,9157,9158,9159,9160,9161,9162,9163, # 8928 +9164,9165,9166,9167,9168,9169,9170,9171,9172,9173,9174,9175,4889,9176,9177,9178, # 8944 +9179,9180,9181,9182,9183,9184,9185,9186,9187,9188,9189,9190,9191,9192,9193,9194, # 8960 +9195,9196,9197,9198,9199,9200,9201,9202,9203,4890,9204,9205,9206,9207,9208,9209, # 8976 +9210,9211,9212,9213,9214,9215,9216,9217,9218,9219,9220,9221,9222,4466,9223,9224, # 8992 +9225,9226,9227,9228,9229,9230,9231,9232,9233,9234,9235,9236,9237,9238,9239,9240, # 9008 +9241,9242,9243,9244,9245,4891,9246,9247,9248,9249,9250,9251,9252,9253,9254,9255, # 9024 +9256,9257,4892,9258,9259,9260,9261,4893,4894,9262,9263,9264,9265,9266,9267,9268, # 9040 +9269,9270,9271,9272,9273,4467,9274,9275,9276,9277,9278,9279,9280,9281,9282,9283, # 9056 +9284,9285,3673,9286,9287,9288,9289,9290,9291,9292,9293,9294,9295,9296,9297,9298, # 9072 +9299,9300,9301,9302,9303,9304,9305,9306,9307,9308,9309,9310,9311,9312,9313,9314, # 9088 +9315,9316,9317,9318,9319,9320,9321,9322,4895,9323,9324,9325,9326,9327,9328,9329, # 9104 +9330,9331,9332,9333,9334,9335,9336,9337,9338,9339,9340,9341,9342,9343,9344,9345, # 9120 +9346,9347,4468,9348,9349,9350,9351,9352,9353,9354,9355,9356,9357,9358,9359,9360, # 9136 +9361,9362,9363,9364,9365,9366,9367,9368,9369,9370,9371,9372,9373,4896,9374,4469, # 9152 +9375,9376,9377,9378,9379,4897,9380,9381,9382,9383,9384,9385,9386,9387,9388,9389, # 9168 +9390,9391,9392,9393,9394,9395,9396,9397,9398,9399,9400,9401,9402,9403,9404,9405, # 9184 +9406,4470,9407,2751,9408,9409,3674,3552,9410,9411,9412,9413,9414,9415,9416,9417, # 9200 +9418,9419,9420,9421,4898,9422,9423,9424,9425,9426,9427,9428,9429,3959,9430,9431, # 9216 +9432,9433,9434,9435,9436,4471,9437,9438,9439,9440,9441,9442,9443,9444,9445,9446, # 9232 +9447,9448,9449,9450,3348,9451,9452,9453,9454,9455,9456,9457,9458,9459,9460,9461, # 9248 +9462,9463,9464,9465,9466,9467,9468,9469,9470,9471,9472,4899,9473,9474,9475,9476, # 9264 +9477,4900,9478,9479,9480,9481,9482,9483,9484,9485,9486,9487,9488,3349,9489,9490, # 9280 +9491,9492,9493,9494,9495,9496,9497,9498,9499,9500,9501,9502,9503,9504,9505,9506, # 9296 +9507,9508,9509,9510,9511,9512,9513,9514,9515,9516,9517,9518,9519,9520,4901,9521, # 9312 +9522,9523,9524,9525,9526,4902,9527,9528,9529,9530,9531,9532,9533,9534,9535,9536, # 9328 +9537,9538,9539,9540,9541,9542,9543,9544,9545,9546,9547,9548,9549,9550,9551,9552, # 9344 +9553,9554,9555,9556,9557,9558,9559,9560,9561,9562,9563,9564,9565,9566,9567,9568, # 9360 +9569,9570,9571,9572,9573,9574,9575,9576,9577,9578,9579,9580,9581,9582,9583,9584, # 9376 +3805,9585,9586,9587,9588,9589,9590,9591,9592,9593,9594,9595,9596,9597,9598,9599, # 9392 +9600,9601,9602,4903,9603,9604,9605,9606,9607,4904,9608,9609,9610,9611,9612,9613, # 9408 +9614,4905,9615,9616,9617,9618,9619,9620,9621,9622,9623,9624,9625,9626,9627,9628, # 9424 +9629,9630,9631,9632,4906,9633,9634,9635,9636,9637,9638,9639,9640,9641,9642,9643, # 9440 +4907,9644,9645,9646,9647,9648,9649,9650,9651,9652,9653,9654,9655,9656,9657,9658, # 9456 +9659,9660,9661,9662,9663,9664,9665,9666,9667,9668,9669,9670,9671,9672,4183,9673, # 9472 +9674,9675,9676,9677,4908,9678,9679,9680,9681,4909,9682,9683,9684,9685,9686,9687, # 9488 +9688,9689,9690,4910,9691,9692,9693,3675,9694,9695,9696,2945,9697,9698,9699,9700, # 9504 +9701,9702,9703,9704,9705,4911,9706,9707,9708,9709,9710,9711,9712,9713,9714,9715, # 9520 +9716,9717,9718,9719,9720,9721,9722,9723,9724,9725,9726,9727,9728,9729,9730,9731, # 9536 +9732,9733,9734,9735,4912,9736,9737,9738,9739,9740,4913,9741,9742,9743,9744,9745, # 9552 +9746,9747,9748,9749,9750,9751,9752,9753,9754,9755,9756,9757,9758,4914,9759,9760, # 9568 +9761,9762,9763,9764,9765,9766,9767,9768,9769,9770,9771,9772,9773,9774,9775,9776, # 9584 +9777,9778,9779,9780,9781,9782,4915,9783,9784,9785,9786,9787,9788,9789,9790,9791, # 9600 +9792,9793,4916,9794,9795,9796,9797,9798,9799,9800,9801,9802,9803,9804,9805,9806, # 9616 +9807,9808,9809,9810,9811,9812,9813,9814,9815,9816,9817,9818,9819,9820,9821,9822, # 9632 +9823,9824,9825,9826,9827,9828,9829,9830,9831,9832,9833,9834,9835,9836,9837,9838, # 9648 +9839,9840,9841,9842,9843,9844,9845,9846,9847,9848,9849,9850,9851,9852,9853,9854, # 9664 +9855,9856,9857,9858,9859,9860,9861,9862,9863,9864,9865,9866,9867,9868,4917,9869, # 9680 +9870,9871,9872,9873,9874,9875,9876,9877,9878,9879,9880,9881,9882,9883,9884,9885, # 9696 +9886,9887,9888,9889,9890,9891,9892,4472,9893,9894,9895,9896,9897,3806,9898,9899, # 9712 +9900,9901,9902,9903,9904,9905,9906,9907,9908,9909,9910,9911,9912,9913,9914,4918, # 9728 +9915,9916,9917,4919,9918,9919,9920,9921,4184,9922,9923,9924,9925,9926,9927,9928, # 9744 +9929,9930,9931,9932,9933,9934,9935,9936,9937,9938,9939,9940,9941,9942,9943,9944, # 9760 +9945,9946,4920,9947,9948,9949,9950,9951,9952,9953,9954,9955,4185,9956,9957,9958, # 9776 +9959,9960,9961,9962,9963,9964,9965,4921,9966,9967,9968,4473,9969,9970,9971,9972, # 9792 +9973,9974,9975,9976,9977,4474,9978,9979,9980,9981,9982,9983,9984,9985,9986,9987, # 9808 +9988,9989,9990,9991,9992,9993,9994,9995,9996,9997,9998,9999,10000,10001,10002,10003, # 9824 +10004,10005,10006,10007,10008,10009,10010,10011,10012,10013,10014,10015,10016,10017,10018,10019, # 9840 +10020,10021,4922,10022,4923,10023,10024,10025,10026,10027,10028,10029,10030,10031,10032,10033, # 9856 +10034,10035,10036,10037,10038,10039,10040,10041,10042,10043,10044,10045,10046,10047,10048,4924, # 9872 +10049,10050,10051,10052,10053,10054,10055,10056,10057,10058,10059,10060,10061,10062,10063,10064, # 9888 +10065,10066,10067,10068,10069,10070,10071,10072,10073,10074,10075,10076,10077,10078,10079,10080, # 9904 +10081,10082,10083,10084,10085,10086,10087,4475,10088,10089,10090,10091,10092,10093,10094,10095, # 9920 +10096,10097,4476,10098,10099,10100,10101,10102,10103,10104,10105,10106,10107,10108,10109,10110, # 9936 +10111,2174,10112,10113,10114,10115,10116,10117,10118,10119,10120,10121,10122,10123,10124,10125, # 9952 +10126,10127,10128,10129,10130,10131,10132,10133,10134,10135,10136,10137,10138,10139,10140,3807, # 9968 +4186,4925,10141,10142,10143,10144,10145,10146,10147,4477,4187,10148,10149,10150,10151,10152, # 9984 +10153,4188,10154,10155,10156,10157,10158,10159,10160,10161,4926,10162,10163,10164,10165,10166, #10000 +10167,10168,10169,10170,10171,10172,10173,10174,10175,10176,10177,10178,10179,10180,10181,10182, #10016 +10183,10184,10185,10186,10187,10188,10189,10190,10191,10192,3203,10193,10194,10195,10196,10197, #10032 +10198,10199,10200,4478,10201,10202,10203,10204,4479,10205,10206,10207,10208,10209,10210,10211, #10048 +10212,10213,10214,10215,10216,10217,10218,10219,10220,10221,10222,10223,10224,10225,10226,10227, #10064 +10228,10229,10230,10231,10232,10233,10234,4927,10235,10236,10237,10238,10239,10240,10241,10242, #10080 +10243,10244,10245,10246,10247,10248,10249,10250,10251,10252,10253,10254,10255,10256,10257,10258, #10096 +10259,10260,10261,10262,10263,10264,10265,10266,10267,10268,10269,10270,10271,10272,10273,4480, #10112 +4928,4929,10274,10275,10276,10277,10278,10279,10280,10281,10282,10283,10284,10285,10286,10287, #10128 +10288,10289,10290,10291,10292,10293,10294,10295,10296,10297,10298,10299,10300,10301,10302,10303, #10144 +10304,10305,10306,10307,10308,10309,10310,10311,10312,10313,10314,10315,10316,10317,10318,10319, #10160 +10320,10321,10322,10323,10324,10325,10326,10327,10328,10329,10330,10331,10332,10333,10334,4930, #10176 +10335,10336,10337,10338,10339,10340,10341,10342,4931,10343,10344,10345,10346,10347,10348,10349, #10192 +10350,10351,10352,10353,10354,10355,3088,10356,2786,10357,10358,10359,10360,4189,10361,10362, #10208 +10363,10364,10365,10366,10367,10368,10369,10370,10371,10372,10373,10374,10375,4932,10376,10377, #10224 +10378,10379,10380,10381,10382,10383,10384,10385,10386,10387,10388,10389,10390,10391,10392,4933, #10240 +10393,10394,10395,4934,10396,10397,10398,10399,10400,10401,10402,10403,10404,10405,10406,10407, #10256 +10408,10409,10410,10411,10412,3446,10413,10414,10415,10416,10417,10418,10419,10420,10421,10422, #10272 +10423,4935,10424,10425,10426,10427,10428,10429,10430,4936,10431,10432,10433,10434,10435,10436, #10288 +10437,10438,10439,10440,10441,10442,10443,4937,10444,10445,10446,10447,4481,10448,10449,10450, #10304 +10451,10452,10453,10454,10455,10456,10457,10458,10459,10460,10461,10462,10463,10464,10465,10466, #10320 +10467,10468,10469,10470,10471,10472,10473,10474,10475,10476,10477,10478,10479,10480,10481,10482, #10336 +10483,10484,10485,10486,10487,10488,10489,10490,10491,10492,10493,10494,10495,10496,10497,10498, #10352 +10499,10500,10501,10502,10503,10504,10505,4938,10506,10507,10508,10509,10510,2552,10511,10512, #10368 +10513,10514,10515,10516,3447,10517,10518,10519,10520,10521,10522,10523,10524,10525,10526,10527, #10384 +10528,10529,10530,10531,10532,10533,10534,10535,10536,10537,10538,10539,10540,10541,10542,10543, #10400 +4482,10544,4939,10545,10546,10547,10548,10549,10550,10551,10552,10553,10554,10555,10556,10557, #10416 +10558,10559,10560,10561,10562,10563,10564,10565,10566,10567,3676,4483,10568,10569,10570,10571, #10432 +10572,3448,10573,10574,10575,10576,10577,10578,10579,10580,10581,10582,10583,10584,10585,10586, #10448 +10587,10588,10589,10590,10591,10592,10593,10594,10595,10596,10597,10598,10599,10600,10601,10602, #10464 +10603,10604,10605,10606,10607,10608,10609,10610,10611,10612,10613,10614,10615,10616,10617,10618, #10480 +10619,10620,10621,10622,10623,10624,10625,10626,10627,4484,10628,10629,10630,10631,10632,4940, #10496 +10633,10634,10635,10636,10637,10638,10639,10640,10641,10642,10643,10644,10645,10646,10647,10648, #10512 +10649,10650,10651,10652,10653,10654,10655,10656,4941,10657,10658,10659,2599,10660,10661,10662, #10528 +10663,10664,10665,10666,3089,10667,10668,10669,10670,10671,10672,10673,10674,10675,10676,10677, #10544 +10678,10679,10680,4942,10681,10682,10683,10684,10685,10686,10687,10688,10689,10690,10691,10692, #10560 +10693,10694,10695,10696,10697,4485,10698,10699,10700,10701,10702,10703,10704,4943,10705,3677, #10576 +10706,10707,10708,10709,10710,10711,10712,4944,10713,10714,10715,10716,10717,10718,10719,10720, #10592 +10721,10722,10723,10724,10725,10726,10727,10728,4945,10729,10730,10731,10732,10733,10734,10735, #10608 +10736,10737,10738,10739,10740,10741,10742,10743,10744,10745,10746,10747,10748,10749,10750,10751, #10624 +10752,10753,10754,10755,10756,10757,10758,10759,10760,10761,4946,10762,10763,10764,10765,10766, #10640 +10767,4947,4948,10768,10769,10770,10771,10772,10773,10774,10775,10776,10777,10778,10779,10780, #10656 +10781,10782,10783,10784,10785,10786,10787,10788,10789,10790,10791,10792,10793,10794,10795,10796, #10672 +10797,10798,10799,10800,10801,10802,10803,10804,10805,10806,10807,10808,10809,10810,10811,10812, #10688 +10813,10814,10815,10816,10817,10818,10819,10820,10821,10822,10823,10824,10825,10826,10827,10828, #10704 +10829,10830,10831,10832,10833,10834,10835,10836,10837,10838,10839,10840,10841,10842,10843,10844, #10720 +10845,10846,10847,10848,10849,10850,10851,10852,10853,10854,10855,10856,10857,10858,10859,10860, #10736 +10861,10862,10863,10864,10865,10866,10867,10868,10869,10870,10871,10872,10873,10874,10875,10876, #10752 +10877,10878,4486,10879,10880,10881,10882,10883,10884,10885,4949,10886,10887,10888,10889,10890, #10768 +10891,10892,10893,10894,10895,10896,10897,10898,10899,10900,10901,10902,10903,10904,10905,10906, #10784 +10907,10908,10909,10910,10911,10912,10913,10914,10915,10916,10917,10918,10919,4487,10920,10921, #10800 +10922,10923,10924,10925,10926,10927,10928,10929,10930,10931,10932,4950,10933,10934,10935,10936, #10816 +10937,10938,10939,10940,10941,10942,10943,10944,10945,10946,10947,10948,10949,4488,10950,10951, #10832 +10952,10953,10954,10955,10956,10957,10958,10959,4190,10960,10961,10962,10963,10964,10965,10966, #10848 +10967,10968,10969,10970,10971,10972,10973,10974,10975,10976,10977,10978,10979,10980,10981,10982, #10864 +10983,10984,10985,10986,10987,10988,10989,10990,10991,10992,10993,10994,10995,10996,10997,10998, #10880 +10999,11000,11001,11002,11003,11004,11005,11006,3960,11007,11008,11009,11010,11011,11012,11013, #10896 +11014,11015,11016,11017,11018,11019,11020,11021,11022,11023,11024,11025,11026,11027,11028,11029, #10912 +11030,11031,11032,4951,11033,11034,11035,11036,11037,11038,11039,11040,11041,11042,11043,11044, #10928 +11045,11046,11047,4489,11048,11049,11050,11051,4952,11052,11053,11054,11055,11056,11057,11058, #10944 +4953,11059,11060,11061,11062,11063,11064,11065,11066,11067,11068,11069,11070,11071,4954,11072, #10960 +11073,11074,11075,11076,11077,11078,11079,11080,11081,11082,11083,11084,11085,11086,11087,11088, #10976 +11089,11090,11091,11092,11093,11094,11095,11096,11097,11098,11099,11100,11101,11102,11103,11104, #10992 +11105,11106,11107,11108,11109,11110,11111,11112,11113,11114,11115,3808,11116,11117,11118,11119, #11008 +11120,11121,11122,11123,11124,11125,11126,11127,11128,11129,11130,11131,11132,11133,11134,4955, #11024 +11135,11136,11137,11138,11139,11140,11141,11142,11143,11144,11145,11146,11147,11148,11149,11150, #11040 +11151,11152,11153,11154,11155,11156,11157,11158,11159,11160,11161,4956,11162,11163,11164,11165, #11056 +11166,11167,11168,11169,11170,11171,11172,11173,11174,11175,11176,11177,11178,11179,11180,4957, #11072 +11181,11182,11183,11184,11185,11186,4958,11187,11188,11189,11190,11191,11192,11193,11194,11195, #11088 +11196,11197,11198,11199,11200,3678,11201,11202,11203,11204,11205,11206,4191,11207,11208,11209, #11104 +11210,11211,11212,11213,11214,11215,11216,11217,11218,11219,11220,11221,11222,11223,11224,11225, #11120 +11226,11227,11228,11229,11230,11231,11232,11233,11234,11235,11236,11237,11238,11239,11240,11241, #11136 +11242,11243,11244,11245,11246,11247,11248,11249,11250,11251,4959,11252,11253,11254,11255,11256, #11152 +11257,11258,11259,11260,11261,11262,11263,11264,11265,11266,11267,11268,11269,11270,11271,11272, #11168 +11273,11274,11275,11276,11277,11278,11279,11280,11281,11282,11283,11284,11285,11286,11287,11288, #11184 +11289,11290,11291,11292,11293,11294,11295,11296,11297,11298,11299,11300,11301,11302,11303,11304, #11200 +11305,11306,11307,11308,11309,11310,11311,11312,11313,11314,3679,11315,11316,11317,11318,4490, #11216 +11319,11320,11321,11322,11323,11324,11325,11326,11327,11328,11329,11330,11331,11332,11333,11334, #11232 +11335,11336,11337,11338,11339,11340,11341,11342,11343,11344,11345,11346,11347,4960,11348,11349, #11248 +11350,11351,11352,11353,11354,11355,11356,11357,11358,11359,11360,11361,11362,11363,11364,11365, #11264 +11366,11367,11368,11369,11370,11371,11372,11373,11374,11375,11376,11377,3961,4961,11378,11379, #11280 +11380,11381,11382,11383,11384,11385,11386,11387,11388,11389,11390,11391,11392,11393,11394,11395, #11296 +11396,11397,4192,11398,11399,11400,11401,11402,11403,11404,11405,11406,11407,11408,11409,11410, #11312 +11411,4962,11412,11413,11414,11415,11416,11417,11418,11419,11420,11421,11422,11423,11424,11425, #11328 +11426,11427,11428,11429,11430,11431,11432,11433,11434,11435,11436,11437,11438,11439,11440,11441, #11344 +11442,11443,11444,11445,11446,11447,11448,11449,11450,11451,11452,11453,11454,11455,11456,11457, #11360 +11458,11459,11460,11461,11462,11463,11464,11465,11466,11467,11468,11469,4963,11470,11471,4491, #11376 +11472,11473,11474,11475,4964,11476,11477,11478,11479,11480,11481,11482,11483,11484,11485,11486, #11392 +11487,11488,11489,11490,11491,11492,4965,11493,11494,11495,11496,11497,11498,11499,11500,11501, #11408 +11502,11503,11504,11505,11506,11507,11508,11509,11510,11511,11512,11513,11514,11515,11516,11517, #11424 +11518,11519,11520,11521,11522,11523,11524,11525,11526,11527,11528,11529,3962,11530,11531,11532, #11440 +11533,11534,11535,11536,11537,11538,11539,11540,11541,11542,11543,11544,11545,11546,11547,11548, #11456 +11549,11550,11551,11552,11553,11554,11555,11556,11557,11558,11559,11560,11561,11562,11563,11564, #11472 +4193,4194,11565,11566,11567,11568,11569,11570,11571,11572,11573,11574,11575,11576,11577,11578, #11488 +11579,11580,11581,11582,11583,11584,11585,11586,11587,11588,11589,11590,11591,4966,4195,11592, #11504 +11593,11594,11595,11596,11597,11598,11599,11600,11601,11602,11603,11604,3090,11605,11606,11607, #11520 +11608,11609,11610,4967,11611,11612,11613,11614,11615,11616,11617,11618,11619,11620,11621,11622, #11536 +11623,11624,11625,11626,11627,11628,11629,11630,11631,11632,11633,11634,11635,11636,11637,11638, #11552 +11639,11640,11641,11642,11643,11644,11645,11646,11647,11648,11649,11650,11651,11652,11653,11654, #11568 +11655,11656,11657,11658,11659,11660,11661,11662,11663,11664,11665,11666,11667,11668,11669,11670, #11584 +11671,11672,11673,11674,4968,11675,11676,11677,11678,11679,11680,11681,11682,11683,11684,11685, #11600 +11686,11687,11688,11689,11690,11691,11692,11693,3809,11694,11695,11696,11697,11698,11699,11700, #11616 +11701,11702,11703,11704,11705,11706,11707,11708,11709,11710,11711,11712,11713,11714,11715,11716, #11632 +11717,11718,3553,11719,11720,11721,11722,11723,11724,11725,11726,11727,11728,11729,11730,4969, #11648 +11731,11732,11733,11734,11735,11736,11737,11738,11739,11740,4492,11741,11742,11743,11744,11745, #11664 +11746,11747,11748,11749,11750,11751,11752,4970,11753,11754,11755,11756,11757,11758,11759,11760, #11680 +11761,11762,11763,11764,11765,11766,11767,11768,11769,11770,11771,11772,11773,11774,11775,11776, #11696 +11777,11778,11779,11780,11781,11782,11783,11784,11785,11786,11787,11788,11789,11790,4971,11791, #11712 +11792,11793,11794,11795,11796,11797,4972,11798,11799,11800,11801,11802,11803,11804,11805,11806, #11728 +11807,11808,11809,11810,4973,11811,11812,11813,11814,11815,11816,11817,11818,11819,11820,11821, #11744 +11822,11823,11824,11825,11826,11827,11828,11829,11830,11831,11832,11833,11834,3680,3810,11835, #11760 +11836,4974,11837,11838,11839,11840,11841,11842,11843,11844,11845,11846,11847,11848,11849,11850, #11776 +11851,11852,11853,11854,11855,11856,11857,11858,11859,11860,11861,11862,11863,11864,11865,11866, #11792 +11867,11868,11869,11870,11871,11872,11873,11874,11875,11876,11877,11878,11879,11880,11881,11882, #11808 +11883,11884,4493,11885,11886,11887,11888,11889,11890,11891,11892,11893,11894,11895,11896,11897, #11824 +11898,11899,11900,11901,11902,11903,11904,11905,11906,11907,11908,11909,11910,11911,11912,11913, #11840 +11914,11915,4975,11916,11917,11918,11919,11920,11921,11922,11923,11924,11925,11926,11927,11928, #11856 +11929,11930,11931,11932,11933,11934,11935,11936,11937,11938,11939,11940,11941,11942,11943,11944, #11872 +11945,11946,11947,11948,11949,4976,11950,11951,11952,11953,11954,11955,11956,11957,11958,11959, #11888 +11960,11961,11962,11963,11964,11965,11966,11967,11968,11969,11970,11971,11972,11973,11974,11975, #11904 +11976,11977,11978,11979,11980,11981,11982,11983,11984,11985,11986,11987,4196,11988,11989,11990, #11920 +11991,11992,4977,11993,11994,11995,11996,11997,11998,11999,12000,12001,12002,12003,12004,12005, #11936 +12006,12007,12008,12009,12010,12011,12012,12013,12014,12015,12016,12017,12018,12019,12020,12021, #11952 +12022,12023,12024,12025,12026,12027,12028,12029,12030,12031,12032,12033,12034,12035,12036,12037, #11968 +12038,12039,12040,12041,12042,12043,12044,12045,12046,12047,12048,12049,12050,12051,12052,12053, #11984 +12054,12055,12056,12057,12058,12059,12060,12061,4978,12062,12063,12064,12065,12066,12067,12068, #12000 +12069,12070,12071,12072,12073,12074,12075,12076,12077,12078,12079,12080,12081,12082,12083,12084, #12016 +12085,12086,12087,12088,12089,12090,12091,12092,12093,12094,12095,12096,12097,12098,12099,12100, #12032 +12101,12102,12103,12104,12105,12106,12107,12108,12109,12110,12111,12112,12113,12114,12115,12116, #12048 +12117,12118,12119,12120,12121,12122,12123,4979,12124,12125,12126,12127,12128,4197,12129,12130, #12064 +12131,12132,12133,12134,12135,12136,12137,12138,12139,12140,12141,12142,12143,12144,12145,12146, #12080 +12147,12148,12149,12150,12151,12152,12153,12154,4980,12155,12156,12157,12158,12159,12160,4494, #12096 +12161,12162,12163,12164,3811,12165,12166,12167,12168,12169,4495,12170,12171,4496,12172,12173, #12112 +12174,12175,12176,3812,12177,12178,12179,12180,12181,12182,12183,12184,12185,12186,12187,12188, #12128 +12189,12190,12191,12192,12193,12194,12195,12196,12197,12198,12199,12200,12201,12202,12203,12204, #12144 +12205,12206,12207,12208,12209,12210,12211,12212,12213,12214,12215,12216,12217,12218,12219,12220, #12160 +12221,4981,12222,12223,12224,12225,12226,12227,12228,12229,12230,12231,12232,12233,12234,12235, #12176 +4982,12236,12237,12238,12239,12240,12241,12242,12243,12244,12245,4983,12246,12247,12248,12249, #12192 +4984,12250,12251,12252,12253,12254,12255,12256,12257,12258,12259,12260,12261,12262,12263,12264, #12208 +4985,12265,4497,12266,12267,12268,12269,12270,12271,12272,12273,12274,12275,12276,12277,12278, #12224 +12279,12280,12281,12282,12283,12284,12285,12286,12287,4986,12288,12289,12290,12291,12292,12293, #12240 +12294,12295,12296,2473,12297,12298,12299,12300,12301,12302,12303,12304,12305,12306,12307,12308, #12256 +12309,12310,12311,12312,12313,12314,12315,12316,12317,12318,12319,3963,12320,12321,12322,12323, #12272 +12324,12325,12326,12327,12328,12329,12330,12331,12332,4987,12333,12334,12335,12336,12337,12338, #12288 +12339,12340,12341,12342,12343,12344,12345,12346,12347,12348,12349,12350,12351,12352,12353,12354, #12304 +12355,12356,12357,12358,12359,3964,12360,12361,12362,12363,12364,12365,12366,12367,12368,12369, #12320 +12370,3965,12371,12372,12373,12374,12375,12376,12377,12378,12379,12380,12381,12382,12383,12384, #12336 +12385,12386,12387,12388,12389,12390,12391,12392,12393,12394,12395,12396,12397,12398,12399,12400, #12352 +12401,12402,12403,12404,12405,12406,12407,12408,4988,12409,12410,12411,12412,12413,12414,12415, #12368 +12416,12417,12418,12419,12420,12421,12422,12423,12424,12425,12426,12427,12428,12429,12430,12431, #12384 +12432,12433,12434,12435,12436,12437,12438,3554,12439,12440,12441,12442,12443,12444,12445,12446, #12400 +12447,12448,12449,12450,12451,12452,12453,12454,12455,12456,12457,12458,12459,12460,12461,12462, #12416 +12463,12464,4989,12465,12466,12467,12468,12469,12470,12471,12472,12473,12474,12475,12476,12477, #12432 +12478,12479,12480,4990,12481,12482,12483,12484,12485,12486,12487,12488,12489,4498,12490,12491, #12448 +12492,12493,12494,12495,12496,12497,12498,12499,12500,12501,12502,12503,12504,12505,12506,12507, #12464 +12508,12509,12510,12511,12512,12513,12514,12515,12516,12517,12518,12519,12520,12521,12522,12523, #12480 +12524,12525,12526,12527,12528,12529,12530,12531,12532,12533,12534,12535,12536,12537,12538,12539, #12496 +12540,12541,12542,12543,12544,12545,12546,12547,12548,12549,12550,12551,4991,12552,12553,12554, #12512 +12555,12556,12557,12558,12559,12560,12561,12562,12563,12564,12565,12566,12567,12568,12569,12570, #12528 +12571,12572,12573,12574,12575,12576,12577,12578,3036,12579,12580,12581,12582,12583,3966,12584, #12544 +12585,12586,12587,12588,12589,12590,12591,12592,12593,12594,12595,12596,12597,12598,12599,12600, #12560 +12601,12602,12603,12604,12605,12606,12607,12608,12609,12610,12611,12612,12613,12614,12615,12616, #12576 +12617,12618,12619,12620,12621,12622,12623,12624,12625,12626,12627,12628,12629,12630,12631,12632, #12592 +12633,12634,12635,12636,12637,12638,12639,12640,12641,12642,12643,12644,12645,12646,4499,12647, #12608 +12648,12649,12650,12651,12652,12653,12654,12655,12656,12657,12658,12659,12660,12661,12662,12663, #12624 +12664,12665,12666,12667,12668,12669,12670,12671,12672,12673,12674,12675,12676,12677,12678,12679, #12640 +12680,12681,12682,12683,12684,12685,12686,12687,12688,12689,12690,12691,12692,12693,12694,12695, #12656 +12696,12697,12698,4992,12699,12700,12701,12702,12703,12704,12705,12706,12707,12708,12709,12710, #12672 +12711,12712,12713,12714,12715,12716,12717,12718,12719,12720,12721,12722,12723,12724,12725,12726, #12688 +12727,12728,12729,12730,12731,12732,12733,12734,12735,12736,12737,12738,12739,12740,12741,12742, #12704 +12743,12744,12745,12746,12747,12748,12749,12750,12751,12752,12753,12754,12755,12756,12757,12758, #12720 +12759,12760,12761,12762,12763,12764,12765,12766,12767,12768,12769,12770,12771,12772,12773,12774, #12736 +12775,12776,12777,12778,4993,2175,12779,12780,12781,12782,12783,12784,12785,12786,4500,12787, #12752 +12788,12789,12790,12791,12792,12793,12794,12795,12796,12797,12798,12799,12800,12801,12802,12803, #12768 +12804,12805,12806,12807,12808,12809,12810,12811,12812,12813,12814,12815,12816,12817,12818,12819, #12784 +12820,12821,12822,12823,12824,12825,12826,4198,3967,12827,12828,12829,12830,12831,12832,12833, #12800 +12834,12835,12836,12837,12838,12839,12840,12841,12842,12843,12844,12845,12846,12847,12848,12849, #12816 +12850,12851,12852,12853,12854,12855,12856,12857,12858,12859,12860,12861,4199,12862,12863,12864, #12832 +12865,12866,12867,12868,12869,12870,12871,12872,12873,12874,12875,12876,12877,12878,12879,12880, #12848 +12881,12882,12883,12884,12885,12886,12887,4501,12888,12889,12890,12891,12892,12893,12894,12895, #12864 +12896,12897,12898,12899,12900,12901,12902,12903,12904,12905,12906,12907,12908,12909,12910,12911, #12880 +12912,4994,12913,12914,12915,12916,12917,12918,12919,12920,12921,12922,12923,12924,12925,12926, #12896 +12927,12928,12929,12930,12931,12932,12933,12934,12935,12936,12937,12938,12939,12940,12941,12942, #12912 +12943,12944,12945,12946,12947,12948,12949,12950,12951,12952,12953,12954,12955,12956,1772,12957, #12928 +12958,12959,12960,12961,12962,12963,12964,12965,12966,12967,12968,12969,12970,12971,12972,12973, #12944 +12974,12975,12976,12977,12978,12979,12980,12981,12982,12983,12984,12985,12986,12987,12988,12989, #12960 +12990,12991,12992,12993,12994,12995,12996,12997,4502,12998,4503,12999,13000,13001,13002,13003, #12976 +4504,13004,13005,13006,13007,13008,13009,13010,13011,13012,13013,13014,13015,13016,13017,13018, #12992 +13019,13020,13021,13022,13023,13024,13025,13026,13027,13028,13029,3449,13030,13031,13032,13033, #13008 +13034,13035,13036,13037,13038,13039,13040,13041,13042,13043,13044,13045,13046,13047,13048,13049, #13024 +13050,13051,13052,13053,13054,13055,13056,13057,13058,13059,13060,13061,13062,13063,13064,13065, #13040 +13066,13067,13068,13069,13070,13071,13072,13073,13074,13075,13076,13077,13078,13079,13080,13081, #13056 +13082,13083,13084,13085,13086,13087,13088,13089,13090,13091,13092,13093,13094,13095,13096,13097, #13072 +13098,13099,13100,13101,13102,13103,13104,13105,13106,13107,13108,13109,13110,13111,13112,13113, #13088 +13114,13115,13116,13117,13118,3968,13119,4995,13120,13121,13122,13123,13124,13125,13126,13127, #13104 +4505,13128,13129,13130,13131,13132,13133,13134,4996,4506,13135,13136,13137,13138,13139,4997, #13120 +13140,13141,13142,13143,13144,13145,13146,13147,13148,13149,13150,13151,13152,13153,13154,13155, #13136 +13156,13157,13158,13159,4998,13160,13161,13162,13163,13164,13165,13166,13167,13168,13169,13170, #13152 +13171,13172,13173,13174,13175,13176,4999,13177,13178,13179,13180,13181,13182,13183,13184,13185, #13168 +13186,13187,13188,13189,13190,13191,13192,13193,13194,13195,13196,13197,13198,13199,13200,13201, #13184 +13202,13203,13204,13205,13206,5000,13207,13208,13209,13210,13211,13212,13213,13214,13215,13216, #13200 +13217,13218,13219,13220,13221,13222,13223,13224,13225,13226,13227,4200,5001,13228,13229,13230, #13216 +13231,13232,13233,13234,13235,13236,13237,13238,13239,13240,3969,13241,13242,13243,13244,3970, #13232 +13245,13246,13247,13248,13249,13250,13251,13252,13253,13254,13255,13256,13257,13258,13259,13260, #13248 +13261,13262,13263,13264,13265,13266,13267,13268,3450,13269,13270,13271,13272,13273,13274,13275, #13264 +13276,5002,13277,13278,13279,13280,13281,13282,13283,13284,13285,13286,13287,13288,13289,13290, #13280 +13291,13292,13293,13294,13295,13296,13297,13298,13299,13300,13301,13302,3813,13303,13304,13305, #13296 +13306,13307,13308,13309,13310,13311,13312,13313,13314,13315,13316,13317,13318,13319,13320,13321, #13312 +13322,13323,13324,13325,13326,13327,13328,4507,13329,13330,13331,13332,13333,13334,13335,13336, #13328 +13337,13338,13339,13340,13341,5003,13342,13343,13344,13345,13346,13347,13348,13349,13350,13351, #13344 +13352,13353,13354,13355,13356,13357,13358,13359,13360,13361,13362,13363,13364,13365,13366,13367, #13360 +5004,13368,13369,13370,13371,13372,13373,13374,13375,13376,13377,13378,13379,13380,13381,13382, #13376 +13383,13384,13385,13386,13387,13388,13389,13390,13391,13392,13393,13394,13395,13396,13397,13398, #13392 +13399,13400,13401,13402,13403,13404,13405,13406,13407,13408,13409,13410,13411,13412,13413,13414, #13408 +13415,13416,13417,13418,13419,13420,13421,13422,13423,13424,13425,13426,13427,13428,13429,13430, #13424 +13431,13432,4508,13433,13434,13435,4201,13436,13437,13438,13439,13440,13441,13442,13443,13444, #13440 +13445,13446,13447,13448,13449,13450,13451,13452,13453,13454,13455,13456,13457,5005,13458,13459, #13456 +13460,13461,13462,13463,13464,13465,13466,13467,13468,13469,13470,4509,13471,13472,13473,13474, #13472 +13475,13476,13477,13478,13479,13480,13481,13482,13483,13484,13485,13486,13487,13488,13489,13490, #13488 +13491,13492,13493,13494,13495,13496,13497,13498,13499,13500,13501,13502,13503,13504,13505,13506, #13504 +13507,13508,13509,13510,13511,13512,13513,13514,13515,13516,13517,13518,13519,13520,13521,13522, #13520 +13523,13524,13525,13526,13527,13528,13529,13530,13531,13532,13533,13534,13535,13536,13537,13538, #13536 +13539,13540,13541,13542,13543,13544,13545,13546,13547,13548,13549,13550,13551,13552,13553,13554, #13552 +13555,13556,13557,13558,13559,13560,13561,13562,13563,13564,13565,13566,13567,13568,13569,13570, #13568 +13571,13572,13573,13574,13575,13576,13577,13578,13579,13580,13581,13582,13583,13584,13585,13586, #13584 +13587,13588,13589,13590,13591,13592,13593,13594,13595,13596,13597,13598,13599,13600,13601,13602, #13600 +13603,13604,13605,13606,13607,13608,13609,13610,13611,13612,13613,13614,13615,13616,13617,13618, #13616 +13619,13620,13621,13622,13623,13624,13625,13626,13627,13628,13629,13630,13631,13632,13633,13634, #13632 +13635,13636,13637,13638,13639,13640,13641,13642,5006,13643,13644,13645,13646,13647,13648,13649, #13648 +13650,13651,5007,13652,13653,13654,13655,13656,13657,13658,13659,13660,13661,13662,13663,13664, #13664 +13665,13666,13667,13668,13669,13670,13671,13672,13673,13674,13675,13676,13677,13678,13679,13680, #13680 +13681,13682,13683,13684,13685,13686,13687,13688,13689,13690,13691,13692,13693,13694,13695,13696, #13696 +13697,13698,13699,13700,13701,13702,13703,13704,13705,13706,13707,13708,13709,13710,13711,13712, #13712 +13713,13714,13715,13716,13717,13718,13719,13720,13721,13722,13723,13724,13725,13726,13727,13728, #13728 +13729,13730,13731,13732,13733,13734,13735,13736,13737,13738,13739,13740,13741,13742,13743,13744, #13744 +13745,13746,13747,13748,13749,13750,13751,13752,13753,13754,13755,13756,13757,13758,13759,13760, #13760 +13761,13762,13763,13764,13765,13766,13767,13768,13769,13770,13771,13772,13773,13774,3273,13775, #13776 +13776,13777,13778,13779,13780,13781,13782,13783,13784,13785,13786,13787,13788,13789,13790,13791, #13792 +13792,13793,13794,13795,13796,13797,13798,13799,13800,13801,13802,13803,13804,13805,13806,13807, #13808 +13808,13809,13810,13811,13812,13813,13814,13815,13816,13817,13818,13819,13820,13821,13822,13823, #13824 +13824,13825,13826,13827,13828,13829,13830,13831,13832,13833,13834,13835,13836,13837,13838,13839, #13840 +13840,13841,13842,13843,13844,13845,13846,13847,13848,13849,13850,13851,13852,13853,13854,13855, #13856 +13856,13857,13858,13859,13860,13861,13862,13863,13864,13865,13866,13867,13868,13869,13870,13871, #13872 +13872,13873,13874,13875,13876,13877,13878,13879,13880,13881,13882,13883,13884,13885,13886,13887, #13888 +13888,13889,13890,13891,13892,13893,13894,13895,13896,13897,13898,13899,13900,13901,13902,13903, #13904 +13904,13905,13906,13907,13908,13909,13910,13911,13912,13913,13914,13915,13916,13917,13918,13919, #13920 +13920,13921,13922,13923,13924,13925,13926,13927,13928,13929,13930,13931,13932,13933,13934,13935, #13936 +13936,13937,13938,13939,13940,13941,13942,13943,13944,13945,13946,13947,13948,13949,13950,13951, #13952 +13952,13953,13954,13955,13956,13957,13958,13959,13960,13961,13962,13963,13964,13965,13966,13967, #13968 +13968,13969,13970,13971,13972) #13973 + +# flake8: noqa diff --git a/resources/lib/libraries/requests/packages/chardet/big5prober.py b/resources/lib/libraries/requests/packages/chardet/big5prober.py new file mode 100644 index 00000000..becce81e --- /dev/null +++ b/resources/lib/libraries/requests/packages/chardet/big5prober.py @@ -0,0 +1,42 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is Mozilla Communicator client code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +from .mbcharsetprober import MultiByteCharSetProber +from .codingstatemachine import CodingStateMachine +from .chardistribution import Big5DistributionAnalysis +from .mbcssm import Big5SMModel + + +class Big5Prober(MultiByteCharSetProber): + def __init__(self): + MultiByteCharSetProber.__init__(self) + self._mCodingSM = CodingStateMachine(Big5SMModel) + self._mDistributionAnalyzer = Big5DistributionAnalysis() + self.reset() + + def get_charset_name(self): + return "Big5" diff --git a/resources/lib/libraries/requests/packages/chardet/chardetect.py b/resources/lib/libraries/requests/packages/chardet/chardetect.py new file mode 100644 index 00000000..ffe892f2 --- /dev/null +++ b/resources/lib/libraries/requests/packages/chardet/chardetect.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python +""" +Script which takes one or more file paths and reports on their detected +encodings + +Example:: + + % chardetect somefile someotherfile + somefile: windows-1252 with confidence 0.5 + someotherfile: ascii with confidence 1.0 + +If no paths are provided, it takes its input from stdin. + +""" + +from __future__ import absolute_import, print_function, unicode_literals + +import argparse +import sys +from io import open + +from chardet import __version__ +from chardet.universaldetector import UniversalDetector + + +def description_of(lines, name='stdin'): + """ + Return a string describing the probable encoding of a file or + list of strings. + + :param lines: The lines to get the encoding of. + :type lines: Iterable of bytes + :param name: Name of file or collection of lines + :type name: str + """ + u = UniversalDetector() + for line in lines: + u.feed(line) + u.close() + result = u.result + if result['encoding']: + return '{0}: {1} with confidence {2}'.format(name, result['encoding'], + result['confidence']) + else: + return '{0}: no result'.format(name) + + +def main(argv=None): + ''' + Handles command line arguments and gets things started. + + :param argv: List of arguments, as if specified on the command-line. + If None, ``sys.argv[1:]`` is used instead. + :type argv: list of str + ''' + # Get command line arguments + parser = argparse.ArgumentParser( + description="Takes one or more file paths and reports their detected \ + encodings", + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + conflict_handler='resolve') + parser.add_argument('input', + help='File whose encoding we would like to determine.', + type=argparse.FileType('rb'), nargs='*', + default=[sys.stdin]) + parser.add_argument('--version', action='version', + version='%(prog)s {0}'.format(__version__)) + args = parser.parse_args(argv) + + for f in args.input: + if f.isatty(): + print("You are running chardetect interactively. Press " + + "CTRL-D twice at the start of a blank line to signal the " + + "end of your input. If you want help, run chardetect " + + "--help\n", file=sys.stderr) + print(description_of(f, f.name)) + + +if __name__ == '__main__': + main() diff --git a/resources/lib/libraries/requests/packages/chardet/chardistribution.py b/resources/lib/libraries/requests/packages/chardet/chardistribution.py new file mode 100644 index 00000000..4e64a00b --- /dev/null +++ b/resources/lib/libraries/requests/packages/chardet/chardistribution.py @@ -0,0 +1,231 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is Mozilla Communicator client code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +from .euctwfreq import (EUCTWCharToFreqOrder, EUCTW_TABLE_SIZE, + EUCTW_TYPICAL_DISTRIBUTION_RATIO) +from .euckrfreq import (EUCKRCharToFreqOrder, EUCKR_TABLE_SIZE, + EUCKR_TYPICAL_DISTRIBUTION_RATIO) +from .gb2312freq import (GB2312CharToFreqOrder, GB2312_TABLE_SIZE, + GB2312_TYPICAL_DISTRIBUTION_RATIO) +from .big5freq import (Big5CharToFreqOrder, BIG5_TABLE_SIZE, + BIG5_TYPICAL_DISTRIBUTION_RATIO) +from .jisfreq import (JISCharToFreqOrder, JIS_TABLE_SIZE, + JIS_TYPICAL_DISTRIBUTION_RATIO) +from .compat import wrap_ord + +ENOUGH_DATA_THRESHOLD = 1024 +SURE_YES = 0.99 +SURE_NO = 0.01 +MINIMUM_DATA_THRESHOLD = 3 + + +class CharDistributionAnalysis: + def __init__(self): + # Mapping table to get frequency order from char order (get from + # GetOrder()) + self._mCharToFreqOrder = None + self._mTableSize = None # Size of above table + # This is a constant value which varies from language to language, + # used in calculating confidence. See + # http://www.mozilla.org/projects/intl/UniversalCharsetDetection.html + # for further detail. + self._mTypicalDistributionRatio = None + self.reset() + + def reset(self): + """reset analyser, clear any state""" + # If this flag is set to True, detection is done and conclusion has + # been made + self._mDone = False + self._mTotalChars = 0 # Total characters encountered + # The number of characters whose frequency order is less than 512 + self._mFreqChars = 0 + + def feed(self, aBuf, aCharLen): + """feed a character with known length""" + if aCharLen == 2: + # we only care about 2-bytes character in our distribution analysis + order = self.get_order(aBuf) + else: + order = -1 + if order >= 0: + self._mTotalChars += 1 + # order is valid + if order < self._mTableSize: + if 512 > self._mCharToFreqOrder[order]: + self._mFreqChars += 1 + + def get_confidence(self): + """return confidence based on existing data""" + # if we didn't receive any character in our consideration range, + # return negative answer + if self._mTotalChars <= 0 or self._mFreqChars <= MINIMUM_DATA_THRESHOLD: + return SURE_NO + + if self._mTotalChars != self._mFreqChars: + r = (self._mFreqChars / ((self._mTotalChars - self._mFreqChars) + * self._mTypicalDistributionRatio)) + if r < SURE_YES: + return r + + # normalize confidence (we don't want to be 100% sure) + return SURE_YES + + def got_enough_data(self): + # It is not necessary to receive all data to draw conclusion. + # For charset detection, certain amount of data is enough + return self._mTotalChars > ENOUGH_DATA_THRESHOLD + + def get_order(self, aBuf): + # We do not handle characters based on the original encoding string, + # but convert this encoding string to a number, here called order. + # This allows multiple encodings of a language to share one frequency + # table. + return -1 + + +class EUCTWDistributionAnalysis(CharDistributionAnalysis): + def __init__(self): + CharDistributionAnalysis.__init__(self) + self._mCharToFreqOrder = EUCTWCharToFreqOrder + self._mTableSize = EUCTW_TABLE_SIZE + self._mTypicalDistributionRatio = EUCTW_TYPICAL_DISTRIBUTION_RATIO + + def get_order(self, aBuf): + # for euc-TW encoding, we are interested + # first byte range: 0xc4 -- 0xfe + # second byte range: 0xa1 -- 0xfe + # no validation needed here. State machine has done that + first_char = wrap_ord(aBuf[0]) + if first_char >= 0xC4: + return 94 * (first_char - 0xC4) + wrap_ord(aBuf[1]) - 0xA1 + else: + return -1 + + +class EUCKRDistributionAnalysis(CharDistributionAnalysis): + def __init__(self): + CharDistributionAnalysis.__init__(self) + self._mCharToFreqOrder = EUCKRCharToFreqOrder + self._mTableSize = EUCKR_TABLE_SIZE + self._mTypicalDistributionRatio = EUCKR_TYPICAL_DISTRIBUTION_RATIO + + def get_order(self, aBuf): + # for euc-KR encoding, we are interested + # first byte range: 0xb0 -- 0xfe + # second byte range: 0xa1 -- 0xfe + # no validation needed here. State machine has done that + first_char = wrap_ord(aBuf[0]) + if first_char >= 0xB0: + return 94 * (first_char - 0xB0) + wrap_ord(aBuf[1]) - 0xA1 + else: + return -1 + + +class GB2312DistributionAnalysis(CharDistributionAnalysis): + def __init__(self): + CharDistributionAnalysis.__init__(self) + self._mCharToFreqOrder = GB2312CharToFreqOrder + self._mTableSize = GB2312_TABLE_SIZE + self._mTypicalDistributionRatio = GB2312_TYPICAL_DISTRIBUTION_RATIO + + def get_order(self, aBuf): + # for GB2312 encoding, we are interested + # first byte range: 0xb0 -- 0xfe + # second byte range: 0xa1 -- 0xfe + # no validation needed here. State machine has done that + first_char, second_char = wrap_ord(aBuf[0]), wrap_ord(aBuf[1]) + if (first_char >= 0xB0) and (second_char >= 0xA1): + return 94 * (first_char - 0xB0) + second_char - 0xA1 + else: + return -1 + + +class Big5DistributionAnalysis(CharDistributionAnalysis): + def __init__(self): + CharDistributionAnalysis.__init__(self) + self._mCharToFreqOrder = Big5CharToFreqOrder + self._mTableSize = BIG5_TABLE_SIZE + self._mTypicalDistributionRatio = BIG5_TYPICAL_DISTRIBUTION_RATIO + + def get_order(self, aBuf): + # for big5 encoding, we are interested + # first byte range: 0xa4 -- 0xfe + # second byte range: 0x40 -- 0x7e , 0xa1 -- 0xfe + # no validation needed here. State machine has done that + first_char, second_char = wrap_ord(aBuf[0]), wrap_ord(aBuf[1]) + if first_char >= 0xA4: + if second_char >= 0xA1: + return 157 * (first_char - 0xA4) + second_char - 0xA1 + 63 + else: + return 157 * (first_char - 0xA4) + second_char - 0x40 + else: + return -1 + + +class SJISDistributionAnalysis(CharDistributionAnalysis): + def __init__(self): + CharDistributionAnalysis.__init__(self) + self._mCharToFreqOrder = JISCharToFreqOrder + self._mTableSize = JIS_TABLE_SIZE + self._mTypicalDistributionRatio = JIS_TYPICAL_DISTRIBUTION_RATIO + + def get_order(self, aBuf): + # for sjis encoding, we are interested + # first byte range: 0x81 -- 0x9f , 0xe0 -- 0xfe + # second byte range: 0x40 -- 0x7e, 0x81 -- oxfe + # no validation needed here. State machine has done that + first_char, second_char = wrap_ord(aBuf[0]), wrap_ord(aBuf[1]) + if (first_char >= 0x81) and (first_char <= 0x9F): + order = 188 * (first_char - 0x81) + elif (first_char >= 0xE0) and (first_char <= 0xEF): + order = 188 * (first_char - 0xE0 + 31) + else: + return -1 + order = order + second_char - 0x40 + if second_char > 0x7F: + order = -1 + return order + + +class EUCJPDistributionAnalysis(CharDistributionAnalysis): + def __init__(self): + CharDistributionAnalysis.__init__(self) + self._mCharToFreqOrder = JISCharToFreqOrder + self._mTableSize = JIS_TABLE_SIZE + self._mTypicalDistributionRatio = JIS_TYPICAL_DISTRIBUTION_RATIO + + def get_order(self, aBuf): + # for euc-JP encoding, we are interested + # first byte range: 0xa0 -- 0xfe + # second byte range: 0xa1 -- 0xfe + # no validation needed here. State machine has done that + char = wrap_ord(aBuf[0]) + if char >= 0xA0: + return 94 * (char - 0xA1) + wrap_ord(aBuf[1]) - 0xa1 + else: + return -1 diff --git a/resources/lib/libraries/requests/packages/chardet/charsetgroupprober.py b/resources/lib/libraries/requests/packages/chardet/charsetgroupprober.py new file mode 100644 index 00000000..85e7a1c6 --- /dev/null +++ b/resources/lib/libraries/requests/packages/chardet/charsetgroupprober.py @@ -0,0 +1,106 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is Mozilla Communicator client code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +from . import constants +import sys +from .charsetprober import CharSetProber + + +class CharSetGroupProber(CharSetProber): + def __init__(self): + CharSetProber.__init__(self) + self._mActiveNum = 0 + self._mProbers = [] + self._mBestGuessProber = None + + def reset(self): + CharSetProber.reset(self) + self._mActiveNum = 0 + for prober in self._mProbers: + if prober: + prober.reset() + prober.active = True + self._mActiveNum += 1 + self._mBestGuessProber = None + + def get_charset_name(self): + if not self._mBestGuessProber: + self.get_confidence() + if not self._mBestGuessProber: + return None +# self._mBestGuessProber = self._mProbers[0] + return self._mBestGuessProber.get_charset_name() + + def feed(self, aBuf): + for prober in self._mProbers: + if not prober: + continue + if not prober.active: + continue + st = prober.feed(aBuf) + if not st: + continue + if st == constants.eFoundIt: + self._mBestGuessProber = prober + return self.get_state() + elif st == constants.eNotMe: + prober.active = False + self._mActiveNum -= 1 + if self._mActiveNum <= 0: + self._mState = constants.eNotMe + return self.get_state() + return self.get_state() + + def get_confidence(self): + st = self.get_state() + if st == constants.eFoundIt: + return 0.99 + elif st == constants.eNotMe: + return 0.01 + bestConf = 0.0 + self._mBestGuessProber = None + for prober in self._mProbers: + if not prober: + continue + if not prober.active: + if constants._debug: + sys.stderr.write(prober.get_charset_name() + + ' not active\n') + continue + cf = prober.get_confidence() + if constants._debug: + sys.stderr.write('%s confidence = %s\n' % + (prober.get_charset_name(), cf)) + if bestConf < cf: + bestConf = cf + self._mBestGuessProber = prober + if not self._mBestGuessProber: + return 0.0 + return bestConf +# else: +# self._mBestGuessProber = self._mProbers[0] +# return self._mBestGuessProber.get_confidence() diff --git a/resources/lib/libraries/requests/packages/chardet/charsetprober.py b/resources/lib/libraries/requests/packages/chardet/charsetprober.py new file mode 100644 index 00000000..97581712 --- /dev/null +++ b/resources/lib/libraries/requests/packages/chardet/charsetprober.py @@ -0,0 +1,62 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is Mozilla Universal charset detector code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 2001 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# Shy Shalom - original C code +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +from . import constants +import re + + +class CharSetProber: + def __init__(self): + pass + + def reset(self): + self._mState = constants.eDetecting + + def get_charset_name(self): + return None + + def feed(self, aBuf): + pass + + def get_state(self): + return self._mState + + def get_confidence(self): + return 0.0 + + def filter_high_bit_only(self, aBuf): + aBuf = re.sub(b'([\x00-\x7F])+', b' ', aBuf) + return aBuf + + def filter_without_english_letters(self, aBuf): + aBuf = re.sub(b'([A-Za-z])+', b' ', aBuf) + return aBuf + + def filter_with_english_letters(self, aBuf): + # TODO + return aBuf diff --git a/resources/lib/libraries/requests/packages/chardet/codingstatemachine.py b/resources/lib/libraries/requests/packages/chardet/codingstatemachine.py new file mode 100644 index 00000000..8dd8c917 --- /dev/null +++ b/resources/lib/libraries/requests/packages/chardet/codingstatemachine.py @@ -0,0 +1,61 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is mozilla.org code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +from .constants import eStart +from .compat import wrap_ord + + +class CodingStateMachine: + def __init__(self, sm): + self._mModel = sm + self._mCurrentBytePos = 0 + self._mCurrentCharLen = 0 + self.reset() + + def reset(self): + self._mCurrentState = eStart + + def next_state(self, c): + # for each byte we get its class + # if it is first byte, we also get byte length + # PY3K: aBuf is a byte stream, so c is an int, not a byte + byteCls = self._mModel['classTable'][wrap_ord(c)] + if self._mCurrentState == eStart: + self._mCurrentBytePos = 0 + self._mCurrentCharLen = self._mModel['charLenTable'][byteCls] + # from byte's class and stateTable, we get its next state + curr_state = (self._mCurrentState * self._mModel['classFactor'] + + byteCls) + self._mCurrentState = self._mModel['stateTable'][curr_state] + self._mCurrentBytePos += 1 + return self._mCurrentState + + def get_current_charlen(self): + return self._mCurrentCharLen + + def get_coding_state_machine(self): + return self._mModel['name'] diff --git a/resources/lib/libraries/requests/packages/chardet/compat.py b/resources/lib/libraries/requests/packages/chardet/compat.py new file mode 100644 index 00000000..d9e30add --- /dev/null +++ b/resources/lib/libraries/requests/packages/chardet/compat.py @@ -0,0 +1,34 @@ +######################## BEGIN LICENSE BLOCK ######################## +# Contributor(s): +# Ian Cordasco - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +import sys + + +if sys.version_info < (3, 0): + base_str = (str, unicode) +else: + base_str = (bytes, str) + + +def wrap_ord(a): + if sys.version_info < (3, 0) and isinstance(a, base_str): + return ord(a) + else: + return a diff --git a/resources/lib/libraries/requests/packages/chardet/constants.py b/resources/lib/libraries/requests/packages/chardet/constants.py new file mode 100644 index 00000000..e4d148b3 --- /dev/null +++ b/resources/lib/libraries/requests/packages/chardet/constants.py @@ -0,0 +1,39 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is Mozilla Universal charset detector code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 2001 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# Shy Shalom - original C code +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +_debug = 0 + +eDetecting = 0 +eFoundIt = 1 +eNotMe = 2 + +eStart = 0 +eError = 1 +eItsMe = 2 + +SHORTCUT_THRESHOLD = 0.95 diff --git a/resources/lib/libraries/requests/packages/chardet/cp949prober.py b/resources/lib/libraries/requests/packages/chardet/cp949prober.py new file mode 100644 index 00000000..ff4272f8 --- /dev/null +++ b/resources/lib/libraries/requests/packages/chardet/cp949prober.py @@ -0,0 +1,44 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is mozilla.org code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +from .mbcharsetprober import MultiByteCharSetProber +from .codingstatemachine import CodingStateMachine +from .chardistribution import EUCKRDistributionAnalysis +from .mbcssm import CP949SMModel + + +class CP949Prober(MultiByteCharSetProber): + def __init__(self): + MultiByteCharSetProber.__init__(self) + self._mCodingSM = CodingStateMachine(CP949SMModel) + # NOTE: CP949 is a superset of EUC-KR, so the distribution should be + # not different. + self._mDistributionAnalyzer = EUCKRDistributionAnalysis() + self.reset() + + def get_charset_name(self): + return "CP949" diff --git a/resources/lib/libraries/requests/packages/chardet/escprober.py b/resources/lib/libraries/requests/packages/chardet/escprober.py new file mode 100644 index 00000000..80a844ff --- /dev/null +++ b/resources/lib/libraries/requests/packages/chardet/escprober.py @@ -0,0 +1,86 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is mozilla.org code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +from . import constants +from .escsm import (HZSMModel, ISO2022CNSMModel, ISO2022JPSMModel, + ISO2022KRSMModel) +from .charsetprober import CharSetProber +from .codingstatemachine import CodingStateMachine +from .compat import wrap_ord + + +class EscCharSetProber(CharSetProber): + def __init__(self): + CharSetProber.__init__(self) + self._mCodingSM = [ + CodingStateMachine(HZSMModel), + CodingStateMachine(ISO2022CNSMModel), + CodingStateMachine(ISO2022JPSMModel), + CodingStateMachine(ISO2022KRSMModel) + ] + self.reset() + + def reset(self): + CharSetProber.reset(self) + for codingSM in self._mCodingSM: + if not codingSM: + continue + codingSM.active = True + codingSM.reset() + self._mActiveSM = len(self._mCodingSM) + self._mDetectedCharset = None + + def get_charset_name(self): + return self._mDetectedCharset + + def get_confidence(self): + if self._mDetectedCharset: + return 0.99 + else: + return 0.00 + + def feed(self, aBuf): + for c in aBuf: + # PY3K: aBuf is a byte array, so c is an int, not a byte + for codingSM in self._mCodingSM: + if not codingSM: + continue + if not codingSM.active: + continue + codingState = codingSM.next_state(wrap_ord(c)) + if codingState == constants.eError: + codingSM.active = False + self._mActiveSM -= 1 + if self._mActiveSM <= 0: + self._mState = constants.eNotMe + return self.get_state() + elif codingState == constants.eItsMe: + self._mState = constants.eFoundIt + self._mDetectedCharset = codingSM.get_coding_state_machine() # nopep8 + return self.get_state() + + return self.get_state() diff --git a/resources/lib/libraries/requests/packages/chardet/escsm.py b/resources/lib/libraries/requests/packages/chardet/escsm.py new file mode 100644 index 00000000..bd302b4c --- /dev/null +++ b/resources/lib/libraries/requests/packages/chardet/escsm.py @@ -0,0 +1,242 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is mozilla.org code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +from .constants import eStart, eError, eItsMe + +HZ_cls = ( +1,0,0,0,0,0,0,0, # 00 - 07 +0,0,0,0,0,0,0,0, # 08 - 0f +0,0,0,0,0,0,0,0, # 10 - 17 +0,0,0,1,0,0,0,0, # 18 - 1f +0,0,0,0,0,0,0,0, # 20 - 27 +0,0,0,0,0,0,0,0, # 28 - 2f +0,0,0,0,0,0,0,0, # 30 - 37 +0,0,0,0,0,0,0,0, # 38 - 3f +0,0,0,0,0,0,0,0, # 40 - 47 +0,0,0,0,0,0,0,0, # 48 - 4f +0,0,0,0,0,0,0,0, # 50 - 57 +0,0,0,0,0,0,0,0, # 58 - 5f +0,0,0,0,0,0,0,0, # 60 - 67 +0,0,0,0,0,0,0,0, # 68 - 6f +0,0,0,0,0,0,0,0, # 70 - 77 +0,0,0,4,0,5,2,0, # 78 - 7f +1,1,1,1,1,1,1,1, # 80 - 87 +1,1,1,1,1,1,1,1, # 88 - 8f +1,1,1,1,1,1,1,1, # 90 - 97 +1,1,1,1,1,1,1,1, # 98 - 9f +1,1,1,1,1,1,1,1, # a0 - a7 +1,1,1,1,1,1,1,1, # a8 - af +1,1,1,1,1,1,1,1, # b0 - b7 +1,1,1,1,1,1,1,1, # b8 - bf +1,1,1,1,1,1,1,1, # c0 - c7 +1,1,1,1,1,1,1,1, # c8 - cf +1,1,1,1,1,1,1,1, # d0 - d7 +1,1,1,1,1,1,1,1, # d8 - df +1,1,1,1,1,1,1,1, # e0 - e7 +1,1,1,1,1,1,1,1, # e8 - ef +1,1,1,1,1,1,1,1, # f0 - f7 +1,1,1,1,1,1,1,1, # f8 - ff +) + +HZ_st = ( +eStart,eError, 3,eStart,eStart,eStart,eError,eError,# 00-07 +eError,eError,eError,eError,eItsMe,eItsMe,eItsMe,eItsMe,# 08-0f +eItsMe,eItsMe,eError,eError,eStart,eStart, 4,eError,# 10-17 + 5,eError, 6,eError, 5, 5, 4,eError,# 18-1f + 4,eError, 4, 4, 4,eError, 4,eError,# 20-27 + 4,eItsMe,eStart,eStart,eStart,eStart,eStart,eStart,# 28-2f +) + +HZCharLenTable = (0, 0, 0, 0, 0, 0) + +HZSMModel = {'classTable': HZ_cls, + 'classFactor': 6, + 'stateTable': HZ_st, + 'charLenTable': HZCharLenTable, + 'name': "HZ-GB-2312"} + +ISO2022CN_cls = ( +2,0,0,0,0,0,0,0, # 00 - 07 +0,0,0,0,0,0,0,0, # 08 - 0f +0,0,0,0,0,0,0,0, # 10 - 17 +0,0,0,1,0,0,0,0, # 18 - 1f +0,0,0,0,0,0,0,0, # 20 - 27 +0,3,0,0,0,0,0,0, # 28 - 2f +0,0,0,0,0,0,0,0, # 30 - 37 +0,0,0,0,0,0,0,0, # 38 - 3f +0,0,0,4,0,0,0,0, # 40 - 47 +0,0,0,0,0,0,0,0, # 48 - 4f +0,0,0,0,0,0,0,0, # 50 - 57 +0,0,0,0,0,0,0,0, # 58 - 5f +0,0,0,0,0,0,0,0, # 60 - 67 +0,0,0,0,0,0,0,0, # 68 - 6f +0,0,0,0,0,0,0,0, # 70 - 77 +0,0,0,0,0,0,0,0, # 78 - 7f +2,2,2,2,2,2,2,2, # 80 - 87 +2,2,2,2,2,2,2,2, # 88 - 8f +2,2,2,2,2,2,2,2, # 90 - 97 +2,2,2,2,2,2,2,2, # 98 - 9f +2,2,2,2,2,2,2,2, # a0 - a7 +2,2,2,2,2,2,2,2, # a8 - af +2,2,2,2,2,2,2,2, # b0 - b7 +2,2,2,2,2,2,2,2, # b8 - bf +2,2,2,2,2,2,2,2, # c0 - c7 +2,2,2,2,2,2,2,2, # c8 - cf +2,2,2,2,2,2,2,2, # d0 - d7 +2,2,2,2,2,2,2,2, # d8 - df +2,2,2,2,2,2,2,2, # e0 - e7 +2,2,2,2,2,2,2,2, # e8 - ef +2,2,2,2,2,2,2,2, # f0 - f7 +2,2,2,2,2,2,2,2, # f8 - ff +) + +ISO2022CN_st = ( +eStart, 3,eError,eStart,eStart,eStart,eStart,eStart,# 00-07 +eStart,eError,eError,eError,eError,eError,eError,eError,# 08-0f +eError,eError,eItsMe,eItsMe,eItsMe,eItsMe,eItsMe,eItsMe,# 10-17 +eItsMe,eItsMe,eItsMe,eError,eError,eError, 4,eError,# 18-1f +eError,eError,eError,eItsMe,eError,eError,eError,eError,# 20-27 + 5, 6,eError,eError,eError,eError,eError,eError,# 28-2f +eError,eError,eError,eItsMe,eError,eError,eError,eError,# 30-37 +eError,eError,eError,eError,eError,eItsMe,eError,eStart,# 38-3f +) + +ISO2022CNCharLenTable = (0, 0, 0, 0, 0, 0, 0, 0, 0) + +ISO2022CNSMModel = {'classTable': ISO2022CN_cls, + 'classFactor': 9, + 'stateTable': ISO2022CN_st, + 'charLenTable': ISO2022CNCharLenTable, + 'name': "ISO-2022-CN"} + +ISO2022JP_cls = ( +2,0,0,0,0,0,0,0, # 00 - 07 +0,0,0,0,0,0,2,2, # 08 - 0f +0,0,0,0,0,0,0,0, # 10 - 17 +0,0,0,1,0,0,0,0, # 18 - 1f +0,0,0,0,7,0,0,0, # 20 - 27 +3,0,0,0,0,0,0,0, # 28 - 2f +0,0,0,0,0,0,0,0, # 30 - 37 +0,0,0,0,0,0,0,0, # 38 - 3f +6,0,4,0,8,0,0,0, # 40 - 47 +0,9,5,0,0,0,0,0, # 48 - 4f +0,0,0,0,0,0,0,0, # 50 - 57 +0,0,0,0,0,0,0,0, # 58 - 5f +0,0,0,0,0,0,0,0, # 60 - 67 +0,0,0,0,0,0,0,0, # 68 - 6f +0,0,0,0,0,0,0,0, # 70 - 77 +0,0,0,0,0,0,0,0, # 78 - 7f +2,2,2,2,2,2,2,2, # 80 - 87 +2,2,2,2,2,2,2,2, # 88 - 8f +2,2,2,2,2,2,2,2, # 90 - 97 +2,2,2,2,2,2,2,2, # 98 - 9f +2,2,2,2,2,2,2,2, # a0 - a7 +2,2,2,2,2,2,2,2, # a8 - af +2,2,2,2,2,2,2,2, # b0 - b7 +2,2,2,2,2,2,2,2, # b8 - bf +2,2,2,2,2,2,2,2, # c0 - c7 +2,2,2,2,2,2,2,2, # c8 - cf +2,2,2,2,2,2,2,2, # d0 - d7 +2,2,2,2,2,2,2,2, # d8 - df +2,2,2,2,2,2,2,2, # e0 - e7 +2,2,2,2,2,2,2,2, # e8 - ef +2,2,2,2,2,2,2,2, # f0 - f7 +2,2,2,2,2,2,2,2, # f8 - ff +) + +ISO2022JP_st = ( +eStart, 3,eError,eStart,eStart,eStart,eStart,eStart,# 00-07 +eStart,eStart,eError,eError,eError,eError,eError,eError,# 08-0f +eError,eError,eError,eError,eItsMe,eItsMe,eItsMe,eItsMe,# 10-17 +eItsMe,eItsMe,eItsMe,eItsMe,eItsMe,eItsMe,eError,eError,# 18-1f +eError, 5,eError,eError,eError, 4,eError,eError,# 20-27 +eError,eError,eError, 6,eItsMe,eError,eItsMe,eError,# 28-2f +eError,eError,eError,eError,eError,eError,eItsMe,eItsMe,# 30-37 +eError,eError,eError,eItsMe,eError,eError,eError,eError,# 38-3f +eError,eError,eError,eError,eItsMe,eError,eStart,eStart,# 40-47 +) + +ISO2022JPCharLenTable = (0, 0, 0, 0, 0, 0, 0, 0, 0, 0) + +ISO2022JPSMModel = {'classTable': ISO2022JP_cls, + 'classFactor': 10, + 'stateTable': ISO2022JP_st, + 'charLenTable': ISO2022JPCharLenTable, + 'name': "ISO-2022-JP"} + +ISO2022KR_cls = ( +2,0,0,0,0,0,0,0, # 00 - 07 +0,0,0,0,0,0,0,0, # 08 - 0f +0,0,0,0,0,0,0,0, # 10 - 17 +0,0,0,1,0,0,0,0, # 18 - 1f +0,0,0,0,3,0,0,0, # 20 - 27 +0,4,0,0,0,0,0,0, # 28 - 2f +0,0,0,0,0,0,0,0, # 30 - 37 +0,0,0,0,0,0,0,0, # 38 - 3f +0,0,0,5,0,0,0,0, # 40 - 47 +0,0,0,0,0,0,0,0, # 48 - 4f +0,0,0,0,0,0,0,0, # 50 - 57 +0,0,0,0,0,0,0,0, # 58 - 5f +0,0,0,0,0,0,0,0, # 60 - 67 +0,0,0,0,0,0,0,0, # 68 - 6f +0,0,0,0,0,0,0,0, # 70 - 77 +0,0,0,0,0,0,0,0, # 78 - 7f +2,2,2,2,2,2,2,2, # 80 - 87 +2,2,2,2,2,2,2,2, # 88 - 8f +2,2,2,2,2,2,2,2, # 90 - 97 +2,2,2,2,2,2,2,2, # 98 - 9f +2,2,2,2,2,2,2,2, # a0 - a7 +2,2,2,2,2,2,2,2, # a8 - af +2,2,2,2,2,2,2,2, # b0 - b7 +2,2,2,2,2,2,2,2, # b8 - bf +2,2,2,2,2,2,2,2, # c0 - c7 +2,2,2,2,2,2,2,2, # c8 - cf +2,2,2,2,2,2,2,2, # d0 - d7 +2,2,2,2,2,2,2,2, # d8 - df +2,2,2,2,2,2,2,2, # e0 - e7 +2,2,2,2,2,2,2,2, # e8 - ef +2,2,2,2,2,2,2,2, # f0 - f7 +2,2,2,2,2,2,2,2, # f8 - ff +) + +ISO2022KR_st = ( +eStart, 3,eError,eStart,eStart,eStart,eError,eError,# 00-07 +eError,eError,eError,eError,eItsMe,eItsMe,eItsMe,eItsMe,# 08-0f +eItsMe,eItsMe,eError,eError,eError, 4,eError,eError,# 10-17 +eError,eError,eError,eError, 5,eError,eError,eError,# 18-1f +eError,eError,eError,eItsMe,eStart,eStart,eStart,eStart,# 20-27 +) + +ISO2022KRCharLenTable = (0, 0, 0, 0, 0, 0) + +ISO2022KRSMModel = {'classTable': ISO2022KR_cls, + 'classFactor': 6, + 'stateTable': ISO2022KR_st, + 'charLenTable': ISO2022KRCharLenTable, + 'name': "ISO-2022-KR"} + +# flake8: noqa diff --git a/resources/lib/libraries/requests/packages/chardet/eucjpprober.py b/resources/lib/libraries/requests/packages/chardet/eucjpprober.py new file mode 100644 index 00000000..8e64fdcc --- /dev/null +++ b/resources/lib/libraries/requests/packages/chardet/eucjpprober.py @@ -0,0 +1,90 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is mozilla.org code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +import sys +from . import constants +from .mbcharsetprober import MultiByteCharSetProber +from .codingstatemachine import CodingStateMachine +from .chardistribution import EUCJPDistributionAnalysis +from .jpcntx import EUCJPContextAnalysis +from .mbcssm import EUCJPSMModel + + +class EUCJPProber(MultiByteCharSetProber): + def __init__(self): + MultiByteCharSetProber.__init__(self) + self._mCodingSM = CodingStateMachine(EUCJPSMModel) + self._mDistributionAnalyzer = EUCJPDistributionAnalysis() + self._mContextAnalyzer = EUCJPContextAnalysis() + self.reset() + + def reset(self): + MultiByteCharSetProber.reset(self) + self._mContextAnalyzer.reset() + + def get_charset_name(self): + return "EUC-JP" + + def feed(self, aBuf): + aLen = len(aBuf) + for i in range(0, aLen): + # PY3K: aBuf is a byte array, so aBuf[i] is an int, not a byte + codingState = self._mCodingSM.next_state(aBuf[i]) + if codingState == constants.eError: + if constants._debug: + sys.stderr.write(self.get_charset_name() + + ' prober hit error at byte ' + str(i) + + '\n') + self._mState = constants.eNotMe + break + elif codingState == constants.eItsMe: + self._mState = constants.eFoundIt + break + elif codingState == constants.eStart: + charLen = self._mCodingSM.get_current_charlen() + if i == 0: + self._mLastChar[1] = aBuf[0] + self._mContextAnalyzer.feed(self._mLastChar, charLen) + self._mDistributionAnalyzer.feed(self._mLastChar, charLen) + else: + self._mContextAnalyzer.feed(aBuf[i - 1:i + 1], charLen) + self._mDistributionAnalyzer.feed(aBuf[i - 1:i + 1], + charLen) + + self._mLastChar[0] = aBuf[aLen - 1] + + if self.get_state() == constants.eDetecting: + if (self._mContextAnalyzer.got_enough_data() and + (self.get_confidence() > constants.SHORTCUT_THRESHOLD)): + self._mState = constants.eFoundIt + + return self.get_state() + + def get_confidence(self): + contxtCf = self._mContextAnalyzer.get_confidence() + distribCf = self._mDistributionAnalyzer.get_confidence() + return max(contxtCf, distribCf) diff --git a/resources/lib/libraries/requests/packages/chardet/euckrfreq.py b/resources/lib/libraries/requests/packages/chardet/euckrfreq.py new file mode 100644 index 00000000..a179e4c2 --- /dev/null +++ b/resources/lib/libraries/requests/packages/chardet/euckrfreq.py @@ -0,0 +1,596 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is Mozilla Communicator client code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +# Sampling from about 20M text materials include literature and computer technology + +# 128 --> 0.79 +# 256 --> 0.92 +# 512 --> 0.986 +# 1024 --> 0.99944 +# 2048 --> 0.99999 +# +# Idea Distribution Ratio = 0.98653 / (1-0.98653) = 73.24 +# Random Distribution Ration = 512 / (2350-512) = 0.279. +# +# Typical Distribution Ratio + +EUCKR_TYPICAL_DISTRIBUTION_RATIO = 6.0 + +EUCKR_TABLE_SIZE = 2352 + +# Char to FreqOrder table , +EUCKRCharToFreqOrder = ( \ + 13, 130, 120,1396, 481,1719,1720, 328, 609, 212,1721, 707, 400, 299,1722, 87, +1397,1723, 104, 536,1117,1203,1724,1267, 685,1268, 508,1725,1726,1727,1728,1398, +1399,1729,1730,1731, 141, 621, 326,1057, 368,1732, 267, 488, 20,1733,1269,1734, + 945,1400,1735, 47, 904,1270,1736,1737, 773, 248,1738, 409, 313, 786, 429,1739, + 116, 987, 813,1401, 683, 75,1204, 145,1740,1741,1742,1743, 16, 847, 667, 622, + 708,1744,1745,1746, 966, 787, 304, 129,1747, 60, 820, 123, 676,1748,1749,1750, +1751, 617,1752, 626,1753,1754,1755,1756, 653,1757,1758,1759,1760,1761,1762, 856, + 344,1763,1764,1765,1766, 89, 401, 418, 806, 905, 848,1767,1768,1769, 946,1205, + 709,1770,1118,1771, 241,1772,1773,1774,1271,1775, 569,1776, 999,1777,1778,1779, +1780, 337, 751,1058, 28, 628, 254,1781, 177, 906, 270, 349, 891,1079,1782, 19, +1783, 379,1784, 315,1785, 629, 754,1402, 559,1786, 636, 203,1206,1787, 710, 567, +1788, 935, 814,1789,1790,1207, 766, 528,1791,1792,1208,1793,1794,1795,1796,1797, +1403,1798,1799, 533,1059,1404,1405,1156,1406, 936, 884,1080,1800, 351,1801,1802, +1803,1804,1805, 801,1806,1807,1808,1119,1809,1157, 714, 474,1407,1810, 298, 899, + 885,1811,1120, 802,1158,1812, 892,1813,1814,1408, 659,1815,1816,1121,1817,1818, +1819,1820,1821,1822, 319,1823, 594, 545,1824, 815, 937,1209,1825,1826, 573,1409, +1022,1827,1210,1828,1829,1830,1831,1832,1833, 556, 722, 807,1122,1060,1834, 697, +1835, 900, 557, 715,1836,1410, 540,1411, 752,1159, 294, 597,1211, 976, 803, 770, +1412,1837,1838, 39, 794,1413, 358,1839, 371, 925,1840, 453, 661, 788, 531, 723, + 544,1023,1081, 869, 91,1841, 392, 430, 790, 602,1414, 677,1082, 457,1415,1416, +1842,1843, 475, 327,1024,1417, 795, 121,1844, 733, 403,1418,1845,1846,1847, 300, + 119, 711,1212, 627,1848,1272, 207,1849,1850, 796,1213, 382,1851, 519,1852,1083, + 893,1853,1854,1855, 367, 809, 487, 671,1856, 663,1857,1858, 956, 471, 306, 857, +1859,1860,1160,1084,1861,1862,1863,1864,1865,1061,1866,1867,1868,1869,1870,1871, + 282, 96, 574,1872, 502,1085,1873,1214,1874, 907,1875,1876, 827, 977,1419,1420, +1421, 268,1877,1422,1878,1879,1880, 308,1881, 2, 537,1882,1883,1215,1884,1885, + 127, 791,1886,1273,1423,1887, 34, 336, 404, 643,1888, 571, 654, 894, 840,1889, + 0, 886,1274, 122, 575, 260, 908, 938,1890,1275, 410, 316,1891,1892, 100,1893, +1894,1123, 48,1161,1124,1025,1895, 633, 901,1276,1896,1897, 115, 816,1898, 317, +1899, 694,1900, 909, 734,1424, 572, 866,1425, 691, 85, 524,1010, 543, 394, 841, +1901,1902,1903,1026,1904,1905,1906,1907,1908,1909, 30, 451, 651, 988, 310,1910, +1911,1426, 810,1216, 93,1912,1913,1277,1217,1914, 858, 759, 45, 58, 181, 610, + 269,1915,1916, 131,1062, 551, 443,1000, 821,1427, 957, 895,1086,1917,1918, 375, +1919, 359,1920, 687,1921, 822,1922, 293,1923,1924, 40, 662, 118, 692, 29, 939, + 887, 640, 482, 174,1925, 69,1162, 728,1428, 910,1926,1278,1218,1279, 386, 870, + 217, 854,1163, 823,1927,1928,1929,1930, 834,1931, 78,1932, 859,1933,1063,1934, +1935,1936,1937, 438,1164, 208, 595,1938,1939,1940,1941,1219,1125,1942, 280, 888, +1429,1430,1220,1431,1943,1944,1945,1946,1947,1280, 150, 510,1432,1948,1949,1950, +1951,1952,1953,1954,1011,1087,1955,1433,1043,1956, 881,1957, 614, 958,1064,1065, +1221,1958, 638,1001, 860, 967, 896,1434, 989, 492, 553,1281,1165,1959,1282,1002, +1283,1222,1960,1961,1962,1963, 36, 383, 228, 753, 247, 454,1964, 876, 678,1965, +1966,1284, 126, 464, 490, 835, 136, 672, 529, 940,1088,1435, 473,1967,1968, 467, + 50, 390, 227, 587, 279, 378, 598, 792, 968, 240, 151, 160, 849, 882,1126,1285, + 639,1044, 133, 140, 288, 360, 811, 563,1027, 561, 142, 523,1969,1970,1971, 7, + 103, 296, 439, 407, 506, 634, 990,1972,1973,1974,1975, 645,1976,1977,1978,1979, +1980,1981, 236,1982,1436,1983,1984,1089, 192, 828, 618, 518,1166, 333,1127,1985, + 818,1223,1986,1987,1988,1989,1990,1991,1992,1993, 342,1128,1286, 746, 842,1994, +1995, 560, 223,1287, 98, 8, 189, 650, 978,1288,1996,1437,1997, 17, 345, 250, + 423, 277, 234, 512, 226, 97, 289, 42, 167,1998, 201,1999,2000, 843, 836, 824, + 532, 338, 783,1090, 182, 576, 436,1438,1439, 527, 500,2001, 947, 889,2002,2003, +2004,2005, 262, 600, 314, 447,2006, 547,2007, 693, 738,1129,2008, 71,1440, 745, + 619, 688,2009, 829,2010,2011, 147,2012, 33, 948,2013,2014, 74, 224,2015, 61, + 191, 918, 399, 637,2016,1028,1130, 257, 902,2017,2018,2019,2020,2021,2022,2023, +2024,2025,2026, 837,2027,2028,2029,2030, 179, 874, 591, 52, 724, 246,2031,2032, +2033,2034,1167, 969,2035,1289, 630, 605, 911,1091,1168,2036,2037,2038,1441, 912, +2039, 623,2040,2041, 253,1169,1290,2042,1442, 146, 620, 611, 577, 433,2043,1224, + 719,1170, 959, 440, 437, 534, 84, 388, 480,1131, 159, 220, 198, 679,2044,1012, + 819,1066,1443, 113,1225, 194, 318,1003,1029,2045,2046,2047,2048,1067,2049,2050, +2051,2052,2053, 59, 913, 112,2054, 632,2055, 455, 144, 739,1291,2056, 273, 681, + 499,2057, 448,2058,2059, 760,2060,2061, 970, 384, 169, 245,1132,2062,2063, 414, +1444,2064,2065, 41, 235,2066, 157, 252, 877, 568, 919, 789, 580,2067, 725,2068, +2069,1292,2070,2071,1445,2072,1446,2073,2074, 55, 588, 66,1447, 271,1092,2075, +1226,2076, 960,1013, 372,2077,2078,2079,2080,2081,1293,2082,2083,2084,2085, 850, +2086,2087,2088,2089,2090, 186,2091,1068, 180,2092,2093,2094, 109,1227, 522, 606, +2095, 867,1448,1093, 991,1171, 926, 353,1133,2096, 581,2097,2098,2099,1294,1449, +1450,2100, 596,1172,1014,1228,2101,1451,1295,1173,1229,2102,2103,1296,1134,1452, + 949,1135,2104,2105,1094,1453,1454,1455,2106,1095,2107,2108,2109,2110,2111,2112, +2113,2114,2115,2116,2117, 804,2118,2119,1230,1231, 805,1456, 405,1136,2120,2121, +2122,2123,2124, 720, 701,1297, 992,1457, 927,1004,2125,2126,2127,2128,2129,2130, + 22, 417,2131, 303,2132, 385,2133, 971, 520, 513,2134,1174, 73,1096, 231, 274, + 962,1458, 673,2135,1459,2136, 152,1137,2137,2138,2139,2140,1005,1138,1460,1139, +2141,2142,2143,2144, 11, 374, 844,2145, 154,1232, 46,1461,2146, 838, 830, 721, +1233, 106,2147, 90, 428, 462, 578, 566,1175, 352,2148,2149, 538,1234, 124,1298, +2150,1462, 761, 565,2151, 686,2152, 649,2153, 72, 173,2154, 460, 415,2155,1463, +2156,1235, 305,2157,2158,2159,2160,2161,2162, 579,2163,2164,2165,2166,2167, 747, +2168,2169,2170,2171,1464, 669,2172,2173,2174,2175,2176,1465,2177, 23, 530, 285, +2178, 335, 729,2179, 397,2180,2181,2182,1030,2183,2184, 698,2185,2186, 325,2187, +2188, 369,2189, 799,1097,1015, 348,2190,1069, 680,2191, 851,1466,2192,2193, 10, +2194, 613, 424,2195, 979, 108, 449, 589, 27, 172, 81,1031, 80, 774, 281, 350, +1032, 525, 301, 582,1176,2196, 674,1045,2197,2198,1467, 730, 762,2199,2200,2201, +2202,1468,2203, 993,2204,2205, 266,1070, 963,1140,2206,2207,2208, 664,1098, 972, +2209,2210,2211,1177,1469,1470, 871,2212,2213,2214,2215,2216,1471,2217,2218,2219, +2220,2221,2222,2223,2224,2225,2226,2227,1472,1236,2228,2229,2230,2231,2232,2233, +2234,2235,1299,2236,2237, 200,2238, 477, 373,2239,2240, 731, 825, 777,2241,2242, +2243, 521, 486, 548,2244,2245,2246,1473,1300, 53, 549, 137, 875, 76, 158,2247, +1301,1474, 469, 396,1016, 278, 712,2248, 321, 442, 503, 767, 744, 941,1237,1178, +1475,2249, 82, 178,1141,1179, 973,2250,1302,2251, 297,2252,2253, 570,2254,2255, +2256, 18, 450, 206,2257, 290, 292,1142,2258, 511, 162, 99, 346, 164, 735,2259, +1476,1477, 4, 554, 343, 798,1099,2260,1100,2261, 43, 171,1303, 139, 215,2262, +2263, 717, 775,2264,1033, 322, 216,2265, 831,2266, 149,2267,1304,2268,2269, 702, +1238, 135, 845, 347, 309,2270, 484,2271, 878, 655, 238,1006,1478,2272, 67,2273, + 295,2274,2275, 461,2276, 478, 942, 412,2277,1034,2278,2279,2280, 265,2281, 541, +2282,2283,2284,2285,2286, 70, 852,1071,2287,2288,2289,2290, 21, 56, 509, 117, + 432,2291,2292, 331, 980, 552,1101, 148, 284, 105, 393,1180,1239, 755,2293, 187, +2294,1046,1479,2295, 340,2296, 63,1047, 230,2297,2298,1305, 763,1306, 101, 800, + 808, 494,2299,2300,2301, 903,2302, 37,1072, 14, 5,2303, 79, 675,2304, 312, +2305,2306,2307,2308,2309,1480, 6,1307,2310,2311,2312, 1, 470, 35, 24, 229, +2313, 695, 210, 86, 778, 15, 784, 592, 779, 32, 77, 855, 964,2314, 259,2315, + 501, 380,2316,2317, 83, 981, 153, 689,1308,1481,1482,1483,2318,2319, 716,1484, +2320,2321,2322,2323,2324,2325,1485,2326,2327, 128, 57, 68, 261,1048, 211, 170, +1240, 31,2328, 51, 435, 742,2329,2330,2331, 635,2332, 264, 456,2333,2334,2335, + 425,2336,1486, 143, 507, 263, 943,2337, 363, 920,1487, 256,1488,1102, 243, 601, +1489,2338,2339,2340,2341,2342,2343,2344, 861,2345,2346,2347,2348,2349,2350, 395, +2351,1490,1491, 62, 535, 166, 225,2352,2353, 668, 419,1241, 138, 604, 928,2354, +1181,2355,1492,1493,2356,2357,2358,1143,2359, 696,2360, 387, 307,1309, 682, 476, +2361,2362, 332, 12, 222, 156,2363, 232,2364, 641, 276, 656, 517,1494,1495,1035, + 416, 736,1496,2365,1017, 586,2366,2367,2368,1497,2369, 242,2370,2371,2372,1498, +2373, 965, 713,2374,2375,2376,2377, 740, 982,1499, 944,1500,1007,2378,2379,1310, +1501,2380,2381,2382, 785, 329,2383,2384,1502,2385,2386,2387, 932,2388,1503,2389, +2390,2391,2392,1242,2393,2394,2395,2396,2397, 994, 950,2398,2399,2400,2401,1504, +1311,2402,2403,2404,2405,1049, 749,2406,2407, 853, 718,1144,1312,2408,1182,1505, +2409,2410, 255, 516, 479, 564, 550, 214,1506,1507,1313, 413, 239, 444, 339,1145, +1036,1508,1509,1314,1037,1510,1315,2411,1511,2412,2413,2414, 176, 703, 497, 624, + 593, 921, 302,2415, 341, 165,1103,1512,2416,1513,2417,2418,2419, 376,2420, 700, +2421,2422,2423, 258, 768,1316,2424,1183,2425, 995, 608,2426,2427,2428,2429, 221, +2430,2431,2432,2433,2434,2435,2436,2437, 195, 323, 726, 188, 897, 983,1317, 377, + 644,1050, 879,2438, 452,2439,2440,2441,2442,2443,2444, 914,2445,2446,2447,2448, + 915, 489,2449,1514,1184,2450,2451, 515, 64, 427, 495,2452, 583,2453, 483, 485, +1038, 562, 213,1515, 748, 666,2454,2455,2456,2457, 334,2458, 780, 996,1008, 705, +1243,2459,2460,2461,2462,2463, 114,2464, 493,1146, 366, 163,1516, 961,1104,2465, + 291,2466,1318,1105,2467,1517, 365,2468, 355, 951,1244,2469,1319,2470, 631,2471, +2472, 218,1320, 364, 320, 756,1518,1519,1321,1520,1322,2473,2474,2475,2476, 997, +2477,2478,2479,2480, 665,1185,2481, 916,1521,2482,2483,2484, 584, 684,2485,2486, + 797,2487,1051,1186,2488,2489,2490,1522,2491,2492, 370,2493,1039,1187, 65,2494, + 434, 205, 463,1188,2495, 125, 812, 391, 402, 826, 699, 286, 398, 155, 781, 771, + 585,2496, 590, 505,1073,2497, 599, 244, 219, 917,1018, 952, 646,1523,2498,1323, +2499,2500, 49, 984, 354, 741,2501, 625,2502,1324,2503,1019, 190, 357, 757, 491, + 95, 782, 868,2504,2505,2506,2507,2508,2509, 134,1524,1074, 422,1525, 898,2510, + 161,2511,2512,2513,2514, 769,2515,1526,2516,2517, 411,1325,2518, 472,1527,2519, +2520,2521,2522,2523,2524, 985,2525,2526,2527,2528,2529,2530, 764,2531,1245,2532, +2533, 25, 204, 311,2534, 496,2535,1052,2536,2537,2538,2539,2540,2541,2542, 199, + 704, 504, 468, 758, 657,1528, 196, 44, 839,1246, 272, 750,2543, 765, 862,2544, +2545,1326,2546, 132, 615, 933,2547, 732,2548,2549,2550,1189,1529,2551, 283,1247, +1053, 607, 929,2552,2553,2554, 930, 183, 872, 616,1040,1147,2555,1148,1020, 441, + 249,1075,2556,2557,2558, 466, 743,2559,2560,2561, 92, 514, 426, 420, 526,2562, +2563,2564,2565,2566,2567,2568, 185,2569,2570,2571,2572, 776,1530, 658,2573, 362, +2574, 361, 922,1076, 793,2575,2576,2577,2578,2579,2580,1531, 251,2581,2582,2583, +2584,1532, 54, 612, 237,1327,2585,2586, 275, 408, 647, 111,2587,1533,1106, 465, + 3, 458, 9, 38,2588, 107, 110, 890, 209, 26, 737, 498,2589,1534,2590, 431, + 202, 88,1535, 356, 287,1107, 660,1149,2591, 381,1536, 986,1150, 445,1248,1151, + 974,2592,2593, 846,2594, 446, 953, 184,1249,1250, 727,2595, 923, 193, 883,2596, +2597,2598, 102, 324, 539, 817,2599, 421,1041,2600, 832,2601, 94, 175, 197, 406, +2602, 459,2603,2604,2605,2606,2607, 330, 555,2608,2609,2610, 706,1108, 389,2611, +2612,2613,2614, 233,2615, 833, 558, 931, 954,1251,2616,2617,1537, 546,2618,2619, +1009,2620,2621,2622,1538, 690,1328,2623, 955,2624,1539,2625,2626, 772,2627,2628, +2629,2630,2631, 924, 648, 863, 603,2632,2633, 934,1540, 864, 865,2634, 642,1042, + 670,1190,2635,2636,2637,2638, 168,2639, 652, 873, 542,1054,1541,2640,2641,2642, # 512, 256 +#Everything below is of no interest for detection purpose +2643,2644,2645,2646,2647,2648,2649,2650,2651,2652,2653,2654,2655,2656,2657,2658, +2659,2660,2661,2662,2663,2664,2665,2666,2667,2668,2669,2670,2671,2672,2673,2674, +2675,2676,2677,2678,2679,2680,2681,2682,2683,2684,2685,2686,2687,2688,2689,2690, +2691,2692,2693,2694,2695,2696,2697,2698,2699,1542, 880,2700,2701,2702,2703,2704, +2705,2706,2707,2708,2709,2710,2711,2712,2713,2714,2715,2716,2717,2718,2719,2720, +2721,2722,2723,2724,2725,1543,2726,2727,2728,2729,2730,2731,2732,1544,2733,2734, +2735,2736,2737,2738,2739,2740,2741,2742,2743,2744,2745,2746,2747,2748,2749,2750, +2751,2752,2753,2754,1545,2755,2756,2757,2758,2759,2760,2761,2762,2763,2764,2765, +2766,1546,2767,1547,2768,2769,2770,2771,2772,2773,2774,2775,2776,2777,2778,2779, +2780,2781,2782,2783,2784,2785,2786,1548,2787,2788,2789,1109,2790,2791,2792,2793, +2794,2795,2796,2797,2798,2799,2800,2801,2802,2803,2804,2805,2806,2807,2808,2809, +2810,2811,2812,1329,2813,2814,2815,2816,2817,2818,2819,2820,2821,2822,2823,2824, +2825,2826,2827,2828,2829,2830,2831,2832,2833,2834,2835,2836,2837,2838,2839,2840, +2841,2842,2843,2844,2845,2846,2847,2848,2849,2850,2851,2852,2853,2854,2855,2856, +1549,2857,2858,2859,2860,1550,2861,2862,1551,2863,2864,2865,2866,2867,2868,2869, +2870,2871,2872,2873,2874,1110,1330,2875,2876,2877,2878,2879,2880,2881,2882,2883, +2884,2885,2886,2887,2888,2889,2890,2891,2892,2893,2894,2895,2896,2897,2898,2899, +2900,2901,2902,2903,2904,2905,2906,2907,2908,2909,2910,2911,2912,2913,2914,2915, +2916,2917,2918,2919,2920,2921,2922,2923,2924,2925,2926,2927,2928,2929,2930,1331, +2931,2932,2933,2934,2935,2936,2937,2938,2939,2940,2941,2942,2943,1552,2944,2945, +2946,2947,2948,2949,2950,2951,2952,2953,2954,2955,2956,2957,2958,2959,2960,2961, +2962,2963,2964,1252,2965,2966,2967,2968,2969,2970,2971,2972,2973,2974,2975,2976, +2977,2978,2979,2980,2981,2982,2983,2984,2985,2986,2987,2988,2989,2990,2991,2992, +2993,2994,2995,2996,2997,2998,2999,3000,3001,3002,3003,3004,3005,3006,3007,3008, +3009,3010,3011,3012,1553,3013,3014,3015,3016,3017,1554,3018,1332,3019,3020,3021, +3022,3023,3024,3025,3026,3027,3028,3029,3030,3031,3032,3033,3034,3035,3036,3037, +3038,3039,3040,3041,3042,3043,3044,3045,3046,3047,3048,3049,3050,1555,3051,3052, +3053,1556,1557,3054,3055,3056,3057,3058,3059,3060,3061,3062,3063,3064,3065,3066, +3067,1558,3068,3069,3070,3071,3072,3073,3074,3075,3076,1559,3077,3078,3079,3080, +3081,3082,3083,1253,3084,3085,3086,3087,3088,3089,3090,3091,3092,3093,3094,3095, +3096,3097,3098,3099,3100,3101,3102,3103,3104,3105,3106,3107,3108,1152,3109,3110, +3111,3112,3113,1560,3114,3115,3116,3117,1111,3118,3119,3120,3121,3122,3123,3124, +3125,3126,3127,3128,3129,3130,3131,3132,3133,3134,3135,3136,3137,3138,3139,3140, +3141,3142,3143,3144,3145,3146,3147,3148,3149,3150,3151,3152,3153,3154,3155,3156, +3157,3158,3159,3160,3161,3162,3163,3164,3165,3166,3167,3168,3169,3170,3171,3172, +3173,3174,3175,3176,1333,3177,3178,3179,3180,3181,3182,3183,3184,3185,3186,3187, +3188,3189,1561,3190,3191,1334,3192,3193,3194,3195,3196,3197,3198,3199,3200,3201, +3202,3203,3204,3205,3206,3207,3208,3209,3210,3211,3212,3213,3214,3215,3216,3217, +3218,3219,3220,3221,3222,3223,3224,3225,3226,3227,3228,3229,3230,3231,3232,3233, +3234,1562,3235,3236,3237,3238,3239,3240,3241,3242,3243,3244,3245,3246,3247,3248, +3249,3250,3251,3252,3253,3254,3255,3256,3257,3258,3259,3260,3261,3262,3263,3264, +3265,3266,3267,3268,3269,3270,3271,3272,3273,3274,3275,3276,3277,1563,3278,3279, +3280,3281,3282,3283,3284,3285,3286,3287,3288,3289,3290,3291,3292,3293,3294,3295, +3296,3297,3298,3299,3300,3301,3302,3303,3304,3305,3306,3307,3308,3309,3310,3311, +3312,3313,3314,3315,3316,3317,3318,3319,3320,3321,3322,3323,3324,3325,3326,3327, +3328,3329,3330,3331,3332,3333,3334,3335,3336,3337,3338,3339,3340,3341,3342,3343, +3344,3345,3346,3347,3348,3349,3350,3351,3352,3353,3354,3355,3356,3357,3358,3359, +3360,3361,3362,3363,3364,1335,3365,3366,3367,3368,3369,3370,3371,3372,3373,3374, +3375,3376,3377,3378,3379,3380,3381,3382,3383,3384,3385,3386,3387,1336,3388,3389, +3390,3391,3392,3393,3394,3395,3396,3397,3398,3399,3400,3401,3402,3403,3404,3405, +3406,3407,3408,3409,3410,3411,3412,3413,3414,1337,3415,3416,3417,3418,3419,1338, +3420,3421,3422,1564,1565,3423,3424,3425,3426,3427,3428,3429,3430,3431,1254,3432, +3433,3434,1339,3435,3436,3437,3438,3439,1566,3440,3441,3442,3443,3444,3445,3446, +3447,3448,3449,3450,3451,3452,3453,3454,1255,3455,3456,3457,3458,3459,1567,1191, +3460,1568,1569,3461,3462,3463,1570,3464,3465,3466,3467,3468,1571,3469,3470,3471, +3472,3473,1572,3474,3475,3476,3477,3478,3479,3480,3481,3482,3483,3484,3485,3486, +1340,3487,3488,3489,3490,3491,3492,1021,3493,3494,3495,3496,3497,3498,1573,3499, +1341,3500,3501,3502,3503,3504,3505,3506,3507,3508,3509,3510,3511,1342,3512,3513, +3514,3515,3516,1574,1343,3517,3518,3519,1575,3520,1576,3521,3522,3523,3524,3525, +3526,3527,3528,3529,3530,3531,3532,3533,3534,3535,3536,3537,3538,3539,3540,3541, +3542,3543,3544,3545,3546,3547,3548,3549,3550,3551,3552,3553,3554,3555,3556,3557, +3558,3559,3560,3561,3562,3563,3564,3565,3566,3567,3568,3569,3570,3571,3572,3573, +3574,3575,3576,3577,3578,3579,3580,1577,3581,3582,1578,3583,3584,3585,3586,3587, +3588,3589,3590,3591,3592,3593,3594,3595,3596,3597,3598,3599,3600,3601,3602,3603, +3604,1579,3605,3606,3607,3608,3609,3610,3611,3612,3613,3614,3615,3616,3617,3618, +3619,3620,3621,3622,3623,3624,3625,3626,3627,3628,3629,1580,3630,3631,1581,3632, +3633,3634,3635,3636,3637,3638,3639,3640,3641,3642,3643,3644,3645,3646,3647,3648, +3649,3650,3651,3652,3653,3654,3655,3656,1582,3657,3658,3659,3660,3661,3662,3663, +3664,3665,3666,3667,3668,3669,3670,3671,3672,3673,3674,3675,3676,3677,3678,3679, +3680,3681,3682,3683,3684,3685,3686,3687,3688,3689,3690,3691,3692,3693,3694,3695, +3696,3697,3698,3699,3700,1192,3701,3702,3703,3704,1256,3705,3706,3707,3708,1583, +1257,3709,3710,3711,3712,3713,3714,3715,3716,1584,3717,3718,3719,3720,3721,3722, +3723,3724,3725,3726,3727,3728,3729,3730,3731,3732,3733,3734,3735,3736,3737,3738, +3739,3740,3741,3742,3743,3744,3745,1344,3746,3747,3748,3749,3750,3751,3752,3753, +3754,3755,3756,1585,3757,3758,3759,3760,3761,3762,3763,3764,3765,3766,1586,3767, +3768,3769,3770,3771,3772,3773,3774,3775,3776,3777,3778,1345,3779,3780,3781,3782, +3783,3784,3785,3786,3787,3788,3789,3790,3791,3792,3793,3794,3795,1346,1587,3796, +3797,1588,3798,3799,3800,3801,3802,3803,3804,3805,3806,1347,3807,3808,3809,3810, +3811,1589,3812,3813,3814,3815,3816,3817,3818,3819,3820,3821,1590,3822,3823,1591, +1348,3824,3825,3826,3827,3828,3829,3830,1592,3831,3832,1593,3833,3834,3835,3836, +3837,3838,3839,3840,3841,3842,3843,3844,1349,3845,3846,3847,3848,3849,3850,3851, +3852,3853,3854,3855,3856,3857,3858,1594,3859,3860,3861,3862,3863,3864,3865,3866, +3867,3868,3869,1595,3870,3871,3872,3873,1596,3874,3875,3876,3877,3878,3879,3880, +3881,3882,3883,3884,3885,3886,1597,3887,3888,3889,3890,3891,3892,3893,3894,3895, +1598,3896,3897,3898,1599,1600,3899,1350,3900,1351,3901,3902,1352,3903,3904,3905, +3906,3907,3908,3909,3910,3911,3912,3913,3914,3915,3916,3917,3918,3919,3920,3921, +3922,3923,3924,1258,3925,3926,3927,3928,3929,3930,3931,1193,3932,1601,3933,3934, +3935,3936,3937,3938,3939,3940,3941,3942,3943,1602,3944,3945,3946,3947,3948,1603, +3949,3950,3951,3952,3953,3954,3955,3956,3957,3958,3959,3960,3961,3962,3963,3964, +3965,1604,3966,3967,3968,3969,3970,3971,3972,3973,3974,3975,3976,3977,1353,3978, +3979,3980,3981,3982,3983,3984,3985,3986,3987,3988,3989,3990,3991,1354,3992,3993, +3994,3995,3996,3997,3998,3999,4000,4001,4002,4003,4004,4005,4006,4007,4008,4009, +4010,4011,4012,4013,4014,4015,4016,4017,4018,4019,4020,4021,4022,4023,1355,4024, +4025,4026,4027,4028,4029,4030,4031,4032,4033,4034,4035,4036,4037,4038,4039,4040, +1605,4041,4042,4043,4044,4045,4046,4047,4048,4049,4050,4051,4052,4053,4054,4055, +4056,4057,4058,4059,4060,1606,4061,4062,4063,4064,1607,4065,4066,4067,4068,4069, +4070,4071,4072,4073,4074,4075,4076,1194,4077,4078,1608,4079,4080,4081,4082,4083, +4084,4085,4086,4087,1609,4088,4089,4090,4091,4092,4093,4094,4095,4096,4097,4098, +4099,4100,4101,4102,4103,4104,4105,4106,4107,4108,1259,4109,4110,4111,4112,4113, +4114,4115,4116,4117,4118,4119,4120,4121,4122,4123,4124,1195,4125,4126,4127,1610, +4128,4129,4130,4131,4132,4133,4134,4135,4136,4137,1356,4138,4139,4140,4141,4142, +4143,4144,1611,4145,4146,4147,4148,4149,4150,4151,4152,4153,4154,4155,4156,4157, +4158,4159,4160,4161,4162,4163,4164,4165,4166,4167,4168,4169,4170,4171,4172,4173, +4174,4175,4176,4177,4178,4179,4180,4181,4182,4183,4184,4185,4186,4187,4188,4189, +4190,4191,4192,4193,4194,4195,4196,4197,4198,4199,4200,4201,4202,4203,4204,4205, +4206,4207,4208,4209,4210,4211,4212,4213,4214,4215,4216,4217,4218,4219,1612,4220, +4221,4222,4223,4224,4225,4226,4227,1357,4228,1613,4229,4230,4231,4232,4233,4234, +4235,4236,4237,4238,4239,4240,4241,4242,4243,1614,4244,4245,4246,4247,4248,4249, +4250,4251,4252,4253,4254,4255,4256,4257,4258,4259,4260,4261,4262,4263,4264,4265, +4266,4267,4268,4269,4270,1196,1358,4271,4272,4273,4274,4275,4276,4277,4278,4279, +4280,4281,4282,4283,4284,4285,4286,4287,1615,4288,4289,4290,4291,4292,4293,4294, +4295,4296,4297,4298,4299,4300,4301,4302,4303,4304,4305,4306,4307,4308,4309,4310, +4311,4312,4313,4314,4315,4316,4317,4318,4319,4320,4321,4322,4323,4324,4325,4326, +4327,4328,4329,4330,4331,4332,4333,4334,1616,4335,4336,4337,4338,4339,4340,4341, +4342,4343,4344,4345,4346,4347,4348,4349,4350,4351,4352,4353,4354,4355,4356,4357, +4358,4359,4360,1617,4361,4362,4363,4364,4365,1618,4366,4367,4368,4369,4370,4371, +4372,4373,4374,4375,4376,4377,4378,4379,4380,4381,4382,4383,4384,4385,4386,4387, +4388,4389,4390,4391,4392,4393,4394,4395,4396,4397,4398,4399,4400,4401,4402,4403, +4404,4405,4406,4407,4408,4409,4410,4411,4412,4413,4414,4415,4416,1619,4417,4418, +4419,4420,4421,4422,4423,4424,4425,1112,4426,4427,4428,4429,4430,1620,4431,4432, +4433,4434,4435,4436,4437,4438,4439,4440,4441,4442,1260,1261,4443,4444,4445,4446, +4447,4448,4449,4450,4451,4452,4453,4454,4455,1359,4456,4457,4458,4459,4460,4461, +4462,4463,4464,4465,1621,4466,4467,4468,4469,4470,4471,4472,4473,4474,4475,4476, +4477,4478,4479,4480,4481,4482,4483,4484,4485,4486,4487,4488,4489,1055,4490,4491, +4492,4493,4494,4495,4496,4497,4498,4499,4500,4501,4502,4503,4504,4505,4506,4507, +4508,4509,4510,4511,4512,4513,4514,4515,4516,4517,4518,1622,4519,4520,4521,1623, +4522,4523,4524,4525,4526,4527,4528,4529,4530,4531,4532,4533,4534,4535,1360,4536, +4537,4538,4539,4540,4541,4542,4543, 975,4544,4545,4546,4547,4548,4549,4550,4551, +4552,4553,4554,4555,4556,4557,4558,4559,4560,4561,4562,4563,4564,4565,4566,4567, +4568,4569,4570,4571,1624,4572,4573,4574,4575,4576,1625,4577,4578,4579,4580,4581, +4582,4583,4584,1626,4585,4586,4587,4588,4589,4590,4591,4592,4593,4594,4595,1627, +4596,4597,4598,4599,4600,4601,4602,4603,4604,4605,4606,4607,4608,4609,4610,4611, +4612,4613,4614,4615,1628,4616,4617,4618,4619,4620,4621,4622,4623,4624,4625,4626, +4627,4628,4629,4630,4631,4632,4633,4634,4635,4636,4637,4638,4639,4640,4641,4642, +4643,4644,4645,4646,4647,4648,4649,1361,4650,4651,4652,4653,4654,4655,4656,4657, +4658,4659,4660,4661,1362,4662,4663,4664,4665,4666,4667,4668,4669,4670,4671,4672, +4673,4674,4675,4676,4677,4678,4679,4680,4681,4682,1629,4683,4684,4685,4686,4687, +1630,4688,4689,4690,4691,1153,4692,4693,4694,1113,4695,4696,4697,4698,4699,4700, +4701,4702,4703,4704,4705,4706,4707,4708,4709,4710,4711,1197,4712,4713,4714,4715, +4716,4717,4718,4719,4720,4721,4722,4723,4724,4725,4726,4727,4728,4729,4730,4731, +4732,4733,4734,4735,1631,4736,1632,4737,4738,4739,4740,4741,4742,4743,4744,1633, +4745,4746,4747,4748,4749,1262,4750,4751,4752,4753,4754,1363,4755,4756,4757,4758, +4759,4760,4761,4762,4763,4764,4765,4766,4767,4768,1634,4769,4770,4771,4772,4773, +4774,4775,4776,4777,4778,1635,4779,4780,4781,4782,4783,4784,4785,4786,4787,4788, +4789,1636,4790,4791,4792,4793,4794,4795,4796,4797,4798,4799,4800,4801,4802,4803, +4804,4805,4806,1637,4807,4808,4809,1638,4810,4811,4812,4813,4814,4815,4816,4817, +4818,1639,4819,4820,4821,4822,4823,4824,4825,4826,4827,4828,4829,4830,4831,4832, +4833,1077,4834,4835,4836,4837,4838,4839,4840,4841,4842,4843,4844,4845,4846,4847, +4848,4849,4850,4851,4852,4853,4854,4855,4856,4857,4858,4859,4860,4861,4862,4863, +4864,4865,4866,4867,4868,4869,4870,4871,4872,4873,4874,4875,4876,4877,4878,4879, +4880,4881,4882,4883,1640,4884,4885,1641,4886,4887,4888,4889,4890,4891,4892,4893, +4894,4895,4896,4897,4898,4899,4900,4901,4902,4903,4904,4905,4906,4907,4908,4909, +4910,4911,1642,4912,4913,4914,1364,4915,4916,4917,4918,4919,4920,4921,4922,4923, +4924,4925,4926,4927,4928,4929,4930,4931,1643,4932,4933,4934,4935,4936,4937,4938, +4939,4940,4941,4942,4943,4944,4945,4946,4947,4948,4949,4950,4951,4952,4953,4954, +4955,4956,4957,4958,4959,4960,4961,4962,4963,4964,4965,4966,4967,4968,4969,4970, +4971,4972,4973,4974,4975,4976,4977,4978,4979,4980,1644,4981,4982,4983,4984,1645, +4985,4986,1646,4987,4988,4989,4990,4991,4992,4993,4994,4995,4996,4997,4998,4999, +5000,5001,5002,5003,5004,5005,1647,5006,1648,5007,5008,5009,5010,5011,5012,1078, +5013,5014,5015,5016,5017,5018,5019,5020,5021,5022,5023,5024,5025,5026,5027,5028, +1365,5029,5030,5031,5032,5033,5034,5035,5036,5037,5038,5039,1649,5040,5041,5042, +5043,5044,5045,1366,5046,5047,5048,5049,5050,5051,5052,5053,5054,5055,1650,5056, +5057,5058,5059,5060,5061,5062,5063,5064,5065,5066,5067,5068,5069,5070,5071,5072, +5073,5074,5075,5076,5077,1651,5078,5079,5080,5081,5082,5083,5084,5085,5086,5087, +5088,5089,5090,5091,5092,5093,5094,5095,5096,5097,5098,5099,5100,5101,5102,5103, +5104,5105,5106,5107,5108,5109,5110,1652,5111,5112,5113,5114,5115,5116,5117,5118, +1367,5119,5120,5121,5122,5123,5124,5125,5126,5127,5128,5129,1653,5130,5131,5132, +5133,5134,5135,5136,5137,5138,5139,5140,5141,5142,5143,5144,5145,5146,5147,5148, +5149,1368,5150,1654,5151,1369,5152,5153,5154,5155,5156,5157,5158,5159,5160,5161, +5162,5163,5164,5165,5166,5167,5168,5169,5170,5171,5172,5173,5174,5175,5176,5177, +5178,1370,5179,5180,5181,5182,5183,5184,5185,5186,5187,5188,5189,5190,5191,5192, +5193,5194,5195,5196,5197,5198,1655,5199,5200,5201,5202,1656,5203,5204,5205,5206, +1371,5207,1372,5208,5209,5210,5211,1373,5212,5213,1374,5214,5215,5216,5217,5218, +5219,5220,5221,5222,5223,5224,5225,5226,5227,5228,5229,5230,5231,5232,5233,5234, +5235,5236,5237,5238,5239,5240,5241,5242,5243,5244,5245,5246,5247,1657,5248,5249, +5250,5251,1658,1263,5252,5253,5254,5255,5256,1375,5257,5258,5259,5260,5261,5262, +5263,5264,5265,5266,5267,5268,5269,5270,5271,5272,5273,5274,5275,5276,5277,5278, +5279,5280,5281,5282,5283,1659,5284,5285,5286,5287,5288,5289,5290,5291,5292,5293, +5294,5295,5296,5297,5298,5299,5300,1660,5301,5302,5303,5304,5305,5306,5307,5308, +5309,5310,5311,5312,5313,5314,5315,5316,5317,5318,5319,5320,5321,1376,5322,5323, +5324,5325,5326,5327,5328,5329,5330,5331,5332,5333,1198,5334,5335,5336,5337,5338, +5339,5340,5341,5342,5343,1661,5344,5345,5346,5347,5348,5349,5350,5351,5352,5353, +5354,5355,5356,5357,5358,5359,5360,5361,5362,5363,5364,5365,5366,5367,5368,5369, +5370,5371,5372,5373,5374,5375,5376,5377,5378,5379,5380,5381,5382,5383,5384,5385, +5386,5387,5388,5389,5390,5391,5392,5393,5394,5395,5396,5397,5398,1264,5399,5400, +5401,5402,5403,5404,5405,5406,5407,5408,5409,5410,5411,5412,1662,5413,5414,5415, +5416,1663,5417,5418,5419,5420,5421,5422,5423,5424,5425,5426,5427,5428,5429,5430, +5431,5432,5433,5434,5435,5436,5437,5438,1664,5439,5440,5441,5442,5443,5444,5445, +5446,5447,5448,5449,5450,5451,5452,5453,5454,5455,5456,5457,5458,5459,5460,5461, +5462,5463,5464,5465,5466,5467,5468,5469,5470,5471,5472,5473,5474,5475,5476,5477, +5478,1154,5479,5480,5481,5482,5483,5484,5485,1665,5486,5487,5488,5489,5490,5491, +5492,5493,5494,5495,5496,5497,5498,5499,5500,5501,5502,5503,5504,5505,5506,5507, +5508,5509,5510,5511,5512,5513,5514,5515,5516,5517,5518,5519,5520,5521,5522,5523, +5524,5525,5526,5527,5528,5529,5530,5531,5532,5533,5534,5535,5536,5537,5538,5539, +5540,5541,5542,5543,5544,5545,5546,5547,5548,1377,5549,5550,5551,5552,5553,5554, +5555,5556,5557,5558,5559,5560,5561,5562,5563,5564,5565,5566,5567,5568,5569,5570, +1114,5571,5572,5573,5574,5575,5576,5577,5578,5579,5580,5581,5582,5583,5584,5585, +5586,5587,5588,5589,5590,5591,5592,1378,5593,5594,5595,5596,5597,5598,5599,5600, +5601,5602,5603,5604,5605,5606,5607,5608,5609,5610,5611,5612,5613,5614,1379,5615, +5616,5617,5618,5619,5620,5621,5622,5623,5624,5625,5626,5627,5628,5629,5630,5631, +5632,5633,5634,1380,5635,5636,5637,5638,5639,5640,5641,5642,5643,5644,5645,5646, +5647,5648,5649,1381,1056,5650,5651,5652,5653,5654,5655,5656,5657,5658,5659,5660, +1666,5661,5662,5663,5664,5665,5666,5667,5668,1667,5669,1668,5670,5671,5672,5673, +5674,5675,5676,5677,5678,1155,5679,5680,5681,5682,5683,5684,5685,5686,5687,5688, +5689,5690,5691,5692,5693,5694,5695,5696,5697,5698,1669,5699,5700,5701,5702,5703, +5704,5705,1670,5706,5707,5708,5709,5710,1671,5711,5712,5713,5714,1382,5715,5716, +5717,5718,5719,5720,5721,5722,5723,5724,5725,1672,5726,5727,1673,1674,5728,5729, +5730,5731,5732,5733,5734,5735,5736,1675,5737,5738,5739,5740,5741,5742,5743,5744, +1676,5745,5746,5747,5748,5749,5750,5751,1383,5752,5753,5754,5755,5756,5757,5758, +5759,5760,5761,5762,5763,5764,5765,5766,5767,5768,1677,5769,5770,5771,5772,5773, +1678,5774,5775,5776, 998,5777,5778,5779,5780,5781,5782,5783,5784,5785,1384,5786, +5787,5788,5789,5790,5791,5792,5793,5794,5795,5796,5797,5798,5799,5800,1679,5801, +5802,5803,1115,1116,5804,5805,5806,5807,5808,5809,5810,5811,5812,5813,5814,5815, +5816,5817,5818,5819,5820,5821,5822,5823,5824,5825,5826,5827,5828,5829,5830,5831, +5832,5833,5834,5835,5836,5837,5838,5839,5840,5841,5842,5843,5844,5845,5846,5847, +5848,5849,5850,5851,5852,5853,5854,5855,1680,5856,5857,5858,5859,5860,5861,5862, +5863,5864,1681,5865,5866,5867,1682,5868,5869,5870,5871,5872,5873,5874,5875,5876, +5877,5878,5879,1683,5880,1684,5881,5882,5883,5884,1685,5885,5886,5887,5888,5889, +5890,5891,5892,5893,5894,5895,5896,5897,5898,5899,5900,5901,5902,5903,5904,5905, +5906,5907,1686,5908,5909,5910,5911,5912,5913,5914,5915,5916,5917,5918,5919,5920, +5921,5922,5923,5924,5925,5926,5927,5928,5929,5930,5931,5932,5933,5934,5935,1687, +5936,5937,5938,5939,5940,5941,5942,5943,5944,5945,5946,5947,5948,5949,5950,5951, +5952,1688,1689,5953,1199,5954,5955,5956,5957,5958,5959,5960,5961,1690,5962,5963, +5964,5965,5966,5967,5968,5969,5970,5971,5972,5973,5974,5975,5976,5977,5978,5979, +5980,5981,1385,5982,1386,5983,5984,5985,5986,5987,5988,5989,5990,5991,5992,5993, +5994,5995,5996,5997,5998,5999,6000,6001,6002,6003,6004,6005,6006,6007,6008,6009, +6010,6011,6012,6013,6014,6015,6016,6017,6018,6019,6020,6021,6022,6023,6024,6025, +6026,6027,1265,6028,6029,1691,6030,6031,6032,6033,6034,6035,6036,6037,6038,6039, +6040,6041,6042,6043,6044,6045,6046,6047,6048,6049,6050,6051,6052,6053,6054,6055, +6056,6057,6058,6059,6060,6061,6062,6063,6064,6065,6066,6067,6068,6069,6070,6071, +6072,6073,6074,6075,6076,6077,6078,6079,6080,6081,6082,6083,6084,1692,6085,6086, +6087,6088,6089,6090,6091,6092,6093,6094,6095,6096,6097,6098,6099,6100,6101,6102, +6103,6104,6105,6106,6107,6108,6109,6110,6111,6112,6113,6114,6115,6116,6117,6118, +6119,6120,6121,6122,6123,6124,6125,6126,6127,6128,6129,6130,6131,1693,6132,6133, +6134,6135,6136,1694,6137,6138,6139,6140,6141,1695,6142,6143,6144,6145,6146,6147, +6148,6149,6150,6151,6152,6153,6154,6155,6156,6157,6158,6159,6160,6161,6162,6163, +6164,6165,6166,6167,6168,6169,6170,6171,6172,6173,6174,6175,6176,6177,6178,6179, +6180,6181,6182,6183,6184,6185,1696,6186,6187,6188,6189,6190,6191,6192,6193,6194, +6195,6196,6197,6198,6199,6200,6201,6202,6203,6204,6205,6206,6207,6208,6209,6210, +6211,6212,6213,6214,6215,6216,6217,6218,6219,1697,6220,6221,6222,6223,6224,6225, +6226,6227,6228,6229,6230,6231,6232,6233,6234,6235,6236,6237,6238,6239,6240,6241, +6242,6243,6244,6245,6246,6247,6248,6249,6250,6251,6252,6253,1698,6254,6255,6256, +6257,6258,6259,6260,6261,6262,6263,1200,6264,6265,6266,6267,6268,6269,6270,6271, #1024 +6272,6273,6274,6275,6276,6277,6278,6279,6280,6281,6282,6283,6284,6285,6286,6287, +6288,6289,6290,6291,6292,6293,6294,6295,6296,6297,6298,6299,6300,6301,6302,1699, +6303,6304,1700,6305,6306,6307,6308,6309,6310,6311,6312,6313,6314,6315,6316,6317, +6318,6319,6320,6321,6322,6323,6324,6325,6326,6327,6328,6329,6330,6331,6332,6333, +6334,6335,6336,6337,6338,6339,1701,6340,6341,6342,6343,6344,1387,6345,6346,6347, +6348,6349,6350,6351,6352,6353,6354,6355,6356,6357,6358,6359,6360,6361,6362,6363, +6364,6365,6366,6367,6368,6369,6370,6371,6372,6373,6374,6375,6376,6377,6378,6379, +6380,6381,6382,6383,6384,6385,6386,6387,6388,6389,6390,6391,6392,6393,6394,6395, +6396,6397,6398,6399,6400,6401,6402,6403,6404,6405,6406,6407,6408,6409,6410,6411, +6412,6413,1702,6414,6415,6416,6417,6418,6419,6420,6421,6422,1703,6423,6424,6425, +6426,6427,6428,6429,6430,6431,6432,6433,6434,6435,6436,6437,6438,1704,6439,6440, +6441,6442,6443,6444,6445,6446,6447,6448,6449,6450,6451,6452,6453,6454,6455,6456, +6457,6458,6459,6460,6461,6462,6463,6464,6465,6466,6467,6468,6469,6470,6471,6472, +6473,6474,6475,6476,6477,6478,6479,6480,6481,6482,6483,6484,6485,6486,6487,6488, +6489,6490,6491,6492,6493,6494,6495,6496,6497,6498,6499,6500,6501,6502,6503,1266, +6504,6505,6506,6507,6508,6509,6510,6511,6512,6513,6514,6515,6516,6517,6518,6519, +6520,6521,6522,6523,6524,6525,6526,6527,6528,6529,6530,6531,6532,6533,6534,6535, +6536,6537,6538,6539,6540,6541,6542,6543,6544,6545,6546,6547,6548,6549,6550,6551, +1705,1706,6552,6553,6554,6555,6556,6557,6558,6559,6560,6561,6562,6563,6564,6565, +6566,6567,6568,6569,6570,6571,6572,6573,6574,6575,6576,6577,6578,6579,6580,6581, +6582,6583,6584,6585,6586,6587,6588,6589,6590,6591,6592,6593,6594,6595,6596,6597, +6598,6599,6600,6601,6602,6603,6604,6605,6606,6607,6608,6609,6610,6611,6612,6613, +6614,6615,6616,6617,6618,6619,6620,6621,6622,6623,6624,6625,6626,6627,6628,6629, +6630,6631,6632,6633,6634,6635,6636,6637,1388,6638,6639,6640,6641,6642,6643,6644, +1707,6645,6646,6647,6648,6649,6650,6651,6652,6653,6654,6655,6656,6657,6658,6659, +6660,6661,6662,6663,1708,6664,6665,6666,6667,6668,6669,6670,6671,6672,6673,6674, +1201,6675,6676,6677,6678,6679,6680,6681,6682,6683,6684,6685,6686,6687,6688,6689, +6690,6691,6692,6693,6694,6695,6696,6697,6698,6699,6700,6701,6702,6703,6704,6705, +6706,6707,6708,6709,6710,6711,6712,6713,6714,6715,6716,6717,6718,6719,6720,6721, +6722,6723,6724,6725,1389,6726,6727,6728,6729,6730,6731,6732,6733,6734,6735,6736, +1390,1709,6737,6738,6739,6740,6741,6742,1710,6743,6744,6745,6746,1391,6747,6748, +6749,6750,6751,6752,6753,6754,6755,6756,6757,1392,6758,6759,6760,6761,6762,6763, +6764,6765,6766,6767,6768,6769,6770,6771,6772,6773,6774,6775,6776,6777,6778,6779, +6780,1202,6781,6782,6783,6784,6785,6786,6787,6788,6789,6790,6791,6792,6793,6794, +6795,6796,6797,6798,6799,6800,6801,6802,6803,6804,6805,6806,6807,6808,6809,1711, +6810,6811,6812,6813,6814,6815,6816,6817,6818,6819,6820,6821,6822,6823,6824,6825, +6826,6827,6828,6829,6830,6831,6832,6833,6834,6835,6836,1393,6837,6838,6839,6840, +6841,6842,6843,6844,6845,6846,6847,6848,6849,6850,6851,6852,6853,6854,6855,6856, +6857,6858,6859,6860,6861,6862,6863,6864,6865,6866,6867,6868,6869,6870,6871,6872, +6873,6874,6875,6876,6877,6878,6879,6880,6881,6882,6883,6884,6885,6886,6887,6888, +6889,6890,6891,6892,6893,6894,6895,6896,6897,6898,6899,6900,6901,6902,1712,6903, +6904,6905,6906,6907,6908,6909,6910,1713,6911,6912,6913,6914,6915,6916,6917,6918, +6919,6920,6921,6922,6923,6924,6925,6926,6927,6928,6929,6930,6931,6932,6933,6934, +6935,6936,6937,6938,6939,6940,6941,6942,6943,6944,6945,6946,6947,6948,6949,6950, +6951,6952,6953,6954,6955,6956,6957,6958,6959,6960,6961,6962,6963,6964,6965,6966, +6967,6968,6969,6970,6971,6972,6973,6974,1714,6975,6976,6977,6978,6979,6980,6981, +6982,6983,6984,6985,6986,6987,6988,1394,6989,6990,6991,6992,6993,6994,6995,6996, +6997,6998,6999,7000,1715,7001,7002,7003,7004,7005,7006,7007,7008,7009,7010,7011, +7012,7013,7014,7015,7016,7017,7018,7019,7020,7021,7022,7023,7024,7025,7026,7027, +7028,1716,7029,7030,7031,7032,7033,7034,7035,7036,7037,7038,7039,7040,7041,7042, +7043,7044,7045,7046,7047,7048,7049,7050,7051,7052,7053,7054,7055,7056,7057,7058, +7059,7060,7061,7062,7063,7064,7065,7066,7067,7068,7069,7070,7071,7072,7073,7074, +7075,7076,7077,7078,7079,7080,7081,7082,7083,7084,7085,7086,7087,7088,7089,7090, +7091,7092,7093,7094,7095,7096,7097,7098,7099,7100,7101,7102,7103,7104,7105,7106, +7107,7108,7109,7110,7111,7112,7113,7114,7115,7116,7117,7118,7119,7120,7121,7122, +7123,7124,7125,7126,7127,7128,7129,7130,7131,7132,7133,7134,7135,7136,7137,7138, +7139,7140,7141,7142,7143,7144,7145,7146,7147,7148,7149,7150,7151,7152,7153,7154, +7155,7156,7157,7158,7159,7160,7161,7162,7163,7164,7165,7166,7167,7168,7169,7170, +7171,7172,7173,7174,7175,7176,7177,7178,7179,7180,7181,7182,7183,7184,7185,7186, +7187,7188,7189,7190,7191,7192,7193,7194,7195,7196,7197,7198,7199,7200,7201,7202, +7203,7204,7205,7206,7207,1395,7208,7209,7210,7211,7212,7213,1717,7214,7215,7216, +7217,7218,7219,7220,7221,7222,7223,7224,7225,7226,7227,7228,7229,7230,7231,7232, +7233,7234,7235,7236,7237,7238,7239,7240,7241,7242,7243,7244,7245,7246,7247,7248, +7249,7250,7251,7252,7253,7254,7255,7256,7257,7258,7259,7260,7261,7262,7263,7264, +7265,7266,7267,7268,7269,7270,7271,7272,7273,7274,7275,7276,7277,7278,7279,7280, +7281,7282,7283,7284,7285,7286,7287,7288,7289,7290,7291,7292,7293,7294,7295,7296, +7297,7298,7299,7300,7301,7302,7303,7304,7305,7306,7307,7308,7309,7310,7311,7312, +7313,1718,7314,7315,7316,7317,7318,7319,7320,7321,7322,7323,7324,7325,7326,7327, +7328,7329,7330,7331,7332,7333,7334,7335,7336,7337,7338,7339,7340,7341,7342,7343, +7344,7345,7346,7347,7348,7349,7350,7351,7352,7353,7354,7355,7356,7357,7358,7359, +7360,7361,7362,7363,7364,7365,7366,7367,7368,7369,7370,7371,7372,7373,7374,7375, +7376,7377,7378,7379,7380,7381,7382,7383,7384,7385,7386,7387,7388,7389,7390,7391, +7392,7393,7394,7395,7396,7397,7398,7399,7400,7401,7402,7403,7404,7405,7406,7407, +7408,7409,7410,7411,7412,7413,7414,7415,7416,7417,7418,7419,7420,7421,7422,7423, +7424,7425,7426,7427,7428,7429,7430,7431,7432,7433,7434,7435,7436,7437,7438,7439, +7440,7441,7442,7443,7444,7445,7446,7447,7448,7449,7450,7451,7452,7453,7454,7455, +7456,7457,7458,7459,7460,7461,7462,7463,7464,7465,7466,7467,7468,7469,7470,7471, +7472,7473,7474,7475,7476,7477,7478,7479,7480,7481,7482,7483,7484,7485,7486,7487, +7488,7489,7490,7491,7492,7493,7494,7495,7496,7497,7498,7499,7500,7501,7502,7503, +7504,7505,7506,7507,7508,7509,7510,7511,7512,7513,7514,7515,7516,7517,7518,7519, +7520,7521,7522,7523,7524,7525,7526,7527,7528,7529,7530,7531,7532,7533,7534,7535, +7536,7537,7538,7539,7540,7541,7542,7543,7544,7545,7546,7547,7548,7549,7550,7551, +7552,7553,7554,7555,7556,7557,7558,7559,7560,7561,7562,7563,7564,7565,7566,7567, +7568,7569,7570,7571,7572,7573,7574,7575,7576,7577,7578,7579,7580,7581,7582,7583, +7584,7585,7586,7587,7588,7589,7590,7591,7592,7593,7594,7595,7596,7597,7598,7599, +7600,7601,7602,7603,7604,7605,7606,7607,7608,7609,7610,7611,7612,7613,7614,7615, +7616,7617,7618,7619,7620,7621,7622,7623,7624,7625,7626,7627,7628,7629,7630,7631, +7632,7633,7634,7635,7636,7637,7638,7639,7640,7641,7642,7643,7644,7645,7646,7647, +7648,7649,7650,7651,7652,7653,7654,7655,7656,7657,7658,7659,7660,7661,7662,7663, +7664,7665,7666,7667,7668,7669,7670,7671,7672,7673,7674,7675,7676,7677,7678,7679, +7680,7681,7682,7683,7684,7685,7686,7687,7688,7689,7690,7691,7692,7693,7694,7695, +7696,7697,7698,7699,7700,7701,7702,7703,7704,7705,7706,7707,7708,7709,7710,7711, +7712,7713,7714,7715,7716,7717,7718,7719,7720,7721,7722,7723,7724,7725,7726,7727, +7728,7729,7730,7731,7732,7733,7734,7735,7736,7737,7738,7739,7740,7741,7742,7743, +7744,7745,7746,7747,7748,7749,7750,7751,7752,7753,7754,7755,7756,7757,7758,7759, +7760,7761,7762,7763,7764,7765,7766,7767,7768,7769,7770,7771,7772,7773,7774,7775, +7776,7777,7778,7779,7780,7781,7782,7783,7784,7785,7786,7787,7788,7789,7790,7791, +7792,7793,7794,7795,7796,7797,7798,7799,7800,7801,7802,7803,7804,7805,7806,7807, +7808,7809,7810,7811,7812,7813,7814,7815,7816,7817,7818,7819,7820,7821,7822,7823, +7824,7825,7826,7827,7828,7829,7830,7831,7832,7833,7834,7835,7836,7837,7838,7839, +7840,7841,7842,7843,7844,7845,7846,7847,7848,7849,7850,7851,7852,7853,7854,7855, +7856,7857,7858,7859,7860,7861,7862,7863,7864,7865,7866,7867,7868,7869,7870,7871, +7872,7873,7874,7875,7876,7877,7878,7879,7880,7881,7882,7883,7884,7885,7886,7887, +7888,7889,7890,7891,7892,7893,7894,7895,7896,7897,7898,7899,7900,7901,7902,7903, +7904,7905,7906,7907,7908,7909,7910,7911,7912,7913,7914,7915,7916,7917,7918,7919, +7920,7921,7922,7923,7924,7925,7926,7927,7928,7929,7930,7931,7932,7933,7934,7935, +7936,7937,7938,7939,7940,7941,7942,7943,7944,7945,7946,7947,7948,7949,7950,7951, +7952,7953,7954,7955,7956,7957,7958,7959,7960,7961,7962,7963,7964,7965,7966,7967, +7968,7969,7970,7971,7972,7973,7974,7975,7976,7977,7978,7979,7980,7981,7982,7983, +7984,7985,7986,7987,7988,7989,7990,7991,7992,7993,7994,7995,7996,7997,7998,7999, +8000,8001,8002,8003,8004,8005,8006,8007,8008,8009,8010,8011,8012,8013,8014,8015, +8016,8017,8018,8019,8020,8021,8022,8023,8024,8025,8026,8027,8028,8029,8030,8031, +8032,8033,8034,8035,8036,8037,8038,8039,8040,8041,8042,8043,8044,8045,8046,8047, +8048,8049,8050,8051,8052,8053,8054,8055,8056,8057,8058,8059,8060,8061,8062,8063, +8064,8065,8066,8067,8068,8069,8070,8071,8072,8073,8074,8075,8076,8077,8078,8079, +8080,8081,8082,8083,8084,8085,8086,8087,8088,8089,8090,8091,8092,8093,8094,8095, +8096,8097,8098,8099,8100,8101,8102,8103,8104,8105,8106,8107,8108,8109,8110,8111, +8112,8113,8114,8115,8116,8117,8118,8119,8120,8121,8122,8123,8124,8125,8126,8127, +8128,8129,8130,8131,8132,8133,8134,8135,8136,8137,8138,8139,8140,8141,8142,8143, +8144,8145,8146,8147,8148,8149,8150,8151,8152,8153,8154,8155,8156,8157,8158,8159, +8160,8161,8162,8163,8164,8165,8166,8167,8168,8169,8170,8171,8172,8173,8174,8175, +8176,8177,8178,8179,8180,8181,8182,8183,8184,8185,8186,8187,8188,8189,8190,8191, +8192,8193,8194,8195,8196,8197,8198,8199,8200,8201,8202,8203,8204,8205,8206,8207, +8208,8209,8210,8211,8212,8213,8214,8215,8216,8217,8218,8219,8220,8221,8222,8223, +8224,8225,8226,8227,8228,8229,8230,8231,8232,8233,8234,8235,8236,8237,8238,8239, +8240,8241,8242,8243,8244,8245,8246,8247,8248,8249,8250,8251,8252,8253,8254,8255, +8256,8257,8258,8259,8260,8261,8262,8263,8264,8265,8266,8267,8268,8269,8270,8271, +8272,8273,8274,8275,8276,8277,8278,8279,8280,8281,8282,8283,8284,8285,8286,8287, +8288,8289,8290,8291,8292,8293,8294,8295,8296,8297,8298,8299,8300,8301,8302,8303, +8304,8305,8306,8307,8308,8309,8310,8311,8312,8313,8314,8315,8316,8317,8318,8319, +8320,8321,8322,8323,8324,8325,8326,8327,8328,8329,8330,8331,8332,8333,8334,8335, +8336,8337,8338,8339,8340,8341,8342,8343,8344,8345,8346,8347,8348,8349,8350,8351, +8352,8353,8354,8355,8356,8357,8358,8359,8360,8361,8362,8363,8364,8365,8366,8367, +8368,8369,8370,8371,8372,8373,8374,8375,8376,8377,8378,8379,8380,8381,8382,8383, +8384,8385,8386,8387,8388,8389,8390,8391,8392,8393,8394,8395,8396,8397,8398,8399, +8400,8401,8402,8403,8404,8405,8406,8407,8408,8409,8410,8411,8412,8413,8414,8415, +8416,8417,8418,8419,8420,8421,8422,8423,8424,8425,8426,8427,8428,8429,8430,8431, +8432,8433,8434,8435,8436,8437,8438,8439,8440,8441,8442,8443,8444,8445,8446,8447, +8448,8449,8450,8451,8452,8453,8454,8455,8456,8457,8458,8459,8460,8461,8462,8463, +8464,8465,8466,8467,8468,8469,8470,8471,8472,8473,8474,8475,8476,8477,8478,8479, +8480,8481,8482,8483,8484,8485,8486,8487,8488,8489,8490,8491,8492,8493,8494,8495, +8496,8497,8498,8499,8500,8501,8502,8503,8504,8505,8506,8507,8508,8509,8510,8511, +8512,8513,8514,8515,8516,8517,8518,8519,8520,8521,8522,8523,8524,8525,8526,8527, +8528,8529,8530,8531,8532,8533,8534,8535,8536,8537,8538,8539,8540,8541,8542,8543, +8544,8545,8546,8547,8548,8549,8550,8551,8552,8553,8554,8555,8556,8557,8558,8559, +8560,8561,8562,8563,8564,8565,8566,8567,8568,8569,8570,8571,8572,8573,8574,8575, +8576,8577,8578,8579,8580,8581,8582,8583,8584,8585,8586,8587,8588,8589,8590,8591, +8592,8593,8594,8595,8596,8597,8598,8599,8600,8601,8602,8603,8604,8605,8606,8607, +8608,8609,8610,8611,8612,8613,8614,8615,8616,8617,8618,8619,8620,8621,8622,8623, +8624,8625,8626,8627,8628,8629,8630,8631,8632,8633,8634,8635,8636,8637,8638,8639, +8640,8641,8642,8643,8644,8645,8646,8647,8648,8649,8650,8651,8652,8653,8654,8655, +8656,8657,8658,8659,8660,8661,8662,8663,8664,8665,8666,8667,8668,8669,8670,8671, +8672,8673,8674,8675,8676,8677,8678,8679,8680,8681,8682,8683,8684,8685,8686,8687, +8688,8689,8690,8691,8692,8693,8694,8695,8696,8697,8698,8699,8700,8701,8702,8703, +8704,8705,8706,8707,8708,8709,8710,8711,8712,8713,8714,8715,8716,8717,8718,8719, +8720,8721,8722,8723,8724,8725,8726,8727,8728,8729,8730,8731,8732,8733,8734,8735, +8736,8737,8738,8739,8740,8741) + +# flake8: noqa diff --git a/resources/lib/libraries/requests/packages/chardet/euckrprober.py b/resources/lib/libraries/requests/packages/chardet/euckrprober.py new file mode 100644 index 00000000..5982a46b --- /dev/null +++ b/resources/lib/libraries/requests/packages/chardet/euckrprober.py @@ -0,0 +1,42 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is mozilla.org code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +from .mbcharsetprober import MultiByteCharSetProber +from .codingstatemachine import CodingStateMachine +from .chardistribution import EUCKRDistributionAnalysis +from .mbcssm import EUCKRSMModel + + +class EUCKRProber(MultiByteCharSetProber): + def __init__(self): + MultiByteCharSetProber.__init__(self) + self._mCodingSM = CodingStateMachine(EUCKRSMModel) + self._mDistributionAnalyzer = EUCKRDistributionAnalysis() + self.reset() + + def get_charset_name(self): + return "EUC-KR" diff --git a/resources/lib/libraries/requests/packages/chardet/euctwfreq.py b/resources/lib/libraries/requests/packages/chardet/euctwfreq.py new file mode 100644 index 00000000..576e7504 --- /dev/null +++ b/resources/lib/libraries/requests/packages/chardet/euctwfreq.py @@ -0,0 +1,428 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is Mozilla Communicator client code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +# EUCTW frequency table +# Converted from big5 work +# by Taiwan's Mandarin Promotion Council +# <http:#www.edu.tw:81/mandr/> + +# 128 --> 0.42261 +# 256 --> 0.57851 +# 512 --> 0.74851 +# 1024 --> 0.89384 +# 2048 --> 0.97583 +# +# Idea Distribution Ratio = 0.74851/(1-0.74851) =2.98 +# Random Distribution Ration = 512/(5401-512)=0.105 +# +# Typical Distribution Ratio about 25% of Ideal one, still much higher than RDR + +EUCTW_TYPICAL_DISTRIBUTION_RATIO = 0.75 + +# Char to FreqOrder table , +EUCTW_TABLE_SIZE = 8102 + +EUCTWCharToFreqOrder = ( + 1,1800,1506, 255,1431, 198, 9, 82, 6,7310, 177, 202,3615,1256,2808, 110, # 2742 +3735, 33,3241, 261, 76, 44,2113, 16,2931,2184,1176, 659,3868, 26,3404,2643, # 2758 +1198,3869,3313,4060, 410,2211, 302, 590, 361,1963, 8, 204, 58,4296,7311,1931, # 2774 + 63,7312,7313, 317,1614, 75, 222, 159,4061,2412,1480,7314,3500,3068, 224,2809, # 2790 +3616, 3, 10,3870,1471, 29,2774,1135,2852,1939, 873, 130,3242,1123, 312,7315, # 2806 +4297,2051, 507, 252, 682,7316, 142,1914, 124, 206,2932, 34,3501,3173, 64, 604, # 2822 +7317,2494,1976,1977, 155,1990, 645, 641,1606,7318,3405, 337, 72, 406,7319, 80, # 2838 + 630, 238,3174,1509, 263, 939,1092,2644, 756,1440,1094,3406, 449, 69,2969, 591, # 2854 + 179,2095, 471, 115,2034,1843, 60, 50,2970, 134, 806,1868, 734,2035,3407, 180, # 2870 + 995,1607, 156, 537,2893, 688,7320, 319,1305, 779,2144, 514,2374, 298,4298, 359, # 2886 +2495, 90,2707,1338, 663, 11, 906,1099,2545, 20,2436, 182, 532,1716,7321, 732, # 2902 +1376,4062,1311,1420,3175, 25,2312,1056, 113, 399, 382,1949, 242,3408,2467, 529, # 2918 +3243, 475,1447,3617,7322, 117, 21, 656, 810,1297,2295,2329,3502,7323, 126,4063, # 2934 + 706, 456, 150, 613,4299, 71,1118,2036,4064, 145,3069, 85, 835, 486,2114,1246, # 2950 +1426, 428, 727,1285,1015, 800, 106, 623, 303,1281,7324,2127,2354, 347,3736, 221, # 2966 +3503,3110,7325,1955,1153,4065, 83, 296,1199,3070, 192, 624, 93,7326, 822,1897, # 2982 +2810,3111, 795,2064, 991,1554,1542,1592, 27, 43,2853, 859, 139,1456, 860,4300, # 2998 + 437, 712,3871, 164,2392,3112, 695, 211,3017,2096, 195,3872,1608,3504,3505,3618, # 3014 +3873, 234, 811,2971,2097,3874,2229,1441,3506,1615,2375, 668,2076,1638, 305, 228, # 3030 +1664,4301, 467, 415,7327, 262,2098,1593, 239, 108, 300, 200,1033, 512,1247,2077, # 3046 +7328,7329,2173,3176,3619,2673, 593, 845,1062,3244, 88,1723,2037,3875,1950, 212, # 3062 + 266, 152, 149, 468,1898,4066,4302, 77, 187,7330,3018, 37, 5,2972,7331,3876, # 3078 +7332,7333, 39,2517,4303,2894,3177,2078, 55, 148, 74,4304, 545, 483,1474,1029, # 3094 +1665, 217,1869,1531,3113,1104,2645,4067, 24, 172,3507, 900,3877,3508,3509,4305, # 3110 + 32,1408,2811,1312, 329, 487,2355,2247,2708, 784,2674, 4,3019,3314,1427,1788, # 3126 + 188, 109, 499,7334,3620,1717,1789, 888,1217,3020,4306,7335,3510,7336,3315,1520, # 3142 +3621,3878, 196,1034, 775,7337,7338, 929,1815, 249, 439, 38,7339,1063,7340, 794, # 3158 +3879,1435,2296, 46, 178,3245,2065,7341,2376,7342, 214,1709,4307, 804, 35, 707, # 3174 + 324,3622,1601,2546, 140, 459,4068,7343,7344,1365, 839, 272, 978,2257,2572,3409, # 3190 +2128,1363,3623,1423, 697, 100,3071, 48, 70,1231, 495,3114,2193,7345,1294,7346, # 3206 +2079, 462, 586,1042,3246, 853, 256, 988, 185,2377,3410,1698, 434,1084,7347,3411, # 3222 + 314,2615,2775,4308,2330,2331, 569,2280, 637,1816,2518, 757,1162,1878,1616,3412, # 3238 + 287,1577,2115, 768,4309,1671,2854,3511,2519,1321,3737, 909,2413,7348,4069, 933, # 3254 +3738,7349,2052,2356,1222,4310, 765,2414,1322, 786,4311,7350,1919,1462,1677,2895, # 3270 +1699,7351,4312,1424,2437,3115,3624,2590,3316,1774,1940,3413,3880,4070, 309,1369, # 3286 +1130,2812, 364,2230,1653,1299,3881,3512,3882,3883,2646, 525,1085,3021, 902,2000, # 3302 +1475, 964,4313, 421,1844,1415,1057,2281, 940,1364,3116, 376,4314,4315,1381, 7, # 3318 +2520, 983,2378, 336,1710,2675,1845, 321,3414, 559,1131,3022,2742,1808,1132,1313, # 3334 + 265,1481,1857,7352, 352,1203,2813,3247, 167,1089, 420,2814, 776, 792,1724,3513, # 3350 +4071,2438,3248,7353,4072,7354, 446, 229, 333,2743, 901,3739,1200,1557,4316,2647, # 3366 +1920, 395,2744,2676,3740,4073,1835, 125, 916,3178,2616,4317,7355,7356,3741,7357, # 3382 +7358,7359,4318,3117,3625,1133,2547,1757,3415,1510,2313,1409,3514,7360,2145, 438, # 3398 +2591,2896,2379,3317,1068, 958,3023, 461, 311,2855,2677,4074,1915,3179,4075,1978, # 3414 + 383, 750,2745,2617,4076, 274, 539, 385,1278,1442,7361,1154,1964, 384, 561, 210, # 3430 + 98,1295,2548,3515,7362,1711,2415,1482,3416,3884,2897,1257, 129,7363,3742, 642, # 3446 + 523,2776,2777,2648,7364, 141,2231,1333, 68, 176, 441, 876, 907,4077, 603,2592, # 3462 + 710, 171,3417, 404, 549, 18,3118,2393,1410,3626,1666,7365,3516,4319,2898,4320, # 3478 +7366,2973, 368,7367, 146, 366, 99, 871,3627,1543, 748, 807,1586,1185, 22,2258, # 3494 + 379,3743,3180,7368,3181, 505,1941,2618,1991,1382,2314,7369, 380,2357, 218, 702, # 3510 +1817,1248,3418,3024,3517,3318,3249,7370,2974,3628, 930,3250,3744,7371, 59,7372, # 3526 + 585, 601,4078, 497,3419,1112,1314,4321,1801,7373,1223,1472,2174,7374, 749,1836, # 3542 + 690,1899,3745,1772,3885,1476, 429,1043,1790,2232,2116, 917,4079, 447,1086,1629, # 3558 +7375, 556,7376,7377,2020,1654, 844,1090, 105, 550, 966,1758,2815,1008,1782, 686, # 3574 +1095,7378,2282, 793,1602,7379,3518,2593,4322,4080,2933,2297,4323,3746, 980,2496, # 3590 + 544, 353, 527,4324, 908,2678,2899,7380, 381,2619,1942,1348,7381,1341,1252, 560, # 3606 +3072,7382,3420,2856,7383,2053, 973, 886,2080, 143,4325,7384,7385, 157,3886, 496, # 3622 +4081, 57, 840, 540,2038,4326,4327,3421,2117,1445, 970,2259,1748,1965,2081,4082, # 3638 +3119,1234,1775,3251,2816,3629, 773,1206,2129,1066,2039,1326,3887,1738,1725,4083, # 3654 + 279,3120, 51,1544,2594, 423,1578,2130,2066, 173,4328,1879,7386,7387,1583, 264, # 3670 + 610,3630,4329,2439, 280, 154,7388,7389,7390,1739, 338,1282,3073, 693,2857,1411, # 3686 +1074,3747,2440,7391,4330,7392,7393,1240, 952,2394,7394,2900,1538,2679, 685,1483, # 3702 +4084,2468,1436, 953,4085,2054,4331, 671,2395, 79,4086,2441,3252, 608, 567,2680, # 3718 +3422,4087,4088,1691, 393,1261,1791,2396,7395,4332,7396,7397,7398,7399,1383,1672, # 3734 +3748,3182,1464, 522,1119, 661,1150, 216, 675,4333,3888,1432,3519, 609,4334,2681, # 3750 +2397,7400,7401,7402,4089,3025, 0,7403,2469, 315, 231,2442, 301,3319,4335,2380, # 3766 +7404, 233,4090,3631,1818,4336,4337,7405, 96,1776,1315,2082,7406, 257,7407,1809, # 3782 +3632,2709,1139,1819,4091,2021,1124,2163,2778,1777,2649,7408,3074, 363,1655,3183, # 3798 +7409,2975,7410,7411,7412,3889,1567,3890, 718, 103,3184, 849,1443, 341,3320,2934, # 3814 +1484,7413,1712, 127, 67, 339,4092,2398, 679,1412, 821,7414,7415, 834, 738, 351, # 3830 +2976,2146, 846, 235,1497,1880, 418,1992,3749,2710, 186,1100,2147,2746,3520,1545, # 3846 +1355,2935,2858,1377, 583,3891,4093,2573,2977,7416,1298,3633,1078,2549,3634,2358, # 3862 + 78,3750,3751, 267,1289,2099,2001,1594,4094, 348, 369,1274,2194,2175,1837,4338, # 3878 +1820,2817,3635,2747,2283,2002,4339,2936,2748, 144,3321, 882,4340,3892,2749,3423, # 3894 +4341,2901,7417,4095,1726, 320,7418,3893,3026, 788,2978,7419,2818,1773,1327,2859, # 3910 +3894,2819,7420,1306,4342,2003,1700,3752,3521,2359,2650, 787,2022, 506, 824,3636, # 3926 + 534, 323,4343,1044,3322,2023,1900, 946,3424,7421,1778,1500,1678,7422,1881,4344, # 3942 + 165, 243,4345,3637,2521, 123, 683,4096, 764,4346, 36,3895,1792, 589,2902, 816, # 3958 + 626,1667,3027,2233,1639,1555,1622,3753,3896,7423,3897,2860,1370,1228,1932, 891, # 3974 +2083,2903, 304,4097,7424, 292,2979,2711,3522, 691,2100,4098,1115,4347, 118, 662, # 3990 +7425, 611,1156, 854,2381,1316,2861, 2, 386, 515,2904,7426,7427,3253, 868,2234, # 4006 +1486, 855,2651, 785,2212,3028,7428,1040,3185,3523,7429,3121, 448,7430,1525,7431, # 4022 +2164,4348,7432,3754,7433,4099,2820,3524,3122, 503, 818,3898,3123,1568, 814, 676, # 4038 +1444, 306,1749,7434,3755,1416,1030, 197,1428, 805,2821,1501,4349,7435,7436,7437, # 4054 +1993,7438,4350,7439,7440,2195, 13,2779,3638,2980,3124,1229,1916,7441,3756,2131, # 4070 +7442,4100,4351,2399,3525,7443,2213,1511,1727,1120,7444,7445, 646,3757,2443, 307, # 4086 +7446,7447,1595,3186,7448,7449,7450,3639,1113,1356,3899,1465,2522,2523,7451, 519, # 4102 +7452, 128,2132, 92,2284,1979,7453,3900,1512, 342,3125,2196,7454,2780,2214,1980, # 4118 +3323,7455, 290,1656,1317, 789, 827,2360,7456,3758,4352, 562, 581,3901,7457, 401, # 4134 +4353,2248, 94,4354,1399,2781,7458,1463,2024,4355,3187,1943,7459, 828,1105,4101, # 4150 +1262,1394,7460,4102, 605,4356,7461,1783,2862,7462,2822, 819,2101, 578,2197,2937, # 4166 +7463,1502, 436,3254,4103,3255,2823,3902,2905,3425,3426,7464,2712,2315,7465,7466, # 4182 +2332,2067, 23,4357, 193, 826,3759,2102, 699,1630,4104,3075, 390,1793,1064,3526, # 4198 +7467,1579,3076,3077,1400,7468,4105,1838,1640,2863,7469,4358,4359, 137,4106, 598, # 4214 +3078,1966, 780, 104, 974,2938,7470, 278, 899, 253, 402, 572, 504, 493,1339,7471, # 4230 +3903,1275,4360,2574,2550,7472,3640,3029,3079,2249, 565,1334,2713, 863, 41,7473, # 4246 +7474,4361,7475,1657,2333, 19, 463,2750,4107, 606,7476,2981,3256,1087,2084,1323, # 4262 +2652,2982,7477,1631,1623,1750,4108,2682,7478,2864, 791,2714,2653,2334, 232,2416, # 4278 +7479,2983,1498,7480,2654,2620, 755,1366,3641,3257,3126,2025,1609, 119,1917,3427, # 4294 + 862,1026,4109,7481,3904,3760,4362,3905,4363,2260,1951,2470,7482,1125, 817,4110, # 4310 +4111,3906,1513,1766,2040,1487,4112,3030,3258,2824,3761,3127,7483,7484,1507,7485, # 4326 +2683, 733, 40,1632,1106,2865, 345,4113, 841,2524, 230,4364,2984,1846,3259,3428, # 4342 +7486,1263, 986,3429,7487, 735, 879, 254,1137, 857, 622,1300,1180,1388,1562,3907, # 4358 +3908,2939, 967,2751,2655,1349, 592,2133,1692,3324,2985,1994,4114,1679,3909,1901, # 4374 +2185,7488, 739,3642,2715,1296,1290,7489,4115,2198,2199,1921,1563,2595,2551,1870, # 4390 +2752,2986,7490, 435,7491, 343,1108, 596, 17,1751,4365,2235,3430,3643,7492,4366, # 4406 + 294,3527,2940,1693, 477, 979, 281,2041,3528, 643,2042,3644,2621,2782,2261,1031, # 4422 +2335,2134,2298,3529,4367, 367,1249,2552,7493,3530,7494,4368,1283,3325,2004, 240, # 4438 +1762,3326,4369,4370, 836,1069,3128, 474,7495,2148,2525, 268,3531,7496,3188,1521, # 4454 +1284,7497,1658,1546,4116,7498,3532,3533,7499,4117,3327,2684,1685,4118, 961,1673, # 4470 +2622, 190,2005,2200,3762,4371,4372,7500, 570,2497,3645,1490,7501,4373,2623,3260, # 4486 +1956,4374, 584,1514, 396,1045,1944,7502,4375,1967,2444,7503,7504,4376,3910, 619, # 4502 +7505,3129,3261, 215,2006,2783,2553,3189,4377,3190,4378, 763,4119,3763,4379,7506, # 4518 +7507,1957,1767,2941,3328,3646,1174, 452,1477,4380,3329,3130,7508,2825,1253,2382, # 4534 +2186,1091,2285,4120, 492,7509, 638,1169,1824,2135,1752,3911, 648, 926,1021,1324, # 4550 +4381, 520,4382, 997, 847,1007, 892,4383,3764,2262,1871,3647,7510,2400,1784,4384, # 4566 +1952,2942,3080,3191,1728,4121,2043,3648,4385,2007,1701,3131,1551, 30,2263,4122, # 4582 +7511,2026,4386,3534,7512, 501,7513,4123, 594,3431,2165,1821,3535,3432,3536,3192, # 4598 + 829,2826,4124,7514,1680,3132,1225,4125,7515,3262,4387,4126,3133,2336,7516,4388, # 4614 +4127,7517,3912,3913,7518,1847,2383,2596,3330,7519,4389, 374,3914, 652,4128,4129, # 4630 + 375,1140, 798,7520,7521,7522,2361,4390,2264, 546,1659, 138,3031,2445,4391,7523, # 4646 +2250, 612,1848, 910, 796,3765,1740,1371, 825,3766,3767,7524,2906,2554,7525, 692, # 4662 + 444,3032,2624, 801,4392,4130,7526,1491, 244,1053,3033,4131,4132, 340,7527,3915, # 4678 +1041,2987, 293,1168, 87,1357,7528,1539, 959,7529,2236, 721, 694,4133,3768, 219, # 4694 +1478, 644,1417,3331,2656,1413,1401,1335,1389,3916,7530,7531,2988,2362,3134,1825, # 4710 + 730,1515, 184,2827, 66,4393,7532,1660,2943, 246,3332, 378,1457, 226,3433, 975, # 4726 +3917,2944,1264,3537, 674, 696,7533, 163,7534,1141,2417,2166, 713,3538,3333,4394, # 4742 +3918,7535,7536,1186, 15,7537,1079,1070,7538,1522,3193,3539, 276,1050,2716, 758, # 4758 +1126, 653,2945,3263,7539,2337, 889,3540,3919,3081,2989, 903,1250,4395,3920,3434, # 4774 +3541,1342,1681,1718, 766,3264, 286, 89,2946,3649,7540,1713,7541,2597,3334,2990, # 4790 +7542,2947,2215,3194,2866,7543,4396,2498,2526, 181, 387,1075,3921, 731,2187,3335, # 4806 +7544,3265, 310, 313,3435,2299, 770,4134, 54,3034, 189,4397,3082,3769,3922,7545, # 4822 +1230,1617,1849, 355,3542,4135,4398,3336, 111,4136,3650,1350,3135,3436,3035,4137, # 4838 +2149,3266,3543,7546,2784,3923,3924,2991, 722,2008,7547,1071, 247,1207,2338,2471, # 4854 +1378,4399,2009, 864,1437,1214,4400, 373,3770,1142,2216, 667,4401, 442,2753,2555, # 4870 +3771,3925,1968,4138,3267,1839, 837, 170,1107, 934,1336,1882,7548,7549,2118,4139, # 4886 +2828, 743,1569,7550,4402,4140, 582,2384,1418,3437,7551,1802,7552, 357,1395,1729, # 4902 +3651,3268,2418,1564,2237,7553,3083,3772,1633,4403,1114,2085,4141,1532,7554, 482, # 4918 +2446,4404,7555,7556,1492, 833,1466,7557,2717,3544,1641,2829,7558,1526,1272,3652, # 4934 +4142,1686,1794, 416,2556,1902,1953,1803,7559,3773,2785,3774,1159,2316,7560,2867, # 4950 +4405,1610,1584,3036,2419,2754, 443,3269,1163,3136,7561,7562,3926,7563,4143,2499, # 4966 +3037,4406,3927,3137,2103,1647,3545,2010,1872,4144,7564,4145, 431,3438,7565, 250, # 4982 + 97, 81,4146,7566,1648,1850,1558, 160, 848,7567, 866, 740,1694,7568,2201,2830, # 4998 +3195,4147,4407,3653,1687, 950,2472, 426, 469,3196,3654,3655,3928,7569,7570,1188, # 5014 + 424,1995, 861,3546,4148,3775,2202,2685, 168,1235,3547,4149,7571,2086,1674,4408, # 5030 +3337,3270, 220,2557,1009,7572,3776, 670,2992, 332,1208, 717,7573,7574,3548,2447, # 5046 +3929,3338,7575, 513,7576,1209,2868,3339,3138,4409,1080,7577,7578,7579,7580,2527, # 5062 +3656,3549, 815,1587,3930,3931,7581,3550,3439,3777,1254,4410,1328,3038,1390,3932, # 5078 +1741,3933,3778,3934,7582, 236,3779,2448,3271,7583,7584,3657,3780,1273,3781,4411, # 5094 +7585, 308,7586,4412, 245,4413,1851,2473,1307,2575, 430, 715,2136,2449,7587, 270, # 5110 + 199,2869,3935,7588,3551,2718,1753, 761,1754, 725,1661,1840,4414,3440,3658,7589, # 5126 +7590, 587, 14,3272, 227,2598, 326, 480,2265, 943,2755,3552, 291, 650,1883,7591, # 5142 +1702,1226, 102,1547, 62,3441, 904,4415,3442,1164,4150,7592,7593,1224,1548,2756, # 5158 + 391, 498,1493,7594,1386,1419,7595,2055,1177,4416, 813, 880,1081,2363, 566,1145, # 5174 +4417,2286,1001,1035,2558,2599,2238, 394,1286,7596,7597,2068,7598, 86,1494,1730, # 5190 +3936, 491,1588, 745, 897,2948, 843,3340,3937,2757,2870,3273,1768, 998,2217,2069, # 5206 + 397,1826,1195,1969,3659,2993,3341, 284,7599,3782,2500,2137,2119,1903,7600,3938, # 5222 +2150,3939,4151,1036,3443,1904, 114,2559,4152, 209,1527,7601,7602,2949,2831,2625, # 5238 +2385,2719,3139, 812,2560,7603,3274,7604,1559, 737,1884,3660,1210, 885, 28,2686, # 5254 +3553,3783,7605,4153,1004,1779,4418,7606, 346,1981,2218,2687,4419,3784,1742, 797, # 5270 +1642,3940,1933,1072,1384,2151, 896,3941,3275,3661,3197,2871,3554,7607,2561,1958, # 5286 +4420,2450,1785,7608,7609,7610,3942,4154,1005,1308,3662,4155,2720,4421,4422,1528, # 5302 +2600, 161,1178,4156,1982, 987,4423,1101,4157, 631,3943,1157,3198,2420,1343,1241, # 5318 +1016,2239,2562, 372, 877,2339,2501,1160, 555,1934, 911,3944,7611, 466,1170, 169, # 5334 +1051,2907,2688,3663,2474,2994,1182,2011,2563,1251,2626,7612, 992,2340,3444,1540, # 5350 +2721,1201,2070,2401,1996,2475,7613,4424, 528,1922,2188,1503,1873,1570,2364,3342, # 5366 +3276,7614, 557,1073,7615,1827,3445,2087,2266,3140,3039,3084, 767,3085,2786,4425, # 5382 +1006,4158,4426,2341,1267,2176,3664,3199, 778,3945,3200,2722,1597,2657,7616,4427, # 5398 +7617,3446,7618,7619,7620,3277,2689,1433,3278, 131, 95,1504,3946, 723,4159,3141, # 5414 +1841,3555,2758,2189,3947,2027,2104,3665,7621,2995,3948,1218,7622,3343,3201,3949, # 5430 +4160,2576, 248,1634,3785, 912,7623,2832,3666,3040,3786, 654, 53,7624,2996,7625, # 5446 +1688,4428, 777,3447,1032,3950,1425,7626, 191, 820,2120,2833, 971,4429, 931,3202, # 5462 + 135, 664, 783,3787,1997, 772,2908,1935,3951,3788,4430,2909,3203, 282,2723, 640, # 5478 +1372,3448,1127, 922, 325,3344,7627,7628, 711,2044,7629,7630,3952,2219,2787,1936, # 5494 +3953,3345,2220,2251,3789,2300,7631,4431,3790,1258,3279,3954,3204,2138,2950,3955, # 5510 +3956,7632,2221, 258,3205,4432, 101,1227,7633,3280,1755,7634,1391,3281,7635,2910, # 5526 +2056, 893,7636,7637,7638,1402,4161,2342,7639,7640,3206,3556,7641,7642, 878,1325, # 5542 +1780,2788,4433, 259,1385,2577, 744,1183,2267,4434,7643,3957,2502,7644, 684,1024, # 5558 +4162,7645, 472,3557,3449,1165,3282,3958,3959, 322,2152, 881, 455,1695,1152,1340, # 5574 + 660, 554,2153,4435,1058,4436,4163, 830,1065,3346,3960,4437,1923,7646,1703,1918, # 5590 +7647, 932,2268, 122,7648,4438, 947, 677,7649,3791,2627, 297,1905,1924,2269,4439, # 5606 +2317,3283,7650,7651,4164,7652,4165, 84,4166, 112, 989,7653, 547,1059,3961, 701, # 5622 +3558,1019,7654,4167,7655,3450, 942, 639, 457,2301,2451, 993,2951, 407, 851, 494, # 5638 +4440,3347, 927,7656,1237,7657,2421,3348, 573,4168, 680, 921,2911,1279,1874, 285, # 5654 + 790,1448,1983, 719,2167,7658,7659,4441,3962,3963,1649,7660,1541, 563,7661,1077, # 5670 +7662,3349,3041,3451, 511,2997,3964,3965,3667,3966,1268,2564,3350,3207,4442,4443, # 5686 +7663, 535,1048,1276,1189,2912,2028,3142,1438,1373,2834,2952,1134,2012,7664,4169, # 5702 +1238,2578,3086,1259,7665, 700,7666,2953,3143,3668,4170,7667,4171,1146,1875,1906, # 5718 +4444,2601,3967, 781,2422, 132,1589, 203, 147, 273,2789,2402, 898,1786,2154,3968, # 5734 +3969,7668,3792,2790,7669,7670,4445,4446,7671,3208,7672,1635,3793, 965,7673,1804, # 5750 +2690,1516,3559,1121,1082,1329,3284,3970,1449,3794, 65,1128,2835,2913,2759,1590, # 5766 +3795,7674,7675, 12,2658, 45, 976,2579,3144,4447, 517,2528,1013,1037,3209,7676, # 5782 +3796,2836,7677,3797,7678,3452,7679,2602, 614,1998,2318,3798,3087,2724,2628,7680, # 5798 +2580,4172, 599,1269,7681,1810,3669,7682,2691,3088, 759,1060, 489,1805,3351,3285, # 5814 +1358,7683,7684,2386,1387,1215,2629,2252, 490,7685,7686,4173,1759,2387,2343,7687, # 5830 +4448,3799,1907,3971,2630,1806,3210,4449,3453,3286,2760,2344, 874,7688,7689,3454, # 5846 +3670,1858, 91,2914,3671,3042,3800,4450,7690,3145,3972,2659,7691,3455,1202,1403, # 5862 +3801,2954,2529,1517,2503,4451,3456,2504,7692,4452,7693,2692,1885,1495,1731,3973, # 5878 +2365,4453,7694,2029,7695,7696,3974,2693,1216, 237,2581,4174,2319,3975,3802,4454, # 5894 +4455,2694,3560,3457, 445,4456,7697,7698,7699,7700,2761, 61,3976,3672,1822,3977, # 5910 +7701, 687,2045, 935, 925, 405,2660, 703,1096,1859,2725,4457,3978,1876,1367,2695, # 5926 +3352, 918,2105,1781,2476, 334,3287,1611,1093,4458, 564,3146,3458,3673,3353, 945, # 5942 +2631,2057,4459,7702,1925, 872,4175,7703,3459,2696,3089, 349,4176,3674,3979,4460, # 5958 +3803,4177,3675,2155,3980,4461,4462,4178,4463,2403,2046, 782,3981, 400, 251,4179, # 5974 +1624,7704,7705, 277,3676, 299,1265, 476,1191,3804,2121,4180,4181,1109, 205,7706, # 5990 +2582,1000,2156,3561,1860,7707,7708,7709,4464,7710,4465,2565, 107,2477,2157,3982, # 6006 +3460,3147,7711,1533, 541,1301, 158, 753,4182,2872,3562,7712,1696, 370,1088,4183, # 6022 +4466,3563, 579, 327, 440, 162,2240, 269,1937,1374,3461, 968,3043, 56,1396,3090, # 6038 +2106,3288,3354,7713,1926,2158,4467,2998,7714,3564,7715,7716,3677,4468,2478,7717, # 6054 +2791,7718,1650,4469,7719,2603,7720,7721,3983,2661,3355,1149,3356,3984,3805,3985, # 6070 +7722,1076, 49,7723, 951,3211,3289,3290, 450,2837, 920,7724,1811,2792,2366,4184, # 6086 +1908,1138,2367,3806,3462,7725,3212,4470,1909,1147,1518,2423,4471,3807,7726,4472, # 6102 +2388,2604, 260,1795,3213,7727,7728,3808,3291, 708,7729,3565,1704,7730,3566,1351, # 6118 +1618,3357,2999,1886, 944,4185,3358,4186,3044,3359,4187,7731,3678, 422, 413,1714, # 6134 +3292, 500,2058,2345,4188,2479,7732,1344,1910, 954,7733,1668,7734,7735,3986,2404, # 6150 +4189,3567,3809,4190,7736,2302,1318,2505,3091, 133,3092,2873,4473, 629, 31,2838, # 6166 +2697,3810,4474, 850, 949,4475,3987,2955,1732,2088,4191,1496,1852,7737,3988, 620, # 6182 +3214, 981,1242,3679,3360,1619,3680,1643,3293,2139,2452,1970,1719,3463,2168,7738, # 6198 +3215,7739,7740,3361,1828,7741,1277,4476,1565,2047,7742,1636,3568,3093,7743, 869, # 6214 +2839, 655,3811,3812,3094,3989,3000,3813,1310,3569,4477,7744,7745,7746,1733, 558, # 6230 +4478,3681, 335,1549,3045,1756,4192,3682,1945,3464,1829,1291,1192, 470,2726,2107, # 6246 +2793, 913,1054,3990,7747,1027,7748,3046,3991,4479, 982,2662,3362,3148,3465,3216, # 6262 +3217,1946,2794,7749, 571,4480,7750,1830,7751,3570,2583,1523,2424,7752,2089, 984, # 6278 +4481,3683,1959,7753,3684, 852, 923,2795,3466,3685, 969,1519, 999,2048,2320,1705, # 6294 +7754,3095, 615,1662, 151, 597,3992,2405,2321,1049, 275,4482,3686,4193, 568,3687, # 6310 +3571,2480,4194,3688,7755,2425,2270, 409,3218,7756,1566,2874,3467,1002, 769,2840, # 6326 + 194,2090,3149,3689,2222,3294,4195, 628,1505,7757,7758,1763,2177,3001,3993, 521, # 6342 +1161,2584,1787,2203,2406,4483,3994,1625,4196,4197, 412, 42,3096, 464,7759,2632, # 6358 +4484,3363,1760,1571,2875,3468,2530,1219,2204,3814,2633,2140,2368,4485,4486,3295, # 6374 +1651,3364,3572,7760,7761,3573,2481,3469,7762,3690,7763,7764,2271,2091, 460,7765, # 6390 +4487,7766,3002, 962, 588,3574, 289,3219,2634,1116, 52,7767,3047,1796,7768,7769, # 6406 +7770,1467,7771,1598,1143,3691,4198,1984,1734,1067,4488,1280,3365, 465,4489,1572, # 6422 + 510,7772,1927,2241,1812,1644,3575,7773,4490,3692,7774,7775,2663,1573,1534,7776, # 6438 +7777,4199, 536,1807,1761,3470,3815,3150,2635,7778,7779,7780,4491,3471,2915,1911, # 6454 +2796,7781,3296,1122, 377,3220,7782, 360,7783,7784,4200,1529, 551,7785,2059,3693, # 6470 +1769,2426,7786,2916,4201,3297,3097,2322,2108,2030,4492,1404, 136,1468,1479, 672, # 6486 +1171,3221,2303, 271,3151,7787,2762,7788,2049, 678,2727, 865,1947,4493,7789,2013, # 6502 +3995,2956,7790,2728,2223,1397,3048,3694,4494,4495,1735,2917,3366,3576,7791,3816, # 6518 + 509,2841,2453,2876,3817,7792,7793,3152,3153,4496,4202,2531,4497,2304,1166,1010, # 6534 + 552, 681,1887,7794,7795,2957,2958,3996,1287,1596,1861,3154, 358, 453, 736, 175, # 6550 + 478,1117, 905,1167,1097,7796,1853,1530,7797,1706,7798,2178,3472,2287,3695,3473, # 6566 +3577,4203,2092,4204,7799,3367,1193,2482,4205,1458,2190,2205,1862,1888,1421,3298, # 6582 +2918,3049,2179,3474, 595,2122,7800,3997,7801,7802,4206,1707,2636, 223,3696,1359, # 6598 + 751,3098, 183,3475,7803,2797,3003, 419,2369, 633, 704,3818,2389, 241,7804,7805, # 6614 +7806, 838,3004,3697,2272,2763,2454,3819,1938,2050,3998,1309,3099,2242,1181,7807, # 6630 +1136,2206,3820,2370,1446,4207,2305,4498,7808,7809,4208,1055,2605, 484,3698,7810, # 6646 +3999, 625,4209,2273,3368,1499,4210,4000,7811,4001,4211,3222,2274,2275,3476,7812, # 6662 +7813,2764, 808,2606,3699,3369,4002,4212,3100,2532, 526,3370,3821,4213, 955,7814, # 6678 +1620,4214,2637,2427,7815,1429,3700,1669,1831, 994, 928,7816,3578,1260,7817,7818, # 6694 +7819,1948,2288, 741,2919,1626,4215,2729,2455, 867,1184, 362,3371,1392,7820,7821, # 6710 +4003,4216,1770,1736,3223,2920,4499,4500,1928,2698,1459,1158,7822,3050,3372,2877, # 6726 +1292,1929,2506,2842,3701,1985,1187,2071,2014,2607,4217,7823,2566,2507,2169,3702, # 6742 +2483,3299,7824,3703,4501,7825,7826, 666,1003,3005,1022,3579,4218,7827,4502,1813, # 6758 +2253, 574,3822,1603, 295,1535, 705,3823,4219, 283, 858, 417,7828,7829,3224,4503, # 6774 +4504,3051,1220,1889,1046,2276,2456,4004,1393,1599, 689,2567, 388,4220,7830,2484, # 6790 + 802,7831,2798,3824,2060,1405,2254,7832,4505,3825,2109,1052,1345,3225,1585,7833, # 6806 + 809,7834,7835,7836, 575,2730,3477, 956,1552,1469,1144,2323,7837,2324,1560,2457, # 6822 +3580,3226,4005, 616,2207,3155,2180,2289,7838,1832,7839,3478,4506,7840,1319,3704, # 6838 +3705,1211,3581,1023,3227,1293,2799,7841,7842,7843,3826, 607,2306,3827, 762,2878, # 6854 +1439,4221,1360,7844,1485,3052,7845,4507,1038,4222,1450,2061,2638,4223,1379,4508, # 6870 +2585,7846,7847,4224,1352,1414,2325,2921,1172,7848,7849,3828,3829,7850,1797,1451, # 6886 +7851,7852,7853,7854,2922,4006,4007,2485,2346, 411,4008,4009,3582,3300,3101,4509, # 6902 +1561,2664,1452,4010,1375,7855,7856, 47,2959, 316,7857,1406,1591,2923,3156,7858, # 6918 +1025,2141,3102,3157, 354,2731, 884,2224,4225,2407, 508,3706, 726,3583, 996,2428, # 6934 +3584, 729,7859, 392,2191,1453,4011,4510,3707,7860,7861,2458,3585,2608,1675,2800, # 6950 + 919,2347,2960,2348,1270,4511,4012, 73,7862,7863, 647,7864,3228,2843,2255,1550, # 6966 +1346,3006,7865,1332, 883,3479,7866,7867,7868,7869,3301,2765,7870,1212, 831,1347, # 6982 +4226,4512,2326,3830,1863,3053, 720,3831,4513,4514,3832,7871,4227,7872,7873,4515, # 6998 +7874,7875,1798,4516,3708,2609,4517,3586,1645,2371,7876,7877,2924, 669,2208,2665, # 7014 +2429,7878,2879,7879,7880,1028,3229,7881,4228,2408,7882,2256,1353,7883,7884,4518, # 7030 +3158, 518,7885,4013,7886,4229,1960,7887,2142,4230,7888,7889,3007,2349,2350,3833, # 7046 + 516,1833,1454,4014,2699,4231,4519,2225,2610,1971,1129,3587,7890,2766,7891,2961, # 7062 +1422, 577,1470,3008,1524,3373,7892,7893, 432,4232,3054,3480,7894,2586,1455,2508, # 7078 +2226,1972,1175,7895,1020,2732,4015,3481,4520,7896,2733,7897,1743,1361,3055,3482, # 7094 +2639,4016,4233,4521,2290, 895, 924,4234,2170, 331,2243,3056, 166,1627,3057,1098, # 7110 +7898,1232,2880,2227,3374,4522, 657, 403,1196,2372, 542,3709,3375,1600,4235,3483, # 7126 +7899,4523,2767,3230, 576, 530,1362,7900,4524,2533,2666,3710,4017,7901, 842,3834, # 7142 +7902,2801,2031,1014,4018, 213,2700,3376, 665, 621,4236,7903,3711,2925,2430,7904, # 7158 +2431,3302,3588,3377,7905,4237,2534,4238,4525,3589,1682,4239,3484,1380,7906, 724, # 7174 +2277, 600,1670,7907,1337,1233,4526,3103,2244,7908,1621,4527,7909, 651,4240,7910, # 7190 +1612,4241,2611,7911,2844,7912,2734,2307,3058,7913, 716,2459,3059, 174,1255,2701, # 7206 +4019,3590, 548,1320,1398, 728,4020,1574,7914,1890,1197,3060,4021,7915,3061,3062, # 7222 +3712,3591,3713, 747,7916, 635,4242,4528,7917,7918,7919,4243,7920,7921,4529,7922, # 7238 +3378,4530,2432, 451,7923,3714,2535,2072,4244,2735,4245,4022,7924,1764,4531,7925, # 7254 +4246, 350,7926,2278,2390,2486,7927,4247,4023,2245,1434,4024, 488,4532, 458,4248, # 7270 +4025,3715, 771,1330,2391,3835,2568,3159,2159,2409,1553,2667,3160,4249,7928,2487, # 7286 +2881,2612,1720,2702,4250,3379,4533,7929,2536,4251,7930,3231,4252,2768,7931,2015, # 7302 +2736,7932,1155,1017,3716,3836,7933,3303,2308, 201,1864,4253,1430,7934,4026,7935, # 7318 +7936,7937,7938,7939,4254,1604,7940, 414,1865, 371,2587,4534,4535,3485,2016,3104, # 7334 +4536,1708, 960,4255, 887, 389,2171,1536,1663,1721,7941,2228,4027,2351,2926,1580, # 7350 +7942,7943,7944,1744,7945,2537,4537,4538,7946,4539,7947,2073,7948,7949,3592,3380, # 7366 +2882,4256,7950,4257,2640,3381,2802, 673,2703,2460, 709,3486,4028,3593,4258,7951, # 7382 +1148, 502, 634,7952,7953,1204,4540,3594,1575,4541,2613,3717,7954,3718,3105, 948, # 7398 +3232, 121,1745,3837,1110,7955,4259,3063,2509,3009,4029,3719,1151,1771,3838,1488, # 7414 +4030,1986,7956,2433,3487,7957,7958,2093,7959,4260,3839,1213,1407,2803, 531,2737, # 7430 +2538,3233,1011,1537,7960,2769,4261,3106,1061,7961,3720,3721,1866,2883,7962,2017, # 7446 + 120,4262,4263,2062,3595,3234,2309,3840,2668,3382,1954,4542,7963,7964,3488,1047, # 7462 +2704,1266,7965,1368,4543,2845, 649,3383,3841,2539,2738,1102,2846,2669,7966,7967, # 7478 +1999,7968,1111,3596,2962,7969,2488,3842,3597,2804,1854,3384,3722,7970,7971,3385, # 7494 +2410,2884,3304,3235,3598,7972,2569,7973,3599,2805,4031,1460, 856,7974,3600,7975, # 7510 +2885,2963,7976,2886,3843,7977,4264, 632,2510, 875,3844,1697,3845,2291,7978,7979, # 7526 +4544,3010,1239, 580,4545,4265,7980, 914, 936,2074,1190,4032,1039,2123,7981,7982, # 7542 +7983,3386,1473,7984,1354,4266,3846,7985,2172,3064,4033, 915,3305,4267,4268,3306, # 7558 +1605,1834,7986,2739, 398,3601,4269,3847,4034, 328,1912,2847,4035,3848,1331,4270, # 7574 +3011, 937,4271,7987,3602,4036,4037,3387,2160,4546,3388, 524, 742, 538,3065,1012, # 7590 +7988,7989,3849,2461,7990, 658,1103, 225,3850,7991,7992,4547,7993,4548,7994,3236, # 7606 +1243,7995,4038, 963,2246,4549,7996,2705,3603,3161,7997,7998,2588,2327,7999,4550, # 7622 +8000,8001,8002,3489,3307, 957,3389,2540,2032,1930,2927,2462, 870,2018,3604,1746, # 7638 +2770,2771,2434,2463,8003,3851,8004,3723,3107,3724,3490,3390,3725,8005,1179,3066, # 7654 +8006,3162,2373,4272,3726,2541,3163,3108,2740,4039,8007,3391,1556,2542,2292, 977, # 7670 +2887,2033,4040,1205,3392,8008,1765,3393,3164,2124,1271,1689, 714,4551,3491,8009, # 7686 +2328,3852, 533,4273,3605,2181, 617,8010,2464,3308,3492,2310,8011,8012,3165,8013, # 7702 +8014,3853,1987, 618, 427,2641,3493,3394,8015,8016,1244,1690,8017,2806,4274,4552, # 7718 +8018,3494,8019,8020,2279,1576, 473,3606,4275,3395, 972,8021,3607,8022,3067,8023, # 7734 +8024,4553,4554,8025,3727,4041,4042,8026, 153,4555, 356,8027,1891,2888,4276,2143, # 7750 + 408, 803,2352,8028,3854,8029,4277,1646,2570,2511,4556,4557,3855,8030,3856,4278, # 7766 +8031,2411,3396, 752,8032,8033,1961,2964,8034, 746,3012,2465,8035,4279,3728, 698, # 7782 +4558,1892,4280,3608,2543,4559,3609,3857,8036,3166,3397,8037,1823,1302,4043,2706, # 7798 +3858,1973,4281,8038,4282,3167, 823,1303,1288,1236,2848,3495,4044,3398, 774,3859, # 7814 +8039,1581,4560,1304,2849,3860,4561,8040,2435,2161,1083,3237,4283,4045,4284, 344, # 7830 +1173, 288,2311, 454,1683,8041,8042,1461,4562,4046,2589,8043,8044,4563, 985, 894, # 7846 +8045,3399,3168,8046,1913,2928,3729,1988,8047,2110,1974,8048,4047,8049,2571,1194, # 7862 + 425,8050,4564,3169,1245,3730,4285,8051,8052,2850,8053, 636,4565,1855,3861, 760, # 7878 +1799,8054,4286,2209,1508,4566,4048,1893,1684,2293,8055,8056,8057,4287,4288,2210, # 7894 + 479,8058,8059, 832,8060,4049,2489,8061,2965,2490,3731, 990,3109, 627,1814,2642, # 7910 +4289,1582,4290,2125,2111,3496,4567,8062, 799,4291,3170,8063,4568,2112,1737,3013, # 7926 +1018, 543, 754,4292,3309,1676,4569,4570,4050,8064,1489,8065,3497,8066,2614,2889, # 7942 +4051,8067,8068,2966,8069,8070,8071,8072,3171,4571,4572,2182,1722,8073,3238,3239, # 7958 +1842,3610,1715, 481, 365,1975,1856,8074,8075,1962,2491,4573,8076,2126,3611,3240, # 7974 + 433,1894,2063,2075,8077, 602,2741,8078,8079,8080,8081,8082,3014,1628,3400,8083, # 7990 +3172,4574,4052,2890,4575,2512,8084,2544,2772,8085,8086,8087,3310,4576,2891,8088, # 8006 +4577,8089,2851,4578,4579,1221,2967,4053,2513,8090,8091,8092,1867,1989,8093,8094, # 8022 +8095,1895,8096,8097,4580,1896,4054, 318,8098,2094,4055,4293,8099,8100, 485,8101, # 8038 + 938,3862, 553,2670, 116,8102,3863,3612,8103,3498,2671,2773,3401,3311,2807,8104, # 8054 +3613,2929,4056,1747,2930,2968,8105,8106, 207,8107,8108,2672,4581,2514,8109,3015, # 8070 + 890,3614,3864,8110,1877,3732,3402,8111,2183,2353,3403,1652,8112,8113,8114, 941, # 8086 +2294, 208,3499,4057,2019, 330,4294,3865,2892,2492,3733,4295,8115,8116,8117,8118, # 8102 +#Everything below is of no interest for detection purpose +2515,1613,4582,8119,3312,3866,2516,8120,4058,8121,1637,4059,2466,4583,3867,8122, # 8118 +2493,3016,3734,8123,8124,2192,8125,8126,2162,8127,8128,8129,8130,8131,8132,8133, # 8134 +8134,8135,8136,8137,8138,8139,8140,8141,8142,8143,8144,8145,8146,8147,8148,8149, # 8150 +8150,8151,8152,8153,8154,8155,8156,8157,8158,8159,8160,8161,8162,8163,8164,8165, # 8166 +8166,8167,8168,8169,8170,8171,8172,8173,8174,8175,8176,8177,8178,8179,8180,8181, # 8182 +8182,8183,8184,8185,8186,8187,8188,8189,8190,8191,8192,8193,8194,8195,8196,8197, # 8198 +8198,8199,8200,8201,8202,8203,8204,8205,8206,8207,8208,8209,8210,8211,8212,8213, # 8214 +8214,8215,8216,8217,8218,8219,8220,8221,8222,8223,8224,8225,8226,8227,8228,8229, # 8230 +8230,8231,8232,8233,8234,8235,8236,8237,8238,8239,8240,8241,8242,8243,8244,8245, # 8246 +8246,8247,8248,8249,8250,8251,8252,8253,8254,8255,8256,8257,8258,8259,8260,8261, # 8262 +8262,8263,8264,8265,8266,8267,8268,8269,8270,8271,8272,8273,8274,8275,8276,8277, # 8278 +8278,8279,8280,8281,8282,8283,8284,8285,8286,8287,8288,8289,8290,8291,8292,8293, # 8294 +8294,8295,8296,8297,8298,8299,8300,8301,8302,8303,8304,8305,8306,8307,8308,8309, # 8310 +8310,8311,8312,8313,8314,8315,8316,8317,8318,8319,8320,8321,8322,8323,8324,8325, # 8326 +8326,8327,8328,8329,8330,8331,8332,8333,8334,8335,8336,8337,8338,8339,8340,8341, # 8342 +8342,8343,8344,8345,8346,8347,8348,8349,8350,8351,8352,8353,8354,8355,8356,8357, # 8358 +8358,8359,8360,8361,8362,8363,8364,8365,8366,8367,8368,8369,8370,8371,8372,8373, # 8374 +8374,8375,8376,8377,8378,8379,8380,8381,8382,8383,8384,8385,8386,8387,8388,8389, # 8390 +8390,8391,8392,8393,8394,8395,8396,8397,8398,8399,8400,8401,8402,8403,8404,8405, # 8406 +8406,8407,8408,8409,8410,8411,8412,8413,8414,8415,8416,8417,8418,8419,8420,8421, # 8422 +8422,8423,8424,8425,8426,8427,8428,8429,8430,8431,8432,8433,8434,8435,8436,8437, # 8438 +8438,8439,8440,8441,8442,8443,8444,8445,8446,8447,8448,8449,8450,8451,8452,8453, # 8454 +8454,8455,8456,8457,8458,8459,8460,8461,8462,8463,8464,8465,8466,8467,8468,8469, # 8470 +8470,8471,8472,8473,8474,8475,8476,8477,8478,8479,8480,8481,8482,8483,8484,8485, # 8486 +8486,8487,8488,8489,8490,8491,8492,8493,8494,8495,8496,8497,8498,8499,8500,8501, # 8502 +8502,8503,8504,8505,8506,8507,8508,8509,8510,8511,8512,8513,8514,8515,8516,8517, # 8518 +8518,8519,8520,8521,8522,8523,8524,8525,8526,8527,8528,8529,8530,8531,8532,8533, # 8534 +8534,8535,8536,8537,8538,8539,8540,8541,8542,8543,8544,8545,8546,8547,8548,8549, # 8550 +8550,8551,8552,8553,8554,8555,8556,8557,8558,8559,8560,8561,8562,8563,8564,8565, # 8566 +8566,8567,8568,8569,8570,8571,8572,8573,8574,8575,8576,8577,8578,8579,8580,8581, # 8582 +8582,8583,8584,8585,8586,8587,8588,8589,8590,8591,8592,8593,8594,8595,8596,8597, # 8598 +8598,8599,8600,8601,8602,8603,8604,8605,8606,8607,8608,8609,8610,8611,8612,8613, # 8614 +8614,8615,8616,8617,8618,8619,8620,8621,8622,8623,8624,8625,8626,8627,8628,8629, # 8630 +8630,8631,8632,8633,8634,8635,8636,8637,8638,8639,8640,8641,8642,8643,8644,8645, # 8646 +8646,8647,8648,8649,8650,8651,8652,8653,8654,8655,8656,8657,8658,8659,8660,8661, # 8662 +8662,8663,8664,8665,8666,8667,8668,8669,8670,8671,8672,8673,8674,8675,8676,8677, # 8678 +8678,8679,8680,8681,8682,8683,8684,8685,8686,8687,8688,8689,8690,8691,8692,8693, # 8694 +8694,8695,8696,8697,8698,8699,8700,8701,8702,8703,8704,8705,8706,8707,8708,8709, # 8710 +8710,8711,8712,8713,8714,8715,8716,8717,8718,8719,8720,8721,8722,8723,8724,8725, # 8726 +8726,8727,8728,8729,8730,8731,8732,8733,8734,8735,8736,8737,8738,8739,8740,8741) # 8742 + +# flake8: noqa diff --git a/resources/lib/libraries/requests/packages/chardet/euctwprober.py b/resources/lib/libraries/requests/packages/chardet/euctwprober.py new file mode 100644 index 00000000..fe652fe3 --- /dev/null +++ b/resources/lib/libraries/requests/packages/chardet/euctwprober.py @@ -0,0 +1,41 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is mozilla.org code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +from .mbcharsetprober import MultiByteCharSetProber +from .codingstatemachine import CodingStateMachine +from .chardistribution import EUCTWDistributionAnalysis +from .mbcssm import EUCTWSMModel + +class EUCTWProber(MultiByteCharSetProber): + def __init__(self): + MultiByteCharSetProber.__init__(self) + self._mCodingSM = CodingStateMachine(EUCTWSMModel) + self._mDistributionAnalyzer = EUCTWDistributionAnalysis() + self.reset() + + def get_charset_name(self): + return "EUC-TW" diff --git a/resources/lib/libraries/requests/packages/chardet/gb2312freq.py b/resources/lib/libraries/requests/packages/chardet/gb2312freq.py new file mode 100644 index 00000000..1238f510 --- /dev/null +++ b/resources/lib/libraries/requests/packages/chardet/gb2312freq.py @@ -0,0 +1,472 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is Mozilla Communicator client code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +# GB2312 most frequently used character table +# +# Char to FreqOrder table , from hz6763 + +# 512 --> 0.79 -- 0.79 +# 1024 --> 0.92 -- 0.13 +# 2048 --> 0.98 -- 0.06 +# 6768 --> 1.00 -- 0.02 +# +# Ideal Distribution Ratio = 0.79135/(1-0.79135) = 3.79 +# Random Distribution Ration = 512 / (3755 - 512) = 0.157 +# +# Typical Distribution Ratio about 25% of Ideal one, still much higher that RDR + +GB2312_TYPICAL_DISTRIBUTION_RATIO = 0.9 + +GB2312_TABLE_SIZE = 3760 + +GB2312CharToFreqOrder = ( +1671, 749,1443,2364,3924,3807,2330,3921,1704,3463,2691,1511,1515, 572,3191,2205, +2361, 224,2558, 479,1711, 963,3162, 440,4060,1905,2966,2947,3580,2647,3961,3842, +2204, 869,4207, 970,2678,5626,2944,2956,1479,4048, 514,3595, 588,1346,2820,3409, + 249,4088,1746,1873,2047,1774, 581,1813, 358,1174,3590,1014,1561,4844,2245, 670, +1636,3112, 889,1286, 953, 556,2327,3060,1290,3141, 613, 185,3477,1367, 850,3820, +1715,2428,2642,2303,2732,3041,2562,2648,3566,3946,1349, 388,3098,2091,1360,3585, + 152,1687,1539, 738,1559, 59,1232,2925,2267,1388,1249,1741,1679,2960, 151,1566, +1125,1352,4271, 924,4296, 385,3166,4459, 310,1245,2850, 70,3285,2729,3534,3575, +2398,3298,3466,1960,2265, 217,3647, 864,1909,2084,4401,2773,1010,3269,5152, 853, +3051,3121,1244,4251,1895, 364,1499,1540,2313,1180,3655,2268, 562, 715,2417,3061, + 544, 336,3768,2380,1752,4075, 950, 280,2425,4382, 183,2759,3272, 333,4297,2155, +1688,2356,1444,1039,4540, 736,1177,3349,2443,2368,2144,2225, 565, 196,1482,3406, + 927,1335,4147, 692, 878,1311,1653,3911,3622,1378,4200,1840,2969,3149,2126,1816, +2534,1546,2393,2760, 737,2494, 13, 447, 245,2747, 38,2765,2129,2589,1079, 606, + 360, 471,3755,2890, 404, 848, 699,1785,1236, 370,2221,1023,3746,2074,2026,2023, +2388,1581,2119, 812,1141,3091,2536,1519, 804,2053, 406,1596,1090, 784, 548,4414, +1806,2264,2936,1100, 343,4114,5096, 622,3358, 743,3668,1510,1626,5020,3567,2513, +3195,4115,5627,2489,2991, 24,2065,2697,1087,2719, 48,1634, 315, 68, 985,2052, + 198,2239,1347,1107,1439, 597,2366,2172, 871,3307, 919,2487,2790,1867, 236,2570, +1413,3794, 906,3365,3381,1701,1982,1818,1524,2924,1205, 616,2586,2072,2004, 575, + 253,3099, 32,1365,1182, 197,1714,2454,1201, 554,3388,3224,2748, 756,2587, 250, +2567,1507,1517,3529,1922,2761,2337,3416,1961,1677,2452,2238,3153, 615, 911,1506, +1474,2495,1265,1906,2749,3756,3280,2161, 898,2714,1759,3450,2243,2444, 563, 26, +3286,2266,3769,3344,2707,3677, 611,1402, 531,1028,2871,4548,1375, 261,2948, 835, +1190,4134, 353, 840,2684,1900,3082,1435,2109,1207,1674, 329,1872,2781,4055,2686, +2104, 608,3318,2423,2957,2768,1108,3739,3512,3271,3985,2203,1771,3520,1418,2054, +1681,1153, 225,1627,2929, 162,2050,2511,3687,1954, 124,1859,2431,1684,3032,2894, + 585,4805,3969,2869,2704,2088,2032,2095,3656,2635,4362,2209, 256, 518,2042,2105, +3777,3657, 643,2298,1148,1779, 190, 989,3544, 414, 11,2135,2063,2979,1471, 403, +3678, 126, 770,1563, 671,2499,3216,2877, 600,1179, 307,2805,4937,1268,1297,2694, + 252,4032,1448,1494,1331,1394, 127,2256, 222,1647,1035,1481,3056,1915,1048, 873, +3651, 210, 33,1608,2516, 200,1520, 415, 102, 0,3389,1287, 817, 91,3299,2940, + 836,1814, 549,2197,1396,1669,2987,3582,2297,2848,4528,1070, 687, 20,1819, 121, +1552,1364,1461,1968,2617,3540,2824,2083, 177, 948,4938,2291, 110,4549,2066, 648, +3359,1755,2110,2114,4642,4845,1693,3937,3308,1257,1869,2123, 208,1804,3159,2992, +2531,2549,3361,2418,1350,2347,2800,2568,1291,2036,2680, 72, 842,1990, 212,1233, +1154,1586, 75,2027,3410,4900,1823,1337,2710,2676, 728,2810,1522,3026,4995, 157, + 755,1050,4022, 710, 785,1936,2194,2085,1406,2777,2400, 150,1250,4049,1206, 807, +1910, 534, 529,3309,1721,1660, 274, 39,2827, 661,2670,1578, 925,3248,3815,1094, +4278,4901,4252, 41,1150,3747,2572,2227,4501,3658,4902,3813,3357,3617,2884,2258, + 887, 538,4187,3199,1294,2439,3042,2329,2343,2497,1255, 107, 543,1527, 521,3478, +3568, 194,5062, 15, 961,3870,1241,1192,2664, 66,5215,3260,2111,1295,1127,2152, +3805,4135, 901,1164,1976, 398,1278, 530,1460, 748, 904,1054,1966,1426, 53,2909, + 509, 523,2279,1534, 536,1019, 239,1685, 460,2353, 673,1065,2401,3600,4298,2272, +1272,2363, 284,1753,3679,4064,1695, 81, 815,2677,2757,2731,1386, 859, 500,4221, +2190,2566, 757,1006,2519,2068,1166,1455, 337,2654,3203,1863,1682,1914,3025,1252, +1409,1366, 847, 714,2834,2038,3209, 964,2970,1901, 885,2553,1078,1756,3049, 301, +1572,3326, 688,2130,1996,2429,1805,1648,2930,3421,2750,3652,3088, 262,1158,1254, + 389,1641,1812, 526,1719, 923,2073,1073,1902, 468, 489,4625,1140, 857,2375,3070, +3319,2863, 380, 116,1328,2693,1161,2244, 273,1212,1884,2769,3011,1775,1142, 461, +3066,1200,2147,2212, 790, 702,2695,4222,1601,1058, 434,2338,5153,3640, 67,2360, +4099,2502, 618,3472,1329, 416,1132, 830,2782,1807,2653,3211,3510,1662, 192,2124, + 296,3979,1739,1611,3684, 23, 118, 324, 446,1239,1225, 293,2520,3814,3795,2535, +3116, 17,1074, 467,2692,2201, 387,2922, 45,1326,3055,1645,3659,2817, 958, 243, +1903,2320,1339,2825,1784,3289, 356, 576, 865,2315,2381,3377,3916,1088,3122,1713, +1655, 935, 628,4689,1034,1327, 441, 800, 720, 894,1979,2183,1528,5289,2702,1071, +4046,3572,2399,1571,3281, 79, 761,1103, 327, 134, 758,1899,1371,1615, 879, 442, + 215,2605,2579, 173,2048,2485,1057,2975,3317,1097,2253,3801,4263,1403,1650,2946, + 814,4968,3487,1548,2644,1567,1285, 2, 295,2636, 97, 946,3576, 832, 141,4257, +3273, 760,3821,3521,3156,2607, 949,1024,1733,1516,1803,1920,2125,2283,2665,3180, +1501,2064,3560,2171,1592, 803,3518,1416, 732,3897,4258,1363,1362,2458, 119,1427, + 602,1525,2608,1605,1639,3175, 694,3064, 10, 465, 76,2000,4846,4208, 444,3781, +1619,3353,2206,1273,3796, 740,2483, 320,1723,2377,3660,2619,1359,1137,1762,1724, +2345,2842,1850,1862, 912, 821,1866, 612,2625,1735,2573,3369,1093, 844, 89, 937, + 930,1424,3564,2413,2972,1004,3046,3019,2011, 711,3171,1452,4178, 428, 801,1943, + 432, 445,2811, 206,4136,1472, 730, 349, 73, 397,2802,2547, 998,1637,1167, 789, + 396,3217, 154,1218, 716,1120,1780,2819,4826,1931,3334,3762,2139,1215,2627, 552, +3664,3628,3232,1405,2383,3111,1356,2652,3577,3320,3101,1703, 640,1045,1370,1246, +4996, 371,1575,2436,1621,2210, 984,4033,1734,2638, 16,4529, 663,2755,3255,1451, +3917,2257,1253,1955,2234,1263,2951, 214,1229, 617, 485, 359,1831,1969, 473,2310, + 750,2058, 165, 80,2864,2419, 361,4344,2416,2479,1134, 796,3726,1266,2943, 860, +2715, 938, 390,2734,1313,1384, 248, 202, 877,1064,2854, 522,3907, 279,1602, 297, +2357, 395,3740, 137,2075, 944,4089,2584,1267,3802, 62,1533,2285, 178, 176, 780, +2440, 201,3707, 590, 478,1560,4354,2117,1075, 30, 74,4643,4004,1635,1441,2745, + 776,2596, 238,1077,1692,1912,2844, 605, 499,1742,3947, 241,3053, 980,1749, 936, +2640,4511,2582, 515,1543,2162,5322,2892,2993, 890,2148,1924, 665,1827,3581,1032, + 968,3163, 339,1044,1896, 270, 583,1791,1720,4367,1194,3488,3669, 43,2523,1657, + 163,2167, 290,1209,1622,3378, 550, 634,2508,2510, 695,2634,2384,2512,1476,1414, + 220,1469,2341,2138,2852,3183,2900,4939,2865,3502,1211,3680, 854,3227,1299,2976, +3172, 186,2998,1459, 443,1067,3251,1495, 321,1932,3054, 909, 753,1410,1828, 436, +2441,1119,1587,3164,2186,1258, 227, 231,1425,1890,3200,3942, 247, 959, 725,5254, +2741, 577,2158,2079, 929, 120, 174, 838,2813, 591,1115, 417,2024, 40,3240,1536, +1037, 291,4151,2354, 632,1298,2406,2500,3535,1825,1846,3451, 205,1171, 345,4238, + 18,1163, 811, 685,2208,1217, 425,1312,1508,1175,4308,2552,1033, 587,1381,3059, +2984,3482, 340,1316,4023,3972, 792,3176, 519, 777,4690, 918, 933,4130,2981,3741, + 90,3360,2911,2200,5184,4550, 609,3079,2030, 272,3379,2736, 363,3881,1130,1447, + 286, 779, 357,1169,3350,3137,1630,1220,2687,2391, 747,1277,3688,2618,2682,2601, +1156,3196,5290,4034,3102,1689,3596,3128, 874, 219,2783, 798, 508,1843,2461, 269, +1658,1776,1392,1913,2983,3287,2866,2159,2372, 829,4076, 46,4253,2873,1889,1894, + 915,1834,1631,2181,2318, 298, 664,2818,3555,2735, 954,3228,3117, 527,3511,2173, + 681,2712,3033,2247,2346,3467,1652, 155,2164,3382, 113,1994, 450, 899, 494, 994, +1237,2958,1875,2336,1926,3727, 545,1577,1550, 633,3473, 204,1305,3072,2410,1956, +2471, 707,2134, 841,2195,2196,2663,3843,1026,4940, 990,3252,4997, 368,1092, 437, +3212,3258,1933,1829, 675,2977,2893, 412, 943,3723,4644,3294,3283,2230,2373,5154, +2389,2241,2661,2323,1404,2524, 593, 787, 677,3008,1275,2059, 438,2709,2609,2240, +2269,2246,1446, 36,1568,1373,3892,1574,2301,1456,3962, 693,2276,5216,2035,1143, +2720,1919,1797,1811,2763,4137,2597,1830,1699,1488,1198,2090, 424,1694, 312,3634, +3390,4179,3335,2252,1214, 561,1059,3243,2295,2561, 975,5155,2321,2751,3772, 472, +1537,3282,3398,1047,2077,2348,2878,1323,3340,3076, 690,2906, 51, 369, 170,3541, +1060,2187,2688,3670,2541,1083,1683, 928,3918, 459, 109,4427, 599,3744,4286, 143, +2101,2730,2490, 82,1588,3036,2121, 281,1860, 477,4035,1238,2812,3020,2716,3312, +1530,2188,2055,1317, 843, 636,1808,1173,3495, 649, 181,1002, 147,3641,1159,2414, +3750,2289,2795, 813,3123,2610,1136,4368, 5,3391,4541,2174, 420, 429,1728, 754, +1228,2115,2219, 347,2223,2733, 735,1518,3003,2355,3134,1764,3948,3329,1888,2424, +1001,1234,1972,3321,3363,1672,1021,1450,1584, 226, 765, 655,2526,3404,3244,2302, +3665, 731, 594,2184, 319,1576, 621, 658,2656,4299,2099,3864,1279,2071,2598,2739, + 795,3086,3699,3908,1707,2352,2402,1382,3136,2475,1465,4847,3496,3865,1085,3004, +2591,1084, 213,2287,1963,3565,2250, 822, 793,4574,3187,1772,1789,3050, 595,1484, +1959,2770,1080,2650, 456, 422,2996, 940,3322,4328,4345,3092,2742, 965,2784, 739, +4124, 952,1358,2498,2949,2565, 332,2698,2378, 660,2260,2473,4194,3856,2919, 535, +1260,2651,1208,1428,1300,1949,1303,2942, 433,2455,2450,1251,1946, 614,1269, 641, +1306,1810,2737,3078,2912, 564,2365,1419,1415,1497,4460,2367,2185,1379,3005,1307, +3218,2175,1897,3063, 682,1157,4040,4005,1712,1160,1941,1399, 394, 402,2952,1573, +1151,2986,2404, 862, 299,2033,1489,3006, 346, 171,2886,3401,1726,2932, 168,2533, + 47,2507,1030,3735,1145,3370,1395,1318,1579,3609,4560,2857,4116,1457,2529,1965, + 504,1036,2690,2988,2405, 745,5871, 849,2397,2056,3081, 863,2359,3857,2096, 99, +1397,1769,2300,4428,1643,3455,1978,1757,3718,1440, 35,4879,3742,1296,4228,2280, + 160,5063,1599,2013, 166, 520,3479,1646,3345,3012, 490,1937,1545,1264,2182,2505, +1096,1188,1369,1436,2421,1667,2792,2460,1270,2122, 727,3167,2143, 806,1706,1012, +1800,3037, 960,2218,1882, 805, 139,2456,1139,1521, 851,1052,3093,3089, 342,2039, + 744,5097,1468,1502,1585,2087, 223, 939, 326,2140,2577, 892,2481,1623,4077, 982, +3708, 135,2131, 87,2503,3114,2326,1106, 876,1616, 547,2997,2831,2093,3441,4530, +4314, 9,3256,4229,4148, 659,1462,1986,1710,2046,2913,2231,4090,4880,5255,3392, +3274,1368,3689,4645,1477, 705,3384,3635,1068,1529,2941,1458,3782,1509, 100,1656, +2548, 718,2339, 408,1590,2780,3548,1838,4117,3719,1345,3530, 717,3442,2778,3220, +2898,1892,4590,3614,3371,2043,1998,1224,3483, 891, 635, 584,2559,3355, 733,1766, +1729,1172,3789,1891,2307, 781,2982,2271,1957,1580,5773,2633,2005,4195,3097,1535, +3213,1189,1934,5693,3262, 586,3118,1324,1598, 517,1564,2217,1868,1893,4445,3728, +2703,3139,1526,1787,1992,3882,2875,1549,1199,1056,2224,1904,2711,5098,4287, 338, +1993,3129,3489,2689,1809,2815,1997, 957,1855,3898,2550,3275,3057,1105,1319, 627, +1505,1911,1883,3526, 698,3629,3456,1833,1431, 746, 77,1261,2017,2296,1977,1885, + 125,1334,1600, 525,1798,1109,2222,1470,1945, 559,2236,1186,3443,2476,1929,1411, +2411,3135,1777,3372,2621,1841,1613,3229, 668,1430,1839,2643,2916, 195,1989,2671, +2358,1387, 629,3205,2293,5256,4439, 123,1310, 888,1879,4300,3021,3605,1003,1162, +3192,2910,2010, 140,2395,2859, 55,1082,2012,2901, 662, 419,2081,1438, 680,2774, +4654,3912,1620,1731,1625,5035,4065,2328, 512,1344, 802,5443,2163,2311,2537, 524, +3399, 98,1155,2103,1918,2606,3925,2816,1393,2465,1504,3773,2177,3963,1478,4346, + 180,1113,4655,3461,2028,1698, 833,2696,1235,1322,1594,4408,3623,3013,3225,2040, +3022, 541,2881, 607,3632,2029,1665,1219, 639,1385,1686,1099,2803,3231,1938,3188, +2858, 427, 676,2772,1168,2025, 454,3253,2486,3556, 230,1950, 580, 791,1991,1280, +1086,1974,2034, 630, 257,3338,2788,4903,1017, 86,4790, 966,2789,1995,1696,1131, + 259,3095,4188,1308, 179,1463,5257, 289,4107,1248, 42,3413,1725,2288, 896,1947, + 774,4474,4254, 604,3430,4264, 392,2514,2588, 452, 237,1408,3018, 988,4531,1970, +3034,3310, 540,2370,1562,1288,2990, 502,4765,1147, 4,1853,2708, 207, 294,2814, +4078,2902,2509, 684, 34,3105,3532,2551, 644, 709,2801,2344, 573,1727,3573,3557, +2021,1081,3100,4315,2100,3681, 199,2263,1837,2385, 146,3484,1195,2776,3949, 997, +1939,3973,1008,1091,1202,1962,1847,1149,4209,5444,1076, 493, 117,5400,2521, 972, +1490,2934,1796,4542,2374,1512,2933,2657, 413,2888,1135,2762,2314,2156,1355,2369, + 766,2007,2527,2170,3124,2491,2593,2632,4757,2437, 234,3125,3591,1898,1750,1376, +1942,3468,3138, 570,2127,2145,3276,4131, 962, 132,1445,4196, 19, 941,3624,3480, +3366,1973,1374,4461,3431,2629, 283,2415,2275, 808,2887,3620,2112,2563,1353,3610, + 955,1089,3103,1053, 96, 88,4097, 823,3808,1583, 399, 292,4091,3313, 421,1128, + 642,4006, 903,2539,1877,2082, 596, 29,4066,1790, 722,2157, 130, 995,1569, 769, +1485, 464, 513,2213, 288,1923,1101,2453,4316, 133, 486,2445, 50, 625, 487,2207, + 57, 423, 481,2962, 159,3729,1558, 491, 303, 482, 501, 240,2837, 112,3648,2392, +1783, 362, 8,3433,3422, 610,2793,3277,1390,1284,1654, 21,3823, 734, 367, 623, + 193, 287, 374,1009,1483, 816, 476, 313,2255,2340,1262,2150,2899,1146,2581, 782, +2116,1659,2018,1880, 255,3586,3314,1110,2867,2137,2564, 986,2767,5185,2006, 650, + 158, 926, 762, 881,3157,2717,2362,3587, 306,3690,3245,1542,3077,2427,1691,2478, +2118,2985,3490,2438, 539,2305, 983, 129,1754, 355,4201,2386, 827,2923, 104,1773, +2838,2771, 411,2905,3919, 376, 767, 122,1114, 828,2422,1817,3506, 266,3460,1007, +1609,4998, 945,2612,4429,2274, 726,1247,1964,2914,2199,2070,4002,4108, 657,3323, +1422, 579, 455,2764,4737,1222,2895,1670, 824,1223,1487,2525, 558, 861,3080, 598, +2659,2515,1967, 752,2583,2376,2214,4180, 977, 704,2464,4999,2622,4109,1210,2961, + 819,1541, 142,2284, 44, 418, 457,1126,3730,4347,4626,1644,1876,3671,1864, 302, +1063,5694, 624, 723,1984,3745,1314,1676,2488,1610,1449,3558,3569,2166,2098, 409, +1011,2325,3704,2306, 818,1732,1383,1824,1844,3757, 999,2705,3497,1216,1423,2683, +2426,2954,2501,2726,2229,1475,2554,5064,1971,1794,1666,2014,1343, 783, 724, 191, +2434,1354,2220,5065,1763,2752,2472,4152, 131, 175,2885,3434, 92,1466,4920,2616, +3871,3872,3866, 128,1551,1632, 669,1854,3682,4691,4125,1230, 188,2973,3290,1302, +1213, 560,3266, 917, 763,3909,3249,1760, 868,1958, 764,1782,2097, 145,2277,3774, +4462, 64,1491,3062, 971,2132,3606,2442, 221,1226,1617, 218, 323,1185,3207,3147, + 571, 619,1473,1005,1744,2281, 449,1887,2396,3685, 275, 375,3816,1743,3844,3731, + 845,1983,2350,4210,1377, 773, 967,3499,3052,3743,2725,4007,1697,1022,3943,1464, +3264,2855,2722,1952,1029,2839,2467, 84,4383,2215, 820,1391,2015,2448,3672, 377, +1948,2168, 797,2545,3536,2578,2645, 94,2874,1678, 405,1259,3071, 771, 546,1315, + 470,1243,3083, 895,2468, 981, 969,2037, 846,4181, 653,1276,2928, 14,2594, 557, +3007,2474, 156, 902,1338,1740,2574, 537,2518, 973,2282,2216,2433,1928, 138,2903, +1293,2631,1612, 646,3457, 839,2935, 111, 496,2191,2847, 589,3186, 149,3994,2060, +4031,2641,4067,3145,1870, 37,3597,2136,1025,2051,3009,3383,3549,1121,1016,3261, +1301, 251,2446,2599,2153, 872,3246, 637, 334,3705, 831, 884, 921,3065,3140,4092, +2198,1944, 246,2964, 108,2045,1152,1921,2308,1031, 203,3173,4170,1907,3890, 810, +1401,2003,1690, 506, 647,1242,2828,1761,1649,3208,2249,1589,3709,2931,5156,1708, + 498, 666,2613, 834,3817,1231, 184,2851,1124, 883,3197,2261,3710,1765,1553,2658, +1178,2639,2351, 93,1193, 942,2538,2141,4402, 235,1821, 870,1591,2192,1709,1871, +3341,1618,4126,2595,2334, 603, 651, 69, 701, 268,2662,3411,2555,1380,1606, 503, + 448, 254,2371,2646, 574,1187,2309,1770, 322,2235,1292,1801, 305, 566,1133, 229, +2067,2057, 706, 167, 483,2002,2672,3295,1820,3561,3067, 316, 378,2746,3452,1112, + 136,1981, 507,1651,2917,1117, 285,4591, 182,2580,3522,1304, 335,3303,1835,2504, +1795,1792,2248, 674,1018,2106,2449,1857,2292,2845, 976,3047,1781,2600,2727,1389, +1281, 52,3152, 153, 265,3950, 672,3485,3951,4463, 430,1183, 365, 278,2169, 27, +1407,1336,2304, 209,1340,1730,2202,1852,2403,2883, 979,1737,1062, 631,2829,2542, +3876,2592, 825,2086,2226,3048,3625, 352,1417,3724, 542, 991, 431,1351,3938,1861, +2294, 826,1361,2927,3142,3503,1738, 463,2462,2723, 582,1916,1595,2808, 400,3845, +3891,2868,3621,2254, 58,2492,1123, 910,2160,2614,1372,1603,1196,1072,3385,1700, +3267,1980, 696, 480,2430, 920, 799,1570,2920,1951,2041,4047,2540,1321,4223,2469, +3562,2228,1271,2602, 401,2833,3351,2575,5157, 907,2312,1256, 410, 263,3507,1582, + 996, 678,1849,2316,1480, 908,3545,2237, 703,2322, 667,1826,2849,1531,2604,2999, +2407,3146,2151,2630,1786,3711, 469,3542, 497,3899,2409, 858, 837,4446,3393,1274, + 786, 620,1845,2001,3311, 484, 308,3367,1204,1815,3691,2332,1532,2557,1842,2020, +2724,1927,2333,4440, 567, 22,1673,2728,4475,1987,1858,1144,1597, 101,1832,3601, + 12, 974,3783,4391, 951,1412, 1,3720, 453,4608,4041, 528,1041,1027,3230,2628, +1129, 875,1051,3291,1203,2262,1069,2860,2799,2149,2615,3278, 144,1758,3040, 31, + 475,1680, 366,2685,3184, 311,1642,4008,2466,5036,1593,1493,2809, 216,1420,1668, + 233, 304,2128,3284, 232,1429,1768,1040,2008,3407,2740,2967,2543, 242,2133, 778, +1565,2022,2620, 505,2189,2756,1098,2273, 372,1614, 708, 553,2846,2094,2278, 169, +3626,2835,4161, 228,2674,3165, 809,1454,1309, 466,1705,1095, 900,3423, 880,2667, +3751,5258,2317,3109,2571,4317,2766,1503,1342, 866,4447,1118, 63,2076, 314,1881, +1348,1061, 172, 978,3515,1747, 532, 511,3970, 6, 601, 905,2699,3300,1751, 276, +1467,3725,2668, 65,4239,2544,2779,2556,1604, 578,2451,1802, 992,2331,2624,1320, +3446, 713,1513,1013, 103,2786,2447,1661, 886,1702, 916, 654,3574,2031,1556, 751, +2178,2821,2179,1498,1538,2176, 271, 914,2251,2080,1325, 638,1953,2937,3877,2432, +2754, 95,3265,1716, 260,1227,4083, 775, 106,1357,3254, 426,1607, 555,2480, 772, +1985, 244,2546, 474, 495,1046,2611,1851,2061, 71,2089,1675,2590, 742,3758,2843, +3222,1433, 267,2180,2576,2826,2233,2092,3913,2435, 956,1745,3075, 856,2113,1116, + 451, 3,1988,2896,1398, 993,2463,1878,2049,1341,2718,2721,2870,2108, 712,2904, +4363,2753,2324, 277,2872,2349,2649, 384, 987, 435, 691,3000, 922, 164,3939, 652, +1500,1184,4153,2482,3373,2165,4848,2335,3775,3508,3154,2806,2830,1554,2102,1664, +2530,1434,2408, 893,1547,2623,3447,2832,2242,2532,3169,2856,3223,2078, 49,3770, +3469, 462, 318, 656,2259,3250,3069, 679,1629,2758, 344,1138,1104,3120,1836,1283, +3115,2154,1437,4448, 934, 759,1999, 794,2862,1038, 533,2560,1722,2342, 855,2626, +1197,1663,4476,3127, 85,4240,2528, 25,1111,1181,3673, 407,3470,4561,2679,2713, + 768,1925,2841,3986,1544,1165, 932, 373,1240,2146,1930,2673, 721,4766, 354,4333, + 391,2963, 187, 61,3364,1442,1102, 330,1940,1767, 341,3809,4118, 393,2496,2062, +2211, 105, 331, 300, 439, 913,1332, 626, 379,3304,1557, 328, 689,3952, 309,1555, + 931, 317,2517,3027, 325, 569, 686,2107,3084, 60,1042,1333,2794, 264,3177,4014, +1628, 258,3712, 7,4464,1176,1043,1778, 683, 114,1975, 78,1492, 383,1886, 510, + 386, 645,5291,2891,2069,3305,4138,3867,2939,2603,2493,1935,1066,1848,3588,1015, +1282,1289,4609, 697,1453,3044,2666,3611,1856,2412, 54, 719,1330, 568,3778,2459, +1748, 788, 492, 551,1191,1000, 488,3394,3763, 282,1799, 348,2016,1523,3155,2390, +1049, 382,2019,1788,1170, 729,2968,3523, 897,3926,2785,2938,3292, 350,2319,3238, +1718,1717,2655,3453,3143,4465, 161,2889,2980,2009,1421, 56,1908,1640,2387,2232, +1917,1874,2477,4921, 148, 83,3438, 592,4245,2882,1822,1055, 741, 115,1496,1624, + 381,1638,4592,1020, 516,3214, 458, 947,4575,1432, 211,1514,2926,1865,2142, 189, + 852,1221,1400,1486, 882,2299,4036, 351, 28,1122, 700,6479,6480,6481,6482,6483, # last 512 +#Everything below is of no interest for detection purpose +5508,6484,3900,3414,3974,4441,4024,3537,4037,5628,5099,3633,6485,3148,6486,3636, +5509,3257,5510,5973,5445,5872,4941,4403,3174,4627,5873,6276,2286,4230,5446,5874, +5122,6102,6103,4162,5447,5123,5323,4849,6277,3980,3851,5066,4246,5774,5067,6278, +3001,2807,5695,3346,5775,5974,5158,5448,6487,5975,5976,5776,3598,6279,5696,4806, +4211,4154,6280,6488,6489,6490,6281,4212,5037,3374,4171,6491,4562,4807,4722,4827, +5977,6104,4532,4079,5159,5324,5160,4404,3858,5359,5875,3975,4288,4610,3486,4512, +5325,3893,5360,6282,6283,5560,2522,4231,5978,5186,5449,2569,3878,6284,5401,3578, +4415,6285,4656,5124,5979,2506,4247,4449,3219,3417,4334,4969,4329,6492,4576,4828, +4172,4416,4829,5402,6286,3927,3852,5361,4369,4830,4477,4867,5876,4173,6493,6105, +4657,6287,6106,5877,5450,6494,4155,4868,5451,3700,5629,4384,6288,6289,5878,3189, +4881,6107,6290,6495,4513,6496,4692,4515,4723,5100,3356,6497,6291,3810,4080,5561, +3570,4430,5980,6498,4355,5697,6499,4724,6108,6109,3764,4050,5038,5879,4093,3226, +6292,5068,5217,4693,3342,5630,3504,4831,4377,4466,4309,5698,4431,5777,6293,5778, +4272,3706,6110,5326,3752,4676,5327,4273,5403,4767,5631,6500,5699,5880,3475,5039, +6294,5562,5125,4348,4301,4482,4068,5126,4593,5700,3380,3462,5981,5563,3824,5404, +4970,5511,3825,4738,6295,6501,5452,4516,6111,5881,5564,6502,6296,5982,6503,4213, +4163,3454,6504,6112,4009,4450,6113,4658,6297,6114,3035,6505,6115,3995,4904,4739, +4563,4942,4110,5040,3661,3928,5362,3674,6506,5292,3612,4791,5565,4149,5983,5328, +5259,5021,4725,4577,4564,4517,4364,6298,5405,4578,5260,4594,4156,4157,5453,3592, +3491,6507,5127,5512,4709,4922,5984,5701,4726,4289,6508,4015,6116,5128,4628,3424, +4241,5779,6299,4905,6509,6510,5454,5702,5780,6300,4365,4923,3971,6511,5161,3270, +3158,5985,4100, 867,5129,5703,6117,5363,3695,3301,5513,4467,6118,6512,5455,4232, +4242,4629,6513,3959,4478,6514,5514,5329,5986,4850,5162,5566,3846,4694,6119,5456, +4869,5781,3779,6301,5704,5987,5515,4710,6302,5882,6120,4392,5364,5705,6515,6121, +6516,6517,3736,5988,5457,5989,4695,2457,5883,4551,5782,6303,6304,6305,5130,4971, +6122,5163,6123,4870,3263,5365,3150,4871,6518,6306,5783,5069,5706,3513,3498,4409, +5330,5632,5366,5458,5459,3991,5990,4502,3324,5991,5784,3696,4518,5633,4119,6519, +4630,5634,4417,5707,4832,5992,3418,6124,5993,5567,4768,5218,6520,4595,3458,5367, +6125,5635,6126,4202,6521,4740,4924,6307,3981,4069,4385,6308,3883,2675,4051,3834, +4302,4483,5568,5994,4972,4101,5368,6309,5164,5884,3922,6127,6522,6523,5261,5460, +5187,4164,5219,3538,5516,4111,3524,5995,6310,6311,5369,3181,3386,2484,5188,3464, +5569,3627,5708,6524,5406,5165,4677,4492,6312,4872,4851,5885,4468,5996,6313,5709, +5710,6128,2470,5886,6314,5293,4882,5785,3325,5461,5101,6129,5711,5786,6525,4906, +6526,6527,4418,5887,5712,4808,2907,3701,5713,5888,6528,3765,5636,5331,6529,6530, +3593,5889,3637,4943,3692,5714,5787,4925,6315,6130,5462,4405,6131,6132,6316,5262, +6531,6532,5715,3859,5716,5070,4696,5102,3929,5788,3987,4792,5997,6533,6534,3920, +4809,5000,5998,6535,2974,5370,6317,5189,5263,5717,3826,6536,3953,5001,4883,3190, +5463,5890,4973,5999,4741,6133,6134,3607,5570,6000,4711,3362,3630,4552,5041,6318, +6001,2950,2953,5637,4646,5371,4944,6002,2044,4120,3429,6319,6537,5103,4833,6538, +6539,4884,4647,3884,6003,6004,4758,3835,5220,5789,4565,5407,6540,6135,5294,4697, +4852,6320,6321,3206,4907,6541,6322,4945,6542,6136,6543,6323,6005,4631,3519,6544, +5891,6545,5464,3784,5221,6546,5571,4659,6547,6324,6137,5190,6548,3853,6549,4016, +4834,3954,6138,5332,3827,4017,3210,3546,4469,5408,5718,3505,4648,5790,5131,5638, +5791,5465,4727,4318,6325,6326,5792,4553,4010,4698,3439,4974,3638,4335,3085,6006, +5104,5042,5166,5892,5572,6327,4356,4519,5222,5573,5333,5793,5043,6550,5639,5071, +4503,6328,6139,6551,6140,3914,3901,5372,6007,5640,4728,4793,3976,3836,4885,6552, +4127,6553,4451,4102,5002,6554,3686,5105,6555,5191,5072,5295,4611,5794,5296,6556, +5893,5264,5894,4975,5466,5265,4699,4976,4370,4056,3492,5044,4886,6557,5795,4432, +4769,4357,5467,3940,4660,4290,6141,4484,4770,4661,3992,6329,4025,4662,5022,4632, +4835,4070,5297,4663,4596,5574,5132,5409,5895,6142,4504,5192,4664,5796,5896,3885, +5575,5797,5023,4810,5798,3732,5223,4712,5298,4084,5334,5468,6143,4052,4053,4336, +4977,4794,6558,5335,4908,5576,5224,4233,5024,4128,5469,5225,4873,6008,5045,4729, +4742,4633,3675,4597,6559,5897,5133,5577,5003,5641,5719,6330,6560,3017,2382,3854, +4406,4811,6331,4393,3964,4946,6561,2420,3722,6562,4926,4378,3247,1736,4442,6332, +5134,6333,5226,3996,2918,5470,4319,4003,4598,4743,4744,4485,3785,3902,5167,5004, +5373,4394,5898,6144,4874,1793,3997,6334,4085,4214,5106,5642,4909,5799,6009,4419, +4189,3330,5899,4165,4420,5299,5720,5227,3347,6145,4081,6335,2876,3930,6146,3293, +3786,3910,3998,5900,5300,5578,2840,6563,5901,5579,6147,3531,5374,6564,6565,5580, +4759,5375,6566,6148,3559,5643,6336,6010,5517,6337,6338,5721,5902,3873,6011,6339, +6567,5518,3868,3649,5722,6568,4771,4947,6569,6149,4812,6570,2853,5471,6340,6341, +5644,4795,6342,6012,5723,6343,5724,6013,4349,6344,3160,6150,5193,4599,4514,4493, +5168,4320,6345,4927,3666,4745,5169,5903,5005,4928,6346,5725,6014,4730,4203,5046, +4948,3395,5170,6015,4150,6016,5726,5519,6347,5047,3550,6151,6348,4197,4310,5904, +6571,5581,2965,6152,4978,3960,4291,5135,6572,5301,5727,4129,4026,5905,4853,5728, +5472,6153,6349,4533,2700,4505,5336,4678,3583,5073,2994,4486,3043,4554,5520,6350, +6017,5800,4487,6351,3931,4103,5376,6352,4011,4321,4311,4190,5136,6018,3988,3233, +4350,5906,5645,4198,6573,5107,3432,4191,3435,5582,6574,4139,5410,6353,5411,3944, +5583,5074,3198,6575,6354,4358,6576,5302,4600,5584,5194,5412,6577,6578,5585,5413, +5303,4248,5414,3879,4433,6579,4479,5025,4854,5415,6355,4760,4772,3683,2978,4700, +3797,4452,3965,3932,3721,4910,5801,6580,5195,3551,5907,3221,3471,3029,6019,3999, +5908,5909,5266,5267,3444,3023,3828,3170,4796,5646,4979,4259,6356,5647,5337,3694, +6357,5648,5338,4520,4322,5802,3031,3759,4071,6020,5586,4836,4386,5048,6581,3571, +4679,4174,4949,6154,4813,3787,3402,3822,3958,3215,3552,5268,4387,3933,4950,4359, +6021,5910,5075,3579,6358,4234,4566,5521,6359,3613,5049,6022,5911,3375,3702,3178, +4911,5339,4521,6582,6583,4395,3087,3811,5377,6023,6360,6155,4027,5171,5649,4421, +4249,2804,6584,2270,6585,4000,4235,3045,6156,5137,5729,4140,4312,3886,6361,4330, +6157,4215,6158,3500,3676,4929,4331,3713,4930,5912,4265,3776,3368,5587,4470,4855, +3038,4980,3631,6159,6160,4132,4680,6161,6362,3923,4379,5588,4255,6586,4121,6587, +6363,4649,6364,3288,4773,4774,6162,6024,6365,3543,6588,4274,3107,3737,5050,5803, +4797,4522,5589,5051,5730,3714,4887,5378,4001,4523,6163,5026,5522,4701,4175,2791, +3760,6589,5473,4224,4133,3847,4814,4815,4775,3259,5416,6590,2738,6164,6025,5304, +3733,5076,5650,4816,5590,6591,6165,6592,3934,5269,6593,3396,5340,6594,5804,3445, +3602,4042,4488,5731,5732,3525,5591,4601,5196,6166,6026,5172,3642,4612,3202,4506, +4798,6366,3818,5108,4303,5138,5139,4776,3332,4304,2915,3415,4434,5077,5109,4856, +2879,5305,4817,6595,5913,3104,3144,3903,4634,5341,3133,5110,5651,5805,6167,4057, +5592,2945,4371,5593,6596,3474,4182,6367,6597,6168,4507,4279,6598,2822,6599,4777, +4713,5594,3829,6169,3887,5417,6170,3653,5474,6368,4216,2971,5228,3790,4579,6369, +5733,6600,6601,4951,4746,4555,6602,5418,5475,6027,3400,4665,5806,6171,4799,6028, +5052,6172,3343,4800,4747,5006,6370,4556,4217,5476,4396,5229,5379,5477,3839,5914, +5652,5807,4714,3068,4635,5808,6173,5342,4192,5078,5419,5523,5734,6174,4557,6175, +4602,6371,6176,6603,5809,6372,5735,4260,3869,5111,5230,6029,5112,6177,3126,4681, +5524,5915,2706,3563,4748,3130,6178,4018,5525,6604,6605,5478,4012,4837,6606,4534, +4193,5810,4857,3615,5479,6030,4082,3697,3539,4086,5270,3662,4508,4931,5916,4912, +5811,5027,3888,6607,4397,3527,3302,3798,2775,2921,2637,3966,4122,4388,4028,4054, +1633,4858,5079,3024,5007,3982,3412,5736,6608,3426,3236,5595,3030,6179,3427,3336, +3279,3110,6373,3874,3039,5080,5917,5140,4489,3119,6374,5812,3405,4494,6031,4666, +4141,6180,4166,6032,5813,4981,6609,5081,4422,4982,4112,3915,5653,3296,3983,6375, +4266,4410,5654,6610,6181,3436,5082,6611,5380,6033,3819,5596,4535,5231,5306,5113, +6612,4952,5918,4275,3113,6613,6376,6182,6183,5814,3073,4731,4838,5008,3831,6614, +4888,3090,3848,4280,5526,5232,3014,5655,5009,5737,5420,5527,6615,5815,5343,5173, +5381,4818,6616,3151,4953,6617,5738,2796,3204,4360,2989,4281,5739,5174,5421,5197, +3132,5141,3849,5142,5528,5083,3799,3904,4839,5480,2880,4495,3448,6377,6184,5271, +5919,3771,3193,6034,6035,5920,5010,6036,5597,6037,6378,6038,3106,5422,6618,5423, +5424,4142,6619,4889,5084,4890,4313,5740,6620,3437,5175,5307,5816,4199,5198,5529, +5817,5199,5656,4913,5028,5344,3850,6185,2955,5272,5011,5818,4567,4580,5029,5921, +3616,5233,6621,6622,6186,4176,6039,6379,6380,3352,5200,5273,2908,5598,5234,3837, +5308,6623,6624,5819,4496,4323,5309,5201,6625,6626,4983,3194,3838,4167,5530,5922, +5274,6381,6382,3860,3861,5599,3333,4292,4509,6383,3553,5481,5820,5531,4778,6187, +3955,3956,4324,4389,4218,3945,4325,3397,2681,5923,4779,5085,4019,5482,4891,5382, +5383,6040,4682,3425,5275,4094,6627,5310,3015,5483,5657,4398,5924,3168,4819,6628, +5925,6629,5532,4932,4613,6041,6630,4636,6384,4780,4204,5658,4423,5821,3989,4683, +5822,6385,4954,6631,5345,6188,5425,5012,5384,3894,6386,4490,4104,6632,5741,5053, +6633,5823,5926,5659,5660,5927,6634,5235,5742,5824,4840,4933,4820,6387,4859,5928, +4955,6388,4143,3584,5825,5346,5013,6635,5661,6389,5014,5484,5743,4337,5176,5662, +6390,2836,6391,3268,6392,6636,6042,5236,6637,4158,6638,5744,5663,4471,5347,3663, +4123,5143,4293,3895,6639,6640,5311,5929,5826,3800,6189,6393,6190,5664,5348,3554, +3594,4749,4603,6641,5385,4801,6043,5827,4183,6642,5312,5426,4761,6394,5665,6191, +4715,2669,6643,6644,5533,3185,5427,5086,5930,5931,5386,6192,6044,6645,4781,4013, +5745,4282,4435,5534,4390,4267,6045,5746,4984,6046,2743,6193,3501,4087,5485,5932, +5428,4184,4095,5747,4061,5054,3058,3862,5933,5600,6646,5144,3618,6395,3131,5055, +5313,6396,4650,4956,3855,6194,3896,5202,4985,4029,4225,6195,6647,5828,5486,5829, +3589,3002,6648,6397,4782,5276,6649,6196,6650,4105,3803,4043,5237,5830,6398,4096, +3643,6399,3528,6651,4453,3315,4637,6652,3984,6197,5535,3182,3339,6653,3096,2660, +6400,6654,3449,5934,4250,4236,6047,6401,5831,6655,5487,3753,4062,5832,6198,6199, +6656,3766,6657,3403,4667,6048,6658,4338,2897,5833,3880,2797,3780,4326,6659,5748, +5015,6660,5387,4351,5601,4411,6661,3654,4424,5935,4339,4072,5277,4568,5536,6402, +6662,5238,6663,5349,5203,6200,5204,6201,5145,4536,5016,5056,4762,5834,4399,4957, +6202,6403,5666,5749,6664,4340,6665,5936,5177,5667,6666,6667,3459,4668,6404,6668, +6669,4543,6203,6670,4276,6405,4480,5537,6671,4614,5205,5668,6672,3348,2193,4763, +6406,6204,5937,5602,4177,5669,3419,6673,4020,6205,4443,4569,5388,3715,3639,6407, +6049,4058,6206,6674,5938,4544,6050,4185,4294,4841,4651,4615,5488,6207,6408,6051, +5178,3241,3509,5835,6208,4958,5836,4341,5489,5278,6209,2823,5538,5350,5206,5429, +6675,4638,4875,4073,3516,4684,4914,4860,5939,5603,5389,6052,5057,3237,5490,3791, +6676,6409,6677,4821,4915,4106,5351,5058,4243,5539,4244,5604,4842,4916,5239,3028, +3716,5837,5114,5605,5390,5940,5430,6210,4332,6678,5540,4732,3667,3840,6053,4305, +3408,5670,5541,6410,2744,5240,5750,6679,3234,5606,6680,5607,5671,3608,4283,4159, +4400,5352,4783,6681,6411,6682,4491,4802,6211,6412,5941,6413,6414,5542,5751,6683, +4669,3734,5942,6684,6415,5943,5059,3328,4670,4144,4268,6685,6686,6687,6688,4372, +3603,6689,5944,5491,4373,3440,6416,5543,4784,4822,5608,3792,4616,5838,5672,3514, +5391,6417,4892,6690,4639,6691,6054,5673,5839,6055,6692,6056,5392,6212,4038,5544, +5674,4497,6057,6693,5840,4284,5675,4021,4545,5609,6418,4454,6419,6213,4113,4472, +5314,3738,5087,5279,4074,5610,4959,4063,3179,4750,6058,6420,6214,3476,4498,4716, +5431,4960,4685,6215,5241,6694,6421,6216,6695,5841,5945,6422,3748,5946,5179,3905, +5752,5545,5947,4374,6217,4455,6423,4412,6218,4803,5353,6696,3832,5280,6219,4327, +4702,6220,6221,6059,4652,5432,6424,3749,4751,6425,5753,4986,5393,4917,5948,5030, +5754,4861,4733,6426,4703,6697,6222,4671,5949,4546,4961,5180,6223,5031,3316,5281, +6698,4862,4295,4934,5207,3644,6427,5842,5950,6428,6429,4570,5843,5282,6430,6224, +5088,3239,6060,6699,5844,5755,6061,6431,2701,5546,6432,5115,5676,4039,3993,3327, +4752,4425,5315,6433,3941,6434,5677,4617,4604,3074,4581,6225,5433,6435,6226,6062, +4823,5756,5116,6227,3717,5678,4717,5845,6436,5679,5846,6063,5847,6064,3977,3354, +6437,3863,5117,6228,5547,5394,4499,4524,6229,4605,6230,4306,4500,6700,5951,6065, +3693,5952,5089,4366,4918,6701,6231,5548,6232,6702,6438,4704,5434,6703,6704,5953, +4168,6705,5680,3420,6706,5242,4407,6066,3812,5757,5090,5954,4672,4525,3481,5681, +4618,5395,5354,5316,5955,6439,4962,6707,4526,6440,3465,4673,6067,6441,5682,6708, +5435,5492,5758,5683,4619,4571,4674,4804,4893,4686,5493,4753,6233,6068,4269,6442, +6234,5032,4705,5146,5243,5208,5848,6235,6443,4963,5033,4640,4226,6236,5849,3387, +6444,6445,4436,4437,5850,4843,5494,4785,4894,6709,4361,6710,5091,5956,3331,6237, +4987,5549,6069,6711,4342,3517,4473,5317,6070,6712,6071,4706,6446,5017,5355,6713, +6714,4988,5436,6447,4734,5759,6715,4735,4547,4456,4754,6448,5851,6449,6450,3547, +5852,5318,6451,6452,5092,4205,6716,6238,4620,4219,5611,6239,6072,4481,5760,5957, +5958,4059,6240,6453,4227,4537,6241,5761,4030,4186,5244,5209,3761,4457,4876,3337, +5495,5181,6242,5959,5319,5612,5684,5853,3493,5854,6073,4169,5613,5147,4895,6074, +5210,6717,5182,6718,3830,6243,2798,3841,6075,6244,5855,5614,3604,4606,5496,5685, +5118,5356,6719,6454,5960,5357,5961,6720,4145,3935,4621,5119,5962,4261,6721,6455, +4786,5963,4375,4582,6245,6246,6247,6076,5437,4877,5856,3376,4380,6248,4160,6722, +5148,6456,5211,6457,6723,4718,6458,6724,6249,5358,4044,3297,6459,6250,5857,5615, +5497,5245,6460,5498,6725,6251,6252,5550,3793,5499,2959,5396,6461,6462,4572,5093, +5500,5964,3806,4146,6463,4426,5762,5858,6077,6253,4755,3967,4220,5965,6254,4989, +5501,6464,4352,6726,6078,4764,2290,5246,3906,5438,5283,3767,4964,2861,5763,5094, +6255,6256,4622,5616,5859,5860,4707,6727,4285,4708,4824,5617,6257,5551,4787,5212, +4965,4935,4687,6465,6728,6466,5686,6079,3494,4413,2995,5247,5966,5618,6729,5967, +5764,5765,5687,5502,6730,6731,6080,5397,6467,4990,6258,6732,4538,5060,5619,6733, +4719,5688,5439,5018,5149,5284,5503,6734,6081,4607,6259,5120,3645,5861,4583,6260, +4584,4675,5620,4098,5440,6261,4863,2379,3306,4585,5552,5689,4586,5285,6735,4864, +6736,5286,6082,6737,4623,3010,4788,4381,4558,5621,4587,4896,3698,3161,5248,4353, +4045,6262,3754,5183,4588,6738,6263,6739,6740,5622,3936,6741,6468,6742,6264,5095, +6469,4991,5968,6743,4992,6744,6083,4897,6745,4256,5766,4307,3108,3968,4444,5287, +3889,4343,6084,4510,6085,4559,6086,4898,5969,6746,5623,5061,4919,5249,5250,5504, +5441,6265,5320,4878,3242,5862,5251,3428,6087,6747,4237,5624,5442,6266,5553,4539, +6748,2585,3533,5398,4262,6088,5150,4736,4438,6089,6267,5505,4966,6749,6268,6750, +6269,5288,5554,3650,6090,6091,4624,6092,5690,6751,5863,4270,5691,4277,5555,5864, +6752,5692,4720,4865,6470,5151,4688,4825,6753,3094,6754,6471,3235,4653,6755,5213, +5399,6756,3201,4589,5865,4967,6472,5866,6473,5019,3016,6757,5321,4756,3957,4573, +6093,4993,5767,4721,6474,6758,5625,6759,4458,6475,6270,6760,5556,4994,5214,5252, +6271,3875,5768,6094,5034,5506,4376,5769,6761,2120,6476,5253,5770,6762,5771,5970, +3990,5971,5557,5558,5772,6477,6095,2787,4641,5972,5121,6096,6097,6272,6763,3703, +5867,5507,6273,4206,6274,4789,6098,6764,3619,3646,3833,3804,2394,3788,4936,3978, +4866,4899,6099,6100,5559,6478,6765,3599,5868,6101,5869,5870,6275,6766,4527,6767) + +# flake8: noqa diff --git a/resources/lib/libraries/requests/packages/chardet/gb2312prober.py b/resources/lib/libraries/requests/packages/chardet/gb2312prober.py new file mode 100644 index 00000000..0325a2d8 --- /dev/null +++ b/resources/lib/libraries/requests/packages/chardet/gb2312prober.py @@ -0,0 +1,41 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is mozilla.org code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +from .mbcharsetprober import MultiByteCharSetProber +from .codingstatemachine import CodingStateMachine +from .chardistribution import GB2312DistributionAnalysis +from .mbcssm import GB2312SMModel + +class GB2312Prober(MultiByteCharSetProber): + def __init__(self): + MultiByteCharSetProber.__init__(self) + self._mCodingSM = CodingStateMachine(GB2312SMModel) + self._mDistributionAnalyzer = GB2312DistributionAnalysis() + self.reset() + + def get_charset_name(self): + return "GB2312" diff --git a/resources/lib/libraries/requests/packages/chardet/hebrewprober.py b/resources/lib/libraries/requests/packages/chardet/hebrewprober.py new file mode 100644 index 00000000..ba225c5e --- /dev/null +++ b/resources/lib/libraries/requests/packages/chardet/hebrewprober.py @@ -0,0 +1,283 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is Mozilla Universal charset detector code. +# +# The Initial Developer of the Original Code is +# Shy Shalom +# Portions created by the Initial Developer are Copyright (C) 2005 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +from .charsetprober import CharSetProber +from .constants import eNotMe, eDetecting +from .compat import wrap_ord + +# This prober doesn't actually recognize a language or a charset. +# It is a helper prober for the use of the Hebrew model probers + +### General ideas of the Hebrew charset recognition ### +# +# Four main charsets exist in Hebrew: +# "ISO-8859-8" - Visual Hebrew +# "windows-1255" - Logical Hebrew +# "ISO-8859-8-I" - Logical Hebrew +# "x-mac-hebrew" - ?? Logical Hebrew ?? +# +# Both "ISO" charsets use a completely identical set of code points, whereas +# "windows-1255" and "x-mac-hebrew" are two different proper supersets of +# these code points. windows-1255 defines additional characters in the range +# 0x80-0x9F as some misc punctuation marks as well as some Hebrew-specific +# diacritics and additional 'Yiddish' ligature letters in the range 0xc0-0xd6. +# x-mac-hebrew defines similar additional code points but with a different +# mapping. +# +# As far as an average Hebrew text with no diacritics is concerned, all four +# charsets are identical with respect to code points. Meaning that for the +# main Hebrew alphabet, all four map the same values to all 27 Hebrew letters +# (including final letters). +# +# The dominant difference between these charsets is their directionality. +# "Visual" directionality means that the text is ordered as if the renderer is +# not aware of a BIDI rendering algorithm. The renderer sees the text and +# draws it from left to right. The text itself when ordered naturally is read +# backwards. A buffer of Visual Hebrew generally looks like so: +# "[last word of first line spelled backwards] [whole line ordered backwards +# and spelled backwards] [first word of first line spelled backwards] +# [end of line] [last word of second line] ... etc' " +# adding punctuation marks, numbers and English text to visual text is +# naturally also "visual" and from left to right. +# +# "Logical" directionality means the text is ordered "naturally" according to +# the order it is read. It is the responsibility of the renderer to display +# the text from right to left. A BIDI algorithm is used to place general +# punctuation marks, numbers and English text in the text. +# +# Texts in x-mac-hebrew are almost impossible to find on the Internet. From +# what little evidence I could find, it seems that its general directionality +# is Logical. +# +# To sum up all of the above, the Hebrew probing mechanism knows about two +# charsets: +# Visual Hebrew - "ISO-8859-8" - backwards text - Words and sentences are +# backwards while line order is natural. For charset recognition purposes +# the line order is unimportant (In fact, for this implementation, even +# word order is unimportant). +# Logical Hebrew - "windows-1255" - normal, naturally ordered text. +# +# "ISO-8859-8-I" is a subset of windows-1255 and doesn't need to be +# specifically identified. +# "x-mac-hebrew" is also identified as windows-1255. A text in x-mac-hebrew +# that contain special punctuation marks or diacritics is displayed with +# some unconverted characters showing as question marks. This problem might +# be corrected using another model prober for x-mac-hebrew. Due to the fact +# that x-mac-hebrew texts are so rare, writing another model prober isn't +# worth the effort and performance hit. +# +#### The Prober #### +# +# The prober is divided between two SBCharSetProbers and a HebrewProber, +# all of which are managed, created, fed data, inquired and deleted by the +# SBCSGroupProber. The two SBCharSetProbers identify that the text is in +# fact some kind of Hebrew, Logical or Visual. The final decision about which +# one is it is made by the HebrewProber by combining final-letter scores +# with the scores of the two SBCharSetProbers to produce a final answer. +# +# The SBCSGroupProber is responsible for stripping the original text of HTML +# tags, English characters, numbers, low-ASCII punctuation characters, spaces +# and new lines. It reduces any sequence of such characters to a single space. +# The buffer fed to each prober in the SBCS group prober is pure text in +# high-ASCII. +# The two SBCharSetProbers (model probers) share the same language model: +# Win1255Model. +# The first SBCharSetProber uses the model normally as any other +# SBCharSetProber does, to recognize windows-1255, upon which this model was +# built. The second SBCharSetProber is told to make the pair-of-letter +# lookup in the language model backwards. This in practice exactly simulates +# a visual Hebrew model using the windows-1255 logical Hebrew model. +# +# The HebrewProber is not using any language model. All it does is look for +# final-letter evidence suggesting the text is either logical Hebrew or visual +# Hebrew. Disjointed from the model probers, the results of the HebrewProber +# alone are meaningless. HebrewProber always returns 0.00 as confidence +# since it never identifies a charset by itself. Instead, the pointer to the +# HebrewProber is passed to the model probers as a helper "Name Prober". +# When the Group prober receives a positive identification from any prober, +# it asks for the name of the charset identified. If the prober queried is a +# Hebrew model prober, the model prober forwards the call to the +# HebrewProber to make the final decision. In the HebrewProber, the +# decision is made according to the final-letters scores maintained and Both +# model probers scores. The answer is returned in the form of the name of the +# charset identified, either "windows-1255" or "ISO-8859-8". + +# windows-1255 / ISO-8859-8 code points of interest +FINAL_KAF = 0xea +NORMAL_KAF = 0xeb +FINAL_MEM = 0xed +NORMAL_MEM = 0xee +FINAL_NUN = 0xef +NORMAL_NUN = 0xf0 +FINAL_PE = 0xf3 +NORMAL_PE = 0xf4 +FINAL_TSADI = 0xf5 +NORMAL_TSADI = 0xf6 + +# Minimum Visual vs Logical final letter score difference. +# If the difference is below this, don't rely solely on the final letter score +# distance. +MIN_FINAL_CHAR_DISTANCE = 5 + +# Minimum Visual vs Logical model score difference. +# If the difference is below this, don't rely at all on the model score +# distance. +MIN_MODEL_DISTANCE = 0.01 + +VISUAL_HEBREW_NAME = "ISO-8859-8" +LOGICAL_HEBREW_NAME = "windows-1255" + + +class HebrewProber(CharSetProber): + def __init__(self): + CharSetProber.__init__(self) + self._mLogicalProber = None + self._mVisualProber = None + self.reset() + + def reset(self): + self._mFinalCharLogicalScore = 0 + self._mFinalCharVisualScore = 0 + # The two last characters seen in the previous buffer, + # mPrev and mBeforePrev are initialized to space in order to simulate + # a word delimiter at the beginning of the data + self._mPrev = ' ' + self._mBeforePrev = ' ' + # These probers are owned by the group prober. + + def set_model_probers(self, logicalProber, visualProber): + self._mLogicalProber = logicalProber + self._mVisualProber = visualProber + + def is_final(self, c): + return wrap_ord(c) in [FINAL_KAF, FINAL_MEM, FINAL_NUN, FINAL_PE, + FINAL_TSADI] + + def is_non_final(self, c): + # The normal Tsadi is not a good Non-Final letter due to words like + # 'lechotet' (to chat) containing an apostrophe after the tsadi. This + # apostrophe is converted to a space in FilterWithoutEnglishLetters + # causing the Non-Final tsadi to appear at an end of a word even + # though this is not the case in the original text. + # The letters Pe and Kaf rarely display a related behavior of not being + # a good Non-Final letter. Words like 'Pop', 'Winamp' and 'Mubarak' + # for example legally end with a Non-Final Pe or Kaf. However, the + # benefit of these letters as Non-Final letters outweighs the damage + # since these words are quite rare. + return wrap_ord(c) in [NORMAL_KAF, NORMAL_MEM, NORMAL_NUN, NORMAL_PE] + + def feed(self, aBuf): + # Final letter analysis for logical-visual decision. + # Look for evidence that the received buffer is either logical Hebrew + # or visual Hebrew. + # The following cases are checked: + # 1) A word longer than 1 letter, ending with a final letter. This is + # an indication that the text is laid out "naturally" since the + # final letter really appears at the end. +1 for logical score. + # 2) A word longer than 1 letter, ending with a Non-Final letter. In + # normal Hebrew, words ending with Kaf, Mem, Nun, Pe or Tsadi, + # should not end with the Non-Final form of that letter. Exceptions + # to this rule are mentioned above in isNonFinal(). This is an + # indication that the text is laid out backwards. +1 for visual + # score + # 3) A word longer than 1 letter, starting with a final letter. Final + # letters should not appear at the beginning of a word. This is an + # indication that the text is laid out backwards. +1 for visual + # score. + # + # The visual score and logical score are accumulated throughout the + # text and are finally checked against each other in GetCharSetName(). + # No checking for final letters in the middle of words is done since + # that case is not an indication for either Logical or Visual text. + # + # We automatically filter out all 7-bit characters (replace them with + # spaces) so the word boundary detection works properly. [MAP] + + if self.get_state() == eNotMe: + # Both model probers say it's not them. No reason to continue. + return eNotMe + + aBuf = self.filter_high_bit_only(aBuf) + + for cur in aBuf: + if cur == ' ': + # We stand on a space - a word just ended + if self._mBeforePrev != ' ': + # next-to-last char was not a space so self._mPrev is not a + # 1 letter word + if self.is_final(self._mPrev): + # case (1) [-2:not space][-1:final letter][cur:space] + self._mFinalCharLogicalScore += 1 + elif self.is_non_final(self._mPrev): + # case (2) [-2:not space][-1:Non-Final letter][ + # cur:space] + self._mFinalCharVisualScore += 1 + else: + # Not standing on a space + if ((self._mBeforePrev == ' ') and + (self.is_final(self._mPrev)) and (cur != ' ')): + # case (3) [-2:space][-1:final letter][cur:not space] + self._mFinalCharVisualScore += 1 + self._mBeforePrev = self._mPrev + self._mPrev = cur + + # Forever detecting, till the end or until both model probers return + # eNotMe (handled above) + return eDetecting + + def get_charset_name(self): + # Make the decision: is it Logical or Visual? + # If the final letter score distance is dominant enough, rely on it. + finalsub = self._mFinalCharLogicalScore - self._mFinalCharVisualScore + if finalsub >= MIN_FINAL_CHAR_DISTANCE: + return LOGICAL_HEBREW_NAME + if finalsub <= -MIN_FINAL_CHAR_DISTANCE: + return VISUAL_HEBREW_NAME + + # It's not dominant enough, try to rely on the model scores instead. + modelsub = (self._mLogicalProber.get_confidence() + - self._mVisualProber.get_confidence()) + if modelsub > MIN_MODEL_DISTANCE: + return LOGICAL_HEBREW_NAME + if modelsub < -MIN_MODEL_DISTANCE: + return VISUAL_HEBREW_NAME + + # Still no good, back to final letter distance, maybe it'll save the + # day. + if finalsub < 0.0: + return VISUAL_HEBREW_NAME + + # (finalsub > 0 - Logical) or (don't know what to do) default to + # Logical. + return LOGICAL_HEBREW_NAME + + def get_state(self): + # Remain active as long as any of the model probers are active. + if (self._mLogicalProber.get_state() == eNotMe) and \ + (self._mVisualProber.get_state() == eNotMe): + return eNotMe + return eDetecting diff --git a/resources/lib/libraries/requests/packages/chardet/jisfreq.py b/resources/lib/libraries/requests/packages/chardet/jisfreq.py new file mode 100644 index 00000000..064345b0 --- /dev/null +++ b/resources/lib/libraries/requests/packages/chardet/jisfreq.py @@ -0,0 +1,569 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is Mozilla Communicator client code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +# Sampling from about 20M text materials include literature and computer technology +# +# Japanese frequency table, applied to both S-JIS and EUC-JP +# They are sorted in order. + +# 128 --> 0.77094 +# 256 --> 0.85710 +# 512 --> 0.92635 +# 1024 --> 0.97130 +# 2048 --> 0.99431 +# +# Ideal Distribution Ratio = 0.92635 / (1-0.92635) = 12.58 +# Random Distribution Ration = 512 / (2965+62+83+86-512) = 0.191 +# +# Typical Distribution Ratio, 25% of IDR + +JIS_TYPICAL_DISTRIBUTION_RATIO = 3.0 + +# Char to FreqOrder table , +JIS_TABLE_SIZE = 4368 + +JISCharToFreqOrder = ( + 40, 1, 6, 182, 152, 180, 295,2127, 285, 381,3295,4304,3068,4606,3165,3510, # 16 +3511,1822,2785,4607,1193,2226,5070,4608, 171,2996,1247, 18, 179,5071, 856,1661, # 32 +1262,5072, 619, 127,3431,3512,3230,1899,1700, 232, 228,1294,1298, 284, 283,2041, # 48 +2042,1061,1062, 48, 49, 44, 45, 433, 434,1040,1041, 996, 787,2997,1255,4305, # 64 +2108,4609,1684,1648,5073,5074,5075,5076,5077,5078,3687,5079,4610,5080,3927,3928, # 80 +5081,3296,3432, 290,2285,1471,2187,5082,2580,2825,1303,2140,1739,1445,2691,3375, # 96 +1691,3297,4306,4307,4611, 452,3376,1182,2713,3688,3069,4308,5083,5084,5085,5086, # 112 +5087,5088,5089,5090,5091,5092,5093,5094,5095,5096,5097,5098,5099,5100,5101,5102, # 128 +5103,5104,5105,5106,5107,5108,5109,5110,5111,5112,4097,5113,5114,5115,5116,5117, # 144 +5118,5119,5120,5121,5122,5123,5124,5125,5126,5127,5128,5129,5130,5131,5132,5133, # 160 +5134,5135,5136,5137,5138,5139,5140,5141,5142,5143,5144,5145,5146,5147,5148,5149, # 176 +5150,5151,5152,4612,5153,5154,5155,5156,5157,5158,5159,5160,5161,5162,5163,5164, # 192 +5165,5166,5167,5168,5169,5170,5171,5172,5173,5174,5175,1472, 598, 618, 820,1205, # 208 +1309,1412,1858,1307,1692,5176,5177,5178,5179,5180,5181,5182,1142,1452,1234,1172, # 224 +1875,2043,2149,1793,1382,2973, 925,2404,1067,1241, 960,1377,2935,1491, 919,1217, # 240 +1865,2030,1406,1499,2749,4098,5183,5184,5185,5186,5187,5188,2561,4099,3117,1804, # 256 +2049,3689,4309,3513,1663,5189,3166,3118,3298,1587,1561,3433,5190,3119,1625,2998, # 272 +3299,4613,1766,3690,2786,4614,5191,5192,5193,5194,2161, 26,3377, 2,3929, 20, # 288 +3691, 47,4100, 50, 17, 16, 35, 268, 27, 243, 42, 155, 24, 154, 29, 184, # 304 + 4, 91, 14, 92, 53, 396, 33, 289, 9, 37, 64, 620, 21, 39, 321, 5, # 320 + 12, 11, 52, 13, 3, 208, 138, 0, 7, 60, 526, 141, 151,1069, 181, 275, # 336 +1591, 83, 132,1475, 126, 331, 829, 15, 69, 160, 59, 22, 157, 55,1079, 312, # 352 + 109, 38, 23, 25, 10, 19, 79,5195, 61, 382,1124, 8, 30,5196,5197,5198, # 368 +5199,5200,5201,5202,5203,5204,5205,5206, 89, 62, 74, 34,2416, 112, 139, 196, # 384 + 271, 149, 84, 607, 131, 765, 46, 88, 153, 683, 76, 874, 101, 258, 57, 80, # 400 + 32, 364, 121,1508, 169,1547, 68, 235, 145,2999, 41, 360,3027, 70, 63, 31, # 416 + 43, 259, 262,1383, 99, 533, 194, 66, 93, 846, 217, 192, 56, 106, 58, 565, # 432 + 280, 272, 311, 256, 146, 82, 308, 71, 100, 128, 214, 655, 110, 261, 104,1140, # 448 + 54, 51, 36, 87, 67,3070, 185,2618,2936,2020, 28,1066,2390,2059,5207,5208, # 464 +5209,5210,5211,5212,5213,5214,5215,5216,4615,5217,5218,5219,5220,5221,5222,5223, # 480 +5224,5225,5226,5227,5228,5229,5230,5231,5232,5233,5234,5235,5236,3514,5237,5238, # 496 +5239,5240,5241,5242,5243,5244,2297,2031,4616,4310,3692,5245,3071,5246,3598,5247, # 512 +4617,3231,3515,5248,4101,4311,4618,3808,4312,4102,5249,4103,4104,3599,5250,5251, # 528 +5252,5253,5254,5255,5256,5257,5258,5259,5260,5261,5262,5263,5264,5265,5266,5267, # 544 +5268,5269,5270,5271,5272,5273,5274,5275,5276,5277,5278,5279,5280,5281,5282,5283, # 560 +5284,5285,5286,5287,5288,5289,5290,5291,5292,5293,5294,5295,5296,5297,5298,5299, # 576 +5300,5301,5302,5303,5304,5305,5306,5307,5308,5309,5310,5311,5312,5313,5314,5315, # 592 +5316,5317,5318,5319,5320,5321,5322,5323,5324,5325,5326,5327,5328,5329,5330,5331, # 608 +5332,5333,5334,5335,5336,5337,5338,5339,5340,5341,5342,5343,5344,5345,5346,5347, # 624 +5348,5349,5350,5351,5352,5353,5354,5355,5356,5357,5358,5359,5360,5361,5362,5363, # 640 +5364,5365,5366,5367,5368,5369,5370,5371,5372,5373,5374,5375,5376,5377,5378,5379, # 656 +5380,5381, 363, 642,2787,2878,2788,2789,2316,3232,2317,3434,2011, 165,1942,3930, # 672 +3931,3932,3933,5382,4619,5383,4620,5384,5385,5386,5387,5388,5389,5390,5391,5392, # 688 +5393,5394,5395,5396,5397,5398,5399,5400,5401,5402,5403,5404,5405,5406,5407,5408, # 704 +5409,5410,5411,5412,5413,5414,5415,5416,5417,5418,5419,5420,5421,5422,5423,5424, # 720 +5425,5426,5427,5428,5429,5430,5431,5432,5433,5434,5435,5436,5437,5438,5439,5440, # 736 +5441,5442,5443,5444,5445,5446,5447,5448,5449,5450,5451,5452,5453,5454,5455,5456, # 752 +5457,5458,5459,5460,5461,5462,5463,5464,5465,5466,5467,5468,5469,5470,5471,5472, # 768 +5473,5474,5475,5476,5477,5478,5479,5480,5481,5482,5483,5484,5485,5486,5487,5488, # 784 +5489,5490,5491,5492,5493,5494,5495,5496,5497,5498,5499,5500,5501,5502,5503,5504, # 800 +5505,5506,5507,5508,5509,5510,5511,5512,5513,5514,5515,5516,5517,5518,5519,5520, # 816 +5521,5522,5523,5524,5525,5526,5527,5528,5529,5530,5531,5532,5533,5534,5535,5536, # 832 +5537,5538,5539,5540,5541,5542,5543,5544,5545,5546,5547,5548,5549,5550,5551,5552, # 848 +5553,5554,5555,5556,5557,5558,5559,5560,5561,5562,5563,5564,5565,5566,5567,5568, # 864 +5569,5570,5571,5572,5573,5574,5575,5576,5577,5578,5579,5580,5581,5582,5583,5584, # 880 +5585,5586,5587,5588,5589,5590,5591,5592,5593,5594,5595,5596,5597,5598,5599,5600, # 896 +5601,5602,5603,5604,5605,5606,5607,5608,5609,5610,5611,5612,5613,5614,5615,5616, # 912 +5617,5618,5619,5620,5621,5622,5623,5624,5625,5626,5627,5628,5629,5630,5631,5632, # 928 +5633,5634,5635,5636,5637,5638,5639,5640,5641,5642,5643,5644,5645,5646,5647,5648, # 944 +5649,5650,5651,5652,5653,5654,5655,5656,5657,5658,5659,5660,5661,5662,5663,5664, # 960 +5665,5666,5667,5668,5669,5670,5671,5672,5673,5674,5675,5676,5677,5678,5679,5680, # 976 +5681,5682,5683,5684,5685,5686,5687,5688,5689,5690,5691,5692,5693,5694,5695,5696, # 992 +5697,5698,5699,5700,5701,5702,5703,5704,5705,5706,5707,5708,5709,5710,5711,5712, # 1008 +5713,5714,5715,5716,5717,5718,5719,5720,5721,5722,5723,5724,5725,5726,5727,5728, # 1024 +5729,5730,5731,5732,5733,5734,5735,5736,5737,5738,5739,5740,5741,5742,5743,5744, # 1040 +5745,5746,5747,5748,5749,5750,5751,5752,5753,5754,5755,5756,5757,5758,5759,5760, # 1056 +5761,5762,5763,5764,5765,5766,5767,5768,5769,5770,5771,5772,5773,5774,5775,5776, # 1072 +5777,5778,5779,5780,5781,5782,5783,5784,5785,5786,5787,5788,5789,5790,5791,5792, # 1088 +5793,5794,5795,5796,5797,5798,5799,5800,5801,5802,5803,5804,5805,5806,5807,5808, # 1104 +5809,5810,5811,5812,5813,5814,5815,5816,5817,5818,5819,5820,5821,5822,5823,5824, # 1120 +5825,5826,5827,5828,5829,5830,5831,5832,5833,5834,5835,5836,5837,5838,5839,5840, # 1136 +5841,5842,5843,5844,5845,5846,5847,5848,5849,5850,5851,5852,5853,5854,5855,5856, # 1152 +5857,5858,5859,5860,5861,5862,5863,5864,5865,5866,5867,5868,5869,5870,5871,5872, # 1168 +5873,5874,5875,5876,5877,5878,5879,5880,5881,5882,5883,5884,5885,5886,5887,5888, # 1184 +5889,5890,5891,5892,5893,5894,5895,5896,5897,5898,5899,5900,5901,5902,5903,5904, # 1200 +5905,5906,5907,5908,5909,5910,5911,5912,5913,5914,5915,5916,5917,5918,5919,5920, # 1216 +5921,5922,5923,5924,5925,5926,5927,5928,5929,5930,5931,5932,5933,5934,5935,5936, # 1232 +5937,5938,5939,5940,5941,5942,5943,5944,5945,5946,5947,5948,5949,5950,5951,5952, # 1248 +5953,5954,5955,5956,5957,5958,5959,5960,5961,5962,5963,5964,5965,5966,5967,5968, # 1264 +5969,5970,5971,5972,5973,5974,5975,5976,5977,5978,5979,5980,5981,5982,5983,5984, # 1280 +5985,5986,5987,5988,5989,5990,5991,5992,5993,5994,5995,5996,5997,5998,5999,6000, # 1296 +6001,6002,6003,6004,6005,6006,6007,6008,6009,6010,6011,6012,6013,6014,6015,6016, # 1312 +6017,6018,6019,6020,6021,6022,6023,6024,6025,6026,6027,6028,6029,6030,6031,6032, # 1328 +6033,6034,6035,6036,6037,6038,6039,6040,6041,6042,6043,6044,6045,6046,6047,6048, # 1344 +6049,6050,6051,6052,6053,6054,6055,6056,6057,6058,6059,6060,6061,6062,6063,6064, # 1360 +6065,6066,6067,6068,6069,6070,6071,6072,6073,6074,6075,6076,6077,6078,6079,6080, # 1376 +6081,6082,6083,6084,6085,6086,6087,6088,6089,6090,6091,6092,6093,6094,6095,6096, # 1392 +6097,6098,6099,6100,6101,6102,6103,6104,6105,6106,6107,6108,6109,6110,6111,6112, # 1408 +6113,6114,2044,2060,4621, 997,1235, 473,1186,4622, 920,3378,6115,6116, 379,1108, # 1424 +4313,2657,2735,3934,6117,3809, 636,3233, 573,1026,3693,3435,2974,3300,2298,4105, # 1440 + 854,2937,2463, 393,2581,2417, 539, 752,1280,2750,2480, 140,1161, 440, 708,1569, # 1456 + 665,2497,1746,1291,1523,3000, 164,1603, 847,1331, 537,1997, 486, 508,1693,2418, # 1472 +1970,2227, 878,1220, 299,1030, 969, 652,2751, 624,1137,3301,2619, 65,3302,2045, # 1488 +1761,1859,3120,1930,3694,3516, 663,1767, 852, 835,3695, 269, 767,2826,2339,1305, # 1504 + 896,1150, 770,1616,6118, 506,1502,2075,1012,2519, 775,2520,2975,2340,2938,4314, # 1520 +3028,2086,1224,1943,2286,6119,3072,4315,2240,1273,1987,3935,1557, 175, 597, 985, # 1536 +3517,2419,2521,1416,3029, 585, 938,1931,1007,1052,1932,1685,6120,3379,4316,4623, # 1552 + 804, 599,3121,1333,2128,2539,1159,1554,2032,3810, 687,2033,2904, 952, 675,1467, # 1568 +3436,6121,2241,1096,1786,2440,1543,1924, 980,1813,2228, 781,2692,1879, 728,1918, # 1584 +3696,4624, 548,1950,4625,1809,1088,1356,3303,2522,1944, 502, 972, 373, 513,2827, # 1600 + 586,2377,2391,1003,1976,1631,6122,2464,1084, 648,1776,4626,2141, 324, 962,2012, # 1616 +2177,2076,1384, 742,2178,1448,1173,1810, 222, 102, 301, 445, 125,2420, 662,2498, # 1632 + 277, 200,1476,1165,1068, 224,2562,1378,1446, 450,1880, 659, 791, 582,4627,2939, # 1648 +3936,1516,1274, 555,2099,3697,1020,1389,1526,3380,1762,1723,1787,2229, 412,2114, # 1664 +1900,2392,3518, 512,2597, 427,1925,2341,3122,1653,1686,2465,2499, 697, 330, 273, # 1680 + 380,2162, 951, 832, 780, 991,1301,3073, 965,2270,3519, 668,2523,2636,1286, 535, # 1696 +1407, 518, 671, 957,2658,2378, 267, 611,2197,3030,6123, 248,2299, 967,1799,2356, # 1712 + 850,1418,3437,1876,1256,1480,2828,1718,6124,6125,1755,1664,2405,6126,4628,2879, # 1728 +2829, 499,2179, 676,4629, 557,2329,2214,2090, 325,3234, 464, 811,3001, 992,2342, # 1744 +2481,1232,1469, 303,2242, 466,1070,2163, 603,1777,2091,4630,2752,4631,2714, 322, # 1760 +2659,1964,1768, 481,2188,1463,2330,2857,3600,2092,3031,2421,4632,2318,2070,1849, # 1776 +2598,4633,1302,2254,1668,1701,2422,3811,2905,3032,3123,2046,4106,1763,1694,4634, # 1792 +1604, 943,1724,1454, 917, 868,2215,1169,2940, 552,1145,1800,1228,1823,1955, 316, # 1808 +1080,2510, 361,1807,2830,4107,2660,3381,1346,1423,1134,4108,6127, 541,1263,1229, # 1824 +1148,2540, 545, 465,1833,2880,3438,1901,3074,2482, 816,3937, 713,1788,2500, 122, # 1840 +1575, 195,1451,2501,1111,6128, 859, 374,1225,2243,2483,4317, 390,1033,3439,3075, # 1856 +2524,1687, 266, 793,1440,2599, 946, 779, 802, 507, 897,1081, 528,2189,1292, 711, # 1872 +1866,1725,1167,1640, 753, 398,2661,1053, 246, 348,4318, 137,1024,3440,1600,2077, # 1888 +2129, 825,4319, 698, 238, 521, 187,2300,1157,2423,1641,1605,1464,1610,1097,2541, # 1904 +1260,1436, 759,2255,1814,2150, 705,3235, 409,2563,3304, 561,3033,2005,2564, 726, # 1920 +1956,2343,3698,4109, 949,3812,3813,3520,1669, 653,1379,2525, 881,2198, 632,2256, # 1936 +1027, 778,1074, 733,1957, 514,1481,2466, 554,2180, 702,3938,1606,1017,1398,6129, # 1952 +1380,3521, 921, 993,1313, 594, 449,1489,1617,1166, 768,1426,1360, 495,1794,3601, # 1968 +1177,3602,1170,4320,2344, 476, 425,3167,4635,3168,1424, 401,2662,1171,3382,1998, # 1984 +1089,4110, 477,3169, 474,6130,1909, 596,2831,1842, 494, 693,1051,1028,1207,3076, # 2000 + 606,2115, 727,2790,1473,1115, 743,3522, 630, 805,1532,4321,2021, 366,1057, 838, # 2016 + 684,1114,2142,4322,2050,1492,1892,1808,2271,3814,2424,1971,1447,1373,3305,1090, # 2032 +1536,3939,3523,3306,1455,2199, 336, 369,2331,1035, 584,2393, 902, 718,2600,6131, # 2048 +2753, 463,2151,1149,1611,2467, 715,1308,3124,1268, 343,1413,3236,1517,1347,2663, # 2064 +2093,3940,2022,1131,1553,2100,2941,1427,3441,2942,1323,2484,6132,1980, 872,2368, # 2080 +2441,2943, 320,2369,2116,1082, 679,1933,3941,2791,3815, 625,1143,2023, 422,2200, # 2096 +3816,6133, 730,1695, 356,2257,1626,2301,2858,2637,1627,1778, 937, 883,2906,2693, # 2112 +3002,1769,1086, 400,1063,1325,3307,2792,4111,3077, 456,2345,1046, 747,6134,1524, # 2128 + 884,1094,3383,1474,2164,1059, 974,1688,2181,2258,1047, 345,1665,1187, 358, 875, # 2144 +3170, 305, 660,3524,2190,1334,1135,3171,1540,1649,2542,1527, 927, 968,2793, 885, # 2160 +1972,1850, 482, 500,2638,1218,1109,1085,2543,1654,2034, 876, 78,2287,1482,1277, # 2176 + 861,1675,1083,1779, 724,2754, 454, 397,1132,1612,2332, 893, 672,1237, 257,2259, # 2192 +2370, 135,3384, 337,2244, 547, 352, 340, 709,2485,1400, 788,1138,2511, 540, 772, # 2208 +1682,2260,2272,2544,2013,1843,1902,4636,1999,1562,2288,4637,2201,1403,1533, 407, # 2224 + 576,3308,1254,2071, 978,3385, 170, 136,1201,3125,2664,3172,2394, 213, 912, 873, # 2240 +3603,1713,2202, 699,3604,3699, 813,3442, 493, 531,1054, 468,2907,1483, 304, 281, # 2256 +4112,1726,1252,2094, 339,2319,2130,2639, 756,1563,2944, 748, 571,2976,1588,2425, # 2272 +2715,1851,1460,2426,1528,1392,1973,3237, 288,3309, 685,3386, 296, 892,2716,2216, # 2288 +1570,2245, 722,1747,2217, 905,3238,1103,6135,1893,1441,1965, 251,1805,2371,3700, # 2304 +2601,1919,1078, 75,2182,1509,1592,1270,2640,4638,2152,6136,3310,3817, 524, 706, # 2320 +1075, 292,3818,1756,2602, 317, 98,3173,3605,3525,1844,2218,3819,2502, 814, 567, # 2336 + 385,2908,1534,6137, 534,1642,3239, 797,6138,1670,1529, 953,4323, 188,1071, 538, # 2352 + 178, 729,3240,2109,1226,1374,2000,2357,2977, 731,2468,1116,2014,2051,6139,1261, # 2368 +1593, 803,2859,2736,3443, 556, 682, 823,1541,6140,1369,2289,1706,2794, 845, 462, # 2384 +2603,2665,1361, 387, 162,2358,1740, 739,1770,1720,1304,1401,3241,1049, 627,1571, # 2400 +2427,3526,1877,3942,1852,1500, 431,1910,1503, 677, 297,2795, 286,1433,1038,1198, # 2416 +2290,1133,1596,4113,4639,2469,1510,1484,3943,6141,2442, 108, 712,4640,2372, 866, # 2432 +3701,2755,3242,1348, 834,1945,1408,3527,2395,3243,1811, 824, 994,1179,2110,1548, # 2448 +1453, 790,3003, 690,4324,4325,2832,2909,3820,1860,3821, 225,1748, 310, 346,1780, # 2464 +2470, 821,1993,2717,2796, 828, 877,3528,2860,2471,1702,2165,2910,2486,1789, 453, # 2480 + 359,2291,1676, 73,1164,1461,1127,3311, 421, 604, 314,1037, 589, 116,2487, 737, # 2496 + 837,1180, 111, 244, 735,6142,2261,1861,1362, 986, 523, 418, 581,2666,3822, 103, # 2512 + 855, 503,1414,1867,2488,1091, 657,1597, 979, 605,1316,4641,1021,2443,2078,2001, # 2528 +1209, 96, 587,2166,1032, 260,1072,2153, 173, 94, 226,3244, 819,2006,4642,4114, # 2544 +2203, 231,1744, 782, 97,2667, 786,3387, 887, 391, 442,2219,4326,1425,6143,2694, # 2560 + 633,1544,1202, 483,2015, 592,2052,1958,2472,1655, 419, 129,4327,3444,3312,1714, # 2576 +1257,3078,4328,1518,1098, 865,1310,1019,1885,1512,1734, 469,2444, 148, 773, 436, # 2592 +1815,1868,1128,1055,4329,1245,2756,3445,2154,1934,1039,4643, 579,1238, 932,2320, # 2608 + 353, 205, 801, 115,2428, 944,2321,1881, 399,2565,1211, 678, 766,3944, 335,2101, # 2624 +1459,1781,1402,3945,2737,2131,1010, 844, 981,1326,1013, 550,1816,1545,2620,1335, # 2640 +1008, 371,2881, 936,1419,1613,3529,1456,1395,2273,1834,2604,1317,2738,2503, 416, # 2656 +1643,4330, 806,1126, 229, 591,3946,1314,1981,1576,1837,1666, 347,1790, 977,3313, # 2672 + 764,2861,1853, 688,2429,1920,1462, 77, 595, 415,2002,3034, 798,1192,4115,6144, # 2688 +2978,4331,3035,2695,2582,2072,2566, 430,2430,1727, 842,1396,3947,3702, 613, 377, # 2704 + 278, 236,1417,3388,3314,3174, 757,1869, 107,3530,6145,1194, 623,2262, 207,1253, # 2720 +2167,3446,3948, 492,1117,1935, 536,1838,2757,1246,4332, 696,2095,2406,1393,1572, # 2736 +3175,1782, 583, 190, 253,1390,2230, 830,3126,3389, 934,3245,1703,1749,2979,1870, # 2752 +2545,1656,2204, 869,2346,4116,3176,1817, 496,1764,4644, 942,1504, 404,1903,1122, # 2768 +1580,3606,2945,1022, 515, 372,1735, 955,2431,3036,6146,2797,1110,2302,2798, 617, # 2784 +6147, 441, 762,1771,3447,3607,3608,1904, 840,3037, 86, 939,1385, 572,1370,2445, # 2800 +1336, 114,3703, 898, 294, 203,3315, 703,1583,2274, 429, 961,4333,1854,1951,3390, # 2816 +2373,3704,4334,1318,1381, 966,1911,2322,1006,1155, 309, 989, 458,2718,1795,1372, # 2832 +1203, 252,1689,1363,3177, 517,1936, 168,1490, 562, 193,3823,1042,4117,1835, 551, # 2848 + 470,4645, 395, 489,3448,1871,1465,2583,2641, 417,1493, 279,1295, 511,1236,1119, # 2864 + 72,1231,1982,1812,3004, 871,1564, 984,3449,1667,2696,2096,4646,2347,2833,1673, # 2880 +3609, 695,3246,2668, 807,1183,4647, 890, 388,2333,1801,1457,2911,1765,1477,1031, # 2896 +3316,3317,1278,3391,2799,2292,2526, 163,3450,4335,2669,1404,1802,6148,2323,2407, # 2912 +1584,1728,1494,1824,1269, 298, 909,3318,1034,1632, 375, 776,1683,2061, 291, 210, # 2928 +1123, 809,1249,1002,2642,3038, 206,1011,2132, 144, 975, 882,1565, 342, 667, 754, # 2944 +1442,2143,1299,2303,2062, 447, 626,2205,1221,2739,2912,1144,1214,2206,2584, 760, # 2960 +1715, 614, 950,1281,2670,2621, 810, 577,1287,2546,4648, 242,2168, 250,2643, 691, # 2976 + 123,2644, 647, 313,1029, 689,1357,2946,1650, 216, 771,1339,1306, 808,2063, 549, # 2992 + 913,1371,2913,2914,6149,1466,1092,1174,1196,1311,2605,2396,1783,1796,3079, 406, # 3008 +2671,2117,3949,4649, 487,1825,2220,6150,2915, 448,2348,1073,6151,2397,1707, 130, # 3024 + 900,1598, 329, 176,1959,2527,1620,6152,2275,4336,3319,1983,2191,3705,3610,2155, # 3040 +3706,1912,1513,1614,6153,1988, 646, 392,2304,1589,3320,3039,1826,1239,1352,1340, # 3056 +2916, 505,2567,1709,1437,2408,2547, 906,6154,2672, 384,1458,1594,1100,1329, 710, # 3072 + 423,3531,2064,2231,2622,1989,2673,1087,1882, 333, 841,3005,1296,2882,2379, 580, # 3088 +1937,1827,1293,2585, 601, 574, 249,1772,4118,2079,1120, 645, 901,1176,1690, 795, # 3104 +2207, 478,1434, 516,1190,1530, 761,2080, 930,1264, 355, 435,1552, 644,1791, 987, # 3120 + 220,1364,1163,1121,1538, 306,2169,1327,1222, 546,2645, 218, 241, 610,1704,3321, # 3136 +1984,1839,1966,2528, 451,6155,2586,3707,2568, 907,3178, 254,2947, 186,1845,4650, # 3152 + 745, 432,1757, 428,1633, 888,2246,2221,2489,3611,2118,1258,1265, 956,3127,1784, # 3168 +4337,2490, 319, 510, 119, 457,3612, 274,2035,2007,4651,1409,3128, 970,2758, 590, # 3184 +2800, 661,2247,4652,2008,3950,1420,1549,3080,3322,3951,1651,1375,2111, 485,2491, # 3200 +1429,1156,6156,2548,2183,1495, 831,1840,2529,2446, 501,1657, 307,1894,3247,1341, # 3216 + 666, 899,2156,1539,2549,1559, 886, 349,2208,3081,2305,1736,3824,2170,2759,1014, # 3232 +1913,1386, 542,1397,2948, 490, 368, 716, 362, 159, 282,2569,1129,1658,1288,1750, # 3248 +2674, 276, 649,2016, 751,1496, 658,1818,1284,1862,2209,2087,2512,3451, 622,2834, # 3264 + 376, 117,1060,2053,1208,1721,1101,1443, 247,1250,3179,1792,3952,2760,2398,3953, # 3280 +6157,2144,3708, 446,2432,1151,2570,3452,2447,2761,2835,1210,2448,3082, 424,2222, # 3296 +1251,2449,2119,2836, 504,1581,4338, 602, 817, 857,3825,2349,2306, 357,3826,1470, # 3312 +1883,2883, 255, 958, 929,2917,3248, 302,4653,1050,1271,1751,2307,1952,1430,2697, # 3328 +2719,2359, 354,3180, 777, 158,2036,4339,1659,4340,4654,2308,2949,2248,1146,2232, # 3344 +3532,2720,1696,2623,3827,6158,3129,1550,2698,1485,1297,1428, 637, 931,2721,2145, # 3360 + 914,2550,2587, 81,2450, 612, 827,2646,1242,4655,1118,2884, 472,1855,3181,3533, # 3376 +3534, 569,1353,2699,1244,1758,2588,4119,2009,2762,2171,3709,1312,1531,6159,1152, # 3392 +1938, 134,1830, 471,3710,2276,1112,1535,3323,3453,3535, 982,1337,2950, 488, 826, # 3408 + 674,1058,1628,4120,2017, 522,2399, 211, 568,1367,3454, 350, 293,1872,1139,3249, # 3424 +1399,1946,3006,1300,2360,3324, 588, 736,6160,2606, 744, 669,3536,3828,6161,1358, # 3440 + 199, 723, 848, 933, 851,1939,1505,1514,1338,1618,1831,4656,1634,3613, 443,2740, # 3456 +3829, 717,1947, 491,1914,6162,2551,1542,4121,1025,6163,1099,1223, 198,3040,2722, # 3472 + 370, 410,1905,2589, 998,1248,3182,2380, 519,1449,4122,1710, 947, 928,1153,4341, # 3488 +2277, 344,2624,1511, 615, 105, 161,1212,1076,1960,3130,2054,1926,1175,1906,2473, # 3504 + 414,1873,2801,6164,2309, 315,1319,3325, 318,2018,2146,2157, 963, 631, 223,4342, # 3520 +4343,2675, 479,3711,1197,2625,3712,2676,2361,6165,4344,4123,6166,2451,3183,1886, # 3536 +2184,1674,1330,1711,1635,1506, 799, 219,3250,3083,3954,1677,3713,3326,2081,3614, # 3552 +1652,2073,4657,1147,3041,1752, 643,1961, 147,1974,3955,6167,1716,2037, 918,3007, # 3568 +1994, 120,1537, 118, 609,3184,4345, 740,3455,1219, 332,1615,3830,6168,1621,2980, # 3584 +1582, 783, 212, 553,2350,3714,1349,2433,2082,4124, 889,6169,2310,1275,1410, 973, # 3600 + 166,1320,3456,1797,1215,3185,2885,1846,2590,2763,4658, 629, 822,3008, 763, 940, # 3616 +1990,2862, 439,2409,1566,1240,1622, 926,1282,1907,2764, 654,2210,1607, 327,1130, # 3632 +3956,1678,1623,6170,2434,2192, 686, 608,3831,3715, 903,3957,3042,6171,2741,1522, # 3648 +1915,1105,1555,2552,1359, 323,3251,4346,3457, 738,1354,2553,2311,2334,1828,2003, # 3664 +3832,1753,2351,1227,6172,1887,4125,1478,6173,2410,1874,1712,1847, 520,1204,2607, # 3680 + 264,4659, 836,2677,2102, 600,4660,3833,2278,3084,6174,4347,3615,1342, 640, 532, # 3696 + 543,2608,1888,2400,2591,1009,4348,1497, 341,1737,3616,2723,1394, 529,3252,1321, # 3712 + 983,4661,1515,2120, 971,2592, 924, 287,1662,3186,4349,2700,4350,1519, 908,1948, # 3728 +2452, 156, 796,1629,1486,2223,2055, 694,4126,1259,1036,3392,1213,2249,2742,1889, # 3744 +1230,3958,1015, 910, 408, 559,3617,4662, 746, 725, 935,4663,3959,3009,1289, 563, # 3760 + 867,4664,3960,1567,2981,2038,2626, 988,2263,2381,4351, 143,2374, 704,1895,6175, # 3776 +1188,3716,2088, 673,3085,2362,4352, 484,1608,1921,2765,2918, 215, 904,3618,3537, # 3792 + 894, 509, 976,3043,2701,3961,4353,2837,2982, 498,6176,6177,1102,3538,1332,3393, # 3808 +1487,1636,1637, 233, 245,3962, 383, 650, 995,3044, 460,1520,1206,2352, 749,3327, # 3824 + 530, 700, 389,1438,1560,1773,3963,2264, 719,2951,2724,3834, 870,1832,1644,1000, # 3840 + 839,2474,3717, 197,1630,3394, 365,2886,3964,1285,2133, 734, 922, 818,1106, 732, # 3856 + 480,2083,1774,3458, 923,2279,1350, 221,3086, 85,2233,2234,3835,1585,3010,2147, # 3872 +1387,1705,2382,1619,2475, 133, 239,2802,1991,1016,2084,2383, 411,2838,1113, 651, # 3888 +1985,1160,3328, 990,1863,3087,1048,1276,2647, 265,2627,1599,3253,2056, 150, 638, # 3904 +2019, 656, 853, 326,1479, 680,1439,4354,1001,1759, 413,3459,3395,2492,1431, 459, # 3920 +4355,1125,3329,2265,1953,1450,2065,2863, 849, 351,2678,3131,3254,3255,1104,1577, # 3936 + 227,1351,1645,2453,2193,1421,2887, 812,2121, 634, 95,2435, 201,2312,4665,1646, # 3952 +1671,2743,1601,2554,2702,2648,2280,1315,1366,2089,3132,1573,3718,3965,1729,1189, # 3968 + 328,2679,1077,1940,1136, 558,1283, 964,1195, 621,2074,1199,1743,3460,3619,1896, # 3984 +1916,1890,3836,2952,1154,2112,1064, 862, 378,3011,2066,2113,2803,1568,2839,6178, # 4000 +3088,2919,1941,1660,2004,1992,2194, 142, 707,1590,1708,1624,1922,1023,1836,1233, # 4016 +1004,2313, 789, 741,3620,6179,1609,2411,1200,4127,3719,3720,4666,2057,3721, 593, # 4032 +2840, 367,2920,1878,6180,3461,1521, 628,1168, 692,2211,2649, 300, 720,2067,2571, # 4048 +2953,3396, 959,2504,3966,3539,3462,1977, 701,6181, 954,1043, 800, 681, 183,3722, # 4064 +1803,1730,3540,4128,2103, 815,2314, 174, 467, 230,2454,1093,2134, 755,3541,3397, # 4080 +1141,1162,6182,1738,2039, 270,3256,2513,1005,1647,2185,3837, 858,1679,1897,1719, # 4096 +2954,2324,1806, 402, 670, 167,4129,1498,2158,2104, 750,6183, 915, 189,1680,1551, # 4112 + 455,4356,1501,2455, 405,1095,2955, 338,1586,1266,1819, 570, 641,1324, 237,1556, # 4128 +2650,1388,3723,6184,1368,2384,1343,1978,3089,2436, 879,3724, 792,1191, 758,3012, # 4144 +1411,2135,1322,4357, 240,4667,1848,3725,1574,6185, 420,3045,1546,1391, 714,4358, # 4160 +1967, 941,1864, 863, 664, 426, 560,1731,2680,1785,2864,1949,2363, 403,3330,1415, # 4176 +1279,2136,1697,2335, 204, 721,2097,3838, 90,6186,2085,2505, 191,3967, 124,2148, # 4192 +1376,1798,1178,1107,1898,1405, 860,4359,1243,1272,2375,2983,1558,2456,1638, 113, # 4208 +3621, 578,1923,2609, 880, 386,4130, 784,2186,2266,1422,2956,2172,1722, 497, 263, # 4224 +2514,1267,2412,2610, 177,2703,3542, 774,1927,1344, 616,1432,1595,1018, 172,4360, # 4240 +2325, 911,4361, 438,1468,3622, 794,3968,2024,2173,1681,1829,2957, 945, 895,3090, # 4256 + 575,2212,2476, 475,2401,2681, 785,2744,1745,2293,2555,1975,3133,2865, 394,4668, # 4272 +3839, 635,4131, 639, 202,1507,2195,2766,1345,1435,2572,3726,1908,1184,1181,2457, # 4288 +3727,3134,4362, 843,2611, 437, 916,4669, 234, 769,1884,3046,3047,3623, 833,6187, # 4304 +1639,2250,2402,1355,1185,2010,2047, 999, 525,1732,1290,1488,2612, 948,1578,3728, # 4320 +2413,2477,1216,2725,2159, 334,3840,1328,3624,2921,1525,4132, 564,1056, 891,4363, # 4336 +1444,1698,2385,2251,3729,1365,2281,2235,1717,6188, 864,3841,2515, 444, 527,2767, # 4352 +2922,3625, 544, 461,6189, 566, 209,2437,3398,2098,1065,2068,3331,3626,3257,2137, # 4368 #last 512 +#Everything below is of no interest for detection purpose +2138,2122,3730,2888,1995,1820,1044,6190,6191,6192,6193,6194,6195,6196,6197,6198, # 4384 +6199,6200,6201,6202,6203,6204,6205,4670,6206,6207,6208,6209,6210,6211,6212,6213, # 4400 +6214,6215,6216,6217,6218,6219,6220,6221,6222,6223,6224,6225,6226,6227,6228,6229, # 4416 +6230,6231,6232,6233,6234,6235,6236,6237,3187,6238,6239,3969,6240,6241,6242,6243, # 4432 +6244,4671,6245,6246,4672,6247,6248,4133,6249,6250,4364,6251,2923,2556,2613,4673, # 4448 +4365,3970,6252,6253,6254,6255,4674,6256,6257,6258,2768,2353,4366,4675,4676,3188, # 4464 +4367,3463,6259,4134,4677,4678,6260,2267,6261,3842,3332,4368,3543,6262,6263,6264, # 4480 +3013,1954,1928,4135,4679,6265,6266,2478,3091,6267,4680,4369,6268,6269,1699,6270, # 4496 +3544,4136,4681,6271,4137,6272,4370,2804,6273,6274,2593,3971,3972,4682,6275,2236, # 4512 +4683,6276,6277,4684,6278,6279,4138,3973,4685,6280,6281,3258,6282,6283,6284,6285, # 4528 +3974,4686,2841,3975,6286,6287,3545,6288,6289,4139,4687,4140,6290,4141,6291,4142, # 4544 +6292,6293,3333,6294,6295,6296,4371,6297,3399,6298,6299,4372,3976,6300,6301,6302, # 4560 +4373,6303,6304,3843,3731,6305,4688,4374,6306,6307,3259,2294,6308,3732,2530,4143, # 4576 +6309,4689,6310,6311,6312,3048,6313,6314,4690,3733,2237,6315,6316,2282,3334,6317, # 4592 +6318,3844,6319,6320,4691,6321,3400,4692,6322,4693,6323,3049,6324,4375,6325,3977, # 4608 +6326,6327,6328,3546,6329,4694,3335,6330,4695,4696,6331,6332,6333,6334,4376,3978, # 4624 +6335,4697,3979,4144,6336,3980,4698,6337,6338,6339,6340,6341,4699,4700,4701,6342, # 4640 +6343,4702,6344,6345,4703,6346,6347,4704,6348,4705,4706,3135,6349,4707,6350,4708, # 4656 +6351,4377,6352,4709,3734,4145,6353,2506,4710,3189,6354,3050,4711,3981,6355,3547, # 4672 +3014,4146,4378,3735,2651,3845,3260,3136,2224,1986,6356,3401,6357,4712,2594,3627, # 4688 +3137,2573,3736,3982,4713,3628,4714,4715,2682,3629,4716,6358,3630,4379,3631,6359, # 4704 +6360,6361,3983,6362,6363,6364,6365,4147,3846,4717,6366,6367,3737,2842,6368,4718, # 4720 +2628,6369,3261,6370,2386,6371,6372,3738,3984,4719,3464,4720,3402,6373,2924,3336, # 4736 +4148,2866,6374,2805,3262,4380,2704,2069,2531,3138,2806,2984,6375,2769,6376,4721, # 4752 +4722,3403,6377,6378,3548,6379,6380,2705,3092,1979,4149,2629,3337,2889,6381,3338, # 4768 +4150,2557,3339,4381,6382,3190,3263,3739,6383,4151,4723,4152,2558,2574,3404,3191, # 4784 +6384,6385,4153,6386,4724,4382,6387,6388,4383,6389,6390,4154,6391,4725,3985,6392, # 4800 +3847,4155,6393,6394,6395,6396,6397,3465,6398,4384,6399,6400,6401,6402,6403,6404, # 4816 +4156,6405,6406,6407,6408,2123,6409,6410,2326,3192,4726,6411,6412,6413,6414,4385, # 4832 +4157,6415,6416,4158,6417,3093,3848,6418,3986,6419,6420,3849,6421,6422,6423,4159, # 4848 +6424,6425,4160,6426,3740,6427,6428,6429,6430,3987,6431,4727,6432,2238,6433,6434, # 4864 +4386,3988,6435,6436,3632,6437,6438,2843,6439,6440,6441,6442,3633,6443,2958,6444, # 4880 +6445,3466,6446,2364,4387,3850,6447,4388,2959,3340,6448,3851,6449,4728,6450,6451, # 4896 +3264,4729,6452,3193,6453,4389,4390,2706,3341,4730,6454,3139,6455,3194,6456,3051, # 4912 +2124,3852,1602,4391,4161,3853,1158,3854,4162,3989,4392,3990,4731,4732,4393,2040, # 4928 +4163,4394,3265,6457,2807,3467,3855,6458,6459,6460,3991,3468,4733,4734,6461,3140, # 4944 +2960,6462,4735,6463,6464,6465,6466,4736,4737,4738,4739,6467,6468,4164,2403,3856, # 4960 +6469,6470,2770,2844,6471,4740,6472,6473,6474,6475,6476,6477,6478,3195,6479,4741, # 4976 +4395,6480,2867,6481,4742,2808,6482,2493,4165,6483,6484,6485,6486,2295,4743,6487, # 4992 +6488,6489,3634,6490,6491,6492,6493,6494,6495,6496,2985,4744,6497,6498,4745,6499, # 5008 +6500,2925,3141,4166,6501,6502,4746,6503,6504,4747,6505,6506,6507,2890,6508,6509, # 5024 +6510,6511,6512,6513,6514,6515,6516,6517,6518,6519,3469,4167,6520,6521,6522,4748, # 5040 +4396,3741,4397,4749,4398,3342,2125,4750,6523,4751,4752,4753,3052,6524,2961,4168, # 5056 +6525,4754,6526,4755,4399,2926,4169,6527,3857,6528,4400,4170,6529,4171,6530,6531, # 5072 +2595,6532,6533,6534,6535,3635,6536,6537,6538,6539,6540,6541,6542,4756,6543,6544, # 5088 +6545,6546,6547,6548,4401,6549,6550,6551,6552,4402,3405,4757,4403,6553,6554,6555, # 5104 +4172,3742,6556,6557,6558,3992,3636,6559,6560,3053,2726,6561,3549,4173,3054,4404, # 5120 +6562,6563,3993,4405,3266,3550,2809,4406,6564,6565,6566,4758,4759,6567,3743,6568, # 5136 +4760,3744,4761,3470,6569,6570,6571,4407,6572,3745,4174,6573,4175,2810,4176,3196, # 5152 +4762,6574,4177,6575,6576,2494,2891,3551,6577,6578,3471,6579,4408,6580,3015,3197, # 5168 +6581,3343,2532,3994,3858,6582,3094,3406,4409,6583,2892,4178,4763,4410,3016,4411, # 5184 +6584,3995,3142,3017,2683,6585,4179,6586,6587,4764,4412,6588,6589,4413,6590,2986, # 5200 +6591,2962,3552,6592,2963,3472,6593,6594,4180,4765,6595,6596,2225,3267,4414,6597, # 5216 +3407,3637,4766,6598,6599,3198,6600,4415,6601,3859,3199,6602,3473,4767,2811,4416, # 5232 +1856,3268,3200,2575,3996,3997,3201,4417,6603,3095,2927,6604,3143,6605,2268,6606, # 5248 +3998,3860,3096,2771,6607,6608,3638,2495,4768,6609,3861,6610,3269,2745,4769,4181, # 5264 +3553,6611,2845,3270,6612,6613,6614,3862,6615,6616,4770,4771,6617,3474,3999,4418, # 5280 +4419,6618,3639,3344,6619,4772,4182,6620,2126,6621,6622,6623,4420,4773,6624,3018, # 5296 +6625,4774,3554,6626,4183,2025,3746,6627,4184,2707,6628,4421,4422,3097,1775,4185, # 5312 +3555,6629,6630,2868,6631,6632,4423,6633,6634,4424,2414,2533,2928,6635,4186,2387, # 5328 +6636,4775,6637,4187,6638,1891,4425,3202,3203,6639,6640,4776,6641,3345,6642,6643, # 5344 +3640,6644,3475,3346,3641,4000,6645,3144,6646,3098,2812,4188,3642,3204,6647,3863, # 5360 +3476,6648,3864,6649,4426,4001,6650,6651,6652,2576,6653,4189,4777,6654,6655,6656, # 5376 +2846,6657,3477,3205,4002,6658,4003,6659,3347,2252,6660,6661,6662,4778,6663,6664, # 5392 +6665,6666,6667,6668,6669,4779,4780,2048,6670,3478,3099,6671,3556,3747,4004,6672, # 5408 +6673,6674,3145,4005,3748,6675,6676,6677,6678,6679,3408,6680,6681,6682,6683,3206, # 5424 +3207,6684,6685,4781,4427,6686,4782,4783,4784,6687,6688,6689,4190,6690,6691,3479, # 5440 +6692,2746,6693,4428,6694,6695,6696,6697,6698,6699,4785,6700,6701,3208,2727,6702, # 5456 +3146,6703,6704,3409,2196,6705,4429,6706,6707,6708,2534,1996,6709,6710,6711,2747, # 5472 +6712,6713,6714,4786,3643,6715,4430,4431,6716,3557,6717,4432,4433,6718,6719,6720, # 5488 +6721,3749,6722,4006,4787,6723,6724,3644,4788,4434,6725,6726,4789,2772,6727,6728, # 5504 +6729,6730,6731,2708,3865,2813,4435,6732,6733,4790,4791,3480,6734,6735,6736,6737, # 5520 +4436,3348,6738,3410,4007,6739,6740,4008,6741,6742,4792,3411,4191,6743,6744,6745, # 5536 +6746,6747,3866,6748,3750,6749,6750,6751,6752,6753,6754,6755,3867,6756,4009,6757, # 5552 +4793,4794,6758,2814,2987,6759,6760,6761,4437,6762,6763,6764,6765,3645,6766,6767, # 5568 +3481,4192,6768,3751,6769,6770,2174,6771,3868,3752,6772,6773,6774,4193,4795,4438, # 5584 +3558,4796,4439,6775,4797,6776,6777,4798,6778,4799,3559,4800,6779,6780,6781,3482, # 5600 +6782,2893,6783,6784,4194,4801,4010,6785,6786,4440,6787,4011,6788,6789,6790,6791, # 5616 +6792,6793,4802,6794,6795,6796,4012,6797,6798,6799,6800,3349,4803,3483,6801,4804, # 5632 +4195,6802,4013,6803,6804,4196,6805,4014,4015,6806,2847,3271,2848,6807,3484,6808, # 5648 +6809,6810,4441,6811,4442,4197,4443,3272,4805,6812,3412,4016,1579,6813,6814,4017, # 5664 +6815,3869,6816,2964,6817,4806,6818,6819,4018,3646,6820,6821,4807,4019,4020,6822, # 5680 +6823,3560,6824,6825,4021,4444,6826,4198,6827,6828,4445,6829,6830,4199,4808,6831, # 5696 +6832,6833,3870,3019,2458,6834,3753,3413,3350,6835,4809,3871,4810,3561,4446,6836, # 5712 +6837,4447,4811,4812,6838,2459,4448,6839,4449,6840,6841,4022,3872,6842,4813,4814, # 5728 +6843,6844,4815,4200,4201,4202,6845,4023,6846,6847,4450,3562,3873,6848,6849,4816, # 5744 +4817,6850,4451,4818,2139,6851,3563,6852,6853,3351,6854,6855,3352,4024,2709,3414, # 5760 +4203,4452,6856,4204,6857,6858,3874,3875,6859,6860,4819,6861,6862,6863,6864,4453, # 5776 +3647,6865,6866,4820,6867,6868,6869,6870,4454,6871,2869,6872,6873,4821,6874,3754, # 5792 +6875,4822,4205,6876,6877,6878,3648,4206,4455,6879,4823,6880,4824,3876,6881,3055, # 5808 +4207,6882,3415,6883,6884,6885,4208,4209,6886,4210,3353,6887,3354,3564,3209,3485, # 5824 +2652,6888,2728,6889,3210,3755,6890,4025,4456,6891,4825,6892,6893,6894,6895,4211, # 5840 +6896,6897,6898,4826,6899,6900,4212,6901,4827,6902,2773,3565,6903,4828,6904,6905, # 5856 +6906,6907,3649,3650,6908,2849,3566,6909,3567,3100,6910,6911,6912,6913,6914,6915, # 5872 +4026,6916,3355,4829,3056,4457,3756,6917,3651,6918,4213,3652,2870,6919,4458,6920, # 5888 +2438,6921,6922,3757,2774,4830,6923,3356,4831,4832,6924,4833,4459,3653,2507,6925, # 5904 +4834,2535,6926,6927,3273,4027,3147,6928,3568,6929,6930,6931,4460,6932,3877,4461, # 5920 +2729,3654,6933,6934,6935,6936,2175,4835,2630,4214,4028,4462,4836,4215,6937,3148, # 5936 +4216,4463,4837,4838,4217,6938,6939,2850,4839,6940,4464,6941,6942,6943,4840,6944, # 5952 +4218,3274,4465,6945,6946,2710,6947,4841,4466,6948,6949,2894,6950,6951,4842,6952, # 5968 +4219,3057,2871,6953,6954,6955,6956,4467,6957,2711,6958,6959,6960,3275,3101,4843, # 5984 +6961,3357,3569,6962,4844,6963,6964,4468,4845,3570,6965,3102,4846,3758,6966,4847, # 6000 +3878,4848,4849,4029,6967,2929,3879,4850,4851,6968,6969,1733,6970,4220,6971,6972, # 6016 +6973,6974,6975,6976,4852,6977,6978,6979,6980,6981,6982,3759,6983,6984,6985,3486, # 6032 +3487,6986,3488,3416,6987,6988,6989,6990,6991,6992,6993,6994,6995,6996,6997,4853, # 6048 +6998,6999,4030,7000,7001,3211,7002,7003,4221,7004,7005,3571,4031,7006,3572,7007, # 6064 +2614,4854,2577,7008,7009,2965,3655,3656,4855,2775,3489,3880,4222,4856,3881,4032, # 6080 +3882,3657,2730,3490,4857,7010,3149,7011,4469,4858,2496,3491,4859,2283,7012,7013, # 6096 +7014,2365,4860,4470,7015,7016,3760,7017,7018,4223,1917,7019,7020,7021,4471,7022, # 6112 +2776,4472,7023,7024,7025,7026,4033,7027,3573,4224,4861,4034,4862,7028,7029,1929, # 6128 +3883,4035,7030,4473,3058,7031,2536,3761,3884,7032,4036,7033,2966,2895,1968,4474, # 6144 +3276,4225,3417,3492,4226,2105,7034,7035,1754,2596,3762,4227,4863,4475,3763,4864, # 6160 +3764,2615,2777,3103,3765,3658,3418,4865,2296,3766,2815,7036,7037,7038,3574,2872, # 6176 +3277,4476,7039,4037,4477,7040,7041,4038,7042,7043,7044,7045,7046,7047,2537,7048, # 6192 +7049,7050,7051,7052,7053,7054,4478,7055,7056,3767,3659,4228,3575,7057,7058,4229, # 6208 +7059,7060,7061,3660,7062,3212,7063,3885,4039,2460,7064,7065,7066,7067,7068,7069, # 6224 +7070,7071,7072,7073,7074,4866,3768,4867,7075,7076,7077,7078,4868,3358,3278,2653, # 6240 +7079,7080,4479,3886,7081,7082,4869,7083,7084,7085,7086,7087,7088,2538,7089,7090, # 6256 +7091,4040,3150,3769,4870,4041,2896,3359,4230,2930,7092,3279,7093,2967,4480,3213, # 6272 +4481,3661,7094,7095,7096,7097,7098,7099,7100,7101,7102,2461,3770,7103,7104,4231, # 6288 +3151,7105,7106,7107,4042,3662,7108,7109,4871,3663,4872,4043,3059,7110,7111,7112, # 6304 +3493,2988,7113,4873,7114,7115,7116,3771,4874,7117,7118,4232,4875,7119,3576,2336, # 6320 +4876,7120,4233,3419,4044,4877,4878,4482,4483,4879,4484,4234,7121,3772,4880,1045, # 6336 +3280,3664,4881,4882,7122,7123,7124,7125,4883,7126,2778,7127,4485,4486,7128,4884, # 6352 +3214,3887,7129,7130,3215,7131,4885,4045,7132,7133,4046,7134,7135,7136,7137,7138, # 6368 +7139,7140,7141,7142,7143,4235,7144,4886,7145,7146,7147,4887,7148,7149,7150,4487, # 6384 +4047,4488,7151,7152,4888,4048,2989,3888,7153,3665,7154,4049,7155,7156,7157,7158, # 6400 +7159,7160,2931,4889,4890,4489,7161,2631,3889,4236,2779,7162,7163,4891,7164,3060, # 6416 +7165,1672,4892,7166,4893,4237,3281,4894,7167,7168,3666,7169,3494,7170,7171,4050, # 6432 +7172,7173,3104,3360,3420,4490,4051,2684,4052,7174,4053,7175,7176,7177,2253,4054, # 6448 +7178,7179,4895,7180,3152,3890,3153,4491,3216,7181,7182,7183,2968,4238,4492,4055, # 6464 +7184,2990,7185,2479,7186,7187,4493,7188,7189,7190,7191,7192,4896,7193,4897,2969, # 6480 +4494,4898,7194,3495,7195,7196,4899,4495,7197,3105,2731,7198,4900,7199,7200,7201, # 6496 +4056,7202,3361,7203,7204,4496,4901,4902,7205,4497,7206,7207,2315,4903,7208,4904, # 6512 +7209,4905,2851,7210,7211,3577,7212,3578,4906,7213,4057,3667,4907,7214,4058,2354, # 6528 +3891,2376,3217,3773,7215,7216,7217,7218,7219,4498,7220,4908,3282,2685,7221,3496, # 6544 +4909,2632,3154,4910,7222,2337,7223,4911,7224,7225,7226,4912,4913,3283,4239,4499, # 6560 +7227,2816,7228,7229,7230,7231,7232,7233,7234,4914,4500,4501,7235,7236,7237,2686, # 6576 +7238,4915,7239,2897,4502,7240,4503,7241,2516,7242,4504,3362,3218,7243,7244,7245, # 6592 +4916,7246,7247,4505,3363,7248,7249,7250,7251,3774,4506,7252,7253,4917,7254,7255, # 6608 +3284,2991,4918,4919,3219,3892,4920,3106,3497,4921,7256,7257,7258,4922,7259,4923, # 6624 +3364,4507,4508,4059,7260,4240,3498,7261,7262,4924,7263,2992,3893,4060,3220,7264, # 6640 +7265,7266,7267,7268,7269,4509,3775,7270,2817,7271,4061,4925,4510,3776,7272,4241, # 6656 +4511,3285,7273,7274,3499,7275,7276,7277,4062,4512,4926,7278,3107,3894,7279,7280, # 6672 +4927,7281,4513,7282,7283,3668,7284,7285,4242,4514,4243,7286,2058,4515,4928,4929, # 6688 +4516,7287,3286,4244,7288,4517,7289,7290,7291,3669,7292,7293,4930,4931,4932,2355, # 6704 +4933,7294,2633,4518,7295,4245,7296,7297,4519,7298,7299,4520,4521,4934,7300,4246, # 6720 +4522,7301,7302,7303,3579,7304,4247,4935,7305,4936,7306,7307,7308,7309,3777,7310, # 6736 +4523,7311,7312,7313,4248,3580,7314,4524,3778,4249,7315,3581,7316,3287,7317,3221, # 6752 +7318,4937,7319,7320,7321,7322,7323,7324,4938,4939,7325,4525,7326,7327,7328,4063, # 6768 +7329,7330,4940,7331,7332,4941,7333,4526,7334,3500,2780,1741,4942,2026,1742,7335, # 6784 +7336,3582,4527,2388,7337,7338,7339,4528,7340,4250,4943,7341,7342,7343,4944,7344, # 6800 +7345,7346,3020,7347,4945,7348,7349,7350,7351,3895,7352,3896,4064,3897,7353,7354, # 6816 +7355,4251,7356,7357,3898,7358,3779,7359,3780,3288,7360,7361,4529,7362,4946,4530, # 6832 +2027,7363,3899,4531,4947,3222,3583,7364,4948,7365,7366,7367,7368,4949,3501,4950, # 6848 +3781,4951,4532,7369,2517,4952,4252,4953,3155,7370,4954,4955,4253,2518,4533,7371, # 6864 +7372,2712,4254,7373,7374,7375,3670,4956,3671,7376,2389,3502,4065,7377,2338,7378, # 6880 +7379,7380,7381,3061,7382,4957,7383,7384,7385,7386,4958,4534,7387,7388,2993,7389, # 6896 +3062,7390,4959,7391,7392,7393,4960,3108,4961,7394,4535,7395,4962,3421,4536,7396, # 6912 +4963,7397,4964,1857,7398,4965,7399,7400,2176,3584,4966,7401,7402,3422,4537,3900, # 6928 +3585,7403,3782,7404,2852,7405,7406,7407,4538,3783,2654,3423,4967,4539,7408,3784, # 6944 +3586,2853,4540,4541,7409,3901,7410,3902,7411,7412,3785,3109,2327,3903,7413,7414, # 6960 +2970,4066,2932,7415,7416,7417,3904,3672,3424,7418,4542,4543,4544,7419,4968,7420, # 6976 +7421,4255,7422,7423,7424,7425,7426,4067,7427,3673,3365,4545,7428,3110,2559,3674, # 6992 +7429,7430,3156,7431,7432,3503,7433,3425,4546,7434,3063,2873,7435,3223,4969,4547, # 7008 +4548,2898,4256,4068,7436,4069,3587,3786,2933,3787,4257,4970,4971,3788,7437,4972, # 7024 +3064,7438,4549,7439,7440,7441,7442,7443,4973,3905,7444,2874,7445,7446,7447,7448, # 7040 +3021,7449,4550,3906,3588,4974,7450,7451,3789,3675,7452,2578,7453,4070,7454,7455, # 7056 +7456,4258,3676,7457,4975,7458,4976,4259,3790,3504,2634,4977,3677,4551,4260,7459, # 7072 +7460,7461,7462,3907,4261,4978,7463,7464,7465,7466,4979,4980,7467,7468,2213,4262, # 7088 +7469,7470,7471,3678,4981,7472,2439,7473,4263,3224,3289,7474,3908,2415,4982,7475, # 7104 +4264,7476,4983,2655,7477,7478,2732,4552,2854,2875,7479,7480,4265,7481,4553,4984, # 7120 +7482,7483,4266,7484,3679,3366,3680,2818,2781,2782,3367,3589,4554,3065,7485,4071, # 7136 +2899,7486,7487,3157,2462,4072,4555,4073,4985,4986,3111,4267,2687,3368,4556,4074, # 7152 +3791,4268,7488,3909,2783,7489,2656,1962,3158,4557,4987,1963,3159,3160,7490,3112, # 7168 +4988,4989,3022,4990,4991,3792,2855,7491,7492,2971,4558,7493,7494,4992,7495,7496, # 7184 +7497,7498,4993,7499,3426,4559,4994,7500,3681,4560,4269,4270,3910,7501,4075,4995, # 7200 +4271,7502,7503,4076,7504,4996,7505,3225,4997,4272,4077,2819,3023,7506,7507,2733, # 7216 +4561,7508,4562,7509,3369,3793,7510,3590,2508,7511,7512,4273,3113,2994,2616,7513, # 7232 +7514,7515,7516,7517,7518,2820,3911,4078,2748,7519,7520,4563,4998,7521,7522,7523, # 7248 +7524,4999,4274,7525,4564,3682,2239,4079,4565,7526,7527,7528,7529,5000,7530,7531, # 7264 +5001,4275,3794,7532,7533,7534,3066,5002,4566,3161,7535,7536,4080,7537,3162,7538, # 7280 +7539,4567,7540,7541,7542,7543,7544,7545,5003,7546,4568,7547,7548,7549,7550,7551, # 7296 +7552,7553,7554,7555,7556,5004,7557,7558,7559,5005,7560,3795,7561,4569,7562,7563, # 7312 +7564,2821,3796,4276,4277,4081,7565,2876,7566,5006,7567,7568,2900,7569,3797,3912, # 7328 +7570,7571,7572,4278,7573,7574,7575,5007,7576,7577,5008,7578,7579,4279,2934,7580, # 7344 +7581,5009,7582,4570,7583,4280,7584,7585,7586,4571,4572,3913,7587,4573,3505,7588, # 7360 +5010,7589,7590,7591,7592,3798,4574,7593,7594,5011,7595,4281,7596,7597,7598,4282, # 7376 +5012,7599,7600,5013,3163,7601,5014,7602,3914,7603,7604,2734,4575,4576,4577,7605, # 7392 +7606,7607,7608,7609,3506,5015,4578,7610,4082,7611,2822,2901,2579,3683,3024,4579, # 7408 +3507,7612,4580,7613,3226,3799,5016,7614,7615,7616,7617,7618,7619,7620,2995,3290, # 7424 +7621,4083,7622,5017,7623,7624,7625,7626,7627,4581,3915,7628,3291,7629,5018,7630, # 7440 +7631,7632,7633,4084,7634,7635,3427,3800,7636,7637,4582,7638,5019,4583,5020,7639, # 7456 +3916,7640,3801,5021,4584,4283,7641,7642,3428,3591,2269,7643,2617,7644,4585,3592, # 7472 +7645,4586,2902,7646,7647,3227,5022,7648,4587,7649,4284,7650,7651,7652,4588,2284, # 7488 +7653,5023,7654,7655,7656,4589,5024,3802,7657,7658,5025,3508,4590,7659,7660,7661, # 7504 +1969,5026,7662,7663,3684,1821,2688,7664,2028,2509,4285,7665,2823,1841,7666,2689, # 7520 +3114,7667,3917,4085,2160,5027,5028,2972,7668,5029,7669,7670,7671,3593,4086,7672, # 7536 +4591,4087,5030,3803,7673,7674,7675,7676,7677,7678,7679,4286,2366,4592,4593,3067, # 7552 +2328,7680,7681,4594,3594,3918,2029,4287,7682,5031,3919,3370,4288,4595,2856,7683, # 7568 +3509,7684,7685,5032,5033,7686,7687,3804,2784,7688,7689,7690,7691,3371,7692,7693, # 7584 +2877,5034,7694,7695,3920,4289,4088,7696,7697,7698,5035,7699,5036,4290,5037,5038, # 7600 +5039,7700,7701,7702,5040,5041,3228,7703,1760,7704,5042,3229,4596,2106,4089,7705, # 7616 +4597,2824,5043,2107,3372,7706,4291,4090,5044,7707,4091,7708,5045,3025,3805,4598, # 7632 +4292,4293,4294,3373,7709,4599,7710,5046,7711,7712,5047,5048,3806,7713,7714,7715, # 7648 +5049,7716,7717,7718,7719,4600,5050,7720,7721,7722,5051,7723,4295,3429,7724,7725, # 7664 +7726,7727,3921,7728,3292,5052,4092,7729,7730,7731,7732,7733,7734,7735,5053,5054, # 7680 +7736,7737,7738,7739,3922,3685,7740,7741,7742,7743,2635,5055,7744,5056,4601,7745, # 7696 +7746,2560,7747,7748,7749,7750,3923,7751,7752,7753,7754,7755,4296,2903,7756,7757, # 7712 +7758,7759,7760,3924,7761,5057,4297,7762,7763,5058,4298,7764,4093,7765,7766,5059, # 7728 +3925,7767,7768,7769,7770,7771,7772,7773,7774,7775,7776,3595,7777,4299,5060,4094, # 7744 +7778,3293,5061,7779,7780,4300,7781,7782,4602,7783,3596,7784,7785,3430,2367,7786, # 7760 +3164,5062,5063,4301,7787,7788,4095,5064,5065,7789,3374,3115,7790,7791,7792,7793, # 7776 +7794,7795,7796,3597,4603,7797,7798,3686,3116,3807,5066,7799,7800,5067,7801,7802, # 7792 +4604,4302,5068,4303,4096,7803,7804,3294,7805,7806,5069,4605,2690,7807,3026,7808, # 7808 +7809,7810,7811,7812,7813,7814,7815,7816,7817,7818,7819,7820,7821,7822,7823,7824, # 7824 +7825,7826,7827,7828,7829,7830,7831,7832,7833,7834,7835,7836,7837,7838,7839,7840, # 7840 +7841,7842,7843,7844,7845,7846,7847,7848,7849,7850,7851,7852,7853,7854,7855,7856, # 7856 +7857,7858,7859,7860,7861,7862,7863,7864,7865,7866,7867,7868,7869,7870,7871,7872, # 7872 +7873,7874,7875,7876,7877,7878,7879,7880,7881,7882,7883,7884,7885,7886,7887,7888, # 7888 +7889,7890,7891,7892,7893,7894,7895,7896,7897,7898,7899,7900,7901,7902,7903,7904, # 7904 +7905,7906,7907,7908,7909,7910,7911,7912,7913,7914,7915,7916,7917,7918,7919,7920, # 7920 +7921,7922,7923,7924,3926,7925,7926,7927,7928,7929,7930,7931,7932,7933,7934,7935, # 7936 +7936,7937,7938,7939,7940,7941,7942,7943,7944,7945,7946,7947,7948,7949,7950,7951, # 7952 +7952,7953,7954,7955,7956,7957,7958,7959,7960,7961,7962,7963,7964,7965,7966,7967, # 7968 +7968,7969,7970,7971,7972,7973,7974,7975,7976,7977,7978,7979,7980,7981,7982,7983, # 7984 +7984,7985,7986,7987,7988,7989,7990,7991,7992,7993,7994,7995,7996,7997,7998,7999, # 8000 +8000,8001,8002,8003,8004,8005,8006,8007,8008,8009,8010,8011,8012,8013,8014,8015, # 8016 +8016,8017,8018,8019,8020,8021,8022,8023,8024,8025,8026,8027,8028,8029,8030,8031, # 8032 +8032,8033,8034,8035,8036,8037,8038,8039,8040,8041,8042,8043,8044,8045,8046,8047, # 8048 +8048,8049,8050,8051,8052,8053,8054,8055,8056,8057,8058,8059,8060,8061,8062,8063, # 8064 +8064,8065,8066,8067,8068,8069,8070,8071,8072,8073,8074,8075,8076,8077,8078,8079, # 8080 +8080,8081,8082,8083,8084,8085,8086,8087,8088,8089,8090,8091,8092,8093,8094,8095, # 8096 +8096,8097,8098,8099,8100,8101,8102,8103,8104,8105,8106,8107,8108,8109,8110,8111, # 8112 +8112,8113,8114,8115,8116,8117,8118,8119,8120,8121,8122,8123,8124,8125,8126,8127, # 8128 +8128,8129,8130,8131,8132,8133,8134,8135,8136,8137,8138,8139,8140,8141,8142,8143, # 8144 +8144,8145,8146,8147,8148,8149,8150,8151,8152,8153,8154,8155,8156,8157,8158,8159, # 8160 +8160,8161,8162,8163,8164,8165,8166,8167,8168,8169,8170,8171,8172,8173,8174,8175, # 8176 +8176,8177,8178,8179,8180,8181,8182,8183,8184,8185,8186,8187,8188,8189,8190,8191, # 8192 +8192,8193,8194,8195,8196,8197,8198,8199,8200,8201,8202,8203,8204,8205,8206,8207, # 8208 +8208,8209,8210,8211,8212,8213,8214,8215,8216,8217,8218,8219,8220,8221,8222,8223, # 8224 +8224,8225,8226,8227,8228,8229,8230,8231,8232,8233,8234,8235,8236,8237,8238,8239, # 8240 +8240,8241,8242,8243,8244,8245,8246,8247,8248,8249,8250,8251,8252,8253,8254,8255, # 8256 +8256,8257,8258,8259,8260,8261,8262,8263,8264,8265,8266,8267,8268,8269,8270,8271) # 8272 + +# flake8: noqa diff --git a/resources/lib/libraries/requests/packages/chardet/jpcntx.py b/resources/lib/libraries/requests/packages/chardet/jpcntx.py new file mode 100644 index 00000000..59aeb6a8 --- /dev/null +++ b/resources/lib/libraries/requests/packages/chardet/jpcntx.py @@ -0,0 +1,227 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is Mozilla Communicator client code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +from .compat import wrap_ord + +NUM_OF_CATEGORY = 6 +DONT_KNOW = -1 +ENOUGH_REL_THRESHOLD = 100 +MAX_REL_THRESHOLD = 1000 +MINIMUM_DATA_THRESHOLD = 4 + +# This is hiragana 2-char sequence table, the number in each cell represents its frequency category +jp2CharContext = ( +(0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1), +(2,4,0,4,0,3,0,4,0,3,4,4,4,2,4,3,3,4,3,2,3,3,4,2,3,3,3,2,4,1,4,3,3,1,5,4,3,4,3,4,3,5,3,0,3,5,4,2,0,3,1,0,3,3,0,3,3,0,1,1,0,4,3,0,3,3,0,4,0,2,0,3,5,5,5,5,4,0,4,1,0,3,4), +(0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2), +(0,4,0,5,0,5,0,4,0,4,5,4,4,3,5,3,5,1,5,3,4,3,4,4,3,4,3,3,4,3,5,4,4,3,5,5,3,5,5,5,3,5,5,3,4,5,5,3,1,3,2,0,3,4,0,4,2,0,4,2,1,5,3,2,3,5,0,4,0,2,0,5,4,4,5,4,5,0,4,0,0,4,4), +(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0), +(0,3,0,4,0,3,0,3,0,4,5,4,3,3,3,3,4,3,5,4,4,3,5,4,4,3,4,3,4,4,4,4,5,3,4,4,3,4,5,5,4,5,5,1,4,5,4,3,0,3,3,1,3,3,0,4,4,0,3,3,1,5,3,3,3,5,0,4,0,3,0,4,4,3,4,3,3,0,4,1,1,3,4), +(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0), +(0,4,0,3,0,3,0,4,0,3,4,4,3,2,2,1,2,1,3,1,3,3,3,3,3,4,3,1,3,3,5,3,3,0,4,3,0,5,4,3,3,5,4,4,3,4,4,5,0,1,2,0,1,2,0,2,2,0,1,0,0,5,2,2,1,4,0,3,0,1,0,4,4,3,5,4,3,0,2,1,0,4,3), +(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0), +(0,3,0,5,0,4,0,2,1,4,4,2,4,1,4,2,4,2,4,3,3,3,4,3,3,3,3,1,4,2,3,3,3,1,4,4,1,1,1,4,3,3,2,0,2,4,3,2,0,3,3,0,3,1,1,0,0,0,3,3,0,4,2,2,3,4,0,4,0,3,0,4,4,5,3,4,4,0,3,0,0,1,4), +(1,4,0,4,0,4,0,4,0,3,5,4,4,3,4,3,5,4,3,3,4,3,5,4,4,4,4,3,4,2,4,3,3,1,5,4,3,2,4,5,4,5,5,4,4,5,4,4,0,3,2,2,3,3,0,4,3,1,3,2,1,4,3,3,4,5,0,3,0,2,0,4,5,5,4,5,4,0,4,0,0,5,4), +(0,5,0,5,0,4,0,3,0,4,4,3,4,3,3,3,4,0,4,4,4,3,4,3,4,3,3,1,4,2,4,3,4,0,5,4,1,4,5,4,4,5,3,2,4,3,4,3,2,4,1,3,3,3,2,3,2,0,4,3,3,4,3,3,3,4,0,4,0,3,0,4,5,4,4,4,3,0,4,1,0,1,3), +(0,3,1,4,0,3,0,2,0,3,4,4,3,1,4,2,3,3,4,3,4,3,4,3,4,4,3,2,3,1,5,4,4,1,4,4,3,5,4,4,3,5,5,4,3,4,4,3,1,2,3,1,2,2,0,3,2,0,3,1,0,5,3,3,3,4,3,3,3,3,4,4,4,4,5,4,2,0,3,3,2,4,3), +(0,2,0,3,0,1,0,1,0,0,3,2,0,0,2,0,1,0,2,1,3,3,3,1,2,3,1,0,1,0,4,2,1,1,3,3,0,4,3,3,1,4,3,3,0,3,3,2,0,0,0,0,1,0,0,2,0,0,0,0,0,4,1,0,2,3,2,2,2,1,3,3,3,4,4,3,2,0,3,1,0,3,3), +(0,4,0,4,0,3,0,3,0,4,4,4,3,3,3,3,3,3,4,3,4,2,4,3,4,3,3,2,4,3,4,5,4,1,4,5,3,5,4,5,3,5,4,0,3,5,5,3,1,3,3,2,2,3,0,3,4,1,3,3,2,4,3,3,3,4,0,4,0,3,0,4,5,4,4,5,3,0,4,1,0,3,4), +(0,2,0,3,0,3,0,0,0,2,2,2,1,0,1,0,0,0,3,0,3,0,3,0,1,3,1,0,3,1,3,3,3,1,3,3,3,0,1,3,1,3,4,0,0,3,1,1,0,3,2,0,0,0,0,1,3,0,1,0,0,3,3,2,0,3,0,0,0,0,0,3,4,3,4,3,3,0,3,0,0,2,3), +(2,3,0,3,0,2,0,1,0,3,3,4,3,1,3,1,1,1,3,1,4,3,4,3,3,3,0,0,3,1,5,4,3,1,4,3,2,5,5,4,4,4,4,3,3,4,4,4,0,2,1,1,3,2,0,1,2,0,0,1,0,4,1,3,3,3,0,3,0,1,0,4,4,4,5,5,3,0,2,0,0,4,4), +(0,2,0,1,0,3,1,3,0,2,3,3,3,0,3,1,0,0,3,0,3,2,3,1,3,2,1,1,0,0,4,2,1,0,2,3,1,4,3,2,0,4,4,3,1,3,1,3,0,1,0,0,1,0,0,0,1,0,0,0,0,4,1,1,1,2,0,3,0,0,0,3,4,2,4,3,2,0,1,0,0,3,3), +(0,1,0,4,0,5,0,4,0,2,4,4,2,3,3,2,3,3,5,3,3,3,4,3,4,2,3,0,4,3,3,3,4,1,4,3,2,1,5,5,3,4,5,1,3,5,4,2,0,3,3,0,1,3,0,4,2,0,1,3,1,4,3,3,3,3,0,3,0,1,0,3,4,4,4,5,5,0,3,0,1,4,5), +(0,2,0,3,0,3,0,0,0,2,3,1,3,0,4,0,1,1,3,0,3,4,3,2,3,1,0,3,3,2,3,1,3,0,2,3,0,2,1,4,1,2,2,0,0,3,3,0,0,2,0,0,0,1,0,0,0,0,2,2,0,3,2,1,3,3,0,2,0,2,0,0,3,3,1,2,4,0,3,0,2,2,3), +(2,4,0,5,0,4,0,4,0,2,4,4,4,3,4,3,3,3,1,2,4,3,4,3,4,4,5,0,3,3,3,3,2,0,4,3,1,4,3,4,1,4,4,3,3,4,4,3,1,2,3,0,4,2,0,4,1,0,3,3,0,4,3,3,3,4,0,4,0,2,0,3,5,3,4,5,2,0,3,0,0,4,5), +(0,3,0,4,0,1,0,1,0,1,3,2,2,1,3,0,3,0,2,0,2,0,3,0,2,0,0,0,1,0,1,1,0,0,3,1,0,0,0,4,0,3,1,0,2,1,3,0,0,0,0,0,0,3,0,0,0,0,0,0,0,4,2,2,3,1,0,3,0,0,0,1,4,4,4,3,0,0,4,0,0,1,4), +(1,4,1,5,0,3,0,3,0,4,5,4,4,3,5,3,3,4,4,3,4,1,3,3,3,3,2,1,4,1,5,4,3,1,4,4,3,5,4,4,3,5,4,3,3,4,4,4,0,3,3,1,2,3,0,3,1,0,3,3,0,5,4,4,4,4,4,4,3,3,5,4,4,3,3,5,4,0,3,2,0,4,4), +(0,2,0,3,0,1,0,0,0,1,3,3,3,2,4,1,3,0,3,1,3,0,2,2,1,1,0,0,2,0,4,3,1,0,4,3,0,4,4,4,1,4,3,1,1,3,3,1,0,2,0,0,1,3,0,0,0,0,2,0,0,4,3,2,4,3,5,4,3,3,3,4,3,3,4,3,3,0,2,1,0,3,3), +(0,2,0,4,0,3,0,2,0,2,5,5,3,4,4,4,4,1,4,3,3,0,4,3,4,3,1,3,3,2,4,3,0,3,4,3,0,3,4,4,2,4,4,0,4,5,3,3,2,2,1,1,1,2,0,1,5,0,3,3,2,4,3,3,3,4,0,3,0,2,0,4,4,3,5,5,0,0,3,0,2,3,3), +(0,3,0,4,0,3,0,1,0,3,4,3,3,1,3,3,3,0,3,1,3,0,4,3,3,1,1,0,3,0,3,3,0,0,4,4,0,1,5,4,3,3,5,0,3,3,4,3,0,2,0,1,1,1,0,1,3,0,1,2,1,3,3,2,3,3,0,3,0,1,0,1,3,3,4,4,1,0,1,2,2,1,3), +(0,1,0,4,0,4,0,3,0,1,3,3,3,2,3,1,1,0,3,0,3,3,4,3,2,4,2,0,1,0,4,3,2,0,4,3,0,5,3,3,2,4,4,4,3,3,3,4,0,1,3,0,0,1,0,0,1,0,0,0,0,4,2,3,3,3,0,3,0,0,0,4,4,4,5,3,2,0,3,3,0,3,5), +(0,2,0,3,0,0,0,3,0,1,3,0,2,0,0,0,1,0,3,1,1,3,3,0,0,3,0,0,3,0,2,3,1,0,3,1,0,3,3,2,0,4,2,2,0,2,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,2,1,2,0,1,0,1,0,0,0,1,3,1,2,0,0,0,1,0,0,1,4), +(0,3,0,3,0,5,0,1,0,2,4,3,1,3,3,2,1,1,5,2,1,0,5,1,2,0,0,0,3,3,2,2,3,2,4,3,0,0,3,3,1,3,3,0,2,5,3,4,0,3,3,0,1,2,0,2,2,0,3,2,0,2,2,3,3,3,0,2,0,1,0,3,4,4,2,5,4,0,3,0,0,3,5), +(0,3,0,3,0,3,0,1,0,3,3,3,3,0,3,0,2,0,2,1,1,0,2,0,1,0,0,0,2,1,0,0,1,0,3,2,0,0,3,3,1,2,3,1,0,3,3,0,0,1,0,0,0,0,0,2,0,0,0,0,0,2,3,1,2,3,0,3,0,1,0,3,2,1,0,4,3,0,1,1,0,3,3), +(0,4,0,5,0,3,0,3,0,4,5,5,4,3,5,3,4,3,5,3,3,2,5,3,4,4,4,3,4,3,4,5,5,3,4,4,3,4,4,5,4,4,4,3,4,5,5,4,2,3,4,2,3,4,0,3,3,1,4,3,2,4,3,3,5,5,0,3,0,3,0,5,5,5,5,4,4,0,4,0,1,4,4), +(0,4,0,4,0,3,0,3,0,3,5,4,4,2,3,2,5,1,3,2,5,1,4,2,3,2,3,3,4,3,3,3,3,2,5,4,1,3,3,5,3,4,4,0,4,4,3,1,1,3,1,0,2,3,0,2,3,0,3,0,0,4,3,1,3,4,0,3,0,2,0,4,4,4,3,4,5,0,4,0,0,3,4), +(0,3,0,3,0,3,1,2,0,3,4,4,3,3,3,0,2,2,4,3,3,1,3,3,3,1,1,0,3,1,4,3,2,3,4,4,2,4,4,4,3,4,4,3,2,4,4,3,1,3,3,1,3,3,0,4,1,0,2,2,1,4,3,2,3,3,5,4,3,3,5,4,4,3,3,0,4,0,3,2,2,4,4), +(0,2,0,1,0,0,0,0,0,1,2,1,3,0,0,0,0,0,2,0,1,2,1,0,0,1,0,0,0,0,3,0,0,1,0,1,1,3,1,0,0,0,1,1,0,1,1,0,0,0,0,0,2,0,0,0,0,0,0,0,0,1,1,2,2,0,3,4,0,0,0,1,1,0,0,1,0,0,0,0,0,1,1), +(0,1,0,0,0,1,0,0,0,0,4,0,4,1,4,0,3,0,4,0,3,0,4,0,3,0,3,0,4,1,5,1,4,0,0,3,0,5,0,5,2,0,1,0,0,0,2,1,4,0,1,3,0,0,3,0,0,3,1,1,4,1,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0), +(1,4,0,5,0,3,0,2,0,3,5,4,4,3,4,3,5,3,4,3,3,0,4,3,3,3,3,3,3,2,4,4,3,1,3,4,4,5,4,4,3,4,4,1,3,5,4,3,3,3,1,2,2,3,3,1,3,1,3,3,3,5,3,3,4,5,0,3,0,3,0,3,4,3,4,4,3,0,3,0,2,4,3), +(0,1,0,4,0,0,0,0,0,1,4,0,4,1,4,2,4,0,3,0,1,0,1,0,0,0,0,0,2,0,3,1,1,1,0,3,0,0,0,1,2,1,0,0,1,1,1,1,0,1,0,0,0,1,0,0,3,0,0,0,0,3,2,0,2,2,0,1,0,0,0,2,3,2,3,3,0,0,0,0,2,1,0), +(0,5,1,5,0,3,0,3,0,5,4,4,5,1,5,3,3,0,4,3,4,3,5,3,4,3,3,2,4,3,4,3,3,0,3,3,1,4,4,3,4,4,4,3,4,5,5,3,2,3,1,1,3,3,1,3,1,1,3,3,2,4,5,3,3,5,0,4,0,3,0,4,4,3,5,3,3,0,3,4,0,4,3), +(0,5,0,5,0,3,0,2,0,4,4,3,5,2,4,3,3,3,4,4,4,3,5,3,5,3,3,1,4,0,4,3,3,0,3,3,0,4,4,4,4,5,4,3,3,5,5,3,2,3,1,2,3,2,0,1,0,0,3,2,2,4,4,3,1,5,0,4,0,3,0,4,3,1,3,2,1,0,3,3,0,3,3), +(0,4,0,5,0,5,0,4,0,4,5,5,5,3,4,3,3,2,5,4,4,3,5,3,5,3,4,0,4,3,4,4,3,2,4,4,3,4,5,4,4,5,5,0,3,5,5,4,1,3,3,2,3,3,1,3,1,0,4,3,1,4,4,3,4,5,0,4,0,2,0,4,3,4,4,3,3,0,4,0,0,5,5), +(0,4,0,4,0,5,0,1,1,3,3,4,4,3,4,1,3,0,5,1,3,0,3,1,3,1,1,0,3,0,3,3,4,0,4,3,0,4,4,4,3,4,4,0,3,5,4,1,0,3,0,0,2,3,0,3,1,0,3,1,0,3,2,1,3,5,0,3,0,1,0,3,2,3,3,4,4,0,2,2,0,4,4), +(2,4,0,5,0,4,0,3,0,4,5,5,4,3,5,3,5,3,5,3,5,2,5,3,4,3,3,4,3,4,5,3,2,1,5,4,3,2,3,4,5,3,4,1,2,5,4,3,0,3,3,0,3,2,0,2,3,0,4,1,0,3,4,3,3,5,0,3,0,1,0,4,5,5,5,4,3,0,4,2,0,3,5), +(0,5,0,4,0,4,0,2,0,5,4,3,4,3,4,3,3,3,4,3,4,2,5,3,5,3,4,1,4,3,4,4,4,0,3,5,0,4,4,4,4,5,3,1,3,4,5,3,3,3,3,3,3,3,0,2,2,0,3,3,2,4,3,3,3,5,3,4,1,3,3,5,3,2,0,0,0,0,4,3,1,3,3), +(0,1,0,3,0,3,0,1,0,1,3,3,3,2,3,3,3,0,3,0,0,0,3,1,3,0,0,0,2,2,2,3,0,0,3,2,0,1,2,4,1,3,3,0,0,3,3,3,0,1,0,0,2,1,0,0,3,0,3,1,0,3,0,0,1,3,0,2,0,1,0,3,3,1,3,3,0,0,1,1,0,3,3), +(0,2,0,3,0,2,1,4,0,2,2,3,1,1,3,1,1,0,2,0,3,1,2,3,1,3,0,0,1,0,4,3,2,3,3,3,1,4,2,3,3,3,3,1,0,3,1,4,0,1,1,0,1,2,0,1,1,0,1,1,0,3,1,3,2,2,0,1,0,0,0,2,3,3,3,1,0,0,0,0,0,2,3), +(0,5,0,4,0,5,0,2,0,4,5,5,3,3,4,3,3,1,5,4,4,2,4,4,4,3,4,2,4,3,5,5,4,3,3,4,3,3,5,5,4,5,5,1,3,4,5,3,1,4,3,1,3,3,0,3,3,1,4,3,1,4,5,3,3,5,0,4,0,3,0,5,3,3,1,4,3,0,4,0,1,5,3), +(0,5,0,5,0,4,0,2,0,4,4,3,4,3,3,3,3,3,5,4,4,4,4,4,4,5,3,3,5,2,4,4,4,3,4,4,3,3,4,4,5,5,3,3,4,3,4,3,3,4,3,3,3,3,1,2,2,1,4,3,3,5,4,4,3,4,0,4,0,3,0,4,4,4,4,4,1,0,4,2,0,2,4), +(0,4,0,4,0,3,0,1,0,3,5,2,3,0,3,0,2,1,4,2,3,3,4,1,4,3,3,2,4,1,3,3,3,0,3,3,0,0,3,3,3,5,3,3,3,3,3,2,0,2,0,0,2,0,0,2,0,0,1,0,0,3,1,2,2,3,0,3,0,2,0,4,4,3,3,4,1,0,3,0,0,2,4), +(0,0,0,4,0,0,0,0,0,0,1,0,1,0,2,0,0,0,0,0,1,0,2,0,1,0,0,0,0,0,3,1,3,0,3,2,0,0,0,1,0,3,2,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,4,0,2,0,0,0,0,0,0,2), +(0,2,1,3,0,2,0,2,0,3,3,3,3,1,3,1,3,3,3,3,3,3,4,2,2,1,2,1,4,0,4,3,1,3,3,3,2,4,3,5,4,3,3,3,3,3,3,3,0,1,3,0,2,0,0,1,0,0,1,0,0,4,2,0,2,3,0,3,3,0,3,3,4,2,3,1,4,0,1,2,0,2,3), +(0,3,0,3,0,1,0,3,0,2,3,3,3,0,3,1,2,0,3,3,2,3,3,2,3,2,3,1,3,0,4,3,2,0,3,3,1,4,3,3,2,3,4,3,1,3,3,1,1,0,1,1,0,1,0,1,0,1,0,0,0,4,1,1,0,3,0,3,1,0,2,3,3,3,3,3,1,0,0,2,0,3,3), +(0,0,0,0,0,0,0,0,0,0,3,0,2,0,3,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,3,0,3,0,3,1,0,1,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,2,0,2,3,0,0,0,0,0,0,0,0,3), +(0,2,0,3,1,3,0,3,0,2,3,3,3,1,3,1,3,1,3,1,3,3,3,1,3,0,2,3,1,1,4,3,3,2,3,3,1,2,2,4,1,3,3,0,1,4,2,3,0,1,3,0,3,0,0,1,3,0,2,0,0,3,3,2,1,3,0,3,0,2,0,3,4,4,4,3,1,0,3,0,0,3,3), +(0,2,0,1,0,2,0,0,0,1,3,2,2,1,3,0,1,1,3,0,3,2,3,1,2,0,2,0,1,1,3,3,3,0,3,3,1,1,2,3,2,3,3,1,2,3,2,0,0,1,0,0,0,0,0,0,3,0,1,0,0,2,1,2,1,3,0,3,0,0,0,3,4,4,4,3,2,0,2,0,0,2,4), +(0,0,0,1,0,1,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,2,2,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1,3,1,0,0,0,0,0,0,0,3), +(0,3,0,3,0,2,0,3,0,3,3,3,2,3,2,2,2,0,3,1,3,3,3,2,3,3,0,0,3,0,3,2,2,0,2,3,1,4,3,4,3,3,2,3,1,5,4,4,0,3,1,2,1,3,0,3,1,1,2,0,2,3,1,3,1,3,0,3,0,1,0,3,3,4,4,2,1,0,2,1,0,2,4), +(0,1,0,3,0,1,0,2,0,1,4,2,5,1,4,0,2,0,2,1,3,1,4,0,2,1,0,0,2,1,4,1,1,0,3,3,0,5,1,3,2,3,3,1,0,3,2,3,0,1,0,0,0,0,0,0,1,0,0,0,0,4,0,1,0,3,0,2,0,1,0,3,3,3,4,3,3,0,0,0,0,2,3), +(0,0,0,1,0,0,0,0,0,0,2,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,1,0,0,1,0,0,0,0,0,3), +(0,1,0,3,0,4,0,3,0,2,4,3,1,0,3,2,2,1,3,1,2,2,3,1,1,1,2,1,3,0,1,2,0,1,3,2,1,3,0,5,5,1,0,0,1,3,2,1,0,3,0,0,1,0,0,0,0,0,3,4,0,1,1,1,3,2,0,2,0,1,0,2,3,3,1,2,3,0,1,0,1,0,4), +(0,0,0,1,0,3,0,3,0,2,2,1,0,0,4,0,3,0,3,1,3,0,3,0,3,0,1,0,3,0,3,1,3,0,3,3,0,0,1,2,1,1,1,0,1,2,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,2,2,1,2,0,0,2,0,0,0,0,2,3,3,3,3,0,0,0,0,1,4), +(0,0,0,3,0,3,0,0,0,0,3,1,1,0,3,0,1,0,2,0,1,0,0,0,0,0,0,0,1,0,3,0,2,0,2,3,0,0,2,2,3,1,2,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,2,0,0,0,0,2,3), +(2,4,0,5,0,5,0,4,0,3,4,3,3,3,4,3,3,3,4,3,4,4,5,4,5,5,5,2,3,0,5,5,4,1,5,4,3,1,5,4,3,4,4,3,3,4,3,3,0,3,2,0,2,3,0,3,0,0,3,3,0,5,3,2,3,3,0,3,0,3,0,3,4,5,4,5,3,0,4,3,0,3,4), +(0,3,0,3,0,3,0,3,0,3,3,4,3,2,3,2,3,0,4,3,3,3,3,3,3,3,3,0,3,2,4,3,3,1,3,4,3,4,4,4,3,4,4,3,2,4,4,1,0,2,0,0,1,1,0,2,0,0,3,1,0,5,3,2,1,3,0,3,0,1,2,4,3,2,4,3,3,0,3,2,0,4,4), +(0,3,0,3,0,1,0,0,0,1,4,3,3,2,3,1,3,1,4,2,3,2,4,2,3,4,3,0,2,2,3,3,3,0,3,3,3,0,3,4,1,3,3,0,3,4,3,3,0,1,1,0,1,0,0,0,4,0,3,0,0,3,1,2,1,3,0,4,0,1,0,4,3,3,4,3,3,0,2,0,0,3,3), +(0,3,0,4,0,1,0,3,0,3,4,3,3,0,3,3,3,1,3,1,3,3,4,3,3,3,0,0,3,1,5,3,3,1,3,3,2,5,4,3,3,4,5,3,2,5,3,4,0,1,0,0,0,0,0,2,0,0,1,1,0,4,2,2,1,3,0,3,0,2,0,4,4,3,5,3,2,0,1,1,0,3,4), +(0,5,0,4,0,5,0,2,0,4,4,3,3,2,3,3,3,1,4,3,4,1,5,3,4,3,4,0,4,2,4,3,4,1,5,4,0,4,4,4,4,5,4,1,3,5,4,2,1,4,1,1,3,2,0,3,1,0,3,2,1,4,3,3,3,4,0,4,0,3,0,4,4,4,3,3,3,0,4,2,0,3,4), +(1,4,0,4,0,3,0,1,0,3,3,3,1,1,3,3,2,2,3,3,1,0,3,2,2,1,2,0,3,1,2,1,2,0,3,2,0,2,2,3,3,4,3,0,3,3,1,2,0,1,1,3,1,2,0,0,3,0,1,1,0,3,2,2,3,3,0,3,0,0,0,2,3,3,4,3,3,0,1,0,0,1,4), +(0,4,0,4,0,4,0,0,0,3,4,4,3,1,4,2,3,2,3,3,3,1,4,3,4,0,3,0,4,2,3,3,2,2,5,4,2,1,3,4,3,4,3,1,3,3,4,2,0,2,1,0,3,3,0,0,2,0,3,1,0,4,4,3,4,3,0,4,0,1,0,2,4,4,4,4,4,0,3,2,0,3,3), +(0,0,0,1,0,4,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,3,2,0,0,1,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,2), +(0,2,0,3,0,4,0,4,0,1,3,3,3,0,4,0,2,1,2,1,1,1,2,0,3,1,1,0,1,0,3,1,0,0,3,3,2,0,1,1,0,0,0,0,0,1,0,2,0,2,2,0,3,1,0,0,1,0,1,1,0,1,2,0,3,0,0,0,0,1,0,0,3,3,4,3,1,0,1,0,3,0,2), +(0,0,0,3,0,5,0,0,0,0,1,0,2,0,3,1,0,1,3,0,0,0,2,0,0,0,1,0,0,0,1,1,0,0,4,0,0,0,2,3,0,1,4,1,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,1,0,0,0,0,0,0,0,2,0,0,3,0,0,0,0,0,3), +(0,2,0,5,0,5,0,1,0,2,4,3,3,2,5,1,3,2,3,3,3,0,4,1,2,0,3,0,4,0,2,2,1,1,5,3,0,0,1,4,2,3,2,0,3,3,3,2,0,2,4,1,1,2,0,1,1,0,3,1,0,1,3,1,2,3,0,2,0,0,0,1,3,5,4,4,4,0,3,0,0,1,3), +(0,4,0,5,0,4,0,4,0,4,5,4,3,3,4,3,3,3,4,3,4,4,5,3,4,5,4,2,4,2,3,4,3,1,4,4,1,3,5,4,4,5,5,4,4,5,5,5,2,3,3,1,4,3,1,3,3,0,3,3,1,4,3,4,4,4,0,3,0,4,0,3,3,4,4,5,0,0,4,3,0,4,5), +(0,4,0,4,0,3,0,3,0,3,4,4,4,3,3,2,4,3,4,3,4,3,5,3,4,3,2,1,4,2,4,4,3,1,3,4,2,4,5,5,3,4,5,4,1,5,4,3,0,3,2,2,3,2,1,3,1,0,3,3,3,5,3,3,3,5,4,4,2,3,3,4,3,3,3,2,1,0,3,2,1,4,3), +(0,4,0,5,0,4,0,3,0,3,5,5,3,2,4,3,4,0,5,4,4,1,4,4,4,3,3,3,4,3,5,5,2,3,3,4,1,2,5,5,3,5,5,2,3,5,5,4,0,3,2,0,3,3,1,1,5,1,4,1,0,4,3,2,3,5,0,4,0,3,0,5,4,3,4,3,0,0,4,1,0,4,4), +(1,3,0,4,0,2,0,2,0,2,5,5,3,3,3,3,3,0,4,2,3,4,4,4,3,4,0,0,3,4,5,4,3,3,3,3,2,5,5,4,5,5,5,4,3,5,5,5,1,3,1,0,1,0,0,3,2,0,4,2,0,5,2,3,2,4,1,3,0,3,0,4,5,4,5,4,3,0,4,2,0,5,4), +(0,3,0,4,0,5,0,3,0,3,4,4,3,2,3,2,3,3,3,3,3,2,4,3,3,2,2,0,3,3,3,3,3,1,3,3,3,0,4,4,3,4,4,1,1,4,4,2,0,3,1,0,1,1,0,4,1,0,2,3,1,3,3,1,3,4,0,3,0,1,0,3,1,3,0,0,1,0,2,0,0,4,4), +(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0), +(0,3,0,3,0,2,0,3,0,1,5,4,3,3,3,1,4,2,1,2,3,4,4,2,4,4,5,0,3,1,4,3,4,0,4,3,3,3,2,3,2,5,3,4,3,2,2,3,0,0,3,0,2,1,0,1,2,0,0,0,0,2,1,1,3,1,0,2,0,4,0,3,4,4,4,5,2,0,2,0,0,1,3), +(0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,1,1,0,0,1,1,0,0,0,4,2,1,1,0,1,0,3,2,0,0,3,1,1,1,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,1,0,0,0,2,0,0,0,1,4,0,4,2,1,0,0,0,0,0,1), +(0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,1,0,0,0,0,0,0,1,0,1,0,0,0,0,3,1,0,0,0,2,0,2,1,0,0,1,2,1,0,1,1,0,0,3,0,0,0,0,0,0,0,0,0,0,0,1,3,1,0,0,0,0,0,1,0,0,2,1,0,0,0,0,0,0,0,0,2), +(0,4,0,4,0,4,0,3,0,4,4,3,4,2,4,3,2,0,4,4,4,3,5,3,5,3,3,2,4,2,4,3,4,3,1,4,0,2,3,4,4,4,3,3,3,4,4,4,3,4,1,3,4,3,2,1,2,1,3,3,3,4,4,3,3,5,0,4,0,3,0,4,3,3,3,2,1,0,3,0,0,3,3), +(0,4,0,3,0,3,0,3,0,3,5,5,3,3,3,3,4,3,4,3,3,3,4,4,4,3,3,3,3,4,3,5,3,3,1,3,2,4,5,5,5,5,4,3,4,5,5,3,2,2,3,3,3,3,2,3,3,1,2,3,2,4,3,3,3,4,0,4,0,2,0,4,3,2,2,1,2,0,3,0,0,4,1), +) + +class JapaneseContextAnalysis: + def __init__(self): + self.reset() + + def reset(self): + self._mTotalRel = 0 # total sequence received + # category counters, each interger counts sequence in its category + self._mRelSample = [0] * NUM_OF_CATEGORY + # if last byte in current buffer is not the last byte of a character, + # we need to know how many bytes to skip in next buffer + self._mNeedToSkipCharNum = 0 + self._mLastCharOrder = -1 # The order of previous char + # If this flag is set to True, detection is done and conclusion has + # been made + self._mDone = False + + def feed(self, aBuf, aLen): + if self._mDone: + return + + # The buffer we got is byte oriented, and a character may span in more than one + # buffers. In case the last one or two byte in last buffer is not + # complete, we record how many byte needed to complete that character + # and skip these bytes here. We can choose to record those bytes as + # well and analyse the character once it is complete, but since a + # character will not make much difference, by simply skipping + # this character will simply our logic and improve performance. + i = self._mNeedToSkipCharNum + while i < aLen: + order, charLen = self.get_order(aBuf[i:i + 2]) + i += charLen + if i > aLen: + self._mNeedToSkipCharNum = i - aLen + self._mLastCharOrder = -1 + else: + if (order != -1) and (self._mLastCharOrder != -1): + self._mTotalRel += 1 + if self._mTotalRel > MAX_REL_THRESHOLD: + self._mDone = True + break + self._mRelSample[jp2CharContext[self._mLastCharOrder][order]] += 1 + self._mLastCharOrder = order + + def got_enough_data(self): + return self._mTotalRel > ENOUGH_REL_THRESHOLD + + def get_confidence(self): + # This is just one way to calculate confidence. It works well for me. + if self._mTotalRel > MINIMUM_DATA_THRESHOLD: + return (self._mTotalRel - self._mRelSample[0]) / self._mTotalRel + else: + return DONT_KNOW + + def get_order(self, aBuf): + return -1, 1 + +class SJISContextAnalysis(JapaneseContextAnalysis): + def __init__(self): + self.charset_name = "SHIFT_JIS" + + def get_charset_name(self): + return self.charset_name + + def get_order(self, aBuf): + if not aBuf: + return -1, 1 + # find out current char's byte length + first_char = wrap_ord(aBuf[0]) + if ((0x81 <= first_char <= 0x9F) or (0xE0 <= first_char <= 0xFC)): + charLen = 2 + if (first_char == 0x87) or (0xFA <= first_char <= 0xFC): + self.charset_name = "CP932" + else: + charLen = 1 + + # return its order if it is hiragana + if len(aBuf) > 1: + second_char = wrap_ord(aBuf[1]) + if (first_char == 202) and (0x9F <= second_char <= 0xF1): + return second_char - 0x9F, charLen + + return -1, charLen + +class EUCJPContextAnalysis(JapaneseContextAnalysis): + def get_order(self, aBuf): + if not aBuf: + return -1, 1 + # find out current char's byte length + first_char = wrap_ord(aBuf[0]) + if (first_char == 0x8E) or (0xA1 <= first_char <= 0xFE): + charLen = 2 + elif first_char == 0x8F: + charLen = 3 + else: + charLen = 1 + + # return its order if it is hiragana + if len(aBuf) > 1: + second_char = wrap_ord(aBuf[1]) + if (first_char == 0xA4) and (0xA1 <= second_char <= 0xF3): + return second_char - 0xA1, charLen + + return -1, charLen + +# flake8: noqa diff --git a/resources/lib/libraries/requests/packages/chardet/langbulgarianmodel.py b/resources/lib/libraries/requests/packages/chardet/langbulgarianmodel.py new file mode 100644 index 00000000..e5788fc6 --- /dev/null +++ b/resources/lib/libraries/requests/packages/chardet/langbulgarianmodel.py @@ -0,0 +1,229 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is Mozilla Communicator client code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +# 255: Control characters that usually does not exist in any text +# 254: Carriage/Return +# 253: symbol (punctuation) that does not belong to word +# 252: 0 - 9 + +# Character Mapping Table: +# this table is modified base on win1251BulgarianCharToOrderMap, so +# only number <64 is sure valid + +Latin5_BulgarianCharToOrderMap = ( +255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 +253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 +252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 +253, 77, 90, 99,100, 72,109,107,101, 79,185, 81,102, 76, 94, 82, # 40 +110,186,108, 91, 74,119, 84, 96,111,187,115,253,253,253,253,253, # 50 +253, 65, 69, 70, 66, 63, 68,112,103, 92,194,104, 95, 86, 87, 71, # 60 +116,195, 85, 93, 97,113,196,197,198,199,200,253,253,253,253,253, # 70 +194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209, # 80 +210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225, # 90 + 81,226,227,228,229,230,105,231,232,233,234,235,236, 45,237,238, # a0 + 31, 32, 35, 43, 37, 44, 55, 47, 40, 59, 33, 46, 38, 36, 41, 30, # b0 + 39, 28, 34, 51, 48, 49, 53, 50, 54, 57, 61,239, 67,240, 60, 56, # c0 + 1, 18, 9, 20, 11, 3, 23, 15, 2, 26, 12, 10, 14, 6, 4, 13, # d0 + 7, 8, 5, 19, 29, 25, 22, 21, 27, 24, 17, 75, 52,241, 42, 16, # e0 + 62,242,243,244, 58,245, 98,246,247,248,249,250,251, 91,252,253, # f0 +) + +win1251BulgarianCharToOrderMap = ( +255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 +253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 +252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 +253, 77, 90, 99,100, 72,109,107,101, 79,185, 81,102, 76, 94, 82, # 40 +110,186,108, 91, 74,119, 84, 96,111,187,115,253,253,253,253,253, # 50 +253, 65, 69, 70, 66, 63, 68,112,103, 92,194,104, 95, 86, 87, 71, # 60 +116,195, 85, 93, 97,113,196,197,198,199,200,253,253,253,253,253, # 70 +206,207,208,209,210,211,212,213,120,214,215,216,217,218,219,220, # 80 +221, 78, 64, 83,121, 98,117,105,222,223,224,225,226,227,228,229, # 90 + 88,230,231,232,233,122, 89,106,234,235,236,237,238, 45,239,240, # a0 + 73, 80,118,114,241,242,243,244,245, 62, 58,246,247,248,249,250, # b0 + 31, 32, 35, 43, 37, 44, 55, 47, 40, 59, 33, 46, 38, 36, 41, 30, # c0 + 39, 28, 34, 51, 48, 49, 53, 50, 54, 57, 61,251, 67,252, 60, 56, # d0 + 1, 18, 9, 20, 11, 3, 23, 15, 2, 26, 12, 10, 14, 6, 4, 13, # e0 + 7, 8, 5, 19, 29, 25, 22, 21, 27, 24, 17, 75, 52,253, 42, 16, # f0 +) + +# Model Table: +# total sequences: 100% +# first 512 sequences: 96.9392% +# first 1024 sequences:3.0618% +# rest sequences: 0.2992% +# negative sequences: 0.0020% +BulgarianLangModel = ( +0,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,3,3,3,3,3,3,3,3,2,3,3,3,3,3, +3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,0,3,3,3,2,2,3,2,2,1,2,2, +3,1,3,3,2,3,3,3,3,3,3,3,3,3,3,3,3,0,3,3,3,3,3,3,3,3,3,3,0,3,0,1, +0,0,0,0,0,0,0,0,0,0,1,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, +3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,3,2,3,3,3,3,3,3,3,3,0,3,1,0, +0,1,0,0,0,0,0,0,0,0,1,1,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, +3,2,2,2,3,3,3,3,3,3,3,3,3,3,3,3,3,1,3,2,3,3,3,3,3,3,3,3,0,3,0,0, +0,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,2,3,3,2,3,3,3,3,3,3,3,3,3,3,3,3,1,3,2,3,3,3,3,3,3,3,3,0,3,0,0, +0,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,3,3,3,3,3,3,3,3,3,2,3,2,2,1,3,3,3,3,2,2,2,1,1,2,0,1,0,1,0,0, +0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,1, +3,3,3,3,3,3,3,2,3,2,2,3,3,1,1,2,3,3,2,3,3,3,3,2,1,2,0,2,0,3,0,0, +0,0,0,0,0,0,0,1,0,0,2,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,1, +3,3,3,3,3,3,3,1,3,3,3,3,3,2,3,2,3,3,3,3,3,2,3,3,1,3,0,3,0,2,0,0, +0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1, +3,3,3,3,3,3,3,3,1,3,3,2,3,3,3,1,3,3,2,3,2,2,2,0,0,2,0,2,0,2,0,0, +0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,1, +3,3,3,3,3,3,3,3,3,0,3,3,3,2,2,3,3,3,1,2,2,3,2,1,1,2,0,2,0,0,0,0, +1,0,0,0,0,0,0,0,0,0,2,0,0,1,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1, +3,3,3,3,3,3,3,2,3,3,1,2,3,2,2,2,3,3,3,3,3,2,2,3,1,2,0,2,1,2,0,0, +0,0,0,0,0,0,0,0,0,0,3,0,0,1,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,1, +3,3,3,3,3,1,3,3,3,3,3,2,3,3,3,2,3,3,2,3,2,2,2,3,1,2,0,1,0,1,0,0, +0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1, +3,3,3,3,3,3,3,3,3,3,3,1,1,1,2,2,1,3,1,3,2,2,3,0,0,1,0,1,0,1,0,0, +0,0,0,1,0,0,0,0,1,0,2,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1, +3,3,3,3,3,2,2,3,2,2,3,1,2,1,1,1,2,3,1,3,1,2,2,0,1,1,1,1,0,1,0,0, +0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1, +3,3,3,3,3,1,3,2,2,3,3,1,2,3,1,1,3,3,3,3,1,2,2,1,1,1,0,2,0,2,0,1, +0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1, +3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,1,2,2,3,3,3,2,2,1,1,2,0,2,0,1,0,0, +0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1, +3,0,1,2,1,3,3,2,3,3,3,3,3,2,3,2,1,0,3,1,2,1,2,1,2,3,2,1,0,1,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +1,1,1,2,3,3,3,3,3,3,3,3,3,3,3,3,0,0,3,1,3,3,2,3,3,2,2,2,0,1,0,0, +0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +2,3,3,3,3,0,3,3,3,3,3,2,1,1,2,1,3,3,0,3,1,1,1,1,3,2,0,1,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1, +3,3,2,2,2,3,3,3,3,3,3,3,3,3,3,3,1,1,3,1,3,3,2,3,2,2,2,3,0,2,0,0, +0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,3,3,3,2,3,3,2,2,3,2,1,1,1,1,1,3,1,3,1,1,0,0,0,1,0,0,0,1,0,0, +0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0, +3,3,3,3,3,2,3,2,0,3,2,0,3,0,2,0,0,2,1,3,1,0,0,1,0,0,0,1,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, +3,3,3,3,2,1,1,1,1,2,1,1,2,1,1,1,2,2,1,2,1,1,1,0,1,1,0,1,0,1,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1, +3,3,3,3,2,1,3,1,1,2,1,3,2,1,1,0,1,2,3,2,1,1,1,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +2,3,3,3,3,2,2,1,0,1,0,0,1,0,0,0,2,1,0,3,0,0,1,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, +3,3,3,2,3,2,3,3,1,3,2,1,1,1,2,1,1,2,1,3,0,1,0,0,0,1,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,1,1,2,2,3,3,2,3,2,2,2,3,1,2,2,1,1,2,1,1,2,2,0,1,1,0,1,0,2,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,3,3,2,1,3,1,0,2,2,1,3,2,1,0,0,2,0,2,0,1,0,0,0,0,0,0,0,1,0,0, +0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1, +3,3,3,3,3,3,1,2,0,2,3,1,2,3,2,0,1,3,1,2,1,1,1,0,0,1,0,0,2,2,2,3, +2,2,2,2,1,2,1,1,2,2,1,1,2,0,1,1,1,0,0,1,1,0,0,1,1,0,0,0,1,1,0,1, +3,3,3,3,3,2,1,2,2,1,2,0,2,0,1,0,1,2,1,2,1,1,0,0,0,1,0,1,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,1, +3,3,2,3,3,1,1,3,1,0,3,2,1,0,0,0,1,2,0,2,0,1,0,0,0,1,0,1,2,1,2,2, +1,1,1,1,1,1,1,2,2,2,1,1,1,1,1,1,1,0,1,2,1,1,1,0,0,0,0,0,1,1,0,0, +3,1,0,1,0,2,3,2,2,2,3,2,2,2,2,2,1,0,2,1,2,1,1,1,0,1,2,1,2,2,2,1, +1,1,2,2,2,2,1,2,1,1,0,1,2,1,2,2,2,1,1,1,0,1,1,1,1,2,0,1,0,0,0,0, +2,3,2,3,3,0,0,2,1,0,2,1,0,0,0,0,2,3,0,2,0,0,0,0,0,1,0,0,2,0,1,2, +2,1,2,1,2,2,1,1,1,2,1,1,1,0,1,2,2,1,1,1,1,1,0,1,1,1,0,0,1,2,0,0, +3,3,2,2,3,0,2,3,1,1,2,0,0,0,1,0,0,2,0,2,0,0,0,1,0,1,0,1,2,0,2,2, +1,1,1,1,2,1,0,1,2,2,2,1,1,1,1,1,1,1,0,1,1,1,0,0,0,0,0,0,1,1,0,0, +2,3,2,3,3,0,0,3,0,1,1,0,1,0,0,0,2,2,1,2,0,0,0,0,0,0,0,0,2,0,1,2, +2,2,1,1,1,1,1,2,2,2,1,0,2,0,1,0,1,0,0,1,0,1,0,0,1,0,0,0,0,1,0,0, +3,3,3,3,2,2,2,2,2,0,2,1,1,1,1,2,1,2,1,1,0,2,0,1,0,1,0,0,2,0,1,2, +1,1,1,1,1,1,1,2,2,1,1,0,2,0,1,0,2,0,0,1,1,1,0,0,2,0,0,0,1,1,0,0, +2,3,3,3,3,1,0,0,0,0,0,0,0,0,0,0,2,0,0,1,1,0,0,0,0,0,0,1,2,0,1,2, +2,2,2,1,1,2,1,1,2,2,2,1,2,0,1,1,1,1,1,1,0,1,1,1,1,0,0,1,1,1,0,0, +2,3,3,3,3,0,2,2,0,2,1,0,0,0,1,1,1,2,0,2,0,0,0,3,0,0,0,0,2,0,2,2, +1,1,1,2,1,2,1,1,2,2,2,1,2,0,1,1,1,0,1,1,1,1,0,2,1,0,0,0,1,1,0,0, +2,3,3,3,3,0,2,1,0,0,2,0,0,0,0,0,1,2,0,2,0,0,0,0,0,0,0,0,2,0,1,2, +1,1,1,2,1,1,1,1,2,2,2,0,1,0,1,1,1,0,0,1,1,1,0,0,1,0,0,0,0,1,0,0, +3,3,2,2,3,0,1,0,1,0,0,0,0,0,0,0,1,1,0,3,0,0,0,0,0,0,0,0,1,0,2,2, +1,1,1,1,1,2,1,1,2,2,1,2,2,1,0,1,1,1,1,1,0,1,0,0,1,0,0,0,1,1,0,0, +3,1,0,1,0,2,2,2,2,3,2,1,1,1,2,3,0,0,1,0,2,1,1,0,1,1,1,1,2,1,1,1, +1,2,2,1,2,1,2,2,1,1,0,1,2,1,2,2,1,1,1,0,0,1,1,1,2,1,0,1,0,0,0,0, +2,1,0,1,0,3,1,2,2,2,2,1,2,2,1,1,1,0,2,1,2,2,1,1,2,1,1,0,2,1,1,1, +1,2,2,2,2,2,2,2,1,2,0,1,1,0,2,1,1,1,1,1,0,0,1,1,1,1,0,1,0,0,0,0, +2,1,1,1,1,2,2,2,2,1,2,2,2,1,2,2,1,1,2,1,2,3,2,2,1,1,1,1,0,1,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +2,2,2,3,2,0,1,2,0,1,2,1,1,0,1,0,1,2,1,2,0,0,0,1,1,0,0,0,1,0,0,2, +1,1,0,0,1,1,0,1,1,1,1,0,2,0,1,1,1,0,0,1,1,0,0,0,0,1,0,0,0,1,0,0, +2,0,0,0,0,1,2,2,2,2,2,2,2,1,2,1,1,1,1,1,1,1,0,1,1,1,1,1,2,1,1,1, +1,2,2,2,2,1,1,2,1,2,1,1,1,0,2,1,2,1,1,1,0,2,1,1,1,1,0,1,0,0,0,0, +3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0, +1,1,0,1,0,1,1,1,1,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +2,2,2,3,2,0,0,0,0,1,0,0,0,0,0,0,1,1,0,2,0,0,0,0,0,0,0,0,1,0,1,2, +1,1,1,1,1,1,0,0,2,2,2,2,2,0,1,1,0,1,1,1,1,1,0,0,1,0,0,0,1,1,0,1, +2,3,1,2,1,0,1,1,0,2,2,2,0,0,1,0,0,1,1,1,1,0,0,0,0,0,0,0,1,0,1,2, +1,1,1,1,2,1,1,1,1,1,1,1,1,0,1,1,0,1,0,1,0,1,0,0,1,0,0,0,0,1,0,0, +2,2,2,2,2,0,0,2,0,0,2,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,2,0,2,2, +1,1,1,1,1,0,0,1,2,1,1,0,1,0,1,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0, +1,2,2,2,2,0,0,2,0,1,1,0,0,0,1,0,0,2,0,2,0,0,0,0,0,0,0,0,0,0,1,1, +0,0,0,1,1,1,1,1,1,1,1,1,1,0,1,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0, +1,2,2,3,2,0,0,1,0,0,1,0,0,0,0,0,0,1,0,2,0,0,0,1,0,0,0,0,0,0,0,2, +1,1,0,0,1,0,0,0,1,1,0,0,1,0,1,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0, +2,1,2,2,2,1,2,1,2,2,1,1,2,1,1,1,0,1,1,1,1,2,0,1,0,1,1,1,1,0,1,1, +1,1,2,1,1,1,1,1,1,0,0,1,2,1,1,1,1,1,1,0,0,1,1,1,0,0,0,0,0,0,0,0, +1,0,0,1,3,1,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +2,2,2,2,1,0,0,1,0,2,0,0,0,0,0,1,1,1,0,1,0,0,0,0,0,0,0,0,2,0,0,1, +0,2,0,1,0,0,1,1,2,0,1,0,1,0,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0, +1,2,2,2,2,0,1,1,0,2,1,0,1,1,1,0,0,1,0,2,0,1,0,0,0,0,0,0,0,0,0,1, +0,1,0,0,1,0,0,0,1,1,0,0,1,0,0,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0, +2,2,2,2,2,0,0,1,0,0,0,1,0,1,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,1, +0,1,0,1,1,1,0,0,1,1,1,0,1,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0, +2,0,1,0,0,1,2,1,1,1,1,1,1,2,2,1,0,0,1,0,1,0,0,0,0,1,1,1,1,0,0,0, +1,1,2,1,1,1,1,0,0,0,1,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +2,2,1,2,1,0,0,1,0,0,0,0,0,0,0,0,1,1,0,1,0,0,0,0,0,0,0,0,0,0,0,1, +0,0,0,0,0,0,0,0,1,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +1,0,0,1,2,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1,0,0,0, +0,1,1,0,1,1,1,0,0,1,0,0,1,0,1,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0, +1,0,1,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,1,0,2,0,0,2,0,1,0,0,1,0,0,1, +1,1,0,0,1,1,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,1,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0, +1,1,1,1,1,1,1,2,0,0,0,0,0,0,2,1,0,1,1,0,0,1,1,1,0,1,0,0,0,0,0,0, +2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,0,1,1,0,1,1,1,1,1,0,1,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, +) + +Latin5BulgarianModel = { + 'charToOrderMap': Latin5_BulgarianCharToOrderMap, + 'precedenceMatrix': BulgarianLangModel, + 'mTypicalPositiveRatio': 0.969392, + 'keepEnglishLetter': False, + 'charsetName': "ISO-8859-5" +} + +Win1251BulgarianModel = { + 'charToOrderMap': win1251BulgarianCharToOrderMap, + 'precedenceMatrix': BulgarianLangModel, + 'mTypicalPositiveRatio': 0.969392, + 'keepEnglishLetter': False, + 'charsetName': "windows-1251" +} + + +# flake8: noqa diff --git a/resources/lib/libraries/requests/packages/chardet/langcyrillicmodel.py b/resources/lib/libraries/requests/packages/chardet/langcyrillicmodel.py new file mode 100644 index 00000000..a86f54bd --- /dev/null +++ b/resources/lib/libraries/requests/packages/chardet/langcyrillicmodel.py @@ -0,0 +1,329 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is Mozilla Communicator client code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +# KOI8-R language model +# Character Mapping Table: +KOI8R_CharToOrderMap = ( +255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 +253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 +252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 +253,142,143,144,145,146,147,148,149,150,151,152, 74,153, 75,154, # 40 +155,156,157,158,159,160,161,162,163,164,165,253,253,253,253,253, # 50 +253, 71,172, 66,173, 65,174, 76,175, 64,176,177, 77, 72,178, 69, # 60 + 67,179, 78, 73,180,181, 79,182,183,184,185,253,253,253,253,253, # 70 +191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206, # 80 +207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222, # 90 +223,224,225, 68,226,227,228,229,230,231,232,233,234,235,236,237, # a0 +238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253, # b0 + 27, 3, 21, 28, 13, 2, 39, 19, 26, 4, 23, 11, 8, 12, 5, 1, # c0 + 15, 16, 9, 7, 6, 14, 24, 10, 17, 18, 20, 25, 30, 29, 22, 54, # d0 + 59, 37, 44, 58, 41, 48, 53, 46, 55, 42, 60, 36, 49, 38, 31, 34, # e0 + 35, 43, 45, 32, 40, 52, 56, 33, 61, 62, 51, 57, 47, 63, 50, 70, # f0 +) + +win1251_CharToOrderMap = ( +255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 +253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 +252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 +253,142,143,144,145,146,147,148,149,150,151,152, 74,153, 75,154, # 40 +155,156,157,158,159,160,161,162,163,164,165,253,253,253,253,253, # 50 +253, 71,172, 66,173, 65,174, 76,175, 64,176,177, 77, 72,178, 69, # 60 + 67,179, 78, 73,180,181, 79,182,183,184,185,253,253,253,253,253, # 70 +191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206, +207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222, +223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238, +239,240,241,242,243,244,245,246, 68,247,248,249,250,251,252,253, + 37, 44, 33, 46, 41, 48, 56, 51, 42, 60, 36, 49, 38, 31, 34, 35, + 45, 32, 40, 52, 53, 55, 58, 50, 57, 63, 70, 62, 61, 47, 59, 43, + 3, 21, 10, 19, 13, 2, 24, 20, 4, 23, 11, 8, 12, 5, 1, 15, + 9, 7, 6, 14, 39, 26, 28, 22, 25, 29, 54, 18, 17, 30, 27, 16, +) + +latin5_CharToOrderMap = ( +255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 +253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 +252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 +253,142,143,144,145,146,147,148,149,150,151,152, 74,153, 75,154, # 40 +155,156,157,158,159,160,161,162,163,164,165,253,253,253,253,253, # 50 +253, 71,172, 66,173, 65,174, 76,175, 64,176,177, 77, 72,178, 69, # 60 + 67,179, 78, 73,180,181, 79,182,183,184,185,253,253,253,253,253, # 70 +191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206, +207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222, +223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238, + 37, 44, 33, 46, 41, 48, 56, 51, 42, 60, 36, 49, 38, 31, 34, 35, + 45, 32, 40, 52, 53, 55, 58, 50, 57, 63, 70, 62, 61, 47, 59, 43, + 3, 21, 10, 19, 13, 2, 24, 20, 4, 23, 11, 8, 12, 5, 1, 15, + 9, 7, 6, 14, 39, 26, 28, 22, 25, 29, 54, 18, 17, 30, 27, 16, +239, 68,240,241,242,243,244,245,246,247,248,249,250,251,252,255, +) + +macCyrillic_CharToOrderMap = ( +255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 +253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 +252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 +253,142,143,144,145,146,147,148,149,150,151,152, 74,153, 75,154, # 40 +155,156,157,158,159,160,161,162,163,164,165,253,253,253,253,253, # 50 +253, 71,172, 66,173, 65,174, 76,175, 64,176,177, 77, 72,178, 69, # 60 + 67,179, 78, 73,180,181, 79,182,183,184,185,253,253,253,253,253, # 70 + 37, 44, 33, 46, 41, 48, 56, 51, 42, 60, 36, 49, 38, 31, 34, 35, + 45, 32, 40, 52, 53, 55, 58, 50, 57, 63, 70, 62, 61, 47, 59, 43, +191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206, +207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222, +223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238, +239,240,241,242,243,244,245,246,247,248,249,250,251,252, 68, 16, + 3, 21, 10, 19, 13, 2, 24, 20, 4, 23, 11, 8, 12, 5, 1, 15, + 9, 7, 6, 14, 39, 26, 28, 22, 25, 29, 54, 18, 17, 30, 27,255, +) + +IBM855_CharToOrderMap = ( +255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 +253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 +252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 +253,142,143,144,145,146,147,148,149,150,151,152, 74,153, 75,154, # 40 +155,156,157,158,159,160,161,162,163,164,165,253,253,253,253,253, # 50 +253, 71,172, 66,173, 65,174, 76,175, 64,176,177, 77, 72,178, 69, # 60 + 67,179, 78, 73,180,181, 79,182,183,184,185,253,253,253,253,253, # 70 +191,192,193,194, 68,195,196,197,198,199,200,201,202,203,204,205, +206,207,208,209,210,211,212,213,214,215,216,217, 27, 59, 54, 70, + 3, 37, 21, 44, 28, 58, 13, 41, 2, 48, 39, 53, 19, 46,218,219, +220,221,222,223,224, 26, 55, 4, 42,225,226,227,228, 23, 60,229, +230,231,232,233,234,235, 11, 36,236,237,238,239,240,241,242,243, + 8, 49, 12, 38, 5, 31, 1, 34, 15,244,245,246,247, 35, 16,248, + 43, 9, 45, 7, 32, 6, 40, 14, 52, 24, 56, 10, 33, 17, 61,249, +250, 18, 62, 20, 51, 25, 57, 30, 47, 29, 63, 22, 50,251,252,255, +) + +IBM866_CharToOrderMap = ( +255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 +253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 +252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 +253,142,143,144,145,146,147,148,149,150,151,152, 74,153, 75,154, # 40 +155,156,157,158,159,160,161,162,163,164,165,253,253,253,253,253, # 50 +253, 71,172, 66,173, 65,174, 76,175, 64,176,177, 77, 72,178, 69, # 60 + 67,179, 78, 73,180,181, 79,182,183,184,185,253,253,253,253,253, # 70 + 37, 44, 33, 46, 41, 48, 56, 51, 42, 60, 36, 49, 38, 31, 34, 35, + 45, 32, 40, 52, 53, 55, 58, 50, 57, 63, 70, 62, 61, 47, 59, 43, + 3, 21, 10, 19, 13, 2, 24, 20, 4, 23, 11, 8, 12, 5, 1, 15, +191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206, +207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222, +223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238, + 9, 7, 6, 14, 39, 26, 28, 22, 25, 29, 54, 18, 17, 30, 27, 16, +239, 68,240,241,242,243,244,245,246,247,248,249,250,251,252,255, +) + +# Model Table: +# total sequences: 100% +# first 512 sequences: 97.6601% +# first 1024 sequences: 2.3389% +# rest sequences: 0.1237% +# negative sequences: 0.0009% +RussianLangModel = ( +0,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,1,1,3,3,3,3,1,3,3,3,2,3,2,3,3, +3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,0,3,2,2,2,2,2,0,0,2, +3,3,3,2,3,3,3,3,3,3,3,3,3,3,2,3,3,0,0,3,3,3,3,3,3,3,3,3,2,3,2,0, +0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,3,2,2,3,3,3,3,3,3,3,3,3,2,3,3,0,0,3,3,3,3,3,3,3,3,2,3,3,1,0, +0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,2,3,2,3,3,3,3,3,3,3,3,3,3,3,3,3,0,0,3,3,3,3,3,3,3,3,3,3,3,2,1, +0,0,0,0,0,0,0,2,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,3,3,0,0,3,3,3,3,3,3,3,3,3,3,3,2,1, +0,0,0,0,0,1,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,3,3,3,3,3,3,2,2,2,3,1,3,3,1,3,3,3,3,2,2,3,0,2,2,2,3,3,2,1,0, +0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0, +3,3,3,3,3,3,2,3,3,3,3,3,2,2,3,2,3,3,3,2,1,2,2,0,1,2,2,2,2,2,2,0, +0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0, +3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,2,2,3,0,2,2,3,3,2,1,2,0, +0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,1,0,0,2,0,0,0,0,0,0,0,0,0, +3,3,3,3,3,3,2,3,3,1,2,3,2,2,3,2,3,3,3,3,2,2,3,0,3,2,2,3,1,1,1,0, +0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,3,3,3,3,3,3,2,2,3,3,3,3,3,2,3,3,3,3,2,2,2,0,3,3,3,2,2,2,2,0, +0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,3,3,3,3,3,3,3,3,2,3,2,3,3,3,3,3,3,2,3,2,2,0,1,3,2,1,2,2,1,0, +0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0, +3,3,3,3,3,3,3,3,3,3,3,2,1,1,3,0,1,1,1,1,2,1,1,0,2,2,2,1,2,0,1,0, +0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,3,3,3,3,2,3,3,2,2,2,2,1,3,2,3,2,3,2,1,2,2,0,1,1,2,1,2,1,2,0, +0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,3,3,3,3,3,3,3,3,3,3,2,2,3,2,3,3,3,2,2,2,2,0,2,2,2,2,3,1,1,0, +0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0, +3,2,3,2,2,3,3,3,3,3,3,3,3,3,1,3,2,0,0,3,3,3,3,2,3,3,3,3,2,3,2,0, +0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +2,3,3,3,3,3,2,2,3,3,0,2,1,0,3,2,3,2,3,0,0,1,2,0,0,1,0,1,2,1,1,0, +0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,0,3,0,2,3,3,3,3,2,3,3,3,3,1,2,2,0,0,2,3,2,2,2,3,2,3,2,2,3,0,0, +0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,2,3,0,2,3,2,3,0,1,2,3,3,2,0,2,3,0,0,2,3,2,2,0,1,3,1,3,2,2,1,0, +0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,1,3,0,2,3,3,3,3,3,3,3,3,2,1,3,2,0,0,2,2,3,3,3,2,3,3,0,2,2,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,3,3,3,3,2,2,3,3,2,2,2,3,3,0,0,1,1,1,1,1,2,0,0,1,1,1,1,0,1,0, +0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,3,3,3,3,2,2,3,3,3,3,3,3,3,0,3,2,3,3,2,3,2,0,2,1,0,1,1,0,1,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0, +3,3,3,3,3,3,2,3,3,3,2,2,2,2,3,1,3,2,3,1,1,2,1,0,2,2,2,2,1,3,1,0, +0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0, +2,2,3,3,3,3,3,1,2,2,1,3,1,0,3,0,0,3,0,0,0,1,1,0,1,2,1,0,0,0,0,0, +0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,2,2,1,1,3,3,3,2,2,1,2,2,3,1,1,2,0,0,2,2,1,3,0,0,2,1,1,2,1,1,0, +0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,2,3,3,3,3,1,2,2,2,1,2,1,3,3,1,1,2,1,2,1,2,2,0,2,0,0,1,1,0,1,0, +0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +2,3,3,3,3,3,2,1,3,2,2,3,2,0,3,2,0,3,0,1,0,1,1,0,0,1,1,1,1,0,1,0, +0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,2,3,3,3,2,2,2,3,3,1,2,1,2,1,0,1,0,1,1,0,1,0,0,2,1,1,1,0,1,0, +0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0, +3,1,1,2,1,2,3,3,2,2,1,2,2,3,0,2,1,0,0,2,2,3,2,1,2,2,2,2,2,3,1,0, +0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,3,3,3,1,1,0,1,1,2,2,1,1,3,0,0,1,3,1,1,1,0,0,0,1,0,1,1,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +2,1,3,3,3,2,0,0,0,2,1,0,1,0,2,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +2,0,1,0,0,2,3,2,2,2,1,2,2,2,1,2,1,0,0,1,1,1,0,2,0,1,1,1,0,0,1,1, +1,0,0,0,0,0,1,2,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0, +2,3,3,3,3,0,0,0,0,1,0,0,0,0,3,0,1,2,1,0,0,0,0,0,0,0,1,1,0,0,1,1, +1,0,1,0,1,2,0,0,1,1,2,1,0,1,1,1,1,0,1,1,1,1,0,1,0,0,1,0,0,1,1,0, +2,2,3,2,2,2,3,1,2,2,2,2,2,2,2,2,1,1,1,1,1,1,1,0,1,0,1,1,1,0,2,1, +1,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,0,1,0,1,1,0,1,1,1,0,1,1,0, +3,3,3,2,2,2,2,3,2,2,1,1,2,2,2,2,1,1,3,1,2,1,2,0,0,1,1,0,1,0,2,1, +1,1,1,1,1,2,1,0,1,1,1,1,0,1,0,0,1,1,0,0,1,0,1,0,0,1,0,0,0,1,1,0, +2,0,0,1,0,3,2,2,2,2,1,2,1,2,1,2,0,0,0,2,1,2,2,1,1,2,2,0,1,1,0,2, +1,1,1,1,1,0,1,1,1,2,1,1,1,2,1,0,1,2,1,1,1,1,0,1,1,1,0,0,1,0,0,1, +1,3,2,2,2,1,1,1,2,3,0,0,0,0,2,0,2,2,1,0,0,0,0,0,0,1,0,0,0,0,1,1, +1,0,1,1,0,1,0,1,1,0,1,1,0,2,0,0,1,1,0,0,1,0,0,0,0,0,0,0,0,1,1,0, +2,3,2,3,2,1,2,2,2,2,1,0,0,0,2,0,0,1,1,0,0,0,0,0,0,0,1,1,0,0,2,1, +1,1,2,1,0,2,0,0,1,0,1,0,0,1,0,0,1,1,0,1,1,0,0,0,0,0,1,0,0,0,0,0, +3,0,0,1,0,2,2,2,3,2,2,2,2,2,2,2,0,0,0,2,1,2,1,1,1,2,2,0,0,0,1,2, +1,1,1,1,1,0,1,2,1,1,1,1,1,1,1,0,1,1,1,1,1,1,0,1,1,1,1,1,1,0,0,1, +2,3,2,3,3,2,0,1,1,1,0,0,1,0,2,0,1,1,3,1,0,0,0,0,0,0,0,1,0,0,2,1, +1,1,1,1,1,1,1,0,1,0,1,1,1,1,0,1,1,1,0,0,1,1,0,1,0,0,0,0,0,0,1,0, +2,3,3,3,3,1,2,2,2,2,0,1,1,0,2,1,1,1,2,1,0,1,1,0,0,1,0,1,0,0,2,0, +0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +2,3,3,3,2,0,0,1,1,2,2,1,0,0,2,0,1,1,3,0,0,1,0,0,0,0,0,1,0,1,2,1, +1,1,2,0,1,1,1,0,1,0,1,1,0,1,0,1,1,1,1,0,1,0,0,0,0,0,0,1,0,1,1,0, +1,3,2,3,2,1,0,0,2,2,2,0,1,0,2,0,1,1,1,0,1,0,0,0,3,0,1,1,0,0,2,1, +1,1,1,0,1,1,0,0,0,0,1,1,0,1,0,0,2,1,1,0,1,0,0,0,1,0,1,0,0,1,1,0, +3,1,2,1,1,2,2,2,2,2,2,1,2,2,1,1,0,0,0,2,2,2,0,0,0,1,2,1,0,1,0,1, +2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,2,1,1,1,0,1,0,1,1,0,1,1,1,0,0,1, +3,0,0,0,0,2,0,1,1,1,1,1,1,1,0,1,0,0,0,1,1,1,0,1,0,1,1,0,0,1,0,1, +1,1,0,0,1,0,0,0,1,0,1,1,0,0,1,0,1,0,1,0,0,0,0,1,0,0,0,1,0,0,0,1, +1,3,3,2,2,0,0,0,2,2,0,0,0,1,2,0,1,1,2,0,0,0,0,0,0,0,0,1,0,0,2,1, +0,1,1,0,0,1,1,0,0,0,1,1,0,1,1,0,1,1,0,0,1,0,0,0,0,0,0,0,0,0,1,0, +2,3,2,3,2,0,0,0,0,1,1,0,0,0,2,0,2,0,2,0,0,0,0,0,1,0,0,1,0,0,1,1, +1,1,2,0,1,2,1,0,1,1,2,1,1,1,1,1,2,1,1,0,1,0,0,1,1,1,1,1,0,1,1,0, +1,3,2,2,2,1,0,0,2,2,1,0,1,2,2,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,1,1, +0,0,1,1,0,1,1,0,0,1,1,0,1,1,0,0,1,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0, +1,0,0,1,0,2,3,1,2,2,2,2,2,2,1,1,0,0,0,1,0,1,0,2,1,1,1,0,0,0,0,1, +1,1,0,1,1,0,1,1,1,1,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1,0,0,0, +2,0,2,0,0,1,0,3,2,1,2,1,2,2,0,1,0,0,0,2,1,0,0,2,1,1,1,1,0,2,0,2, +2,1,1,1,1,1,1,1,1,1,1,1,1,2,1,0,1,1,1,1,0,0,0,1,1,1,1,0,1,0,0,1, +1,2,2,2,2,1,0,0,1,0,0,0,0,0,2,0,1,1,1,1,0,0,0,0,1,0,1,2,0,0,2,0, +1,0,1,1,1,2,1,0,1,0,1,1,0,0,1,0,1,1,1,0,1,0,0,0,1,0,0,1,0,1,1,0, +2,1,2,2,2,0,3,0,1,1,0,0,0,0,2,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1, +0,0,0,1,1,1,0,0,1,0,1,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0, +1,2,2,3,2,2,0,0,1,1,2,0,1,2,1,0,1,0,1,0,0,1,0,0,0,0,0,0,0,0,0,1, +0,1,1,0,0,1,1,0,0,1,1,0,0,1,1,0,1,1,0,0,1,0,0,0,0,0,0,0,0,1,1,0, +2,2,1,1,2,1,2,2,2,2,2,1,2,2,0,1,0,0,0,1,2,2,2,1,2,1,1,1,1,1,2,1, +1,1,1,1,1,1,1,1,1,1,0,0,1,1,1,0,1,1,1,0,0,0,0,1,1,1,0,1,1,0,0,1, +1,2,2,2,2,0,1,0,2,2,0,0,0,0,2,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,2,0, +0,0,1,0,0,1,0,0,0,0,1,0,1,1,0,0,1,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0, +0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +1,2,2,2,2,0,0,0,2,2,2,0,1,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,1,1, +0,1,1,0,0,1,1,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +1,2,2,2,2,0,0,0,0,1,0,0,1,1,2,0,0,0,0,1,0,1,0,0,1,0,0,2,0,0,0,1, +0,0,1,0,0,1,0,0,0,1,1,0,0,0,0,0,1,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0, +1,2,2,2,1,1,2,0,2,1,1,1,1,0,2,2,0,0,0,0,0,0,0,0,0,1,1,0,0,0,1,1, +0,0,1,0,1,1,0,0,0,0,1,0,0,0,0,0,1,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0, +1,0,2,1,2,0,0,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0, +0,0,1,0,1,1,0,0,0,0,1,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0, +1,0,0,0,0,2,0,1,2,1,0,1,1,1,0,1,0,0,0,1,0,1,0,0,1,0,1,0,0,0,0,1, +0,0,0,0,0,1,0,0,1,1,0,0,1,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,1, +2,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, +1,0,0,0,1,0,0,0,1,1,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,1,0,0,0,0,0, +2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, +1,1,1,0,1,0,1,0,0,1,1,1,1,0,0,0,1,0,0,0,0,1,0,0,0,1,0,1,0,0,0,0, +1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, +1,1,0,1,1,0,1,0,1,0,0,0,0,1,1,0,1,1,0,0,0,0,0,1,0,1,1,0,1,0,0,0, +0,1,1,1,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0, +) + +Koi8rModel = { + 'charToOrderMap': KOI8R_CharToOrderMap, + 'precedenceMatrix': RussianLangModel, + 'mTypicalPositiveRatio': 0.976601, + 'keepEnglishLetter': False, + 'charsetName': "KOI8-R" +} + +Win1251CyrillicModel = { + 'charToOrderMap': win1251_CharToOrderMap, + 'precedenceMatrix': RussianLangModel, + 'mTypicalPositiveRatio': 0.976601, + 'keepEnglishLetter': False, + 'charsetName': "windows-1251" +} + +Latin5CyrillicModel = { + 'charToOrderMap': latin5_CharToOrderMap, + 'precedenceMatrix': RussianLangModel, + 'mTypicalPositiveRatio': 0.976601, + 'keepEnglishLetter': False, + 'charsetName': "ISO-8859-5" +} + +MacCyrillicModel = { + 'charToOrderMap': macCyrillic_CharToOrderMap, + 'precedenceMatrix': RussianLangModel, + 'mTypicalPositiveRatio': 0.976601, + 'keepEnglishLetter': False, + 'charsetName': "MacCyrillic" +}; + +Ibm866Model = { + 'charToOrderMap': IBM866_CharToOrderMap, + 'precedenceMatrix': RussianLangModel, + 'mTypicalPositiveRatio': 0.976601, + 'keepEnglishLetter': False, + 'charsetName': "IBM866" +} + +Ibm855Model = { + 'charToOrderMap': IBM855_CharToOrderMap, + 'precedenceMatrix': RussianLangModel, + 'mTypicalPositiveRatio': 0.976601, + 'keepEnglishLetter': False, + 'charsetName': "IBM855" +} + +# flake8: noqa diff --git a/resources/lib/libraries/requests/packages/chardet/langgreekmodel.py b/resources/lib/libraries/requests/packages/chardet/langgreekmodel.py new file mode 100644 index 00000000..ddb58376 --- /dev/null +++ b/resources/lib/libraries/requests/packages/chardet/langgreekmodel.py @@ -0,0 +1,225 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is Mozilla Communicator client code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +# 255: Control characters that usually does not exist in any text +# 254: Carriage/Return +# 253: symbol (punctuation) that does not belong to word +# 252: 0 - 9 + +# Character Mapping Table: +Latin7_CharToOrderMap = ( +255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 +253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 +252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 +253, 82,100,104, 94, 98,101,116,102,111,187,117, 92, 88,113, 85, # 40 + 79,118,105, 83, 67,114,119, 95, 99,109,188,253,253,253,253,253, # 50 +253, 72, 70, 80, 81, 60, 96, 93, 89, 68,120, 97, 77, 86, 69, 55, # 60 + 78,115, 65, 66, 58, 76,106,103, 87,107,112,253,253,253,253,253, # 70 +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 80 +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 90 +253,233, 90,253,253,253,253,253,253,253,253,253,253, 74,253,253, # a0 +253,253,253,253,247,248, 61, 36, 46, 71, 73,253, 54,253,108,123, # b0 +110, 31, 51, 43, 41, 34, 91, 40, 52, 47, 44, 53, 38, 49, 59, 39, # c0 + 35, 48,250, 37, 33, 45, 56, 50, 84, 57,120,121, 17, 18, 22, 15, # d0 +124, 1, 29, 20, 21, 3, 32, 13, 25, 5, 11, 16, 10, 6, 30, 4, # e0 + 9, 8, 14, 7, 2, 12, 28, 23, 42, 24, 64, 75, 19, 26, 27,253, # f0 +) + +win1253_CharToOrderMap = ( +255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 +253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 +252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 +253, 82,100,104, 94, 98,101,116,102,111,187,117, 92, 88,113, 85, # 40 + 79,118,105, 83, 67,114,119, 95, 99,109,188,253,253,253,253,253, # 50 +253, 72, 70, 80, 81, 60, 96, 93, 89, 68,120, 97, 77, 86, 69, 55, # 60 + 78,115, 65, 66, 58, 76,106,103, 87,107,112,253,253,253,253,253, # 70 +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 80 +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 90 +253,233, 61,253,253,253,253,253,253,253,253,253,253, 74,253,253, # a0 +253,253,253,253,247,253,253, 36, 46, 71, 73,253, 54,253,108,123, # b0 +110, 31, 51, 43, 41, 34, 91, 40, 52, 47, 44, 53, 38, 49, 59, 39, # c0 + 35, 48,250, 37, 33, 45, 56, 50, 84, 57,120,121, 17, 18, 22, 15, # d0 +124, 1, 29, 20, 21, 3, 32, 13, 25, 5, 11, 16, 10, 6, 30, 4, # e0 + 9, 8, 14, 7, 2, 12, 28, 23, 42, 24, 64, 75, 19, 26, 27,253, # f0 +) + +# Model Table: +# total sequences: 100% +# first 512 sequences: 98.2851% +# first 1024 sequences:1.7001% +# rest sequences: 0.0359% +# negative sequences: 0.0148% +GreekLangModel = ( +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,3,2,2,3,3,3,3,3,3,3,3,1,3,3,3,0,2,2,3,3,0,3,0,3,2,0,3,3,3,0, +3,0,0,0,2,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,3,3,3,3,3,0,3,3,0,3,2,3,3,0,3,2,3,3,3,0,0,3,0,3,0,3,3,2,0,0,0, +2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0, +0,2,3,2,2,3,3,3,3,3,3,3,3,0,3,3,3,3,0,2,3,3,0,3,3,3,3,2,3,3,3,0, +2,0,0,0,2,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,2,3,3,2,3,3,3,3,3,3,3,3,3,3,3,3,0,2,1,3,3,3,3,2,3,3,2,3,3,2,0, +0,0,0,0,2,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,3,3,3,3,0,3,3,3,3,3,3,0,3,3,0,3,3,3,3,3,3,3,3,3,3,0,3,2,3,3,0, +2,0,1,0,2,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0, +0,3,3,3,3,3,2,3,0,0,0,0,3,3,0,3,1,3,3,3,0,3,3,0,3,3,3,3,0,0,0,0, +2,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,3,3,3,3,3,0,3,0,3,3,3,3,3,0,3,2,2,2,3,0,2,3,3,3,3,3,2,3,3,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,3,3,3,3,3,3,2,2,2,3,3,3,3,0,3,1,3,3,3,3,2,3,3,3,3,3,3,3,2,2,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,3,3,3,3,3,2,0,3,0,0,0,3,3,2,3,3,3,3,3,0,0,3,2,3,0,2,3,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,3,0,3,3,3,3,0,0,3,3,0,2,3,0,3,0,3,3,3,0,0,3,0,3,0,2,2,3,3,0,0, +0,0,1,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,3,3,3,3,3,2,0,3,2,3,3,3,3,0,3,3,3,3,3,0,3,3,2,3,2,3,3,2,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,3,3,2,3,2,3,3,3,3,3,3,0,2,3,2,3,2,2,2,3,2,3,3,2,3,0,2,2,2,3,0, +2,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,3,0,0,0,3,3,3,2,3,3,0,0,3,0,3,0,0,0,3,2,0,3,0,3,0,0,2,0,2,0, +0,0,0,0,2,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,3,3,3,3,0,3,3,3,3,3,3,0,3,3,0,3,0,0,0,3,3,0,3,3,3,0,0,1,2,3,0, +3,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,3,3,3,3,3,2,0,0,3,2,2,3,3,0,3,3,3,3,3,2,1,3,0,3,2,3,3,2,1,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,3,3,0,2,3,3,3,3,3,3,0,0,3,0,3,0,0,0,3,3,0,3,2,3,0,0,3,3,3,0, +3,0,0,0,2,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,3,3,3,3,0,3,3,3,3,3,3,0,0,3,0,3,0,0,0,3,2,0,3,2,3,0,0,3,2,3,0, +2,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,3,1,2,2,3,3,3,3,3,3,0,2,3,0,3,0,0,0,3,3,0,3,0,2,0,0,2,3,1,0, +2,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,3,0,3,3,3,3,0,3,0,3,3,2,3,0,3,3,3,3,3,3,0,3,3,3,0,2,3,0,0,3,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,3,0,3,3,3,0,0,3,0,0,0,3,3,0,3,0,2,3,3,0,0,3,0,3,0,3,3,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,3,0,0,0,3,3,3,3,3,3,0,0,3,0,2,0,0,0,3,3,0,3,0,3,0,0,2,0,2,0, +0,0,0,0,1,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,3,3,3,3,3,3,0,3,0,2,0,3,2,0,3,2,3,2,3,0,0,3,2,3,2,3,3,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,3,0,0,2,3,3,3,3,3,0,0,0,3,0,2,1,0,0,3,2,2,2,0,3,0,0,2,2,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,3,0,3,3,3,2,0,3,0,3,0,3,3,0,2,1,2,3,3,0,0,3,0,3,0,3,3,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,2,3,3,3,0,3,3,3,3,3,3,0,2,3,0,3,0,0,0,2,1,0,2,2,3,0,0,2,2,2,0, +0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,3,0,0,2,3,3,3,2,3,0,0,1,3,0,2,0,0,0,0,3,0,1,0,2,0,0,1,1,1,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,3,3,3,3,3,1,0,3,0,0,0,3,2,0,3,2,3,3,3,0,0,3,0,3,2,2,2,1,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,3,0,3,3,3,0,0,3,0,0,0,0,2,0,2,3,3,2,2,2,2,3,0,2,0,2,2,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,3,3,3,3,2,0,0,0,0,0,0,2,3,0,2,0,2,3,2,0,0,3,0,3,0,3,1,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,3,2,3,3,2,2,3,0,2,0,3,0,0,0,2,0,0,0,0,1,2,0,2,0,2,0, +0,2,0,2,0,2,2,0,0,1,0,2,2,2,0,2,2,2,0,2,2,2,0,0,2,0,0,1,0,0,0,0, +0,2,0,3,3,2,0,0,0,0,0,0,1,3,0,2,0,2,2,2,0,0,2,0,3,0,0,2,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,3,0,2,3,2,0,2,2,0,2,0,2,2,0,2,0,2,2,2,0,0,0,0,0,0,2,3,0,0,0,2, +0,1,2,0,0,0,0,2,2,0,0,0,2,1,0,2,2,0,0,0,0,0,0,1,0,2,0,0,0,0,0,0, +0,0,2,1,0,2,3,2,2,3,2,3,2,0,0,3,3,3,0,0,3,2,0,0,0,1,1,0,2,0,2,2, +0,2,0,2,0,2,2,0,0,2,0,2,2,2,0,2,2,2,2,0,0,2,0,0,0,2,0,1,0,0,0,0, +0,3,0,3,3,2,2,0,3,0,0,0,2,2,0,2,2,2,1,2,0,0,1,2,2,0,0,3,0,0,0,2, +0,1,2,0,0,0,1,2,0,0,0,0,0,0,0,2,2,0,1,0,0,2,0,0,0,2,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,2,3,3,2,2,0,0,0,2,0,2,3,3,0,2,0,0,0,0,0,0,2,2,2,0,2,2,0,2,0,2, +0,2,2,0,0,2,2,2,2,1,0,0,2,2,0,2,0,0,2,0,0,0,0,0,0,2,0,0,0,0,0,0, +0,2,0,3,2,3,0,0,0,3,0,0,2,2,0,2,0,2,2,2,0,0,2,0,0,0,0,0,0,0,0,2, +0,0,2,2,0,0,2,2,2,0,0,0,0,0,0,2,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,2,0,0,3,2,0,2,2,2,2,2,0,0,0,2,0,0,0,0,2,0,1,0,0,2,0,1,0,0,0, +0,2,2,2,0,2,2,0,1,2,0,2,2,2,0,2,2,2,2,1,2,2,0,0,2,0,0,0,0,0,0,0, +0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0, +0,2,0,2,0,2,2,0,0,0,0,1,2,1,0,0,2,2,0,0,2,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,3,2,3,0,0,2,0,0,0,2,2,0,2,0,0,0,1,0,0,2,0,2,0,2,2,0,0,0,0, +0,0,2,0,0,0,0,2,2,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0, +0,2,2,3,2,2,0,0,0,0,0,0,1,3,0,2,0,2,2,0,0,0,1,0,2,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,2,0,2,0,3,2,0,2,0,0,0,0,0,0,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, +0,0,2,0,0,0,0,1,1,0,0,2,1,2,0,2,2,0,1,0,0,1,0,0,0,2,0,0,0,0,0,0, +0,3,0,2,2,2,0,0,2,0,0,0,2,0,0,0,2,3,0,2,0,0,0,0,0,0,2,2,0,0,0,2, +0,1,2,0,0,0,1,2,2,1,0,0,0,2,0,0,2,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,2,1,2,0,2,2,0,2,0,0,2,0,0,0,0,1,2,1,0,2,1,0,0,0,0,0,0,0,0,0,0, +0,0,2,0,0,0,3,1,2,2,0,2,0,0,0,0,2,0,0,0,2,0,0,3,0,0,0,0,2,2,2,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,2,1,0,2,0,1,2,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,1,0,0,0,0,0,0,2, +0,2,2,0,0,2,2,2,2,2,0,1,2,0,0,0,2,2,0,1,0,2,0,0,2,2,0,0,0,0,0,0, +0,0,0,0,1,0,0,0,0,0,0,0,3,0,0,2,0,0,0,0,0,0,0,0,2,0,2,0,0,0,0,2, +0,1,2,0,0,0,0,2,2,1,0,1,0,1,0,2,2,2,1,0,0,0,0,0,0,1,0,0,0,0,0,0, +0,2,0,1,2,0,0,0,0,0,0,0,0,0,0,2,0,0,2,2,0,0,0,0,1,0,0,0,0,0,0,2, +0,2,2,0,0,0,0,2,2,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,2,0,0,2,0,0,0, +0,2,2,2,2,0,0,0,3,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,2,0,0,0,0,0,0,1, +0,0,2,0,0,0,0,1,2,0,0,0,0,0,0,2,2,1,1,0,0,0,0,0,0,1,0,0,0,0,0,0, +0,2,0,2,2,2,0,0,2,0,0,0,0,0,0,0,2,2,2,0,0,0,2,0,0,0,0,0,0,0,0,2, +0,0,1,0,0,0,0,2,1,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0, +0,3,0,2,0,0,0,0,0,0,0,0,2,0,0,0,0,0,2,0,0,0,0,0,0,0,2,0,0,0,0,2, +0,0,2,0,0,0,0,2,2,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,2,0,2,2,1,0,0,0,0,0,0,2,0,0,2,0,2,2,2,0,0,0,0,0,0,2,0,0,0,0,2, +0,0,2,0,0,2,0,2,2,0,0,0,0,2,0,2,0,0,0,0,0,2,0,0,0,2,0,0,0,0,0,0, +0,0,3,0,0,0,2,2,0,2,2,0,0,0,0,0,2,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,2,0,0,0,0,0, +0,2,2,2,2,2,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,1, +0,0,0,0,0,0,0,2,1,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,2,2,0,0,0,0,0,2,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0, +0,2,0,0,0,2,0,0,0,0,0,1,0,0,0,0,2,2,0,0,0,1,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,1,0,2,0,0,0, +0,2,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,1,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1,0,0,2,0,2,0,0,0, +0,0,0,0,0,0,0,0,2,1,0,0,0,0,0,0,2,0,0,0,1,2,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +) + +Latin7GreekModel = { + 'charToOrderMap': Latin7_CharToOrderMap, + 'precedenceMatrix': GreekLangModel, + 'mTypicalPositiveRatio': 0.982851, + 'keepEnglishLetter': False, + 'charsetName': "ISO-8859-7" +} + +Win1253GreekModel = { + 'charToOrderMap': win1253_CharToOrderMap, + 'precedenceMatrix': GreekLangModel, + 'mTypicalPositiveRatio': 0.982851, + 'keepEnglishLetter': False, + 'charsetName': "windows-1253" +} + +# flake8: noqa diff --git a/resources/lib/libraries/requests/packages/chardet/langhebrewmodel.py b/resources/lib/libraries/requests/packages/chardet/langhebrewmodel.py new file mode 100644 index 00000000..75f2bc7f --- /dev/null +++ b/resources/lib/libraries/requests/packages/chardet/langhebrewmodel.py @@ -0,0 +1,201 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is Mozilla Universal charset detector code. +# +# The Initial Developer of the Original Code is +# Simon Montagu +# Portions created by the Initial Developer are Copyright (C) 2005 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# Shy Shalom - original C code +# Shoshannah Forbes - original C code (?) +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +# 255: Control characters that usually does not exist in any text +# 254: Carriage/Return +# 253: symbol (punctuation) that does not belong to word +# 252: 0 - 9 + +# Windows-1255 language model +# Character Mapping Table: +win1255_CharToOrderMap = ( +255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 +253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 +252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 +253, 69, 91, 79, 80, 92, 89, 97, 90, 68,111,112, 82, 73, 95, 85, # 40 + 78,121, 86, 71, 67,102,107, 84,114,103,115,253,253,253,253,253, # 50 +253, 50, 74, 60, 61, 42, 76, 70, 64, 53,105, 93, 56, 65, 54, 49, # 60 + 66,110, 51, 43, 44, 63, 81, 77, 98, 75,108,253,253,253,253,253, # 70 +124,202,203,204,205, 40, 58,206,207,208,209,210,211,212,213,214, +215, 83, 52, 47, 46, 72, 32, 94,216,113,217,109,218,219,220,221, + 34,116,222,118,100,223,224,117,119,104,125,225,226, 87, 99,227, +106,122,123,228, 55,229,230,101,231,232,120,233, 48, 39, 57,234, + 30, 59, 41, 88, 33, 37, 36, 31, 29, 35,235, 62, 28,236,126,237, +238, 38, 45,239,240,241,242,243,127,244,245,246,247,248,249,250, + 9, 8, 20, 16, 3, 2, 24, 14, 22, 1, 25, 15, 4, 11, 6, 23, + 12, 19, 13, 26, 18, 27, 21, 17, 7, 10, 5,251,252,128, 96,253, +) + +# Model Table: +# total sequences: 100% +# first 512 sequences: 98.4004% +# first 1024 sequences: 1.5981% +# rest sequences: 0.087% +# negative sequences: 0.0015% +HebrewLangModel = ( +0,3,3,3,3,3,3,3,3,3,3,2,3,3,3,3,3,3,3,3,3,3,3,2,3,2,1,2,0,1,0,0, +3,0,3,1,0,0,1,3,2,0,1,1,2,0,2,2,2,1,1,1,1,2,1,1,1,2,0,0,2,2,0,1, +3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,2,2,2, +1,2,1,2,1,2,0,0,2,0,0,0,0,0,1,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0, +3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,2,2, +1,2,1,3,1,1,0,0,2,0,0,0,1,0,1,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0, +3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,1,0,1,2,2,1,3, +1,2,1,1,2,2,0,0,2,2,0,0,0,0,1,0,1,0,0,0,1,0,0,0,0,0,0,1,0,1,1,0, +3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,3,3,2,2,2,2,3,2, +1,2,1,2,2,2,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0, +3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,3,3,2,3,2,2,3,2,2,2,1,2,2,2,2, +1,2,1,1,2,2,0,1,2,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0, +3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,0,2,2,2,2,2, +0,2,0,2,2,2,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0, +3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,3,0,2,2,2, +0,2,1,2,2,2,0,0,2,1,0,0,0,0,1,0,1,0,0,0,0,0,0,2,0,0,0,0,0,0,1,0, +3,3,3,3,3,3,3,3,3,3,3,2,3,3,3,3,3,3,3,3,3,3,3,3,3,2,1,2,3,2,2,2, +1,2,1,2,2,2,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,1,0, +3,3,3,3,3,3,3,3,3,2,3,3,3,2,3,3,3,3,3,3,3,3,3,3,3,3,3,1,0,2,0,2, +0,2,1,2,2,2,0,0,1,2,0,0,0,0,1,0,1,0,0,0,0,0,0,1,0,0,0,2,0,0,1,0, +3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,3,2,3,2,2,3,2,1,2,1,1,1, +0,1,1,1,1,1,3,0,1,0,0,0,0,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0, +3,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,0,1,1,0,0,1,0,0,1,0,0,0,0, +0,0,1,0,0,0,0,0,2,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,2,2,2,2,2,2, +0,2,0,1,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0, +3,3,3,3,3,3,3,3,3,2,3,3,3,2,1,2,3,3,2,3,3,3,3,2,3,2,1,2,0,2,1,2, +0,2,0,2,2,2,0,0,1,2,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,1,0,0,1,0, +3,3,3,3,3,3,3,3,3,2,3,3,3,1,2,2,3,3,2,3,2,3,2,2,3,1,2,2,0,2,2,2, +0,2,1,2,2,2,0,0,1,2,0,0,0,0,1,0,0,0,0,0,1,0,0,1,0,0,0,1,0,0,1,0, +3,3,3,3,3,3,3,3,3,3,3,3,3,2,3,3,3,2,3,3,2,2,2,3,3,3,3,1,3,2,2,2, +0,2,0,1,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0, +3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,2,3,3,3,2,3,2,2,2,1,2,2,0,2,2,2,2, +0,2,0,2,2,2,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0, +3,3,3,3,3,3,3,3,3,3,3,2,3,3,3,1,3,2,3,3,2,3,3,2,2,1,2,2,2,2,2,2, +0,2,1,2,1,2,0,0,1,0,0,0,0,0,1,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,1,0, +3,3,3,3,3,3,2,3,2,3,3,2,3,3,3,3,2,3,2,3,3,3,3,3,2,2,2,2,2,2,2,1, +0,2,0,1,2,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0, +3,3,3,3,3,3,3,3,3,2,1,2,3,3,3,3,3,3,3,2,3,2,3,2,1,2,3,0,2,1,2,2, +0,2,1,1,2,1,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,2,0, +3,3,3,3,3,3,3,3,3,2,3,3,3,3,2,1,3,1,2,2,2,1,2,3,3,1,2,1,2,2,2,2, +0,1,1,1,1,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,2,0,0,0,0,0,0,0,0, +3,3,3,3,3,3,3,3,3,3,0,2,3,3,3,1,3,3,3,1,2,2,2,2,1,1,2,2,2,2,2,2, +0,2,0,1,1,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0, +3,3,3,3,3,3,2,3,3,3,2,2,3,3,3,2,1,2,3,2,3,2,2,2,2,1,2,1,1,1,2,2, +0,2,1,1,1,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0, +3,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,0,0,0,1,0,0,0,0,0, +1,0,1,0,0,0,0,0,2,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,3,3,3,2,3,3,2,3,1,2,2,2,2,3,2,3,1,1,2,2,1,2,2,1,1,0,2,2,2,2, +0,1,0,1,2,2,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0, +3,0,0,1,1,0,1,0,0,1,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,2,2,0, +0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,0,1,0,1,0,1,1,0,1,1,0,0,0,1,1,0,1,1,1,0,0,0,0,0,0,1,0,0,0,0,0, +0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,0,0,0,1,1,0,1,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0, +3,2,2,1,2,2,2,2,2,2,2,1,2,2,1,2,2,1,1,1,1,1,1,1,1,2,1,1,0,3,3,3, +0,3,0,2,2,2,2,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0, +2,2,2,3,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,2,2,1,2,2,2,1,1,1,2,0,1, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +2,2,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,0,2,2,0,0,0,0,0,0, +0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +2,3,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,2,1,0,2,1,0, +0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,1,1,1,1,1,1,1,1,1,1,0,0,1,1,1,1,0,1,1,1,1,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0, +0,3,1,1,2,2,2,2,2,1,2,2,2,1,1,2,2,2,2,2,2,2,1,2,2,1,0,1,1,1,1,0, +0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,2,1,1,1,1,2,1,1,2,1,0,1,1,1,1,1,1,1,1,1,1,1,0,1,0,0,0,0,0,0,0, +0,0,2,0,0,0,0,0,0,0,0,1,1,0,0,0,0,1,1,0,0,1,1,0,0,0,0,0,0,1,0,0, +2,1,1,2,2,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,1,2,1,2,1,1,1,1,0,0,0,0, +0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +1,2,1,2,2,2,2,2,2,2,2,2,2,1,2,1,2,1,1,2,1,1,1,2,1,2,1,2,0,1,0,1, +0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,3,1,2,2,2,1,2,2,2,2,2,2,2,2,1,2,1,1,1,1,1,1,2,1,2,1,1,0,1,0,1, +0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +2,1,2,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2, +0,2,0,1,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0, +3,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +2,1,1,1,1,1,1,1,0,1,1,0,1,0,0,1,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,2,0,1,1,1,0,1,0,0,0,1,1,0,1,1,0,0,0,0,0,1,1,0,0, +0,1,1,1,2,1,2,2,2,0,2,0,2,0,1,1,2,1,1,1,1,2,1,0,1,1,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0, +1,0,1,0,0,0,0,0,1,0,1,2,2,0,1,0,0,1,1,2,2,1,2,0,2,0,0,0,1,2,0,1, +2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,2,0,2,1,2,0,2,0,0,1,1,1,1,1,1,0,1,0,0,0,1,0,0,1, +2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,1,0,0,0,0,0,1,0,2,1,1,0,1,0,0,1,1,1,2,2,0,0,1,0,0,0,1,0,0,1, +1,1,2,1,0,1,1,1,0,1,0,1,1,1,1,0,0,0,1,0,1,0,0,0,0,0,0,0,0,2,2,1, +0,2,0,1,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +2,1,0,0,1,0,1,1,1,1,0,0,0,0,0,1,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +1,1,1,1,1,1,1,1,1,2,1,0,1,1,1,1,1,1,1,1,1,1,1,0,1,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,1,1,1,0,1,1,0,1,0,0,0,1,1,0,1, +2,0,1,0,1,0,1,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,1,0,1,1,1,0,1,0,0,1,1,2,1,1,2,0,1,0,0,0,1,1,0,1, +1,0,0,1,0,0,1,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,1,0,1,1,2,0,1,0,0,0,0,2,1,1,2,0,2,0,0,0,1,1,0,1, +1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,1,0,2,1,1,0,1,0,0,2,2,1,2,1,1,0,1,0,0,0,1,1,0,1, +2,0,1,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,1,2,2,0,0,0,0,0,1,1,0,1,0,0,1,0,0,0,0,1,0,1, +1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,1,2,2,0,0,0,0,2,1,1,1,0,2,1,1,0,0,0,2,1,0,1, +1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,1,0,1,1,2,0,1,0,0,1,1,0,2,1,1,0,1,0,0,0,1,1,0,1, +2,2,1,1,1,0,1,1,0,1,1,0,1,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,1,0,2,1,1,0,1,0,0,1,1,0,1,2,1,0,2,0,0,0,1,1,0,1, +2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0, +0,1,0,0,2,0,2,1,1,0,1,0,1,0,0,1,0,0,0,0,1,0,0,0,1,0,0,0,0,0,1,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,1,0,1,1,2,0,1,0,0,1,1,1,0,1,0,0,1,0,0,0,1,0,0,1, +1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +1,0,0,0,0,0,0,0,1,0,1,1,0,0,1,0,0,2,1,1,1,1,1,0,1,0,0,0,0,1,0,1, +0,1,1,1,2,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,1,2,1,0,0,0,0,0,1,1,1,1,1,0,1,0,0,0,1,1,0,0, +) + +Win1255HebrewModel = { + 'charToOrderMap': win1255_CharToOrderMap, + 'precedenceMatrix': HebrewLangModel, + 'mTypicalPositiveRatio': 0.984004, + 'keepEnglishLetter': False, + 'charsetName': "windows-1255" +} + +# flake8: noqa diff --git a/resources/lib/libraries/requests/packages/chardet/langhungarianmodel.py b/resources/lib/libraries/requests/packages/chardet/langhungarianmodel.py new file mode 100644 index 00000000..49d2f0fe --- /dev/null +++ b/resources/lib/libraries/requests/packages/chardet/langhungarianmodel.py @@ -0,0 +1,225 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is Mozilla Communicator client code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +# 255: Control characters that usually does not exist in any text +# 254: Carriage/Return +# 253: symbol (punctuation) that does not belong to word +# 252: 0 - 9 + +# Character Mapping Table: +Latin2_HungarianCharToOrderMap = ( +255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 +253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 +252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 +253, 28, 40, 54, 45, 32, 50, 49, 38, 39, 53, 36, 41, 34, 35, 47, + 46, 71, 43, 33, 37, 57, 48, 64, 68, 55, 52,253,253,253,253,253, +253, 2, 18, 26, 17, 1, 27, 12, 20, 9, 22, 7, 6, 13, 4, 8, + 23, 67, 10, 5, 3, 21, 19, 65, 62, 16, 11,253,253,253,253,253, +159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174, +175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190, +191,192,193,194,195,196,197, 75,198,199,200,201,202,203,204,205, + 79,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220, +221, 51, 81,222, 78,223,224,225,226, 44,227,228,229, 61,230,231, +232,233,234, 58,235, 66, 59,236,237,238, 60, 69, 63,239,240,241, + 82, 14, 74,242, 70, 80,243, 72,244, 15, 83, 77, 84, 30, 76, 85, +245,246,247, 25, 73, 42, 24,248,249,250, 31, 56, 29,251,252,253, +) + +win1250HungarianCharToOrderMap = ( +255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 +253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 +252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 +253, 28, 40, 54, 45, 32, 50, 49, 38, 39, 53, 36, 41, 34, 35, 47, + 46, 72, 43, 33, 37, 57, 48, 64, 68, 55, 52,253,253,253,253,253, +253, 2, 18, 26, 17, 1, 27, 12, 20, 9, 22, 7, 6, 13, 4, 8, + 23, 67, 10, 5, 3, 21, 19, 65, 62, 16, 11,253,253,253,253,253, +161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176, +177,178,179,180, 78,181, 69,182,183,184,185,186,187,188,189,190, +191,192,193,194,195,196,197, 76,198,199,200,201,202,203,204,205, + 81,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220, +221, 51, 83,222, 80,223,224,225,226, 44,227,228,229, 61,230,231, +232,233,234, 58,235, 66, 59,236,237,238, 60, 70, 63,239,240,241, + 84, 14, 75,242, 71, 82,243, 73,244, 15, 85, 79, 86, 30, 77, 87, +245,246,247, 25, 74, 42, 24,248,249,250, 31, 56, 29,251,252,253, +) + +# Model Table: +# total sequences: 100% +# first 512 sequences: 94.7368% +# first 1024 sequences:5.2623% +# rest sequences: 0.8894% +# negative sequences: 0.0009% +HungarianLangModel = ( +0,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,1,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3, +3,3,3,3,3,3,3,3,3,3,2,3,3,3,3,3,3,3,3,2,2,3,3,1,1,2,2,2,2,2,1,2, +3,2,2,3,3,3,3,3,2,3,3,3,3,3,3,1,2,3,3,3,3,2,3,3,1,1,3,3,0,1,1,1, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0, +3,2,1,3,3,3,3,3,2,3,3,3,3,3,1,1,2,3,3,3,3,3,3,3,1,1,3,2,0,1,1,1, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0, +3,3,3,3,3,3,3,3,3,3,3,1,1,2,3,3,3,1,3,3,3,3,3,1,3,3,2,2,0,3,2,3, +0,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0, +3,3,3,3,3,3,2,3,3,3,2,3,3,2,3,3,3,3,3,2,3,3,2,2,3,2,3,2,0,3,2,2, +0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0, +3,3,3,3,3,3,2,3,3,3,3,3,2,3,3,3,1,2,3,2,2,3,1,2,3,3,2,2,0,3,3,3, +0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0, +3,3,3,3,3,3,3,3,3,3,2,2,3,3,3,3,3,3,2,3,3,3,3,2,3,3,3,3,0,2,3,2, +0,0,0,1,1,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0, +3,3,3,3,3,3,3,3,3,3,3,1,1,1,3,3,2,1,3,2,2,3,2,1,3,2,2,1,0,3,3,1, +0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0, +3,2,2,3,3,3,3,3,1,2,3,3,3,3,1,2,1,3,3,3,3,2,2,3,1,1,3,2,0,1,1,1, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0, +3,3,3,3,3,3,3,3,2,2,3,3,3,3,3,2,1,3,3,3,3,3,2,2,1,3,3,3,0,1,1,2, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0, +3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,3,3,3,2,3,3,2,3,3,3,2,0,3,2,3, +0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,1,0, +3,3,3,3,3,3,2,3,3,3,2,3,2,3,3,3,1,3,2,2,2,3,1,1,3,3,1,1,0,3,3,2, +0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0, +3,3,3,3,3,3,3,2,3,3,3,2,3,2,3,3,3,2,3,3,3,3,3,1,2,3,2,2,0,2,2,2, +0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0, +3,3,3,2,2,2,3,1,3,3,2,2,1,3,3,3,1,1,3,1,2,3,2,3,2,2,2,1,0,2,2,2, +0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0, +3,1,1,3,3,3,3,3,1,2,3,3,3,3,1,2,1,3,3,3,2,2,3,2,1,0,3,2,0,1,1,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,1,1,3,3,3,3,3,1,2,3,3,3,3,1,1,0,3,3,3,3,0,2,3,0,0,2,1,0,1,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,3,3,3,3,2,2,3,3,2,2,2,2,3,3,0,1,2,3,2,3,2,2,3,2,1,2,0,2,2,2, +0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0, +3,3,3,3,3,3,1,2,3,3,3,2,1,2,3,3,2,2,2,3,2,3,3,1,3,3,1,1,0,2,3,2, +0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0, +3,3,3,1,2,2,2,2,3,3,3,1,1,1,3,3,1,1,3,1,1,3,2,1,2,3,1,1,0,2,2,2, +0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0, +3,3,3,2,1,2,1,1,3,3,1,1,1,1,3,3,1,1,2,2,1,2,1,1,2,2,1,1,0,2,2,1, +0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0, +3,3,3,1,1,2,1,1,3,3,1,0,1,1,3,3,2,0,1,1,2,3,1,0,2,2,1,0,0,1,3,2, +0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0, +3,2,1,3,3,3,3,3,1,2,3,2,3,3,2,1,1,3,2,3,2,1,2,2,0,1,2,1,0,0,1,1, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0, +3,3,3,3,2,2,2,2,3,1,2,2,1,1,3,3,0,3,2,1,2,3,2,1,3,3,1,1,0,2,1,3, +0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0, +3,3,3,2,2,2,3,2,3,3,3,2,1,1,3,3,1,1,1,2,2,3,2,3,2,2,2,1,0,2,2,1, +0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0, +1,0,0,3,3,3,3,3,0,0,3,3,2,3,0,0,0,2,3,3,1,0,1,2,0,0,1,1,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,1,2,3,3,3,3,3,1,2,3,3,2,2,1,1,0,3,3,2,2,1,2,2,1,0,2,2,0,1,1,1, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,2,2,1,3,1,2,3,3,2,2,1,1,2,2,1,1,1,1,3,2,1,1,1,1,2,1,0,1,2,1, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0, +2,3,3,1,1,1,1,1,3,3,3,0,1,1,3,3,1,1,1,1,1,2,2,0,3,1,1,2,0,2,1,1, +0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0, +3,1,0,1,2,1,2,2,0,1,2,3,1,2,0,0,0,2,1,1,1,1,1,2,0,0,1,1,0,0,0,0, +1,2,1,2,2,2,1,2,1,2,0,2,0,2,2,1,1,2,1,1,2,1,1,1,0,1,0,0,0,1,1,0, +1,1,1,2,3,2,3,3,0,1,2,2,3,1,0,1,0,2,1,2,2,0,1,1,0,0,1,1,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +1,0,0,3,3,2,2,1,0,0,3,2,3,2,0,0,0,1,1,3,0,0,1,1,0,0,2,1,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,1,1,2,2,3,3,1,0,1,3,2,3,1,1,1,0,1,1,1,1,1,3,1,0,0,2,2,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,1,1,1,2,2,2,1,0,1,2,3,3,2,0,0,0,2,1,1,1,2,1,1,1,0,1,1,1,0,0,0, +1,2,2,2,2,2,1,1,1,2,0,2,1,1,1,1,1,2,1,1,1,1,1,1,0,1,1,1,0,0,1,1, +3,2,2,1,0,0,1,1,2,2,0,3,0,1,2,1,1,0,0,1,1,1,0,1,1,1,1,0,2,1,1,1, +2,2,1,1,1,2,1,2,1,1,1,1,1,1,1,2,1,1,1,2,3,1,1,1,1,1,1,1,1,1,0,1, +2,3,3,0,1,0,0,0,3,3,1,0,0,1,2,2,1,0,0,0,0,2,0,0,1,1,1,0,2,1,1,1, +2,1,1,1,1,1,1,2,1,1,0,1,1,0,1,1,1,0,1,2,1,1,0,1,1,1,1,1,1,1,0,1, +2,3,3,0,1,0,0,0,2,2,0,0,0,0,1,2,2,0,0,0,0,1,0,0,1,1,0,0,2,0,1,0, +2,1,1,1,1,2,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,2,0,1,1,1,1,1,0,1, +3,2,2,0,1,0,1,0,2,3,2,0,0,1,2,2,1,0,0,1,1,1,0,0,2,1,0,1,2,2,1,1, +2,1,1,1,1,1,1,2,1,1,1,1,1,1,0,2,1,0,1,1,0,1,1,1,0,1,1,2,1,1,0,1, +2,2,2,0,0,1,0,0,2,2,1,1,0,0,2,1,1,0,0,0,1,2,0,0,2,1,0,0,2,1,1,1, +2,1,1,1,1,2,1,2,1,1,1,2,2,1,1,2,1,1,1,2,1,1,1,1,1,1,1,1,1,1,0,1, +1,2,3,0,0,0,1,0,3,2,1,0,0,1,2,1,1,0,0,0,0,2,1,0,1,1,0,0,2,1,2,1, +1,1,0,0,0,1,0,1,1,1,1,1,2,0,0,1,0,0,0,2,0,0,1,1,1,1,1,1,1,1,0,1, +3,0,0,2,1,2,2,1,0,0,2,1,2,2,0,0,0,2,1,1,1,0,1,1,0,0,1,1,2,0,0,0, +1,2,1,2,2,1,1,2,1,2,0,1,1,1,1,1,1,1,1,1,2,1,1,0,0,1,1,1,1,0,0,1, +1,3,2,0,0,0,1,0,2,2,2,0,0,0,2,2,1,0,0,0,0,3,1,1,1,1,0,0,2,1,1,1, +2,1,0,1,1,1,0,1,1,1,1,1,1,1,0,2,1,0,0,1,0,1,1,0,1,1,1,1,1,1,0,1, +2,3,2,0,0,0,1,0,2,2,0,0,0,0,2,1,1,0,0,0,0,2,1,0,1,1,0,0,2,1,1,0, +2,1,1,1,1,2,1,2,1,2,0,1,1,1,0,2,1,1,1,2,1,1,1,1,0,1,1,1,1,1,0,1, +3,1,1,2,2,2,3,2,1,1,2,2,1,1,0,1,0,2,2,1,1,1,1,1,0,0,1,1,0,1,1,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +2,2,2,0,0,0,0,0,2,2,0,0,0,0,2,2,1,0,0,0,1,1,0,0,1,2,0,0,2,1,1,1, +2,2,1,1,1,2,1,2,1,1,0,1,1,1,1,2,1,1,1,2,1,1,1,1,0,1,2,1,1,1,0,1, +1,0,0,1,2,3,2,1,0,0,2,0,1,1,0,0,0,1,1,1,1,0,1,1,0,0,1,0,0,0,0,0, +1,2,1,2,1,2,1,1,1,2,0,2,1,1,1,0,1,2,0,0,1,1,1,0,0,0,0,0,0,0,0,0, +2,3,2,0,0,0,0,0,1,1,2,1,0,0,1,1,1,0,0,0,0,2,0,0,1,1,0,0,2,1,1,1, +2,1,1,1,1,1,1,2,1,0,1,1,1,1,0,2,1,1,1,1,1,1,0,1,0,1,1,1,1,1,0,1, +1,2,2,0,1,1,1,0,2,2,2,0,0,0,3,2,1,0,0,0,1,1,0,0,1,1,0,1,1,1,0,0, +1,1,0,1,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1,1,1,0,0,1,1,1,0,1,0,1, +2,1,0,2,1,1,2,2,1,1,2,1,1,1,0,0,0,1,1,0,1,1,1,1,0,0,1,1,1,0,0,0, +1,2,2,2,2,2,1,1,1,2,0,2,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,0,0,0,1,0, +1,2,3,0,0,0,1,0,2,2,0,0,0,0,2,2,0,0,0,0,0,1,0,0,1,0,0,0,2,0,1,0, +2,1,1,1,1,1,0,2,0,0,0,1,2,1,1,1,1,0,1,2,0,1,0,1,0,1,1,1,0,1,0,1, +2,2,2,0,0,0,1,0,2,1,2,0,0,0,1,1,2,0,0,0,0,1,0,0,1,1,0,0,2,1,0,1, +2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,0,1,1,1,1,1,0,1, +1,2,2,0,0,0,1,0,2,2,2,0,0,0,1,1,0,0,0,0,0,1,1,0,2,0,0,1,1,1,0,1, +1,0,1,1,1,1,1,1,0,1,1,1,1,0,0,1,0,0,1,1,0,1,0,1,1,1,1,1,0,0,0,1, +1,0,0,1,0,1,2,1,0,0,1,1,1,2,0,0,0,1,1,0,1,0,1,1,0,0,1,0,0,0,0,0, +0,2,1,2,1,1,1,1,1,2,0,2,0,1,1,0,1,2,1,0,1,1,1,0,0,0,0,0,0,1,0,0, +2,1,1,0,1,2,0,0,1,1,1,0,0,0,1,1,0,0,0,0,0,1,0,0,1,0,0,0,2,1,0,1, +2,2,1,1,1,1,1,2,1,1,0,1,1,1,1,2,1,1,1,2,1,1,0,1,0,1,1,1,1,1,0,1, +1,2,2,0,0,0,0,0,1,1,0,0,0,0,2,1,0,0,0,0,0,2,0,0,2,2,0,0,2,0,0,1, +2,1,1,1,1,1,1,1,0,1,1,0,1,1,0,1,0,0,0,1,1,1,1,0,0,1,1,1,1,0,0,1, +1,1,2,0,0,3,1,0,2,1,1,1,0,0,1,1,1,0,0,0,1,1,0,0,0,1,0,0,1,0,1,0, +1,2,1,0,1,1,1,2,1,1,0,1,1,1,1,1,0,0,0,1,1,1,1,1,0,1,0,0,0,1,0,0, +2,1,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,1,0,0,0,1,0,0,0,0,2,0,0,0, +2,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,2,1,1,0,0,1,1,1,1,1,0,1, +2,1,1,1,2,1,1,1,0,1,1,2,1,0,0,0,0,1,1,1,1,0,1,0,0,0,0,1,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +1,1,0,1,1,1,1,1,0,0,1,1,2,1,0,0,0,1,1,0,0,0,1,1,0,0,1,0,1,0,0,0, +1,2,1,1,1,1,1,1,1,1,0,1,0,1,1,1,1,1,1,0,1,1,1,0,0,0,0,0,0,1,0,0, +2,0,0,0,1,1,1,1,0,0,1,1,0,0,0,0,0,1,1,1,2,0,0,1,0,0,1,0,1,0,0,0, +0,1,1,1,1,1,1,1,1,2,0,1,1,1,1,0,1,1,1,0,1,1,1,0,0,0,0,0,0,0,0,0, +1,0,0,1,1,1,1,1,0,0,2,1,0,1,0,0,0,1,0,1,0,0,0,0,0,0,1,0,0,0,0,0, +0,1,1,1,1,1,1,0,1,1,0,1,0,1,1,0,1,1,0,0,1,1,1,0,0,0,0,0,0,0,0,0, +1,0,0,1,1,1,0,0,0,0,1,0,2,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0, +0,1,1,1,1,1,0,0,1,1,0,1,0,1,0,0,1,1,1,0,1,1,1,0,0,0,0,0,0,0,0,0, +0,0,0,1,0,0,0,0,0,0,1,1,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,1,1,1,0,1,0,0,1,1,0,1,0,1,1,0,1,1,1,0,1,1,1,0,0,0,0,0,0,0,0,0, +2,1,1,1,1,1,1,1,1,1,1,0,0,1,1,1,0,0,1,0,0,1,0,1,0,1,1,1,0,0,1,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +1,0,0,1,1,1,1,0,0,0,1,1,1,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0, +0,1,1,1,1,1,1,0,1,1,0,1,0,1,0,0,1,1,0,0,1,1,0,0,0,0,0,0,0,0,0,0, +) + +Latin2HungarianModel = { + 'charToOrderMap': Latin2_HungarianCharToOrderMap, + 'precedenceMatrix': HungarianLangModel, + 'mTypicalPositiveRatio': 0.947368, + 'keepEnglishLetter': True, + 'charsetName': "ISO-8859-2" +} + +Win1250HungarianModel = { + 'charToOrderMap': win1250HungarianCharToOrderMap, + 'precedenceMatrix': HungarianLangModel, + 'mTypicalPositiveRatio': 0.947368, + 'keepEnglishLetter': True, + 'charsetName': "windows-1250" +} + +# flake8: noqa diff --git a/resources/lib/libraries/requests/packages/chardet/langthaimodel.py b/resources/lib/libraries/requests/packages/chardet/langthaimodel.py new file mode 100644 index 00000000..0508b1b1 --- /dev/null +++ b/resources/lib/libraries/requests/packages/chardet/langthaimodel.py @@ -0,0 +1,200 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is Mozilla Communicator client code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +# 255: Control characters that usually does not exist in any text +# 254: Carriage/Return +# 253: symbol (punctuation) that does not belong to word +# 252: 0 - 9 + +# The following result for thai was collected from a limited sample (1M). + +# Character Mapping Table: +TIS620CharToOrderMap = ( +255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 +253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 +252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 +253,182,106,107,100,183,184,185,101, 94,186,187,108,109,110,111, # 40 +188,189,190, 89, 95,112,113,191,192,193,194,253,253,253,253,253, # 50 +253, 64, 72, 73,114, 74,115,116,102, 81,201,117, 90,103, 78, 82, # 60 + 96,202, 91, 79, 84,104,105, 97, 98, 92,203,253,253,253,253,253, # 70 +209,210,211,212,213, 88,214,215,216,217,218,219,220,118,221,222, +223,224, 99, 85, 83,225,226,227,228,229,230,231,232,233,234,235, +236, 5, 30,237, 24,238, 75, 8, 26, 52, 34, 51,119, 47, 58, 57, + 49, 53, 55, 43, 20, 19, 44, 14, 48, 3, 17, 25, 39, 62, 31, 54, + 45, 9, 16, 2, 61, 15,239, 12, 42, 46, 18, 21, 76, 4, 66, 63, + 22, 10, 1, 36, 23, 13, 40, 27, 32, 35, 86,240,241,242,243,244, + 11, 28, 41, 29, 33,245, 50, 37, 6, 7, 67, 77, 38, 93,246,247, + 68, 56, 59, 65, 69, 60, 70, 80, 71, 87,248,249,250,251,252,253, +) + +# Model Table: +# total sequences: 100% +# first 512 sequences: 92.6386% +# first 1024 sequences:7.3177% +# rest sequences: 1.0230% +# negative sequences: 0.0436% +ThaiLangModel = ( +0,1,3,3,3,3,0,0,3,3,0,3,3,0,3,3,3,3,3,3,3,3,0,0,3,3,3,0,3,3,3,3, +0,3,3,0,0,0,1,3,0,3,3,2,3,3,0,1,2,3,3,3,3,0,2,0,2,0,0,3,2,1,2,2, +3,0,3,3,2,3,0,0,3,3,0,3,3,0,3,3,3,3,3,3,3,3,3,0,3,2,3,0,2,2,2,3, +0,2,3,0,0,0,0,1,0,1,2,3,1,1,3,2,2,0,1,1,0,0,1,0,0,0,0,0,0,0,1,1, +3,3,3,2,3,3,3,3,3,3,3,3,3,3,3,2,2,2,2,2,2,2,3,3,2,3,2,3,3,2,2,2, +3,1,2,3,0,3,3,2,2,1,2,3,3,1,2,0,1,3,0,1,0,0,1,0,0,0,0,0,0,0,1,1, +3,3,2,2,3,3,3,3,1,2,3,3,3,3,3,2,2,2,2,3,3,2,2,3,3,2,2,3,2,3,2,2, +3,3,1,2,3,1,2,2,3,3,1,0,2,1,0,0,3,1,2,1,0,0,1,0,0,0,0,0,0,1,0,1, +3,3,3,3,3,3,2,2,3,3,3,3,2,3,2,2,3,3,2,2,3,2,2,2,2,1,1,3,1,2,1,1, +3,2,1,0,2,1,0,1,0,1,1,0,1,1,0,0,1,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0, +3,3,3,2,3,2,3,3,2,2,3,2,3,3,2,3,1,1,2,3,2,2,2,3,2,2,2,2,2,1,2,1, +2,2,1,1,3,3,2,1,0,1,2,2,0,1,3,0,0,0,1,1,0,0,0,0,0,2,3,0,0,2,1,1, +3,3,2,3,3,2,0,0,3,3,0,3,3,0,2,2,3,1,2,2,1,1,1,0,2,2,2,0,2,2,1,1, +0,2,1,0,2,0,0,2,0,1,0,0,1,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0,0,0,1,0, +3,3,2,3,3,2,0,0,3,3,0,2,3,0,2,1,2,2,2,2,1,2,0,0,2,2,2,0,2,2,1,1, +0,2,1,0,2,0,0,2,0,1,1,0,1,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0, +3,3,2,3,2,3,2,0,2,2,1,3,2,1,3,2,1,2,3,2,2,3,0,2,3,2,2,1,2,2,2,2, +1,2,2,0,0,0,0,2,0,1,2,0,1,1,1,0,1,0,3,1,1,0,0,0,0,0,0,0,0,0,1,0, +3,3,2,3,3,2,3,2,2,2,3,2,2,3,2,2,1,2,3,2,2,3,1,3,2,2,2,3,2,2,2,3, +3,2,1,3,0,1,1,1,0,2,1,1,1,1,1,0,1,0,1,1,0,0,0,0,0,0,0,0,0,2,0,0, +1,0,0,3,0,3,3,3,3,3,0,0,3,0,2,2,3,3,3,3,3,0,0,0,1,1,3,0,0,0,0,2, +0,0,1,0,0,0,0,0,0,0,2,3,0,0,0,3,0,2,0,0,0,0,0,3,0,0,0,0,0,0,0,0, +2,0,3,3,3,3,0,0,2,3,0,0,3,0,3,3,2,3,3,3,3,3,0,0,3,3,3,0,0,0,3,3, +0,0,3,0,0,0,0,2,0,0,2,1,1,3,0,0,1,0,0,2,3,0,1,0,0,0,0,0,0,0,1,0, +3,3,3,3,2,3,3,3,3,3,3,3,1,2,1,3,3,2,2,1,2,2,2,3,1,1,2,0,2,1,2,1, +2,2,1,0,0,0,1,1,0,1,0,1,1,0,0,0,0,0,1,1,0,0,1,0,0,0,0,0,0,0,0,0, +3,0,2,1,2,3,3,3,0,2,0,2,2,0,2,1,3,2,2,1,2,1,0,0,2,2,1,0,2,1,2,2, +0,1,1,0,0,0,0,1,0,1,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,3,3,2,1,3,3,1,1,3,0,2,3,1,1,3,2,1,1,2,0,2,2,3,2,1,1,1,1,1,2, +3,0,0,1,3,1,2,1,2,0,3,0,0,0,1,0,3,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0, +3,3,1,1,3,2,3,3,3,1,3,2,1,3,2,1,3,2,2,2,2,1,3,3,1,2,1,3,1,2,3,0, +2,1,1,3,2,2,2,1,2,1,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2, +3,3,2,3,2,3,3,2,3,2,3,2,3,3,2,1,0,3,2,2,2,1,2,2,2,1,2,2,1,2,1,1, +2,2,2,3,0,1,3,1,1,1,1,0,1,1,0,2,1,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,3,3,2,3,2,2,1,1,3,2,3,2,3,2,0,3,2,2,1,2,0,2,2,2,1,2,2,2,2,1, +3,2,1,2,2,1,0,2,0,1,0,0,1,1,0,0,0,0,0,1,1,0,1,0,0,0,0,0,0,0,0,1, +3,3,3,3,3,2,3,1,2,3,3,2,2,3,0,1,1,2,0,3,3,2,2,3,0,1,1,3,0,0,0,0, +3,1,0,3,3,0,2,0,2,1,0,0,3,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,3,2,3,2,3,3,0,1,3,1,1,2,1,2,1,1,3,1,1,0,2,3,1,1,1,1,1,1,1,1, +3,1,1,2,2,2,2,1,1,1,0,0,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, +3,2,2,1,1,2,1,3,3,2,3,2,2,3,2,2,3,1,2,2,1,2,0,3,2,1,2,2,2,2,2,1, +3,2,1,2,2,2,1,1,1,1,0,0,1,1,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,3,3,3,3,3,3,1,3,3,0,2,1,0,3,2,0,0,3,1,0,1,1,0,1,0,0,0,0,0,1, +1,0,0,1,0,3,2,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,0,2,2,2,3,0,0,1,3,0,3,2,0,3,2,2,3,3,3,3,3,1,0,2,2,2,0,2,2,1,2, +0,2,3,0,0,0,0,1,0,1,0,0,1,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, +3,0,2,3,1,3,3,2,3,3,0,3,3,0,3,2,2,3,2,3,3,3,0,0,2,2,3,0,1,1,1,3, +0,0,3,0,0,0,2,2,0,1,3,0,1,2,2,2,3,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1, +3,2,3,3,2,0,3,3,2,2,3,1,3,2,1,3,2,0,1,2,2,0,2,3,2,1,0,3,0,0,0,0, +3,0,0,2,3,1,3,0,0,3,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,1,3,2,2,2,1,2,0,1,3,1,1,3,1,3,0,0,2,1,1,1,1,2,1,1,1,0,2,1,0,1, +1,2,0,0,0,3,1,1,0,0,0,0,1,0,1,0,0,1,0,1,0,0,0,0,0,3,1,0,0,0,1,0, +3,3,3,3,2,2,2,2,2,1,3,1,1,1,2,0,1,1,2,1,2,1,3,2,0,0,3,1,1,1,1,1, +3,1,0,2,3,0,0,0,3,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,2,3,0,3,3,0,2,0,0,0,0,0,0,0,3,0,0,1,0,0,0,0,0,0,0,0,0,0,0, +0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,2,3,1,3,0,0,1,2,0,0,2,0,3,3,2,3,3,3,2,3,0,0,2,2,2,0,0,0,2,2, +0,0,1,0,0,0,0,3,0,0,0,0,2,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0, +0,0,0,3,0,2,0,0,0,0,0,0,0,0,0,0,1,2,3,1,3,3,0,0,1,0,3,0,0,0,0,0, +0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,1,2,3,1,2,3,1,0,3,0,2,2,1,0,2,1,1,2,0,1,0,0,1,1,1,1,0,1,0,0, +1,0,0,0,0,1,1,0,3,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,3,3,2,1,0,1,1,1,3,1,2,2,2,2,2,2,1,1,1,1,0,3,1,0,1,3,1,1,1,1, +1,1,0,2,0,1,3,1,1,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,2,0,1, +3,0,2,2,1,3,3,2,3,3,0,1,1,0,2,2,1,2,1,3,3,1,0,0,3,2,0,0,0,0,2,1, +0,1,0,0,0,0,1,2,0,1,1,3,1,1,2,2,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0, +0,0,3,0,0,1,0,0,0,3,0,0,3,0,3,1,0,1,1,1,3,2,0,0,0,3,0,0,0,0,2,0, +0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,2,0,0,0,0,0,0,0,0,0, +3,3,1,3,2,1,3,3,1,2,2,0,1,2,1,0,1,2,0,0,0,0,0,3,0,0,0,3,0,0,0,0, +3,0,0,1,1,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,0,1,2,0,3,3,3,2,2,0,1,1,0,1,3,0,0,0,2,2,0,0,0,0,3,1,0,1,0,0,0, +0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,0,2,3,1,2,0,0,2,1,0,3,1,0,1,2,0,1,1,1,1,3,0,0,3,1,1,0,2,2,1,1, +0,2,0,0,0,0,0,1,0,1,0,0,1,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,0,0,3,1,2,0,0,2,2,0,1,2,0,1,0,1,3,1,2,1,0,0,0,2,0,3,0,0,0,1,0, +0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,0,1,1,2,2,0,0,0,2,0,2,1,0,1,1,0,1,1,1,2,1,0,0,1,1,1,0,2,1,1,1, +0,1,1,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,1, +0,0,0,2,0,1,3,1,1,1,1,0,0,0,0,3,2,0,1,0,0,0,1,2,0,0,0,1,0,0,0,0, +0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,3,3,3,3,1,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +1,0,2,3,2,2,0,0,0,1,0,0,0,0,2,3,2,1,2,2,3,0,0,0,2,3,1,0,0,0,1,1, +0,0,1,0,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,1,1,0,1,0,0,0,0,0,0,0,0,0, +3,3,2,2,0,1,0,0,0,0,2,0,2,0,1,0,0,0,1,1,0,0,0,2,1,0,1,0,1,1,0,0, +0,1,0,2,0,0,1,0,3,0,1,0,0,0,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,1,0,0,1,0,0,0,0,0,1,1,2,0,0,0,0,1,0,0,1,3,1,0,0,0,0,1,1,0,0, +0,1,0,0,0,0,3,0,0,0,0,0,0,3,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0, +3,3,1,1,1,1,2,3,0,0,2,1,1,1,1,1,0,2,1,1,0,0,0,2,1,0,1,2,1,1,0,1, +2,1,0,3,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +1,3,1,0,0,0,0,0,0,0,3,0,0,0,3,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,1, +0,0,0,2,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,2,0,0,0,0,0,0,1,2,1,0,1,1,0,2,0,0,1,0,0,2,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,2,0,0,0,1,3,0,1,0,0,0,2,0,0,0,0,0,0,0,1,2,0,0,0,0,0, +3,3,0,0,1,1,2,0,0,1,2,1,0,1,1,1,0,1,1,0,0,2,1,1,0,1,0,0,1,1,1,0, +0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0, +2,2,2,1,0,0,0,0,1,0,0,0,0,3,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0, +2,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +2,3,0,0,1,1,0,0,0,2,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +1,1,0,1,2,0,1,2,0,0,1,1,0,2,0,1,0,0,1,0,0,0,0,1,0,0,0,2,0,0,0,0, +1,0,0,1,0,1,1,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,1,0,0,0,0,0,0,0,1,1,0,1,1,0,2,1,3,0,0,0,0,1,1,0,0,0,0,0,0,0,3, +1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +2,0,1,0,1,0,0,2,0,0,2,0,0,1,1,2,0,0,1,1,0,0,0,1,0,0,0,1,1,0,0,0, +1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0, +1,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,1,1,0,0,0, +2,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +2,0,0,0,0,2,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,1,3,0,0,0, +2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,1,0,0,0,0, +1,0,0,0,0,0,0,0,0,1,0,0,0,0,2,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,1,1,0,0,2,1,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +) + +TIS620ThaiModel = { + 'charToOrderMap': TIS620CharToOrderMap, + 'precedenceMatrix': ThaiLangModel, + 'mTypicalPositiveRatio': 0.926386, + 'keepEnglishLetter': False, + 'charsetName': "TIS-620" +} + +# flake8: noqa diff --git a/resources/lib/libraries/requests/packages/chardet/latin1prober.py b/resources/lib/libraries/requests/packages/chardet/latin1prober.py new file mode 100644 index 00000000..eef35735 --- /dev/null +++ b/resources/lib/libraries/requests/packages/chardet/latin1prober.py @@ -0,0 +1,139 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is Mozilla Universal charset detector code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 2001 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# Shy Shalom - original C code +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +from .charsetprober import CharSetProber +from .constants import eNotMe +from .compat import wrap_ord + +FREQ_CAT_NUM = 4 + +UDF = 0 # undefined +OTH = 1 # other +ASC = 2 # ascii capital letter +ASS = 3 # ascii small letter +ACV = 4 # accent capital vowel +ACO = 5 # accent capital other +ASV = 6 # accent small vowel +ASO = 7 # accent small other +CLASS_NUM = 8 # total classes + +Latin1_CharToClass = ( + OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 00 - 07 + OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 08 - 0F + OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 10 - 17 + OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 18 - 1F + OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 20 - 27 + OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 28 - 2F + OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 30 - 37 + OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 38 - 3F + OTH, ASC, ASC, ASC, ASC, ASC, ASC, ASC, # 40 - 47 + ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, # 48 - 4F + ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, # 50 - 57 + ASC, ASC, ASC, OTH, OTH, OTH, OTH, OTH, # 58 - 5F + OTH, ASS, ASS, ASS, ASS, ASS, ASS, ASS, # 60 - 67 + ASS, ASS, ASS, ASS, ASS, ASS, ASS, ASS, # 68 - 6F + ASS, ASS, ASS, ASS, ASS, ASS, ASS, ASS, # 70 - 77 + ASS, ASS, ASS, OTH, OTH, OTH, OTH, OTH, # 78 - 7F + OTH, UDF, OTH, ASO, OTH, OTH, OTH, OTH, # 80 - 87 + OTH, OTH, ACO, OTH, ACO, UDF, ACO, UDF, # 88 - 8F + UDF, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 90 - 97 + OTH, OTH, ASO, OTH, ASO, UDF, ASO, ACO, # 98 - 9F + OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # A0 - A7 + OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # A8 - AF + OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # B0 - B7 + OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # B8 - BF + ACV, ACV, ACV, ACV, ACV, ACV, ACO, ACO, # C0 - C7 + ACV, ACV, ACV, ACV, ACV, ACV, ACV, ACV, # C8 - CF + ACO, ACO, ACV, ACV, ACV, ACV, ACV, OTH, # D0 - D7 + ACV, ACV, ACV, ACV, ACV, ACO, ACO, ACO, # D8 - DF + ASV, ASV, ASV, ASV, ASV, ASV, ASO, ASO, # E0 - E7 + ASV, ASV, ASV, ASV, ASV, ASV, ASV, ASV, # E8 - EF + ASO, ASO, ASV, ASV, ASV, ASV, ASV, OTH, # F0 - F7 + ASV, ASV, ASV, ASV, ASV, ASO, ASO, ASO, # F8 - FF +) + +# 0 : illegal +# 1 : very unlikely +# 2 : normal +# 3 : very likely +Latin1ClassModel = ( + # UDF OTH ASC ASS ACV ACO ASV ASO + 0, 0, 0, 0, 0, 0, 0, 0, # UDF + 0, 3, 3, 3, 3, 3, 3, 3, # OTH + 0, 3, 3, 3, 3, 3, 3, 3, # ASC + 0, 3, 3, 3, 1, 1, 3, 3, # ASS + 0, 3, 3, 3, 1, 2, 1, 2, # ACV + 0, 3, 3, 3, 3, 3, 3, 3, # ACO + 0, 3, 1, 3, 1, 1, 1, 3, # ASV + 0, 3, 1, 3, 1, 1, 3, 3, # ASO +) + + +class Latin1Prober(CharSetProber): + def __init__(self): + CharSetProber.__init__(self) + self.reset() + + def reset(self): + self._mLastCharClass = OTH + self._mFreqCounter = [0] * FREQ_CAT_NUM + CharSetProber.reset(self) + + def get_charset_name(self): + return "windows-1252" + + def feed(self, aBuf): + aBuf = self.filter_with_english_letters(aBuf) + for c in aBuf: + charClass = Latin1_CharToClass[wrap_ord(c)] + freq = Latin1ClassModel[(self._mLastCharClass * CLASS_NUM) + + charClass] + if freq == 0: + self._mState = eNotMe + break + self._mFreqCounter[freq] += 1 + self._mLastCharClass = charClass + + return self.get_state() + + def get_confidence(self): + if self.get_state() == eNotMe: + return 0.01 + + total = sum(self._mFreqCounter) + if total < 0.01: + confidence = 0.0 + else: + confidence = ((self._mFreqCounter[3] - self._mFreqCounter[1] * 20.0) + / total) + if confidence < 0.0: + confidence = 0.0 + # lower the confidence of latin1 so that other more accurate + # detector can take priority. + confidence = confidence * 0.73 + return confidence diff --git a/resources/lib/libraries/requests/packages/chardet/mbcharsetprober.py b/resources/lib/libraries/requests/packages/chardet/mbcharsetprober.py new file mode 100644 index 00000000..bb42f2fb --- /dev/null +++ b/resources/lib/libraries/requests/packages/chardet/mbcharsetprober.py @@ -0,0 +1,86 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is Mozilla Universal charset detector code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 2001 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# Shy Shalom - original C code +# Proofpoint, Inc. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +import sys +from . import constants +from .charsetprober import CharSetProber + + +class MultiByteCharSetProber(CharSetProber): + def __init__(self): + CharSetProber.__init__(self) + self._mDistributionAnalyzer = None + self._mCodingSM = None + self._mLastChar = [0, 0] + + def reset(self): + CharSetProber.reset(self) + if self._mCodingSM: + self._mCodingSM.reset() + if self._mDistributionAnalyzer: + self._mDistributionAnalyzer.reset() + self._mLastChar = [0, 0] + + def get_charset_name(self): + pass + + def feed(self, aBuf): + aLen = len(aBuf) + for i in range(0, aLen): + codingState = self._mCodingSM.next_state(aBuf[i]) + if codingState == constants.eError: + if constants._debug: + sys.stderr.write(self.get_charset_name() + + ' prober hit error at byte ' + str(i) + + '\n') + self._mState = constants.eNotMe + break + elif codingState == constants.eItsMe: + self._mState = constants.eFoundIt + break + elif codingState == constants.eStart: + charLen = self._mCodingSM.get_current_charlen() + if i == 0: + self._mLastChar[1] = aBuf[0] + self._mDistributionAnalyzer.feed(self._mLastChar, charLen) + else: + self._mDistributionAnalyzer.feed(aBuf[i - 1:i + 1], + charLen) + + self._mLastChar[0] = aBuf[aLen - 1] + + if self.get_state() == constants.eDetecting: + if (self._mDistributionAnalyzer.got_enough_data() and + (self.get_confidence() > constants.SHORTCUT_THRESHOLD)): + self._mState = constants.eFoundIt + + return self.get_state() + + def get_confidence(self): + return self._mDistributionAnalyzer.get_confidence() diff --git a/resources/lib/libraries/requests/packages/chardet/mbcsgroupprober.py b/resources/lib/libraries/requests/packages/chardet/mbcsgroupprober.py new file mode 100644 index 00000000..03c9dcf3 --- /dev/null +++ b/resources/lib/libraries/requests/packages/chardet/mbcsgroupprober.py @@ -0,0 +1,54 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is Mozilla Universal charset detector code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 2001 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# Shy Shalom - original C code +# Proofpoint, Inc. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +from .charsetgroupprober import CharSetGroupProber +from .utf8prober import UTF8Prober +from .sjisprober import SJISProber +from .eucjpprober import EUCJPProber +from .gb2312prober import GB2312Prober +from .euckrprober import EUCKRProber +from .cp949prober import CP949Prober +from .big5prober import Big5Prober +from .euctwprober import EUCTWProber + + +class MBCSGroupProber(CharSetGroupProber): + def __init__(self): + CharSetGroupProber.__init__(self) + self._mProbers = [ + UTF8Prober(), + SJISProber(), + EUCJPProber(), + GB2312Prober(), + EUCKRProber(), + CP949Prober(), + Big5Prober(), + EUCTWProber() + ] + self.reset() diff --git a/resources/lib/libraries/requests/packages/chardet/mbcssm.py b/resources/lib/libraries/requests/packages/chardet/mbcssm.py new file mode 100644 index 00000000..efe678ca --- /dev/null +++ b/resources/lib/libraries/requests/packages/chardet/mbcssm.py @@ -0,0 +1,572 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is mozilla.org code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +from .constants import eStart, eError, eItsMe + +# BIG5 + +BIG5_cls = ( + 1,1,1,1,1,1,1,1, # 00 - 07 #allow 0x00 as legal value + 1,1,1,1,1,1,0,0, # 08 - 0f + 1,1,1,1,1,1,1,1, # 10 - 17 + 1,1,1,0,1,1,1,1, # 18 - 1f + 1,1,1,1,1,1,1,1, # 20 - 27 + 1,1,1,1,1,1,1,1, # 28 - 2f + 1,1,1,1,1,1,1,1, # 30 - 37 + 1,1,1,1,1,1,1,1, # 38 - 3f + 2,2,2,2,2,2,2,2, # 40 - 47 + 2,2,2,2,2,2,2,2, # 48 - 4f + 2,2,2,2,2,2,2,2, # 50 - 57 + 2,2,2,2,2,2,2,2, # 58 - 5f + 2,2,2,2,2,2,2,2, # 60 - 67 + 2,2,2,2,2,2,2,2, # 68 - 6f + 2,2,2,2,2,2,2,2, # 70 - 77 + 2,2,2,2,2,2,2,1, # 78 - 7f + 4,4,4,4,4,4,4,4, # 80 - 87 + 4,4,4,4,4,4,4,4, # 88 - 8f + 4,4,4,4,4,4,4,4, # 90 - 97 + 4,4,4,4,4,4,4,4, # 98 - 9f + 4,3,3,3,3,3,3,3, # a0 - a7 + 3,3,3,3,3,3,3,3, # a8 - af + 3,3,3,3,3,3,3,3, # b0 - b7 + 3,3,3,3,3,3,3,3, # b8 - bf + 3,3,3,3,3,3,3,3, # c0 - c7 + 3,3,3,3,3,3,3,3, # c8 - cf + 3,3,3,3,3,3,3,3, # d0 - d7 + 3,3,3,3,3,3,3,3, # d8 - df + 3,3,3,3,3,3,3,3, # e0 - e7 + 3,3,3,3,3,3,3,3, # e8 - ef + 3,3,3,3,3,3,3,3, # f0 - f7 + 3,3,3,3,3,3,3,0 # f8 - ff +) + +BIG5_st = ( + eError,eStart,eStart, 3,eError,eError,eError,eError,#00-07 + eError,eError,eItsMe,eItsMe,eItsMe,eItsMe,eItsMe,eError,#08-0f + eError,eStart,eStart,eStart,eStart,eStart,eStart,eStart#10-17 +) + +Big5CharLenTable = (0, 1, 1, 2, 0) + +Big5SMModel = {'classTable': BIG5_cls, + 'classFactor': 5, + 'stateTable': BIG5_st, + 'charLenTable': Big5CharLenTable, + 'name': 'Big5'} + +# CP949 + +CP949_cls = ( + 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,0,0, # 00 - 0f + 1,1,1,1,1,1,1,1, 1,1,1,0,1,1,1,1, # 10 - 1f + 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, # 20 - 2f + 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, # 30 - 3f + 1,4,4,4,4,4,4,4, 4,4,4,4,4,4,4,4, # 40 - 4f + 4,4,5,5,5,5,5,5, 5,5,5,1,1,1,1,1, # 50 - 5f + 1,5,5,5,5,5,5,5, 5,5,5,5,5,5,5,5, # 60 - 6f + 5,5,5,5,5,5,5,5, 5,5,5,1,1,1,1,1, # 70 - 7f + 0,6,6,6,6,6,6,6, 6,6,6,6,6,6,6,6, # 80 - 8f + 6,6,6,6,6,6,6,6, 6,6,6,6,6,6,6,6, # 90 - 9f + 6,7,7,7,7,7,7,7, 7,7,7,7,7,8,8,8, # a0 - af + 7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7, # b0 - bf + 7,7,7,7,7,7,9,2, 2,3,2,2,2,2,2,2, # c0 - cf + 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, # d0 - df + 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, # e0 - ef + 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,0, # f0 - ff +) + +CP949_st = ( +#cls= 0 1 2 3 4 5 6 7 8 9 # previous state = + eError,eStart, 3,eError,eStart,eStart, 4, 5,eError, 6, # eStart + eError,eError,eError,eError,eError,eError,eError,eError,eError,eError, # eError + eItsMe,eItsMe,eItsMe,eItsMe,eItsMe,eItsMe,eItsMe,eItsMe,eItsMe,eItsMe, # eItsMe + eError,eError,eStart,eStart,eError,eError,eError,eStart,eStart,eStart, # 3 + eError,eError,eStart,eStart,eStart,eStart,eStart,eStart,eStart,eStart, # 4 + eError,eStart,eStart,eStart,eStart,eStart,eStart,eStart,eStart,eStart, # 5 + eError,eStart,eStart,eStart,eStart,eError,eError,eStart,eStart,eStart, # 6 +) + +CP949CharLenTable = (0, 1, 2, 0, 1, 1, 2, 2, 0, 2) + +CP949SMModel = {'classTable': CP949_cls, + 'classFactor': 10, + 'stateTable': CP949_st, + 'charLenTable': CP949CharLenTable, + 'name': 'CP949'} + +# EUC-JP + +EUCJP_cls = ( + 4,4,4,4,4,4,4,4, # 00 - 07 + 4,4,4,4,4,4,5,5, # 08 - 0f + 4,4,4,4,4,4,4,4, # 10 - 17 + 4,4,4,5,4,4,4,4, # 18 - 1f + 4,4,4,4,4,4,4,4, # 20 - 27 + 4,4,4,4,4,4,4,4, # 28 - 2f + 4,4,4,4,4,4,4,4, # 30 - 37 + 4,4,4,4,4,4,4,4, # 38 - 3f + 4,4,4,4,4,4,4,4, # 40 - 47 + 4,4,4,4,4,4,4,4, # 48 - 4f + 4,4,4,4,4,4,4,4, # 50 - 57 + 4,4,4,4,4,4,4,4, # 58 - 5f + 4,4,4,4,4,4,4,4, # 60 - 67 + 4,4,4,4,4,4,4,4, # 68 - 6f + 4,4,4,4,4,4,4,4, # 70 - 77 + 4,4,4,4,4,4,4,4, # 78 - 7f + 5,5,5,5,5,5,5,5, # 80 - 87 + 5,5,5,5,5,5,1,3, # 88 - 8f + 5,5,5,5,5,5,5,5, # 90 - 97 + 5,5,5,5,5,5,5,5, # 98 - 9f + 5,2,2,2,2,2,2,2, # a0 - a7 + 2,2,2,2,2,2,2,2, # a8 - af + 2,2,2,2,2,2,2,2, # b0 - b7 + 2,2,2,2,2,2,2,2, # b8 - bf + 2,2,2,2,2,2,2,2, # c0 - c7 + 2,2,2,2,2,2,2,2, # c8 - cf + 2,2,2,2,2,2,2,2, # d0 - d7 + 2,2,2,2,2,2,2,2, # d8 - df + 0,0,0,0,0,0,0,0, # e0 - e7 + 0,0,0,0,0,0,0,0, # e8 - ef + 0,0,0,0,0,0,0,0, # f0 - f7 + 0,0,0,0,0,0,0,5 # f8 - ff +) + +EUCJP_st = ( + 3, 4, 3, 5,eStart,eError,eError,eError,#00-07 + eError,eError,eError,eError,eItsMe,eItsMe,eItsMe,eItsMe,#08-0f + eItsMe,eItsMe,eStart,eError,eStart,eError,eError,eError,#10-17 + eError,eError,eStart,eError,eError,eError, 3,eError,#18-1f + 3,eError,eError,eError,eStart,eStart,eStart,eStart#20-27 +) + +EUCJPCharLenTable = (2, 2, 2, 3, 1, 0) + +EUCJPSMModel = {'classTable': EUCJP_cls, + 'classFactor': 6, + 'stateTable': EUCJP_st, + 'charLenTable': EUCJPCharLenTable, + 'name': 'EUC-JP'} + +# EUC-KR + +EUCKR_cls = ( + 1,1,1,1,1,1,1,1, # 00 - 07 + 1,1,1,1,1,1,0,0, # 08 - 0f + 1,1,1,1,1,1,1,1, # 10 - 17 + 1,1,1,0,1,1,1,1, # 18 - 1f + 1,1,1,1,1,1,1,1, # 20 - 27 + 1,1,1,1,1,1,1,1, # 28 - 2f + 1,1,1,1,1,1,1,1, # 30 - 37 + 1,1,1,1,1,1,1,1, # 38 - 3f + 1,1,1,1,1,1,1,1, # 40 - 47 + 1,1,1,1,1,1,1,1, # 48 - 4f + 1,1,1,1,1,1,1,1, # 50 - 57 + 1,1,1,1,1,1,1,1, # 58 - 5f + 1,1,1,1,1,1,1,1, # 60 - 67 + 1,1,1,1,1,1,1,1, # 68 - 6f + 1,1,1,1,1,1,1,1, # 70 - 77 + 1,1,1,1,1,1,1,1, # 78 - 7f + 0,0,0,0,0,0,0,0, # 80 - 87 + 0,0,0,0,0,0,0,0, # 88 - 8f + 0,0,0,0,0,0,0,0, # 90 - 97 + 0,0,0,0,0,0,0,0, # 98 - 9f + 0,2,2,2,2,2,2,2, # a0 - a7 + 2,2,2,2,2,3,3,3, # a8 - af + 2,2,2,2,2,2,2,2, # b0 - b7 + 2,2,2,2,2,2,2,2, # b8 - bf + 2,2,2,2,2,2,2,2, # c0 - c7 + 2,3,2,2,2,2,2,2, # c8 - cf + 2,2,2,2,2,2,2,2, # d0 - d7 + 2,2,2,2,2,2,2,2, # d8 - df + 2,2,2,2,2,2,2,2, # e0 - e7 + 2,2,2,2,2,2,2,2, # e8 - ef + 2,2,2,2,2,2,2,2, # f0 - f7 + 2,2,2,2,2,2,2,0 # f8 - ff +) + +EUCKR_st = ( + eError,eStart, 3,eError,eError,eError,eError,eError,#00-07 + eItsMe,eItsMe,eItsMe,eItsMe,eError,eError,eStart,eStart #08-0f +) + +EUCKRCharLenTable = (0, 1, 2, 0) + +EUCKRSMModel = {'classTable': EUCKR_cls, + 'classFactor': 4, + 'stateTable': EUCKR_st, + 'charLenTable': EUCKRCharLenTable, + 'name': 'EUC-KR'} + +# EUC-TW + +EUCTW_cls = ( + 2,2,2,2,2,2,2,2, # 00 - 07 + 2,2,2,2,2,2,0,0, # 08 - 0f + 2,2,2,2,2,2,2,2, # 10 - 17 + 2,2,2,0,2,2,2,2, # 18 - 1f + 2,2,2,2,2,2,2,2, # 20 - 27 + 2,2,2,2,2,2,2,2, # 28 - 2f + 2,2,2,2,2,2,2,2, # 30 - 37 + 2,2,2,2,2,2,2,2, # 38 - 3f + 2,2,2,2,2,2,2,2, # 40 - 47 + 2,2,2,2,2,2,2,2, # 48 - 4f + 2,2,2,2,2,2,2,2, # 50 - 57 + 2,2,2,2,2,2,2,2, # 58 - 5f + 2,2,2,2,2,2,2,2, # 60 - 67 + 2,2,2,2,2,2,2,2, # 68 - 6f + 2,2,2,2,2,2,2,2, # 70 - 77 + 2,2,2,2,2,2,2,2, # 78 - 7f + 0,0,0,0,0,0,0,0, # 80 - 87 + 0,0,0,0,0,0,6,0, # 88 - 8f + 0,0,0,0,0,0,0,0, # 90 - 97 + 0,0,0,0,0,0,0,0, # 98 - 9f + 0,3,4,4,4,4,4,4, # a0 - a7 + 5,5,1,1,1,1,1,1, # a8 - af + 1,1,1,1,1,1,1,1, # b0 - b7 + 1,1,1,1,1,1,1,1, # b8 - bf + 1,1,3,1,3,3,3,3, # c0 - c7 + 3,3,3,3,3,3,3,3, # c8 - cf + 3,3,3,3,3,3,3,3, # d0 - d7 + 3,3,3,3,3,3,3,3, # d8 - df + 3,3,3,3,3,3,3,3, # e0 - e7 + 3,3,3,3,3,3,3,3, # e8 - ef + 3,3,3,3,3,3,3,3, # f0 - f7 + 3,3,3,3,3,3,3,0 # f8 - ff +) + +EUCTW_st = ( + eError,eError,eStart, 3, 3, 3, 4,eError,#00-07 + eError,eError,eError,eError,eError,eError,eItsMe,eItsMe,#08-0f + eItsMe,eItsMe,eItsMe,eItsMe,eItsMe,eError,eStart,eError,#10-17 + eStart,eStart,eStart,eError,eError,eError,eError,eError,#18-1f + 5,eError,eError,eError,eStart,eError,eStart,eStart,#20-27 + eStart,eError,eStart,eStart,eStart,eStart,eStart,eStart #28-2f +) + +EUCTWCharLenTable = (0, 0, 1, 2, 2, 2, 3) + +EUCTWSMModel = {'classTable': EUCTW_cls, + 'classFactor': 7, + 'stateTable': EUCTW_st, + 'charLenTable': EUCTWCharLenTable, + 'name': 'x-euc-tw'} + +# GB2312 + +GB2312_cls = ( + 1,1,1,1,1,1,1,1, # 00 - 07 + 1,1,1,1,1,1,0,0, # 08 - 0f + 1,1,1,1,1,1,1,1, # 10 - 17 + 1,1,1,0,1,1,1,1, # 18 - 1f + 1,1,1,1,1,1,1,1, # 20 - 27 + 1,1,1,1,1,1,1,1, # 28 - 2f + 3,3,3,3,3,3,3,3, # 30 - 37 + 3,3,1,1,1,1,1,1, # 38 - 3f + 2,2,2,2,2,2,2,2, # 40 - 47 + 2,2,2,2,2,2,2,2, # 48 - 4f + 2,2,2,2,2,2,2,2, # 50 - 57 + 2,2,2,2,2,2,2,2, # 58 - 5f + 2,2,2,2,2,2,2,2, # 60 - 67 + 2,2,2,2,2,2,2,2, # 68 - 6f + 2,2,2,2,2,2,2,2, # 70 - 77 + 2,2,2,2,2,2,2,4, # 78 - 7f + 5,6,6,6,6,6,6,6, # 80 - 87 + 6,6,6,6,6,6,6,6, # 88 - 8f + 6,6,6,6,6,6,6,6, # 90 - 97 + 6,6,6,6,6,6,6,6, # 98 - 9f + 6,6,6,6,6,6,6,6, # a0 - a7 + 6,6,6,6,6,6,6,6, # a8 - af + 6,6,6,6,6,6,6,6, # b0 - b7 + 6,6,6,6,6,6,6,6, # b8 - bf + 6,6,6,6,6,6,6,6, # c0 - c7 + 6,6,6,6,6,6,6,6, # c8 - cf + 6,6,6,6,6,6,6,6, # d0 - d7 + 6,6,6,6,6,6,6,6, # d8 - df + 6,6,6,6,6,6,6,6, # e0 - e7 + 6,6,6,6,6,6,6,6, # e8 - ef + 6,6,6,6,6,6,6,6, # f0 - f7 + 6,6,6,6,6,6,6,0 # f8 - ff +) + +GB2312_st = ( + eError,eStart,eStart,eStart,eStart,eStart, 3,eError,#00-07 + eError,eError,eError,eError,eError,eError,eItsMe,eItsMe,#08-0f + eItsMe,eItsMe,eItsMe,eItsMe,eItsMe,eError,eError,eStart,#10-17 + 4,eError,eStart,eStart,eError,eError,eError,eError,#18-1f + eError,eError, 5,eError,eError,eError,eItsMe,eError,#20-27 + eError,eError,eStart,eStart,eStart,eStart,eStart,eStart #28-2f +) + +# To be accurate, the length of class 6 can be either 2 or 4. +# But it is not necessary to discriminate between the two since +# it is used for frequency analysis only, and we are validing +# each code range there as well. So it is safe to set it to be +# 2 here. +GB2312CharLenTable = (0, 1, 1, 1, 1, 1, 2) + +GB2312SMModel = {'classTable': GB2312_cls, + 'classFactor': 7, + 'stateTable': GB2312_st, + 'charLenTable': GB2312CharLenTable, + 'name': 'GB2312'} + +# Shift_JIS + +SJIS_cls = ( + 1,1,1,1,1,1,1,1, # 00 - 07 + 1,1,1,1,1,1,0,0, # 08 - 0f + 1,1,1,1,1,1,1,1, # 10 - 17 + 1,1,1,0,1,1,1,1, # 18 - 1f + 1,1,1,1,1,1,1,1, # 20 - 27 + 1,1,1,1,1,1,1,1, # 28 - 2f + 1,1,1,1,1,1,1,1, # 30 - 37 + 1,1,1,1,1,1,1,1, # 38 - 3f + 2,2,2,2,2,2,2,2, # 40 - 47 + 2,2,2,2,2,2,2,2, # 48 - 4f + 2,2,2,2,2,2,2,2, # 50 - 57 + 2,2,2,2,2,2,2,2, # 58 - 5f + 2,2,2,2,2,2,2,2, # 60 - 67 + 2,2,2,2,2,2,2,2, # 68 - 6f + 2,2,2,2,2,2,2,2, # 70 - 77 + 2,2,2,2,2,2,2,1, # 78 - 7f + 3,3,3,3,3,2,2,3, # 80 - 87 + 3,3,3,3,3,3,3,3, # 88 - 8f + 3,3,3,3,3,3,3,3, # 90 - 97 + 3,3,3,3,3,3,3,3, # 98 - 9f + #0xa0 is illegal in sjis encoding, but some pages does + #contain such byte. We need to be more error forgiven. + 2,2,2,2,2,2,2,2, # a0 - a7 + 2,2,2,2,2,2,2,2, # a8 - af + 2,2,2,2,2,2,2,2, # b0 - b7 + 2,2,2,2,2,2,2,2, # b8 - bf + 2,2,2,2,2,2,2,2, # c0 - c7 + 2,2,2,2,2,2,2,2, # c8 - cf + 2,2,2,2,2,2,2,2, # d0 - d7 + 2,2,2,2,2,2,2,2, # d8 - df + 3,3,3,3,3,3,3,3, # e0 - e7 + 3,3,3,3,3,4,4,4, # e8 - ef + 3,3,3,3,3,3,3,3, # f0 - f7 + 3,3,3,3,3,0,0,0) # f8 - ff + + +SJIS_st = ( + eError,eStart,eStart, 3,eError,eError,eError,eError,#00-07 + eError,eError,eError,eError,eItsMe,eItsMe,eItsMe,eItsMe,#08-0f + eItsMe,eItsMe,eError,eError,eStart,eStart,eStart,eStart #10-17 +) + +SJISCharLenTable = (0, 1, 1, 2, 0, 0) + +SJISSMModel = {'classTable': SJIS_cls, + 'classFactor': 6, + 'stateTable': SJIS_st, + 'charLenTable': SJISCharLenTable, + 'name': 'Shift_JIS'} + +# UCS2-BE + +UCS2BE_cls = ( + 0,0,0,0,0,0,0,0, # 00 - 07 + 0,0,1,0,0,2,0,0, # 08 - 0f + 0,0,0,0,0,0,0,0, # 10 - 17 + 0,0,0,3,0,0,0,0, # 18 - 1f + 0,0,0,0,0,0,0,0, # 20 - 27 + 0,3,3,3,3,3,0,0, # 28 - 2f + 0,0,0,0,0,0,0,0, # 30 - 37 + 0,0,0,0,0,0,0,0, # 38 - 3f + 0,0,0,0,0,0,0,0, # 40 - 47 + 0,0,0,0,0,0,0,0, # 48 - 4f + 0,0,0,0,0,0,0,0, # 50 - 57 + 0,0,0,0,0,0,0,0, # 58 - 5f + 0,0,0,0,0,0,0,0, # 60 - 67 + 0,0,0,0,0,0,0,0, # 68 - 6f + 0,0,0,0,0,0,0,0, # 70 - 77 + 0,0,0,0,0,0,0,0, # 78 - 7f + 0,0,0,0,0,0,0,0, # 80 - 87 + 0,0,0,0,0,0,0,0, # 88 - 8f + 0,0,0,0,0,0,0,0, # 90 - 97 + 0,0,0,0,0,0,0,0, # 98 - 9f + 0,0,0,0,0,0,0,0, # a0 - a7 + 0,0,0,0,0,0,0,0, # a8 - af + 0,0,0,0,0,0,0,0, # b0 - b7 + 0,0,0,0,0,0,0,0, # b8 - bf + 0,0,0,0,0,0,0,0, # c0 - c7 + 0,0,0,0,0,0,0,0, # c8 - cf + 0,0,0,0,0,0,0,0, # d0 - d7 + 0,0,0,0,0,0,0,0, # d8 - df + 0,0,0,0,0,0,0,0, # e0 - e7 + 0,0,0,0,0,0,0,0, # e8 - ef + 0,0,0,0,0,0,0,0, # f0 - f7 + 0,0,0,0,0,0,4,5 # f8 - ff +) + +UCS2BE_st = ( + 5, 7, 7,eError, 4, 3,eError,eError,#00-07 + eError,eError,eError,eError,eItsMe,eItsMe,eItsMe,eItsMe,#08-0f + eItsMe,eItsMe, 6, 6, 6, 6,eError,eError,#10-17 + 6, 6, 6, 6, 6,eItsMe, 6, 6,#18-1f + 6, 6, 6, 6, 5, 7, 7,eError,#20-27 + 5, 8, 6, 6,eError, 6, 6, 6,#28-2f + 6, 6, 6, 6,eError,eError,eStart,eStart #30-37 +) + +UCS2BECharLenTable = (2, 2, 2, 0, 2, 2) + +UCS2BESMModel = {'classTable': UCS2BE_cls, + 'classFactor': 6, + 'stateTable': UCS2BE_st, + 'charLenTable': UCS2BECharLenTable, + 'name': 'UTF-16BE'} + +# UCS2-LE + +UCS2LE_cls = ( + 0,0,0,0,0,0,0,0, # 00 - 07 + 0,0,1,0,0,2,0,0, # 08 - 0f + 0,0,0,0,0,0,0,0, # 10 - 17 + 0,0,0,3,0,0,0,0, # 18 - 1f + 0,0,0,0,0,0,0,0, # 20 - 27 + 0,3,3,3,3,3,0,0, # 28 - 2f + 0,0,0,0,0,0,0,0, # 30 - 37 + 0,0,0,0,0,0,0,0, # 38 - 3f + 0,0,0,0,0,0,0,0, # 40 - 47 + 0,0,0,0,0,0,0,0, # 48 - 4f + 0,0,0,0,0,0,0,0, # 50 - 57 + 0,0,0,0,0,0,0,0, # 58 - 5f + 0,0,0,0,0,0,0,0, # 60 - 67 + 0,0,0,0,0,0,0,0, # 68 - 6f + 0,0,0,0,0,0,0,0, # 70 - 77 + 0,0,0,0,0,0,0,0, # 78 - 7f + 0,0,0,0,0,0,0,0, # 80 - 87 + 0,0,0,0,0,0,0,0, # 88 - 8f + 0,0,0,0,0,0,0,0, # 90 - 97 + 0,0,0,0,0,0,0,0, # 98 - 9f + 0,0,0,0,0,0,0,0, # a0 - a7 + 0,0,0,0,0,0,0,0, # a8 - af + 0,0,0,0,0,0,0,0, # b0 - b7 + 0,0,0,0,0,0,0,0, # b8 - bf + 0,0,0,0,0,0,0,0, # c0 - c7 + 0,0,0,0,0,0,0,0, # c8 - cf + 0,0,0,0,0,0,0,0, # d0 - d7 + 0,0,0,0,0,0,0,0, # d8 - df + 0,0,0,0,0,0,0,0, # e0 - e7 + 0,0,0,0,0,0,0,0, # e8 - ef + 0,0,0,0,0,0,0,0, # f0 - f7 + 0,0,0,0,0,0,4,5 # f8 - ff +) + +UCS2LE_st = ( + 6, 6, 7, 6, 4, 3,eError,eError,#00-07 + eError,eError,eError,eError,eItsMe,eItsMe,eItsMe,eItsMe,#08-0f + eItsMe,eItsMe, 5, 5, 5,eError,eItsMe,eError,#10-17 + 5, 5, 5,eError, 5,eError, 6, 6,#18-1f + 7, 6, 8, 8, 5, 5, 5,eError,#20-27 + 5, 5, 5,eError,eError,eError, 5, 5,#28-2f + 5, 5, 5,eError, 5,eError,eStart,eStart #30-37 +) + +UCS2LECharLenTable = (2, 2, 2, 2, 2, 2) + +UCS2LESMModel = {'classTable': UCS2LE_cls, + 'classFactor': 6, + 'stateTable': UCS2LE_st, + 'charLenTable': UCS2LECharLenTable, + 'name': 'UTF-16LE'} + +# UTF-8 + +UTF8_cls = ( + 1,1,1,1,1,1,1,1, # 00 - 07 #allow 0x00 as a legal value + 1,1,1,1,1,1,0,0, # 08 - 0f + 1,1,1,1,1,1,1,1, # 10 - 17 + 1,1,1,0,1,1,1,1, # 18 - 1f + 1,1,1,1,1,1,1,1, # 20 - 27 + 1,1,1,1,1,1,1,1, # 28 - 2f + 1,1,1,1,1,1,1,1, # 30 - 37 + 1,1,1,1,1,1,1,1, # 38 - 3f + 1,1,1,1,1,1,1,1, # 40 - 47 + 1,1,1,1,1,1,1,1, # 48 - 4f + 1,1,1,1,1,1,1,1, # 50 - 57 + 1,1,1,1,1,1,1,1, # 58 - 5f + 1,1,1,1,1,1,1,1, # 60 - 67 + 1,1,1,1,1,1,1,1, # 68 - 6f + 1,1,1,1,1,1,1,1, # 70 - 77 + 1,1,1,1,1,1,1,1, # 78 - 7f + 2,2,2,2,3,3,3,3, # 80 - 87 + 4,4,4,4,4,4,4,4, # 88 - 8f + 4,4,4,4,4,4,4,4, # 90 - 97 + 4,4,4,4,4,4,4,4, # 98 - 9f + 5,5,5,5,5,5,5,5, # a0 - a7 + 5,5,5,5,5,5,5,5, # a8 - af + 5,5,5,5,5,5,5,5, # b0 - b7 + 5,5,5,5,5,5,5,5, # b8 - bf + 0,0,6,6,6,6,6,6, # c0 - c7 + 6,6,6,6,6,6,6,6, # c8 - cf + 6,6,6,6,6,6,6,6, # d0 - d7 + 6,6,6,6,6,6,6,6, # d8 - df + 7,8,8,8,8,8,8,8, # e0 - e7 + 8,8,8,8,8,9,8,8, # e8 - ef + 10,11,11,11,11,11,11,11, # f0 - f7 + 12,13,13,13,14,15,0,0 # f8 - ff +) + +UTF8_st = ( + eError,eStart,eError,eError,eError,eError, 12, 10,#00-07 + 9, 11, 8, 7, 6, 5, 4, 3,#08-0f + eError,eError,eError,eError,eError,eError,eError,eError,#10-17 + eError,eError,eError,eError,eError,eError,eError,eError,#18-1f + eItsMe,eItsMe,eItsMe,eItsMe,eItsMe,eItsMe,eItsMe,eItsMe,#20-27 + eItsMe,eItsMe,eItsMe,eItsMe,eItsMe,eItsMe,eItsMe,eItsMe,#28-2f + eError,eError, 5, 5, 5, 5,eError,eError,#30-37 + eError,eError,eError,eError,eError,eError,eError,eError,#38-3f + eError,eError,eError, 5, 5, 5,eError,eError,#40-47 + eError,eError,eError,eError,eError,eError,eError,eError,#48-4f + eError,eError, 7, 7, 7, 7,eError,eError,#50-57 + eError,eError,eError,eError,eError,eError,eError,eError,#58-5f + eError,eError,eError,eError, 7, 7,eError,eError,#60-67 + eError,eError,eError,eError,eError,eError,eError,eError,#68-6f + eError,eError, 9, 9, 9, 9,eError,eError,#70-77 + eError,eError,eError,eError,eError,eError,eError,eError,#78-7f + eError,eError,eError,eError,eError, 9,eError,eError,#80-87 + eError,eError,eError,eError,eError,eError,eError,eError,#88-8f + eError,eError, 12, 12, 12, 12,eError,eError,#90-97 + eError,eError,eError,eError,eError,eError,eError,eError,#98-9f + eError,eError,eError,eError,eError, 12,eError,eError,#a0-a7 + eError,eError,eError,eError,eError,eError,eError,eError,#a8-af + eError,eError, 12, 12, 12,eError,eError,eError,#b0-b7 + eError,eError,eError,eError,eError,eError,eError,eError,#b8-bf + eError,eError,eStart,eStart,eStart,eStart,eError,eError,#c0-c7 + eError,eError,eError,eError,eError,eError,eError,eError #c8-cf +) + +UTF8CharLenTable = (0, 1, 0, 0, 0, 0, 2, 3, 3, 3, 4, 4, 5, 5, 6, 6) + +UTF8SMModel = {'classTable': UTF8_cls, + 'classFactor': 16, + 'stateTable': UTF8_st, + 'charLenTable': UTF8CharLenTable, + 'name': 'UTF-8'} diff --git a/resources/lib/libraries/requests/packages/chardet/sbcharsetprober.py b/resources/lib/libraries/requests/packages/chardet/sbcharsetprober.py new file mode 100644 index 00000000..37291bd2 --- /dev/null +++ b/resources/lib/libraries/requests/packages/chardet/sbcharsetprober.py @@ -0,0 +1,120 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is Mozilla Universal charset detector code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 2001 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# Shy Shalom - original C code +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +import sys +from . import constants +from .charsetprober import CharSetProber +from .compat import wrap_ord + +SAMPLE_SIZE = 64 +SB_ENOUGH_REL_THRESHOLD = 1024 +POSITIVE_SHORTCUT_THRESHOLD = 0.95 +NEGATIVE_SHORTCUT_THRESHOLD = 0.05 +SYMBOL_CAT_ORDER = 250 +NUMBER_OF_SEQ_CAT = 4 +POSITIVE_CAT = NUMBER_OF_SEQ_CAT - 1 +#NEGATIVE_CAT = 0 + + +class SingleByteCharSetProber(CharSetProber): + def __init__(self, model, reversed=False, nameProber=None): + CharSetProber.__init__(self) + self._mModel = model + # TRUE if we need to reverse every pair in the model lookup + self._mReversed = reversed + # Optional auxiliary prober for name decision + self._mNameProber = nameProber + self.reset() + + def reset(self): + CharSetProber.reset(self) + # char order of last character + self._mLastOrder = 255 + self._mSeqCounters = [0] * NUMBER_OF_SEQ_CAT + self._mTotalSeqs = 0 + self._mTotalChar = 0 + # characters that fall in our sampling range + self._mFreqChar = 0 + + def get_charset_name(self): + if self._mNameProber: + return self._mNameProber.get_charset_name() + else: + return self._mModel['charsetName'] + + def feed(self, aBuf): + if not self._mModel['keepEnglishLetter']: + aBuf = self.filter_without_english_letters(aBuf) + aLen = len(aBuf) + if not aLen: + return self.get_state() + for c in aBuf: + order = self._mModel['charToOrderMap'][wrap_ord(c)] + if order < SYMBOL_CAT_ORDER: + self._mTotalChar += 1 + if order < SAMPLE_SIZE: + self._mFreqChar += 1 + if self._mLastOrder < SAMPLE_SIZE: + self._mTotalSeqs += 1 + if not self._mReversed: + i = (self._mLastOrder * SAMPLE_SIZE) + order + model = self._mModel['precedenceMatrix'][i] + else: # reverse the order of the letters in the lookup + i = (order * SAMPLE_SIZE) + self._mLastOrder + model = self._mModel['precedenceMatrix'][i] + self._mSeqCounters[model] += 1 + self._mLastOrder = order + + if self.get_state() == constants.eDetecting: + if self._mTotalSeqs > SB_ENOUGH_REL_THRESHOLD: + cf = self.get_confidence() + if cf > POSITIVE_SHORTCUT_THRESHOLD: + if constants._debug: + sys.stderr.write('%s confidence = %s, we have a' + 'winner\n' % + (self._mModel['charsetName'], cf)) + self._mState = constants.eFoundIt + elif cf < NEGATIVE_SHORTCUT_THRESHOLD: + if constants._debug: + sys.stderr.write('%s confidence = %s, below negative' + 'shortcut threshhold %s\n' % + (self._mModel['charsetName'], cf, + NEGATIVE_SHORTCUT_THRESHOLD)) + self._mState = constants.eNotMe + + return self.get_state() + + def get_confidence(self): + r = 0.01 + if self._mTotalSeqs > 0: + r = ((1.0 * self._mSeqCounters[POSITIVE_CAT]) / self._mTotalSeqs + / self._mModel['mTypicalPositiveRatio']) + r = r * self._mFreqChar / self._mTotalChar + if r >= 1.0: + r = 0.99 + return r diff --git a/resources/lib/libraries/requests/packages/chardet/sbcsgroupprober.py b/resources/lib/libraries/requests/packages/chardet/sbcsgroupprober.py new file mode 100644 index 00000000..1b6196cd --- /dev/null +++ b/resources/lib/libraries/requests/packages/chardet/sbcsgroupprober.py @@ -0,0 +1,69 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is Mozilla Universal charset detector code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 2001 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# Shy Shalom - original C code +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +from .charsetgroupprober import CharSetGroupProber +from .sbcharsetprober import SingleByteCharSetProber +from .langcyrillicmodel import (Win1251CyrillicModel, Koi8rModel, + Latin5CyrillicModel, MacCyrillicModel, + Ibm866Model, Ibm855Model) +from .langgreekmodel import Latin7GreekModel, Win1253GreekModel +from .langbulgarianmodel import Latin5BulgarianModel, Win1251BulgarianModel +from .langhungarianmodel import Latin2HungarianModel, Win1250HungarianModel +from .langthaimodel import TIS620ThaiModel +from .langhebrewmodel import Win1255HebrewModel +from .hebrewprober import HebrewProber + + +class SBCSGroupProber(CharSetGroupProber): + def __init__(self): + CharSetGroupProber.__init__(self) + self._mProbers = [ + SingleByteCharSetProber(Win1251CyrillicModel), + SingleByteCharSetProber(Koi8rModel), + SingleByteCharSetProber(Latin5CyrillicModel), + SingleByteCharSetProber(MacCyrillicModel), + SingleByteCharSetProber(Ibm866Model), + SingleByteCharSetProber(Ibm855Model), + SingleByteCharSetProber(Latin7GreekModel), + SingleByteCharSetProber(Win1253GreekModel), + SingleByteCharSetProber(Latin5BulgarianModel), + SingleByteCharSetProber(Win1251BulgarianModel), + SingleByteCharSetProber(Latin2HungarianModel), + SingleByteCharSetProber(Win1250HungarianModel), + SingleByteCharSetProber(TIS620ThaiModel), + ] + hebrewProber = HebrewProber() + logicalHebrewProber = SingleByteCharSetProber(Win1255HebrewModel, + False, hebrewProber) + visualHebrewProber = SingleByteCharSetProber(Win1255HebrewModel, True, + hebrewProber) + hebrewProber.set_model_probers(logicalHebrewProber, visualHebrewProber) + self._mProbers.extend([hebrewProber, logicalHebrewProber, + visualHebrewProber]) + + self.reset() diff --git a/resources/lib/libraries/requests/packages/chardet/sjisprober.py b/resources/lib/libraries/requests/packages/chardet/sjisprober.py new file mode 100644 index 00000000..cd0e9e70 --- /dev/null +++ b/resources/lib/libraries/requests/packages/chardet/sjisprober.py @@ -0,0 +1,91 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is mozilla.org code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +import sys +from .mbcharsetprober import MultiByteCharSetProber +from .codingstatemachine import CodingStateMachine +from .chardistribution import SJISDistributionAnalysis +from .jpcntx import SJISContextAnalysis +from .mbcssm import SJISSMModel +from . import constants + + +class SJISProber(MultiByteCharSetProber): + def __init__(self): + MultiByteCharSetProber.__init__(self) + self._mCodingSM = CodingStateMachine(SJISSMModel) + self._mDistributionAnalyzer = SJISDistributionAnalysis() + self._mContextAnalyzer = SJISContextAnalysis() + self.reset() + + def reset(self): + MultiByteCharSetProber.reset(self) + self._mContextAnalyzer.reset() + + def get_charset_name(self): + return self._mContextAnalyzer.get_charset_name() + + def feed(self, aBuf): + aLen = len(aBuf) + for i in range(0, aLen): + codingState = self._mCodingSM.next_state(aBuf[i]) + if codingState == constants.eError: + if constants._debug: + sys.stderr.write(self.get_charset_name() + + ' prober hit error at byte ' + str(i) + + '\n') + self._mState = constants.eNotMe + break + elif codingState == constants.eItsMe: + self._mState = constants.eFoundIt + break + elif codingState == constants.eStart: + charLen = self._mCodingSM.get_current_charlen() + if i == 0: + self._mLastChar[1] = aBuf[0] + self._mContextAnalyzer.feed(self._mLastChar[2 - charLen:], + charLen) + self._mDistributionAnalyzer.feed(self._mLastChar, charLen) + else: + self._mContextAnalyzer.feed(aBuf[i + 1 - charLen:i + 3 + - charLen], charLen) + self._mDistributionAnalyzer.feed(aBuf[i - 1:i + 1], + charLen) + + self._mLastChar[0] = aBuf[aLen - 1] + + if self.get_state() == constants.eDetecting: + if (self._mContextAnalyzer.got_enough_data() and + (self.get_confidence() > constants.SHORTCUT_THRESHOLD)): + self._mState = constants.eFoundIt + + return self.get_state() + + def get_confidence(self): + contxtCf = self._mContextAnalyzer.get_confidence() + distribCf = self._mDistributionAnalyzer.get_confidence() + return max(contxtCf, distribCf) diff --git a/resources/lib/libraries/requests/packages/chardet/universaldetector.py b/resources/lib/libraries/requests/packages/chardet/universaldetector.py new file mode 100644 index 00000000..476522b9 --- /dev/null +++ b/resources/lib/libraries/requests/packages/chardet/universaldetector.py @@ -0,0 +1,170 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is Mozilla Universal charset detector code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 2001 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# Shy Shalom - original C code +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +from . import constants +import sys +import codecs +from .latin1prober import Latin1Prober # windows-1252 +from .mbcsgroupprober import MBCSGroupProber # multi-byte character sets +from .sbcsgroupprober import SBCSGroupProber # single-byte character sets +from .escprober import EscCharSetProber # ISO-2122, etc. +import re + +MINIMUM_THRESHOLD = 0.20 +ePureAscii = 0 +eEscAscii = 1 +eHighbyte = 2 + + +class UniversalDetector: + def __init__(self): + self._highBitDetector = re.compile(b'[\x80-\xFF]') + self._escDetector = re.compile(b'(\033|~{)') + self._mEscCharSetProber = None + self._mCharSetProbers = [] + self.reset() + + def reset(self): + self.result = {'encoding': None, 'confidence': 0.0} + self.done = False + self._mStart = True + self._mGotData = False + self._mInputState = ePureAscii + self._mLastChar = b'' + if self._mEscCharSetProber: + self._mEscCharSetProber.reset() + for prober in self._mCharSetProbers: + prober.reset() + + def feed(self, aBuf): + if self.done: + return + + aLen = len(aBuf) + if not aLen: + return + + if not self._mGotData: + # If the data starts with BOM, we know it is UTF + if aBuf[:3] == codecs.BOM_UTF8: + # EF BB BF UTF-8 with BOM + self.result = {'encoding': "UTF-8-SIG", 'confidence': 1.0} + elif aBuf[:4] == codecs.BOM_UTF32_LE: + # FF FE 00 00 UTF-32, little-endian BOM + self.result = {'encoding': "UTF-32LE", 'confidence': 1.0} + elif aBuf[:4] == codecs.BOM_UTF32_BE: + # 00 00 FE FF UTF-32, big-endian BOM + self.result = {'encoding': "UTF-32BE", 'confidence': 1.0} + elif aBuf[:4] == b'\xFE\xFF\x00\x00': + # FE FF 00 00 UCS-4, unusual octet order BOM (3412) + self.result = { + 'encoding': "X-ISO-10646-UCS-4-3412", + 'confidence': 1.0 + } + elif aBuf[:4] == b'\x00\x00\xFF\xFE': + # 00 00 FF FE UCS-4, unusual octet order BOM (2143) + self.result = { + 'encoding': "X-ISO-10646-UCS-4-2143", + 'confidence': 1.0 + } + elif aBuf[:2] == codecs.BOM_LE: + # FF FE UTF-16, little endian BOM + self.result = {'encoding': "UTF-16LE", 'confidence': 1.0} + elif aBuf[:2] == codecs.BOM_BE: + # FE FF UTF-16, big endian BOM + self.result = {'encoding': "UTF-16BE", 'confidence': 1.0} + + self._mGotData = True + if self.result['encoding'] and (self.result['confidence'] > 0.0): + self.done = True + return + + if self._mInputState == ePureAscii: + if self._highBitDetector.search(aBuf): + self._mInputState = eHighbyte + elif ((self._mInputState == ePureAscii) and + self._escDetector.search(self._mLastChar + aBuf)): + self._mInputState = eEscAscii + + self._mLastChar = aBuf[-1:] + + if self._mInputState == eEscAscii: + if not self._mEscCharSetProber: + self._mEscCharSetProber = EscCharSetProber() + if self._mEscCharSetProber.feed(aBuf) == constants.eFoundIt: + self.result = {'encoding': self._mEscCharSetProber.get_charset_name(), + 'confidence': self._mEscCharSetProber.get_confidence()} + self.done = True + elif self._mInputState == eHighbyte: + if not self._mCharSetProbers: + self._mCharSetProbers = [MBCSGroupProber(), SBCSGroupProber(), + Latin1Prober()] + for prober in self._mCharSetProbers: + if prober.feed(aBuf) == constants.eFoundIt: + self.result = {'encoding': prober.get_charset_name(), + 'confidence': prober.get_confidence()} + self.done = True + break + + def close(self): + if self.done: + return + if not self._mGotData: + if constants._debug: + sys.stderr.write('no data received!\n') + return + self.done = True + + if self._mInputState == ePureAscii: + self.result = {'encoding': 'ascii', 'confidence': 1.0} + return self.result + + if self._mInputState == eHighbyte: + proberConfidence = None + maxProberConfidence = 0.0 + maxProber = None + for prober in self._mCharSetProbers: + if not prober: + continue + proberConfidence = prober.get_confidence() + if proberConfidence > maxProberConfidence: + maxProberConfidence = proberConfidence + maxProber = prober + if maxProber and (maxProberConfidence > MINIMUM_THRESHOLD): + self.result = {'encoding': maxProber.get_charset_name(), + 'confidence': maxProber.get_confidence()} + return self.result + + if constants._debug: + sys.stderr.write('no probers hit minimum threshhold\n') + for prober in self._mCharSetProbers[0].mProbers: + if not prober: + continue + sys.stderr.write('%s confidence = %s\n' % + (prober.get_charset_name(), + prober.get_confidence())) diff --git a/resources/lib/libraries/requests/packages/chardet/utf8prober.py b/resources/lib/libraries/requests/packages/chardet/utf8prober.py new file mode 100644 index 00000000..1c0bb5d8 --- /dev/null +++ b/resources/lib/libraries/requests/packages/chardet/utf8prober.py @@ -0,0 +1,76 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is mozilla.org code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +from . import constants +from .charsetprober import CharSetProber +from .codingstatemachine import CodingStateMachine +from .mbcssm import UTF8SMModel + +ONE_CHAR_PROB = 0.5 + + +class UTF8Prober(CharSetProber): + def __init__(self): + CharSetProber.__init__(self) + self._mCodingSM = CodingStateMachine(UTF8SMModel) + self.reset() + + def reset(self): + CharSetProber.reset(self) + self._mCodingSM.reset() + self._mNumOfMBChar = 0 + + def get_charset_name(self): + return "utf-8" + + def feed(self, aBuf): + for c in aBuf: + codingState = self._mCodingSM.next_state(c) + if codingState == constants.eError: + self._mState = constants.eNotMe + break + elif codingState == constants.eItsMe: + self._mState = constants.eFoundIt + break + elif codingState == constants.eStart: + if self._mCodingSM.get_current_charlen() >= 2: + self._mNumOfMBChar += 1 + + if self.get_state() == constants.eDetecting: + if self.get_confidence() > constants.SHORTCUT_THRESHOLD: + self._mState = constants.eFoundIt + + return self.get_state() + + def get_confidence(self): + unlike = 0.99 + if self._mNumOfMBChar < 6: + for i in range(0, self._mNumOfMBChar): + unlike = unlike * ONE_CHAR_PROB + return 1.0 - unlike + else: + return unlike diff --git a/resources/lib/libraries/requests/packages/urllib3/__init__.py b/resources/lib/libraries/requests/packages/urllib3/__init__.py new file mode 100644 index 00000000..e43991a9 --- /dev/null +++ b/resources/lib/libraries/requests/packages/urllib3/__init__.py @@ -0,0 +1,93 @@ +""" +urllib3 - Thread-safe connection pooling and re-using. +""" + +from __future__ import absolute_import +import warnings + +from .connectionpool import ( + HTTPConnectionPool, + HTTPSConnectionPool, + connection_from_url +) + +from . import exceptions +from .filepost import encode_multipart_formdata +from .poolmanager import PoolManager, ProxyManager, proxy_from_url +from .response import HTTPResponse +from .util.request import make_headers +from .util.url import get_host +from .util.timeout import Timeout +from .util.retry import Retry + + +# Set default logging handler to avoid "No handler found" warnings. +import logging +try: # Python 2.7+ + from logging import NullHandler +except ImportError: + class NullHandler(logging.Handler): + def emit(self, record): + pass + +__author__ = 'Andrey Petrov (andrey.petrov@shazow.net)' +__license__ = 'MIT' +__version__ = '1.13.1' + +__all__ = ( + 'HTTPConnectionPool', + 'HTTPSConnectionPool', + 'PoolManager', + 'ProxyManager', + 'HTTPResponse', + 'Retry', + 'Timeout', + 'add_stderr_logger', + 'connection_from_url', + 'disable_warnings', + 'encode_multipart_formdata', + 'get_host', + 'make_headers', + 'proxy_from_url', +) + +logging.getLogger(__name__).addHandler(NullHandler()) + + +def add_stderr_logger(level=logging.DEBUG): + """ + Helper for quickly adding a StreamHandler to the logger. Useful for + debugging. + + Returns the handler after adding it. + """ + # This method needs to be in this __init__.py to get the __name__ correct + # even if urllib3 is vendored within another package. + logger = logging.getLogger(__name__) + handler = logging.StreamHandler() + handler.setFormatter(logging.Formatter('%(asctime)s %(levelname)s %(message)s')) + logger.addHandler(handler) + logger.setLevel(level) + logger.debug('Added a stderr logging handler to logger: %s' % __name__) + return handler + +# ... Clean up. +del NullHandler + + +# SecurityWarning's always go off by default. +warnings.simplefilter('always', exceptions.SecurityWarning, append=True) +# SubjectAltNameWarning's should go off once per host +warnings.simplefilter('default', exceptions.SubjectAltNameWarning) +# InsecurePlatformWarning's don't vary between requests, so we keep it default. +warnings.simplefilter('default', exceptions.InsecurePlatformWarning, + append=True) +# SNIMissingWarnings should go off only once. +warnings.simplefilter('default', exceptions.SNIMissingWarning) + + +def disable_warnings(category=exceptions.HTTPWarning): + """ + Helper for quickly disabling all urllib3 warnings. + """ + warnings.simplefilter('ignore', category) diff --git a/resources/lib/libraries/requests/packages/urllib3/_collections.py b/resources/lib/libraries/requests/packages/urllib3/_collections.py new file mode 100644 index 00000000..67f3ce99 --- /dev/null +++ b/resources/lib/libraries/requests/packages/urllib3/_collections.py @@ -0,0 +1,324 @@ +from __future__ import absolute_import +from collections import Mapping, MutableMapping +try: + from threading import RLock +except ImportError: # Platform-specific: No threads available + class RLock: + def __enter__(self): + pass + + def __exit__(self, exc_type, exc_value, traceback): + pass + + +try: # Python 2.7+ + from collections import OrderedDict +except ImportError: + from .packages.ordered_dict import OrderedDict +from .packages.six import iterkeys, itervalues, PY3 + + +__all__ = ['RecentlyUsedContainer', 'HTTPHeaderDict'] + + +_Null = object() + + +class RecentlyUsedContainer(MutableMapping): + """ + Provides a thread-safe dict-like container which maintains up to + ``maxsize`` keys while throwing away the least-recently-used keys beyond + ``maxsize``. + + :param maxsize: + Maximum number of recent elements to retain. + + :param dispose_func: + Every time an item is evicted from the container, + ``dispose_func(value)`` is called. Callback which will get called + """ + + ContainerCls = OrderedDict + + def __init__(self, maxsize=10, dispose_func=None): + self._maxsize = maxsize + self.dispose_func = dispose_func + + self._container = self.ContainerCls() + self.lock = RLock() + + def __getitem__(self, key): + # Re-insert the item, moving it to the end of the eviction line. + with self.lock: + item = self._container.pop(key) + self._container[key] = item + return item + + def __setitem__(self, key, value): + evicted_value = _Null + with self.lock: + # Possibly evict the existing value of 'key' + evicted_value = self._container.get(key, _Null) + self._container[key] = value + + # If we didn't evict an existing value, we might have to evict the + # least recently used item from the beginning of the container. + if len(self._container) > self._maxsize: + _key, evicted_value = self._container.popitem(last=False) + + if self.dispose_func and evicted_value is not _Null: + self.dispose_func(evicted_value) + + def __delitem__(self, key): + with self.lock: + value = self._container.pop(key) + + if self.dispose_func: + self.dispose_func(value) + + def __len__(self): + with self.lock: + return len(self._container) + + def __iter__(self): + raise NotImplementedError('Iteration over this class is unlikely to be threadsafe.') + + def clear(self): + with self.lock: + # Copy pointers to all values, then wipe the mapping + values = list(itervalues(self._container)) + self._container.clear() + + if self.dispose_func: + for value in values: + self.dispose_func(value) + + def keys(self): + with self.lock: + return list(iterkeys(self._container)) + + +class HTTPHeaderDict(MutableMapping): + """ + :param headers: + An iterable of field-value pairs. Must not contain multiple field names + when compared case-insensitively. + + :param kwargs: + Additional field-value pairs to pass in to ``dict.update``. + + A ``dict`` like container for storing HTTP Headers. + + Field names are stored and compared case-insensitively in compliance with + RFC 7230. Iteration provides the first case-sensitive key seen for each + case-insensitive pair. + + Using ``__setitem__`` syntax overwrites fields that compare equal + case-insensitively in order to maintain ``dict``'s api. For fields that + compare equal, instead create a new ``HTTPHeaderDict`` and use ``.add`` + in a loop. + + If multiple fields that are equal case-insensitively are passed to the + constructor or ``.update``, the behavior is undefined and some will be + lost. + + >>> headers = HTTPHeaderDict() + >>> headers.add('Set-Cookie', 'foo=bar') + >>> headers.add('set-cookie', 'baz=quxx') + >>> headers['content-length'] = '7' + >>> headers['SET-cookie'] + 'foo=bar, baz=quxx' + >>> headers['Content-Length'] + '7' + """ + + def __init__(self, headers=None, **kwargs): + super(HTTPHeaderDict, self).__init__() + self._container = {} + if headers is not None: + if isinstance(headers, HTTPHeaderDict): + self._copy_from(headers) + else: + self.extend(headers) + if kwargs: + self.extend(kwargs) + + def __setitem__(self, key, val): + self._container[key.lower()] = (key, val) + return self._container[key.lower()] + + def __getitem__(self, key): + val = self._container[key.lower()] + return ', '.join(val[1:]) + + def __delitem__(self, key): + del self._container[key.lower()] + + def __contains__(self, key): + return key.lower() in self._container + + def __eq__(self, other): + if not isinstance(other, Mapping) and not hasattr(other, 'keys'): + return False + if not isinstance(other, type(self)): + other = type(self)(other) + return (dict((k.lower(), v) for k, v in self.itermerged()) == + dict((k.lower(), v) for k, v in other.itermerged())) + + def __ne__(self, other): + return not self.__eq__(other) + + if not PY3: # Python 2 + iterkeys = MutableMapping.iterkeys + itervalues = MutableMapping.itervalues + + __marker = object() + + def __len__(self): + return len(self._container) + + def __iter__(self): + # Only provide the originally cased names + for vals in self._container.values(): + yield vals[0] + + def pop(self, key, default=__marker): + '''D.pop(k[,d]) -> v, remove specified key and return the corresponding value. + If key is not found, d is returned if given, otherwise KeyError is raised. + ''' + # Using the MutableMapping function directly fails due to the private marker. + # Using ordinary dict.pop would expose the internal structures. + # So let's reinvent the wheel. + try: + value = self[key] + except KeyError: + if default is self.__marker: + raise + return default + else: + del self[key] + return value + + def discard(self, key): + try: + del self[key] + except KeyError: + pass + + def add(self, key, val): + """Adds a (name, value) pair, doesn't overwrite the value if it already + exists. + + >>> headers = HTTPHeaderDict(foo='bar') + >>> headers.add('Foo', 'baz') + >>> headers['foo'] + 'bar, baz' + """ + key_lower = key.lower() + new_vals = key, val + # Keep the common case aka no item present as fast as possible + vals = self._container.setdefault(key_lower, new_vals) + if new_vals is not vals: + # new_vals was not inserted, as there was a previous one + if isinstance(vals, list): + # If already several items got inserted, we have a list + vals.append(val) + else: + # vals should be a tuple then, i.e. only one item so far + # Need to convert the tuple to list for further extension + self._container[key_lower] = [vals[0], vals[1], val] + + def extend(self, *args, **kwargs): + """Generic import function for any type of header-like object. + Adapted version of MutableMapping.update in order to insert items + with self.add instead of self.__setitem__ + """ + if len(args) > 1: + raise TypeError("extend() takes at most 1 positional " + "arguments ({0} given)".format(len(args))) + other = args[0] if len(args) >= 1 else () + + if isinstance(other, HTTPHeaderDict): + for key, val in other.iteritems(): + self.add(key, val) + elif isinstance(other, Mapping): + for key in other: + self.add(key, other[key]) + elif hasattr(other, "keys"): + for key in other.keys(): + self.add(key, other[key]) + else: + for key, value in other: + self.add(key, value) + + for key, value in kwargs.items(): + self.add(key, value) + + def getlist(self, key): + """Returns a list of all the values for the named field. Returns an + empty list if the key doesn't exist.""" + try: + vals = self._container[key.lower()] + except KeyError: + return [] + else: + if isinstance(vals, tuple): + return [vals[1]] + else: + return vals[1:] + + # Backwards compatibility for httplib + getheaders = getlist + getallmatchingheaders = getlist + iget = getlist + + def __repr__(self): + return "%s(%s)" % (type(self).__name__, dict(self.itermerged())) + + def _copy_from(self, other): + for key in other: + val = other.getlist(key) + if isinstance(val, list): + # Don't need to convert tuples + val = list(val) + self._container[key.lower()] = [key] + val + + def copy(self): + clone = type(self)() + clone._copy_from(self) + return clone + + def iteritems(self): + """Iterate over all header lines, including duplicate ones.""" + for key in self: + vals = self._container[key.lower()] + for val in vals[1:]: + yield vals[0], val + + def itermerged(self): + """Iterate over all headers, merging duplicate ones together.""" + for key in self: + val = self._container[key.lower()] + yield val[0], ', '.join(val[1:]) + + def items(self): + return list(self.iteritems()) + + @classmethod + def from_httplib(cls, message): # Python 2 + """Read headers from a Python 2 httplib message object.""" + # python2.7 does not expose a proper API for exporting multiheaders + # efficiently. This function re-reads raw lines from the message + # object and extracts the multiheaders properly. + headers = [] + + for line in message.headers: + if line.startswith((' ', '\t')): + key, value = headers[-1] + headers[-1] = (key, value + '\r\n' + line.rstrip()) + continue + + key, value = line.split(':', 1) + headers.append((key, value.strip())) + + return cls(headers) diff --git a/resources/lib/libraries/requests/packages/urllib3/connection.py b/resources/lib/libraries/requests/packages/urllib3/connection.py new file mode 100644 index 00000000..1e4cd417 --- /dev/null +++ b/resources/lib/libraries/requests/packages/urllib3/connection.py @@ -0,0 +1,288 @@ +from __future__ import absolute_import +import datetime +import os +import sys +import socket +from socket import error as SocketError, timeout as SocketTimeout +import warnings +from .packages import six + +try: # Python 3 + from http.client import HTTPConnection as _HTTPConnection + from http.client import HTTPException # noqa: unused in this module +except ImportError: + from httplib import HTTPConnection as _HTTPConnection + from httplib import HTTPException # noqa: unused in this module + +try: # Compiled with SSL? + import ssl + BaseSSLError = ssl.SSLError +except (ImportError, AttributeError): # Platform-specific: No SSL. + ssl = None + + class BaseSSLError(BaseException): + pass + + +try: # Python 3: + # Not a no-op, we're adding this to the namespace so it can be imported. + ConnectionError = ConnectionError +except NameError: # Python 2: + class ConnectionError(Exception): + pass + + +from .exceptions import ( + NewConnectionError, + ConnectTimeoutError, + SubjectAltNameWarning, + SystemTimeWarning, +) +from .packages.ssl_match_hostname import match_hostname + +from .util.ssl_ import ( + resolve_cert_reqs, + resolve_ssl_version, + ssl_wrap_socket, + assert_fingerprint, +) + + +from .util import connection + +port_by_scheme = { + 'http': 80, + 'https': 443, +} + +RECENT_DATE = datetime.date(2014, 1, 1) + + +class DummyConnection(object): + """Used to detect a failed ConnectionCls import.""" + pass + + +class HTTPConnection(_HTTPConnection, object): + """ + Based on httplib.HTTPConnection but provides an extra constructor + backwards-compatibility layer between older and newer Pythons. + + Additional keyword parameters are used to configure attributes of the connection. + Accepted parameters include: + + - ``strict``: See the documentation on :class:`urllib3.connectionpool.HTTPConnectionPool` + - ``source_address``: Set the source address for the current connection. + + .. note:: This is ignored for Python 2.6. It is only applied for 2.7 and 3.x + + - ``socket_options``: Set specific options on the underlying socket. If not specified, then + defaults are loaded from ``HTTPConnection.default_socket_options`` which includes disabling + Nagle's algorithm (sets TCP_NODELAY to 1) unless the connection is behind a proxy. + + For example, if you wish to enable TCP Keep Alive in addition to the defaults, + you might pass:: + + HTTPConnection.default_socket_options + [ + (socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1), + ] + + Or you may want to disable the defaults by passing an empty list (e.g., ``[]``). + """ + + default_port = port_by_scheme['http'] + + #: Disable Nagle's algorithm by default. + #: ``[(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)]`` + default_socket_options = [(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)] + + #: Whether this connection verifies the host's certificate. + is_verified = False + + def __init__(self, *args, **kw): + if six.PY3: # Python 3 + kw.pop('strict', None) + + # Pre-set source_address in case we have an older Python like 2.6. + self.source_address = kw.get('source_address') + + if sys.version_info < (2, 7): # Python 2.6 + # _HTTPConnection on Python 2.6 will balk at this keyword arg, but + # not newer versions. We can still use it when creating a + # connection though, so we pop it *after* we have saved it as + # self.source_address. + kw.pop('source_address', None) + + #: The socket options provided by the user. If no options are + #: provided, we use the default options. + self.socket_options = kw.pop('socket_options', self.default_socket_options) + + # Superclass also sets self.source_address in Python 2.7+. + _HTTPConnection.__init__(self, *args, **kw) + + def _new_conn(self): + """ Establish a socket connection and set nodelay settings on it. + + :return: New socket connection. + """ + extra_kw = {} + if self.source_address: + extra_kw['source_address'] = self.source_address + + if self.socket_options: + extra_kw['socket_options'] = self.socket_options + + try: + conn = connection.create_connection( + (self.host, self.port), self.timeout, **extra_kw) + + except SocketTimeout as e: + raise ConnectTimeoutError( + self, "Connection to %s timed out. (connect timeout=%s)" % + (self.host, self.timeout)) + + except SocketError as e: + raise NewConnectionError( + self, "Failed to establish a new connection: %s" % e) + + return conn + + def _prepare_conn(self, conn): + self.sock = conn + # the _tunnel_host attribute was added in python 2.6.3 (via + # http://hg.python.org/cpython/rev/0f57b30a152f) so pythons 2.6(0-2) do + # not have them. + if getattr(self, '_tunnel_host', None): + # TODO: Fix tunnel so it doesn't depend on self.sock state. + self._tunnel() + # Mark this connection as not reusable + self.auto_open = 0 + + def connect(self): + conn = self._new_conn() + self._prepare_conn(conn) + + +class HTTPSConnection(HTTPConnection): + default_port = port_by_scheme['https'] + + def __init__(self, host, port=None, key_file=None, cert_file=None, + strict=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT, **kw): + + HTTPConnection.__init__(self, host, port, strict=strict, + timeout=timeout, **kw) + + self.key_file = key_file + self.cert_file = cert_file + + # Required property for Google AppEngine 1.9.0 which otherwise causes + # HTTPS requests to go out as HTTP. (See Issue #356) + self._protocol = 'https' + + def connect(self): + conn = self._new_conn() + self._prepare_conn(conn) + self.sock = ssl.wrap_socket(conn, self.key_file, self.cert_file) + + +class VerifiedHTTPSConnection(HTTPSConnection): + """ + Based on httplib.HTTPSConnection but wraps the socket with + SSL certification. + """ + cert_reqs = None + ca_certs = None + ca_cert_dir = None + ssl_version = None + assert_fingerprint = None + + def set_cert(self, key_file=None, cert_file=None, + cert_reqs=None, ca_certs=None, + assert_hostname=None, assert_fingerprint=None, + ca_cert_dir=None): + + if (ca_certs or ca_cert_dir) and cert_reqs is None: + cert_reqs = 'CERT_REQUIRED' + + self.key_file = key_file + self.cert_file = cert_file + self.cert_reqs = cert_reqs + self.assert_hostname = assert_hostname + self.assert_fingerprint = assert_fingerprint + self.ca_certs = ca_certs and os.path.expanduser(ca_certs) + self.ca_cert_dir = ca_cert_dir and os.path.expanduser(ca_cert_dir) + + def connect(self): + # Add certificate verification + conn = self._new_conn() + + resolved_cert_reqs = resolve_cert_reqs(self.cert_reqs) + resolved_ssl_version = resolve_ssl_version(self.ssl_version) + + hostname = self.host + if getattr(self, '_tunnel_host', None): + # _tunnel_host was added in Python 2.6.3 + # (See: http://hg.python.org/cpython/rev/0f57b30a152f) + + self.sock = conn + # Calls self._set_hostport(), so self.host is + # self._tunnel_host below. + self._tunnel() + # Mark this connection as not reusable + self.auto_open = 0 + + # Override the host with the one we're requesting data from. + hostname = self._tunnel_host + + is_time_off = datetime.date.today() < RECENT_DATE + if is_time_off: + warnings.warn(( + 'System time is way off (before {0}). This will probably ' + 'lead to SSL verification errors').format(RECENT_DATE), + SystemTimeWarning + ) + + # Wrap socket using verification with the root certs in + # trusted_root_certs + self.sock = ssl_wrap_socket(conn, self.key_file, self.cert_file, + cert_reqs=resolved_cert_reqs, + ca_certs=self.ca_certs, + ca_cert_dir=self.ca_cert_dir, + server_hostname=hostname, + ssl_version=resolved_ssl_version) + + if self.assert_fingerprint: + assert_fingerprint(self.sock.getpeercert(binary_form=True), + self.assert_fingerprint) + elif resolved_cert_reqs != ssl.CERT_NONE \ + and self.assert_hostname is not False: + cert = self.sock.getpeercert() + if not cert.get('subjectAltName', ()): + warnings.warn(( + 'Certificate for {0} has no `subjectAltName`, falling back to check for a ' + '`commonName` for now. This feature is being removed by major browsers and ' + 'deprecated by RFC 2818. (See https://github.com/shazow/urllib3/issues/497 ' + 'for details.)'.format(hostname)), + SubjectAltNameWarning + ) + + # In case the hostname is an IPv6 address, strip the square + # brackets from it before using it to validate. This is because + # a certificate with an IPv6 address in it won't have square + # brackets around that address. Sadly, match_hostname won't do this + # for us: it expects the plain host part without any extra work + # that might have been done to make it palatable to httplib. + asserted_hostname = self.assert_hostname or hostname + asserted_hostname = asserted_hostname.strip('[]') + match_hostname(cert, asserted_hostname) + + self.is_verified = (resolved_cert_reqs == ssl.CERT_REQUIRED or + self.assert_fingerprint is not None) + + +if ssl: + # Make a copy for testing. + UnverifiedHTTPSConnection = HTTPSConnection + HTTPSConnection = VerifiedHTTPSConnection +else: + HTTPSConnection = DummyConnection diff --git a/resources/lib/libraries/requests/packages/urllib3/connectionpool.py b/resources/lib/libraries/requests/packages/urllib3/connectionpool.py new file mode 100644 index 00000000..995b4167 --- /dev/null +++ b/resources/lib/libraries/requests/packages/urllib3/connectionpool.py @@ -0,0 +1,818 @@ +from __future__ import absolute_import +import errno +import logging +import sys +import warnings + +from socket import error as SocketError, timeout as SocketTimeout +import socket + +try: # Python 3 + from queue import LifoQueue, Empty, Full +except ImportError: + from Queue import LifoQueue, Empty, Full + # Queue is imported for side effects on MS Windows + import Queue as _unused_module_Queue # noqa: unused + + +from .exceptions import ( + ClosedPoolError, + ProtocolError, + EmptyPoolError, + HeaderParsingError, + HostChangedError, + LocationValueError, + MaxRetryError, + ProxyError, + ReadTimeoutError, + SSLError, + TimeoutError, + InsecureRequestWarning, + NewConnectionError, +) +from .packages.ssl_match_hostname import CertificateError +from .packages import six +from .connection import ( + port_by_scheme, + DummyConnection, + HTTPConnection, HTTPSConnection, VerifiedHTTPSConnection, + HTTPException, BaseSSLError, +) +from .request import RequestMethods +from .response import HTTPResponse + +from .util.connection import is_connection_dropped +from .util.response import assert_header_parsing +from .util.retry import Retry +from .util.timeout import Timeout +from .util.url import get_host, Url + + +xrange = six.moves.xrange + +log = logging.getLogger(__name__) + +_Default = object() + + +# Pool objects +class ConnectionPool(object): + """ + Base class for all connection pools, such as + :class:`.HTTPConnectionPool` and :class:`.HTTPSConnectionPool`. + """ + + scheme = None + QueueCls = LifoQueue + + def __init__(self, host, port=None): + if not host: + raise LocationValueError("No host specified.") + + self.host = host + self.port = port + + def __str__(self): + return '%s(host=%r, port=%r)' % (type(self).__name__, + self.host, self.port) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + self.close() + # Return False to re-raise any potential exceptions + return False + + def close(): + """ + Close all pooled connections and disable the pool. + """ + pass + + +# This is taken from http://hg.python.org/cpython/file/7aaba721ebc0/Lib/socket.py#l252 +_blocking_errnos = set([errno.EAGAIN, errno.EWOULDBLOCK]) + + +class HTTPConnectionPool(ConnectionPool, RequestMethods): + """ + Thread-safe connection pool for one host. + + :param host: + Host used for this HTTP Connection (e.g. "localhost"), passed into + :class:`httplib.HTTPConnection`. + + :param port: + Port used for this HTTP Connection (None is equivalent to 80), passed + into :class:`httplib.HTTPConnection`. + + :param strict: + Causes BadStatusLine to be raised if the status line can't be parsed + as a valid HTTP/1.0 or 1.1 status line, passed into + :class:`httplib.HTTPConnection`. + + .. note:: + Only works in Python 2. This parameter is ignored in Python 3. + + :param timeout: + Socket timeout in seconds for each individual connection. This can + be a float or integer, which sets the timeout for the HTTP request, + or an instance of :class:`urllib3.util.Timeout` which gives you more + fine-grained control over request timeouts. After the constructor has + been parsed, this is always a `urllib3.util.Timeout` object. + + :param maxsize: + Number of connections to save that can be reused. More than 1 is useful + in multithreaded situations. If ``block`` is set to False, more + connections will be created but they will not be saved once they've + been used. + + :param block: + If set to True, no more than ``maxsize`` connections will be used at + a time. When no free connections are available, the call will block + until a connection has been released. This is a useful side effect for + particular multithreaded situations where one does not want to use more + than maxsize connections per host to prevent flooding. + + :param headers: + Headers to include with all requests, unless other headers are given + explicitly. + + :param retries: + Retry configuration to use by default with requests in this pool. + + :param _proxy: + Parsed proxy URL, should not be used directly, instead, see + :class:`urllib3.connectionpool.ProxyManager`" + + :param _proxy_headers: + A dictionary with proxy headers, should not be used directly, + instead, see :class:`urllib3.connectionpool.ProxyManager`" + + :param \**conn_kw: + Additional parameters are used to create fresh :class:`urllib3.connection.HTTPConnection`, + :class:`urllib3.connection.HTTPSConnection` instances. + """ + + scheme = 'http' + ConnectionCls = HTTPConnection + + def __init__(self, host, port=None, strict=False, + timeout=Timeout.DEFAULT_TIMEOUT, maxsize=1, block=False, + headers=None, retries=None, + _proxy=None, _proxy_headers=None, + **conn_kw): + ConnectionPool.__init__(self, host, port) + RequestMethods.__init__(self, headers) + + self.strict = strict + + if not isinstance(timeout, Timeout): + timeout = Timeout.from_float(timeout) + + if retries is None: + retries = Retry.DEFAULT + + self.timeout = timeout + self.retries = retries + + self.pool = self.QueueCls(maxsize) + self.block = block + + self.proxy = _proxy + self.proxy_headers = _proxy_headers or {} + + # Fill the queue up so that doing get() on it will block properly + for _ in xrange(maxsize): + self.pool.put(None) + + # These are mostly for testing and debugging purposes. + self.num_connections = 0 + self.num_requests = 0 + self.conn_kw = conn_kw + + if self.proxy: + # Enable Nagle's algorithm for proxies, to avoid packet fragmentation. + # We cannot know if the user has added default socket options, so we cannot replace the + # list. + self.conn_kw.setdefault('socket_options', []) + + def _new_conn(self): + """ + Return a fresh :class:`HTTPConnection`. + """ + self.num_connections += 1 + log.info("Starting new HTTP connection (%d): %s" % + (self.num_connections, self.host)) + + conn = self.ConnectionCls(host=self.host, port=self.port, + timeout=self.timeout.connect_timeout, + strict=self.strict, **self.conn_kw) + return conn + + def _get_conn(self, timeout=None): + """ + Get a connection. Will return a pooled connection if one is available. + + If no connections are available and :prop:`.block` is ``False``, then a + fresh connection is returned. + + :param timeout: + Seconds to wait before giving up and raising + :class:`urllib3.exceptions.EmptyPoolError` if the pool is empty and + :prop:`.block` is ``True``. + """ + conn = None + try: + conn = self.pool.get(block=self.block, timeout=timeout) + + except AttributeError: # self.pool is None + raise ClosedPoolError(self, "Pool is closed.") + + except Empty: + if self.block: + raise EmptyPoolError(self, + "Pool reached maximum size and no more " + "connections are allowed.") + pass # Oh well, we'll create a new connection then + + # If this is a persistent connection, check if it got disconnected + if conn and is_connection_dropped(conn): + log.info("Resetting dropped connection: %s" % self.host) + conn.close() + if getattr(conn, 'auto_open', 1) == 0: + # This is a proxied connection that has been mutated by + # httplib._tunnel() and cannot be reused (since it would + # attempt to bypass the proxy) + conn = None + + return conn or self._new_conn() + + def _put_conn(self, conn): + """ + Put a connection back into the pool. + + :param conn: + Connection object for the current host and port as returned by + :meth:`._new_conn` or :meth:`._get_conn`. + + If the pool is already full, the connection is closed and discarded + because we exceeded maxsize. If connections are discarded frequently, + then maxsize should be increased. + + If the pool is closed, then the connection will be closed and discarded. + """ + try: + self.pool.put(conn, block=False) + return # Everything is dandy, done. + except AttributeError: + # self.pool is None. + pass + except Full: + # This should never happen if self.block == True + log.warning( + "Connection pool is full, discarding connection: %s" % + self.host) + + # Connection never got put back into the pool, close it. + if conn: + conn.close() + + def _validate_conn(self, conn): + """ + Called right before a request is made, after the socket is created. + """ + pass + + def _prepare_proxy(self, conn): + # Nothing to do for HTTP connections. + pass + + def _get_timeout(self, timeout): + """ Helper that always returns a :class:`urllib3.util.Timeout` """ + if timeout is _Default: + return self.timeout.clone() + + if isinstance(timeout, Timeout): + return timeout.clone() + else: + # User passed us an int/float. This is for backwards compatibility, + # can be removed later + return Timeout.from_float(timeout) + + def _raise_timeout(self, err, url, timeout_value): + """Is the error actually a timeout? Will raise a ReadTimeout or pass""" + + if isinstance(err, SocketTimeout): + raise ReadTimeoutError(self, url, "Read timed out. (read timeout=%s)" % timeout_value) + + # See the above comment about EAGAIN in Python 3. In Python 2 we have + # to specifically catch it and throw the timeout error + if hasattr(err, 'errno') and err.errno in _blocking_errnos: + raise ReadTimeoutError(self, url, "Read timed out. (read timeout=%s)" % timeout_value) + + # Catch possible read timeouts thrown as SSL errors. If not the + # case, rethrow the original. We need to do this because of: + # http://bugs.python.org/issue10272 + if 'timed out' in str(err) or 'did not complete (read)' in str(err): # Python 2.6 + raise ReadTimeoutError(self, url, "Read timed out. (read timeout=%s)" % timeout_value) + + def _make_request(self, conn, method, url, timeout=_Default, + **httplib_request_kw): + """ + Perform a request on a given urllib connection object taken from our + pool. + + :param conn: + a connection from one of our connection pools + + :param timeout: + Socket timeout in seconds for the request. This can be a + float or integer, which will set the same timeout value for + the socket connect and the socket read, or an instance of + :class:`urllib3.util.Timeout`, which gives you more fine-grained + control over your timeouts. + """ + self.num_requests += 1 + + timeout_obj = self._get_timeout(timeout) + timeout_obj.start_connect() + conn.timeout = timeout_obj.connect_timeout + + # Trigger any extra validation we need to do. + try: + self._validate_conn(conn) + except (SocketTimeout, BaseSSLError) as e: + # Py2 raises this as a BaseSSLError, Py3 raises it as socket timeout. + self._raise_timeout(err=e, url=url, timeout_value=conn.timeout) + raise + + # conn.request() calls httplib.*.request, not the method in + # urllib3.request. It also calls makefile (recv) on the socket. + conn.request(method, url, **httplib_request_kw) + + # Reset the timeout for the recv() on the socket + read_timeout = timeout_obj.read_timeout + + # App Engine doesn't have a sock attr + if getattr(conn, 'sock', None): + # In Python 3 socket.py will catch EAGAIN and return None when you + # try and read into the file pointer created by http.client, which + # instead raises a BadStatusLine exception. Instead of catching + # the exception and assuming all BadStatusLine exceptions are read + # timeouts, check for a zero timeout before making the request. + if read_timeout == 0: + raise ReadTimeoutError( + self, url, "Read timed out. (read timeout=%s)" % read_timeout) + if read_timeout is Timeout.DEFAULT_TIMEOUT: + conn.sock.settimeout(socket.getdefaulttimeout()) + else: # None or a value + conn.sock.settimeout(read_timeout) + + # Receive the response from the server + try: + try: # Python 2.7, use buffering of HTTP responses + httplib_response = conn.getresponse(buffering=True) + except TypeError: # Python 2.6 and older + httplib_response = conn.getresponse() + except (SocketTimeout, BaseSSLError, SocketError) as e: + self._raise_timeout(err=e, url=url, timeout_value=read_timeout) + raise + + # AppEngine doesn't have a version attr. + http_version = getattr(conn, '_http_vsn_str', 'HTTP/?') + log.debug("\"%s %s %s\" %s %s" % (method, url, http_version, + httplib_response.status, + httplib_response.length)) + + try: + assert_header_parsing(httplib_response.msg) + except HeaderParsingError as hpe: # Platform-specific: Python 3 + log.warning( + 'Failed to parse headers (url=%s): %s', + self._absolute_url(url), hpe, exc_info=True) + + return httplib_response + + def _absolute_url(self, path): + return Url(scheme=self.scheme, host=self.host, port=self.port, path=path).url + + def close(self): + """ + Close all pooled connections and disable the pool. + """ + # Disable access to the pool + old_pool, self.pool = self.pool, None + + try: + while True: + conn = old_pool.get(block=False) + if conn: + conn.close() + + except Empty: + pass # Done. + + def is_same_host(self, url): + """ + Check if the given ``url`` is a member of the same host as this + connection pool. + """ + if url.startswith('/'): + return True + + # TODO: Add optional support for socket.gethostbyname checking. + scheme, host, port = get_host(url) + + # Use explicit default port for comparison when none is given + if self.port and not port: + port = port_by_scheme.get(scheme) + elif not self.port and port == port_by_scheme.get(scheme): + port = None + + return (scheme, host, port) == (self.scheme, self.host, self.port) + + def urlopen(self, method, url, body=None, headers=None, retries=None, + redirect=True, assert_same_host=True, timeout=_Default, + pool_timeout=None, release_conn=None, **response_kw): + """ + Get a connection from the pool and perform an HTTP request. This is the + lowest level call for making a request, so you'll need to specify all + the raw details. + + .. note:: + + More commonly, it's appropriate to use a convenience method provided + by :class:`.RequestMethods`, such as :meth:`request`. + + .. note:: + + `release_conn` will only behave as expected if + `preload_content=False` because we want to make + `preload_content=False` the default behaviour someday soon without + breaking backwards compatibility. + + :param method: + HTTP request method (such as GET, POST, PUT, etc.) + + :param body: + Data to send in the request body (useful for creating + POST requests, see HTTPConnectionPool.post_url for + more convenience). + + :param headers: + Dictionary of custom headers to send, such as User-Agent, + If-None-Match, etc. If None, pool headers are used. If provided, + these headers completely replace any pool-specific headers. + + :param retries: + Configure the number of retries to allow before raising a + :class:`~urllib3.exceptions.MaxRetryError` exception. + + Pass ``None`` to retry until you receive a response. Pass a + :class:`~urllib3.util.retry.Retry` object for fine-grained control + over different types of retries. + Pass an integer number to retry connection errors that many times, + but no other types of errors. Pass zero to never retry. + + If ``False``, then retries are disabled and any exception is raised + immediately. Also, instead of raising a MaxRetryError on redirects, + the redirect response will be returned. + + :type retries: :class:`~urllib3.util.retry.Retry`, False, or an int. + + :param redirect: + If True, automatically handle redirects (status codes 301, 302, + 303, 307, 308). Each redirect counts as a retry. Disabling retries + will disable redirect, too. + + :param assert_same_host: + If ``True``, will make sure that the host of the pool requests is + consistent else will raise HostChangedError. When False, you can + use the pool on an HTTP proxy and request foreign hosts. + + :param timeout: + If specified, overrides the default timeout for this one + request. It may be a float (in seconds) or an instance of + :class:`urllib3.util.Timeout`. + + :param pool_timeout: + If set and the pool is set to block=True, then this method will + block for ``pool_timeout`` seconds and raise EmptyPoolError if no + connection is available within the time period. + + :param release_conn: + If False, then the urlopen call will not release the connection + back into the pool once a response is received (but will release if + you read the entire contents of the response such as when + `preload_content=True`). This is useful if you're not preloading + the response's content immediately. You will need to call + ``r.release_conn()`` on the response ``r`` to return the connection + back into the pool. If None, it takes the value of + ``response_kw.get('preload_content', True)``. + + :param \**response_kw: + Additional parameters are passed to + :meth:`urllib3.response.HTTPResponse.from_httplib` + """ + if headers is None: + headers = self.headers + + if not isinstance(retries, Retry): + retries = Retry.from_int(retries, redirect=redirect, default=self.retries) + + if release_conn is None: + release_conn = response_kw.get('preload_content', True) + + # Check host + if assert_same_host and not self.is_same_host(url): + raise HostChangedError(self, url, retries) + + conn = None + + # Merge the proxy headers. Only do this in HTTP. We have to copy the + # headers dict so we can safely change it without those changes being + # reflected in anyone else's copy. + if self.scheme == 'http': + headers = headers.copy() + headers.update(self.proxy_headers) + + # Must keep the exception bound to a separate variable or else Python 3 + # complains about UnboundLocalError. + err = None + + try: + # Request a connection from the queue. + timeout_obj = self._get_timeout(timeout) + conn = self._get_conn(timeout=pool_timeout) + + conn.timeout = timeout_obj.connect_timeout + + is_new_proxy_conn = self.proxy is not None and not getattr(conn, 'sock', None) + if is_new_proxy_conn: + self._prepare_proxy(conn) + + # Make the request on the httplib connection object. + httplib_response = self._make_request(conn, method, url, + timeout=timeout_obj, + body=body, headers=headers) + + # If we're going to release the connection in ``finally:``, then + # the request doesn't need to know about the connection. Otherwise + # it will also try to release it and we'll have a double-release + # mess. + response_conn = not release_conn and conn + + # Import httplib's response into our own wrapper object + response = HTTPResponse.from_httplib(httplib_response, + pool=self, + connection=response_conn, + **response_kw) + + # else: + # The connection will be put back into the pool when + # ``response.release_conn()`` is called (implicitly by + # ``response.read()``) + + except Empty: + # Timed out by queue. + raise EmptyPoolError(self, "No pool connections are available.") + + except (BaseSSLError, CertificateError) as e: + # Close the connection. If a connection is reused on which there + # was a Certificate error, the next request will certainly raise + # another Certificate error. + conn = conn and conn.close() + release_conn = True + raise SSLError(e) + + except SSLError: + # Treat SSLError separately from BaseSSLError to preserve + # traceback. + conn = conn and conn.close() + release_conn = True + raise + + except (TimeoutError, HTTPException, SocketError, ProtocolError) as e: + # Discard the connection for these exceptions. It will be + # be replaced during the next _get_conn() call. + conn = conn and conn.close() + release_conn = True + + if isinstance(e, (SocketError, NewConnectionError)) and self.proxy: + e = ProxyError('Cannot connect to proxy.', e) + elif isinstance(e, (SocketError, HTTPException)): + e = ProtocolError('Connection aborted.', e) + + retries = retries.increment(method, url, error=e, _pool=self, + _stacktrace=sys.exc_info()[2]) + retries.sleep() + + # Keep track of the error for the retry warning. + err = e + + finally: + if release_conn: + # Put the connection back to be reused. If the connection is + # expired then it will be None, which will get replaced with a + # fresh connection during _get_conn. + self._put_conn(conn) + + if not conn: + # Try again + log.warning("Retrying (%r) after connection " + "broken by '%r': %s" % (retries, err, url)) + return self.urlopen(method, url, body, headers, retries, + redirect, assert_same_host, + timeout=timeout, pool_timeout=pool_timeout, + release_conn=release_conn, **response_kw) + + # Handle redirect? + redirect_location = redirect and response.get_redirect_location() + if redirect_location: + if response.status == 303: + method = 'GET' + + try: + retries = retries.increment(method, url, response=response, _pool=self) + except MaxRetryError: + if retries.raise_on_redirect: + # Release the connection for this response, since we're not + # returning it to be released manually. + response.release_conn() + raise + return response + + log.info("Redirecting %s -> %s" % (url, redirect_location)) + return self.urlopen( + method, redirect_location, body, headers, + retries=retries, redirect=redirect, + assert_same_host=assert_same_host, + timeout=timeout, pool_timeout=pool_timeout, + release_conn=release_conn, **response_kw) + + # Check if we should retry the HTTP response. + if retries.is_forced_retry(method, status_code=response.status): + retries = retries.increment(method, url, response=response, _pool=self) + retries.sleep() + log.info("Forced retry: %s" % url) + return self.urlopen( + method, url, body, headers, + retries=retries, redirect=redirect, + assert_same_host=assert_same_host, + timeout=timeout, pool_timeout=pool_timeout, + release_conn=release_conn, **response_kw) + + return response + + +class HTTPSConnectionPool(HTTPConnectionPool): + """ + Same as :class:`.HTTPConnectionPool`, but HTTPS. + + When Python is compiled with the :mod:`ssl` module, then + :class:`.VerifiedHTTPSConnection` is used, which *can* verify certificates, + instead of :class:`.HTTPSConnection`. + + :class:`.VerifiedHTTPSConnection` uses one of ``assert_fingerprint``, + ``assert_hostname`` and ``host`` in this order to verify connections. + If ``assert_hostname`` is False, no verification is done. + + The ``key_file``, ``cert_file``, ``cert_reqs``, ``ca_certs``, + ``ca_cert_dir``, and ``ssl_version`` are only used if :mod:`ssl` is + available and are fed into :meth:`urllib3.util.ssl_wrap_socket` to upgrade + the connection socket into an SSL socket. + """ + + scheme = 'https' + ConnectionCls = HTTPSConnection + + def __init__(self, host, port=None, + strict=False, timeout=Timeout.DEFAULT_TIMEOUT, maxsize=1, + block=False, headers=None, retries=None, + _proxy=None, _proxy_headers=None, + key_file=None, cert_file=None, cert_reqs=None, + ca_certs=None, ssl_version=None, + assert_hostname=None, assert_fingerprint=None, + ca_cert_dir=None, **conn_kw): + + HTTPConnectionPool.__init__(self, host, port, strict, timeout, maxsize, + block, headers, retries, _proxy, _proxy_headers, + **conn_kw) + + if ca_certs and cert_reqs is None: + cert_reqs = 'CERT_REQUIRED' + + self.key_file = key_file + self.cert_file = cert_file + self.cert_reqs = cert_reqs + self.ca_certs = ca_certs + self.ca_cert_dir = ca_cert_dir + self.ssl_version = ssl_version + self.assert_hostname = assert_hostname + self.assert_fingerprint = assert_fingerprint + + def _prepare_conn(self, conn): + """ + Prepare the ``connection`` for :meth:`urllib3.util.ssl_wrap_socket` + and establish the tunnel if proxy is used. + """ + + if isinstance(conn, VerifiedHTTPSConnection): + conn.set_cert(key_file=self.key_file, + cert_file=self.cert_file, + cert_reqs=self.cert_reqs, + ca_certs=self.ca_certs, + ca_cert_dir=self.ca_cert_dir, + assert_hostname=self.assert_hostname, + assert_fingerprint=self.assert_fingerprint) + conn.ssl_version = self.ssl_version + + return conn + + def _prepare_proxy(self, conn): + """ + Establish tunnel connection early, because otherwise httplib + would improperly set Host: header to proxy's IP:port. + """ + # Python 2.7+ + try: + set_tunnel = conn.set_tunnel + except AttributeError: # Platform-specific: Python 2.6 + set_tunnel = conn._set_tunnel + + if sys.version_info <= (2, 6, 4) and not self.proxy_headers: # Python 2.6.4 and older + set_tunnel(self.host, self.port) + else: + set_tunnel(self.host, self.port, self.proxy_headers) + + conn.connect() + + def _new_conn(self): + """ + Return a fresh :class:`httplib.HTTPSConnection`. + """ + self.num_connections += 1 + log.info("Starting new HTTPS connection (%d): %s" + % (self.num_connections, self.host)) + + if not self.ConnectionCls or self.ConnectionCls is DummyConnection: + raise SSLError("Can't connect to HTTPS URL because the SSL " + "module is not available.") + + actual_host = self.host + actual_port = self.port + if self.proxy is not None: + actual_host = self.proxy.host + actual_port = self.proxy.port + + conn = self.ConnectionCls(host=actual_host, port=actual_port, + timeout=self.timeout.connect_timeout, + strict=self.strict, **self.conn_kw) + + return self._prepare_conn(conn) + + def _validate_conn(self, conn): + """ + Called right before a request is made, after the socket is created. + """ + super(HTTPSConnectionPool, self)._validate_conn(conn) + + # Force connect early to allow us to validate the connection. + if not getattr(conn, 'sock', None): # AppEngine might not have `.sock` + conn.connect() + + if not conn.is_verified: + warnings.warn(( + 'Unverified HTTPS request is being made. ' + 'Adding certificate verification is strongly advised. See: ' + 'https://urllib3.readthedocs.org/en/latest/security.html'), + InsecureRequestWarning) + + +def connection_from_url(url, **kw): + """ + Given a url, return an :class:`.ConnectionPool` instance of its host. + + This is a shortcut for not having to parse out the scheme, host, and port + of the url before creating an :class:`.ConnectionPool` instance. + + :param url: + Absolute URL string that must include the scheme. Port is optional. + + :param \**kw: + Passes additional parameters to the constructor of the appropriate + :class:`.ConnectionPool`. Useful for specifying things like + timeout, maxsize, headers, etc. + + Example:: + + >>> conn = connection_from_url('http://google.com/') + >>> r = conn.request('GET', '/') + """ + scheme, host, port = get_host(url) + if scheme == 'https': + return HTTPSConnectionPool(host, port=port, **kw) + else: + return HTTPConnectionPool(host, port=port, **kw) diff --git a/resources/lib/libraries/requests/packages/urllib3/contrib/__init__.py b/resources/lib/libraries/requests/packages/urllib3/contrib/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/resources/lib/libraries/requests/packages/urllib3/contrib/appengine.py b/resources/lib/libraries/requests/packages/urllib3/contrib/appengine.py new file mode 100644 index 00000000..884cdb22 --- /dev/null +++ b/resources/lib/libraries/requests/packages/urllib3/contrib/appengine.py @@ -0,0 +1,223 @@ +from __future__ import absolute_import +import logging +import os +import warnings + +from ..exceptions import ( + HTTPError, + HTTPWarning, + MaxRetryError, + ProtocolError, + TimeoutError, + SSLError +) + +from ..packages.six import BytesIO +from ..request import RequestMethods +from ..response import HTTPResponse +from ..util.timeout import Timeout +from ..util.retry import Retry + +try: + from google.appengine.api import urlfetch +except ImportError: + urlfetch = None + + +log = logging.getLogger(__name__) + + +class AppEnginePlatformWarning(HTTPWarning): + pass + + +class AppEnginePlatformError(HTTPError): + pass + + +class AppEngineManager(RequestMethods): + """ + Connection manager for Google App Engine sandbox applications. + + This manager uses the URLFetch service directly instead of using the + emulated httplib, and is subject to URLFetch limitations as described in + the App Engine documentation here: + + https://cloud.google.com/appengine/docs/python/urlfetch + + Notably it will raise an AppEnginePlatformError if: + * URLFetch is not available. + * If you attempt to use this on GAEv2 (Managed VMs), as full socket + support is available. + * If a request size is more than 10 megabytes. + * If a response size is more than 32 megabtyes. + * If you use an unsupported request method such as OPTIONS. + + Beyond those cases, it will raise normal urllib3 errors. + """ + + def __init__(self, headers=None, retries=None, validate_certificate=True): + if not urlfetch: + raise AppEnginePlatformError( + "URLFetch is not available in this environment.") + + if is_prod_appengine_mvms(): + raise AppEnginePlatformError( + "Use normal urllib3.PoolManager instead of AppEngineManager" + "on Managed VMs, as using URLFetch is not necessary in " + "this environment.") + + warnings.warn( + "urllib3 is using URLFetch on Google App Engine sandbox instead " + "of sockets. To use sockets directly instead of URLFetch see " + "https://urllib3.readthedocs.org/en/latest/contrib.html.", + AppEnginePlatformWarning) + + RequestMethods.__init__(self, headers) + self.validate_certificate = validate_certificate + + self.retries = retries or Retry.DEFAULT + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + # Return False to re-raise any potential exceptions + return False + + def urlopen(self, method, url, body=None, headers=None, + retries=None, redirect=True, timeout=Timeout.DEFAULT_TIMEOUT, + **response_kw): + + retries = self._get_retries(retries, redirect) + + try: + response = urlfetch.fetch( + url, + payload=body, + method=method, + headers=headers or {}, + allow_truncated=False, + follow_redirects=( + redirect and + retries.redirect != 0 and + retries.total), + deadline=self._get_absolute_timeout(timeout), + validate_certificate=self.validate_certificate, + ) + except urlfetch.DeadlineExceededError as e: + raise TimeoutError(self, e) + + except urlfetch.InvalidURLError as e: + if 'too large' in str(e): + raise AppEnginePlatformError( + "URLFetch request too large, URLFetch only " + "supports requests up to 10mb in size.", e) + raise ProtocolError(e) + + except urlfetch.DownloadError as e: + if 'Too many redirects' in str(e): + raise MaxRetryError(self, url, reason=e) + raise ProtocolError(e) + + except urlfetch.ResponseTooLargeError as e: + raise AppEnginePlatformError( + "URLFetch response too large, URLFetch only supports" + "responses up to 32mb in size.", e) + + except urlfetch.SSLCertificateError as e: + raise SSLError(e) + + except urlfetch.InvalidMethodError as e: + raise AppEnginePlatformError( + "URLFetch does not support method: %s" % method, e) + + http_response = self._urlfetch_response_to_http_response( + response, **response_kw) + + # Check for redirect response + if (http_response.get_redirect_location() and + retries.raise_on_redirect and redirect): + raise MaxRetryError(self, url, "too many redirects") + + # Check if we should retry the HTTP response. + if retries.is_forced_retry(method, status_code=http_response.status): + retries = retries.increment( + method, url, response=http_response, _pool=self) + log.info("Forced retry: %s" % url) + retries.sleep() + return self.urlopen( + method, url, + body=body, headers=headers, + retries=retries, redirect=redirect, + timeout=timeout, **response_kw) + + return http_response + + def _urlfetch_response_to_http_response(self, urlfetch_resp, **response_kw): + + if is_prod_appengine(): + # Production GAE handles deflate encoding automatically, but does + # not remove the encoding header. + content_encoding = urlfetch_resp.headers.get('content-encoding') + + if content_encoding == 'deflate': + del urlfetch_resp.headers['content-encoding'] + + return HTTPResponse( + # In order for decoding to work, we must present the content as + # a file-like object. + body=BytesIO(urlfetch_resp.content), + headers=urlfetch_resp.headers, + status=urlfetch_resp.status_code, + **response_kw + ) + + def _get_absolute_timeout(self, timeout): + if timeout is Timeout.DEFAULT_TIMEOUT: + return 5 # 5s is the default timeout for URLFetch. + if isinstance(timeout, Timeout): + if timeout.read is not timeout.connect: + warnings.warn( + "URLFetch does not support granular timeout settings, " + "reverting to total timeout.", AppEnginePlatformWarning) + return timeout.total + return timeout + + def _get_retries(self, retries, redirect): + if not isinstance(retries, Retry): + retries = Retry.from_int( + retries, redirect=redirect, default=self.retries) + + if retries.connect or retries.read or retries.redirect: + warnings.warn( + "URLFetch only supports total retries and does not " + "recognize connect, read, or redirect retry parameters.", + AppEnginePlatformWarning) + + return retries + + +def is_appengine(): + return (is_local_appengine() or + is_prod_appengine() or + is_prod_appengine_mvms()) + + +def is_appengine_sandbox(): + return is_appengine() and not is_prod_appengine_mvms() + + +def is_local_appengine(): + return ('APPENGINE_RUNTIME' in os.environ and + 'Development/' in os.environ['SERVER_SOFTWARE']) + + +def is_prod_appengine(): + return ('APPENGINE_RUNTIME' in os.environ and + 'Google App Engine/' in os.environ['SERVER_SOFTWARE'] and + not is_prod_appengine_mvms()) + + +def is_prod_appengine_mvms(): + return os.environ.get('GAE_VM', False) == 'true' diff --git a/resources/lib/libraries/requests/packages/urllib3/contrib/ntlmpool.py b/resources/lib/libraries/requests/packages/urllib3/contrib/ntlmpool.py new file mode 100644 index 00000000..c136a238 --- /dev/null +++ b/resources/lib/libraries/requests/packages/urllib3/contrib/ntlmpool.py @@ -0,0 +1,115 @@ +""" +NTLM authenticating pool, contributed by erikcederstran + +Issue #10, see: http://code.google.com/p/urllib3/issues/detail?id=10 +""" +from __future__ import absolute_import + +try: + from http.client import HTTPSConnection +except ImportError: + from httplib import HTTPSConnection +from logging import getLogger +from ntlm import ntlm + +from urllib3 import HTTPSConnectionPool + + +log = getLogger(__name__) + + +class NTLMConnectionPool(HTTPSConnectionPool): + """ + Implements an NTLM authentication version of an urllib3 connection pool + """ + + scheme = 'https' + + def __init__(self, user, pw, authurl, *args, **kwargs): + """ + authurl is a random URL on the server that is protected by NTLM. + user is the Windows user, probably in the DOMAIN\\username format. + pw is the password for the user. + """ + super(NTLMConnectionPool, self).__init__(*args, **kwargs) + self.authurl = authurl + self.rawuser = user + user_parts = user.split('\\', 1) + self.domain = user_parts[0].upper() + self.user = user_parts[1] + self.pw = pw + + def _new_conn(self): + # Performs the NTLM handshake that secures the connection. The socket + # must be kept open while requests are performed. + self.num_connections += 1 + log.debug('Starting NTLM HTTPS connection no. %d: https://%s%s' % + (self.num_connections, self.host, self.authurl)) + + headers = {} + headers['Connection'] = 'Keep-Alive' + req_header = 'Authorization' + resp_header = 'www-authenticate' + + conn = HTTPSConnection(host=self.host, port=self.port) + + # Send negotiation message + headers[req_header] = ( + 'NTLM %s' % ntlm.create_NTLM_NEGOTIATE_MESSAGE(self.rawuser)) + log.debug('Request headers: %s' % headers) + conn.request('GET', self.authurl, None, headers) + res = conn.getresponse() + reshdr = dict(res.getheaders()) + log.debug('Response status: %s %s' % (res.status, res.reason)) + log.debug('Response headers: %s' % reshdr) + log.debug('Response data: %s [...]' % res.read(100)) + + # Remove the reference to the socket, so that it can not be closed by + # the response object (we want to keep the socket open) + res.fp = None + + # Server should respond with a challenge message + auth_header_values = reshdr[resp_header].split(', ') + auth_header_value = None + for s in auth_header_values: + if s[:5] == 'NTLM ': + auth_header_value = s[5:] + if auth_header_value is None: + raise Exception('Unexpected %s response header: %s' % + (resp_header, reshdr[resp_header])) + + # Send authentication message + ServerChallenge, NegotiateFlags = \ + ntlm.parse_NTLM_CHALLENGE_MESSAGE(auth_header_value) + auth_msg = ntlm.create_NTLM_AUTHENTICATE_MESSAGE(ServerChallenge, + self.user, + self.domain, + self.pw, + NegotiateFlags) + headers[req_header] = 'NTLM %s' % auth_msg + log.debug('Request headers: %s' % headers) + conn.request('GET', self.authurl, None, headers) + res = conn.getresponse() + log.debug('Response status: %s %s' % (res.status, res.reason)) + log.debug('Response headers: %s' % dict(res.getheaders())) + log.debug('Response data: %s [...]' % res.read()[:100]) + if res.status != 200: + if res.status == 401: + raise Exception('Server rejected request: wrong ' + 'username or password') + raise Exception('Wrong server response: %s %s' % + (res.status, res.reason)) + + res.fp = None + log.debug('Connection established') + return conn + + def urlopen(self, method, url, body=None, headers=None, retries=3, + redirect=True, assert_same_host=True): + if headers is None: + headers = {} + headers['Connection'] = 'Keep-Alive' + return super(NTLMConnectionPool, self).urlopen(method, url, body, + headers, retries, + redirect, + assert_same_host) diff --git a/resources/lib/libraries/requests/packages/urllib3/contrib/pyopenssl.py b/resources/lib/libraries/requests/packages/urllib3/contrib/pyopenssl.py new file mode 100644 index 00000000..5996153a --- /dev/null +++ b/resources/lib/libraries/requests/packages/urllib3/contrib/pyopenssl.py @@ -0,0 +1,310 @@ +'''SSL with SNI_-support for Python 2. Follow these instructions if you would +like to verify SSL certificates in Python 2. Note, the default libraries do +*not* do certificate checking; you need to do additional work to validate +certificates yourself. + +This needs the following packages installed: + +* pyOpenSSL (tested with 0.13) +* ndg-httpsclient (tested with 0.3.2) +* pyasn1 (tested with 0.1.6) + +You can install them with the following command: + + pip install pyopenssl ndg-httpsclient pyasn1 + +To activate certificate checking, call +:func:`~urllib3.contrib.pyopenssl.inject_into_urllib3` from your Python code +before you begin making HTTP requests. This can be done in a ``sitecustomize`` +module, or at any other time before your application begins using ``urllib3``, +like this:: + + try: + import urllib3.contrib.pyopenssl + urllib3.contrib.pyopenssl.inject_into_urllib3() + except ImportError: + pass + +Now you can use :mod:`urllib3` as you normally would, and it will support SNI +when the required modules are installed. + +Activating this module also has the positive side effect of disabling SSL/TLS +compression in Python 2 (see `CRIME attack`_). + +If you want to configure the default list of supported cipher suites, you can +set the ``urllib3.contrib.pyopenssl.DEFAULT_SSL_CIPHER_LIST`` variable. + +Module Variables +---------------- + +:var DEFAULT_SSL_CIPHER_LIST: The list of supported SSL/TLS cipher suites. + +.. _sni: https://en.wikipedia.org/wiki/Server_Name_Indication +.. _crime attack: https://en.wikipedia.org/wiki/CRIME_(security_exploit) + +''' +from __future__ import absolute_import + +try: + from ndg.httpsclient.ssl_peer_verification import SUBJ_ALT_NAME_SUPPORT + from ndg.httpsclient.subj_alt_name import SubjectAltName as BaseSubjectAltName +except SyntaxError as e: + raise ImportError(e) + +import OpenSSL.SSL +from pyasn1.codec.der import decoder as der_decoder +from pyasn1.type import univ, constraint +from socket import _fileobject, timeout, error as SocketError +import ssl +import select + +from .. import connection +from .. import util + +__all__ = ['inject_into_urllib3', 'extract_from_urllib3'] + +# SNI only *really* works if we can read the subjectAltName of certificates. +HAS_SNI = SUBJ_ALT_NAME_SUPPORT + +# Map from urllib3 to PyOpenSSL compatible parameter-values. +_openssl_versions = { + ssl.PROTOCOL_SSLv23: OpenSSL.SSL.SSLv23_METHOD, + ssl.PROTOCOL_TLSv1: OpenSSL.SSL.TLSv1_METHOD, +} + +if hasattr(ssl, 'PROTOCOL_TLSv1_1') and hasattr(OpenSSL.SSL, 'TLSv1_1_METHOD'): + _openssl_versions[ssl.PROTOCOL_TLSv1_1] = OpenSSL.SSL.TLSv1_1_METHOD + +if hasattr(ssl, 'PROTOCOL_TLSv1_2') and hasattr(OpenSSL.SSL, 'TLSv1_2_METHOD'): + _openssl_versions[ssl.PROTOCOL_TLSv1_2] = OpenSSL.SSL.TLSv1_2_METHOD + +try: + _openssl_versions.update({ssl.PROTOCOL_SSLv3: OpenSSL.SSL.SSLv3_METHOD}) +except AttributeError: + pass + +_openssl_verify = { + ssl.CERT_NONE: OpenSSL.SSL.VERIFY_NONE, + ssl.CERT_OPTIONAL: OpenSSL.SSL.VERIFY_PEER, + ssl.CERT_REQUIRED: + OpenSSL.SSL.VERIFY_PEER + OpenSSL.SSL.VERIFY_FAIL_IF_NO_PEER_CERT, +} + +DEFAULT_SSL_CIPHER_LIST = util.ssl_.DEFAULT_CIPHERS + +# OpenSSL will only write 16K at a time +SSL_WRITE_BLOCKSIZE = 16384 + +orig_util_HAS_SNI = util.HAS_SNI +orig_connection_ssl_wrap_socket = connection.ssl_wrap_socket + + +def inject_into_urllib3(): + 'Monkey-patch urllib3 with PyOpenSSL-backed SSL-support.' + + connection.ssl_wrap_socket = ssl_wrap_socket + util.HAS_SNI = HAS_SNI + + +def extract_from_urllib3(): + 'Undo monkey-patching by :func:`inject_into_urllib3`.' + + connection.ssl_wrap_socket = orig_connection_ssl_wrap_socket + util.HAS_SNI = orig_util_HAS_SNI + + +# Note: This is a slightly bug-fixed version of same from ndg-httpsclient. +class SubjectAltName(BaseSubjectAltName): + '''ASN.1 implementation for subjectAltNames support''' + + # There is no limit to how many SAN certificates a certificate may have, + # however this needs to have some limit so we'll set an arbitrarily high + # limit. + sizeSpec = univ.SequenceOf.sizeSpec + \ + constraint.ValueSizeConstraint(1, 1024) + + +# Note: This is a slightly bug-fixed version of same from ndg-httpsclient. +def get_subj_alt_name(peer_cert): + # Search through extensions + dns_name = [] + if not SUBJ_ALT_NAME_SUPPORT: + return dns_name + + general_names = SubjectAltName() + for i in range(peer_cert.get_extension_count()): + ext = peer_cert.get_extension(i) + ext_name = ext.get_short_name() + if ext_name != 'subjectAltName': + continue + + # PyOpenSSL returns extension data in ASN.1 encoded form + ext_dat = ext.get_data() + decoded_dat = der_decoder.decode(ext_dat, + asn1Spec=general_names) + + for name in decoded_dat: + if not isinstance(name, SubjectAltName): + continue + for entry in range(len(name)): + component = name.getComponentByPosition(entry) + if component.getName() != 'dNSName': + continue + dns_name.append(str(component.getComponent())) + + return dns_name + + +class WrappedSocket(object): + '''API-compatibility wrapper for Python OpenSSL's Connection-class. + + Note: _makefile_refs, _drop() and _reuse() are needed for the garbage + collector of pypy. + ''' + + def __init__(self, connection, socket, suppress_ragged_eofs=True): + self.connection = connection + self.socket = socket + self.suppress_ragged_eofs = suppress_ragged_eofs + self._makefile_refs = 0 + + def fileno(self): + return self.socket.fileno() + + def makefile(self, mode, bufsize=-1): + self._makefile_refs += 1 + return _fileobject(self, mode, bufsize, close=True) + + def recv(self, *args, **kwargs): + try: + data = self.connection.recv(*args, **kwargs) + except OpenSSL.SSL.SysCallError as e: + if self.suppress_ragged_eofs and e.args == (-1, 'Unexpected EOF'): + return b'' + else: + raise SocketError(e) + except OpenSSL.SSL.ZeroReturnError as e: + if self.connection.get_shutdown() == OpenSSL.SSL.RECEIVED_SHUTDOWN: + return b'' + else: + raise + except OpenSSL.SSL.WantReadError: + rd, wd, ed = select.select( + [self.socket], [], [], self.socket.gettimeout()) + if not rd: + raise timeout('The read operation timed out') + else: + return self.recv(*args, **kwargs) + else: + return data + + def settimeout(self, timeout): + return self.socket.settimeout(timeout) + + def _send_until_done(self, data): + while True: + try: + return self.connection.send(data) + except OpenSSL.SSL.WantWriteError: + _, wlist, _ = select.select([], [self.socket], [], + self.socket.gettimeout()) + if not wlist: + raise timeout() + continue + + def sendall(self, data): + total_sent = 0 + while total_sent < len(data): + sent = self._send_until_done(data[total_sent:total_sent + SSL_WRITE_BLOCKSIZE]) + total_sent += sent + + def shutdown(self): + # FIXME rethrow compatible exceptions should we ever use this + self.connection.shutdown() + + def close(self): + if self._makefile_refs < 1: + try: + return self.connection.close() + except OpenSSL.SSL.Error: + return + else: + self._makefile_refs -= 1 + + def getpeercert(self, binary_form=False): + x509 = self.connection.get_peer_certificate() + + if not x509: + return x509 + + if binary_form: + return OpenSSL.crypto.dump_certificate( + OpenSSL.crypto.FILETYPE_ASN1, + x509) + + return { + 'subject': ( + (('commonName', x509.get_subject().CN),), + ), + 'subjectAltName': [ + ('DNS', value) + for value in get_subj_alt_name(x509) + ] + } + + def _reuse(self): + self._makefile_refs += 1 + + def _drop(self): + if self._makefile_refs < 1: + self.close() + else: + self._makefile_refs -= 1 + + +def _verify_callback(cnx, x509, err_no, err_depth, return_code): + return err_no == 0 + + +def ssl_wrap_socket(sock, keyfile=None, certfile=None, cert_reqs=None, + ca_certs=None, server_hostname=None, + ssl_version=None, ca_cert_dir=None): + ctx = OpenSSL.SSL.Context(_openssl_versions[ssl_version]) + if certfile: + keyfile = keyfile or certfile # Match behaviour of the normal python ssl library + ctx.use_certificate_file(certfile) + if keyfile: + ctx.use_privatekey_file(keyfile) + if cert_reqs != ssl.CERT_NONE: + ctx.set_verify(_openssl_verify[cert_reqs], _verify_callback) + if ca_certs or ca_cert_dir: + try: + ctx.load_verify_locations(ca_certs, ca_cert_dir) + except OpenSSL.SSL.Error as e: + raise ssl.SSLError('bad ca_certs: %r' % ca_certs, e) + else: + ctx.set_default_verify_paths() + + # Disable TLS compression to migitate CRIME attack (issue #309) + OP_NO_COMPRESSION = 0x20000 + ctx.set_options(OP_NO_COMPRESSION) + + # Set list of supported ciphersuites. + ctx.set_cipher_list(DEFAULT_SSL_CIPHER_LIST) + + cnx = OpenSSL.SSL.Connection(ctx, sock) + cnx.set_tlsext_host_name(server_hostname) + cnx.set_connect_state() + while True: + try: + cnx.do_handshake() + except OpenSSL.SSL.WantReadError: + rd, _, _ = select.select([sock], [], [], sock.gettimeout()) + if not rd: + raise timeout('select timed out') + continue + except OpenSSL.SSL.Error as e: + raise ssl.SSLError('bad handshake: %r' % e) + break + + return WrappedSocket(cnx, sock) diff --git a/resources/lib/libraries/requests/packages/urllib3/exceptions.py b/resources/lib/libraries/requests/packages/urllib3/exceptions.py new file mode 100644 index 00000000..8e07eb61 --- /dev/null +++ b/resources/lib/libraries/requests/packages/urllib3/exceptions.py @@ -0,0 +1,201 @@ +from __future__ import absolute_import +# Base Exceptions + + +class HTTPError(Exception): + "Base exception used by this module." + pass + + +class HTTPWarning(Warning): + "Base warning used by this module." + pass + + +class PoolError(HTTPError): + "Base exception for errors caused within a pool." + def __init__(self, pool, message): + self.pool = pool + HTTPError.__init__(self, "%s: %s" % (pool, message)) + + def __reduce__(self): + # For pickling purposes. + return self.__class__, (None, None) + + +class RequestError(PoolError): + "Base exception for PoolErrors that have associated URLs." + def __init__(self, pool, url, message): + self.url = url + PoolError.__init__(self, pool, message) + + def __reduce__(self): + # For pickling purposes. + return self.__class__, (None, self.url, None) + + +class SSLError(HTTPError): + "Raised when SSL certificate fails in an HTTPS connection." + pass + + +class ProxyError(HTTPError): + "Raised when the connection to a proxy fails." + pass + + +class DecodeError(HTTPError): + "Raised when automatic decoding based on Content-Type fails." + pass + + +class ProtocolError(HTTPError): + "Raised when something unexpected happens mid-request/response." + pass + + +#: Renamed to ProtocolError but aliased for backwards compatibility. +ConnectionError = ProtocolError + + +# Leaf Exceptions + +class MaxRetryError(RequestError): + """Raised when the maximum number of retries is exceeded. + + :param pool: The connection pool + :type pool: :class:`~urllib3.connectionpool.HTTPConnectionPool` + :param string url: The requested Url + :param exceptions.Exception reason: The underlying error + + """ + + def __init__(self, pool, url, reason=None): + self.reason = reason + + message = "Max retries exceeded with url: %s (Caused by %r)" % ( + url, reason) + + RequestError.__init__(self, pool, url, message) + + +class HostChangedError(RequestError): + "Raised when an existing pool gets a request for a foreign host." + + def __init__(self, pool, url, retries=3): + message = "Tried to open a foreign host with url: %s" % url + RequestError.__init__(self, pool, url, message) + self.retries = retries + + +class TimeoutStateError(HTTPError): + """ Raised when passing an invalid state to a timeout """ + pass + + +class TimeoutError(HTTPError): + """ Raised when a socket timeout error occurs. + + Catching this error will catch both :exc:`ReadTimeoutErrors + <ReadTimeoutError>` and :exc:`ConnectTimeoutErrors <ConnectTimeoutError>`. + """ + pass + + +class ReadTimeoutError(TimeoutError, RequestError): + "Raised when a socket timeout occurs while receiving data from a server" + pass + + +# This timeout error does not have a URL attached and needs to inherit from the +# base HTTPError +class ConnectTimeoutError(TimeoutError): + "Raised when a socket timeout occurs while connecting to a server" + pass + + +class NewConnectionError(ConnectTimeoutError, PoolError): + "Raised when we fail to establish a new connection. Usually ECONNREFUSED." + pass + + +class EmptyPoolError(PoolError): + "Raised when a pool runs out of connections and no more are allowed." + pass + + +class ClosedPoolError(PoolError): + "Raised when a request enters a pool after the pool has been closed." + pass + + +class LocationValueError(ValueError, HTTPError): + "Raised when there is something wrong with a given URL input." + pass + + +class LocationParseError(LocationValueError): + "Raised when get_host or similar fails to parse the URL input." + + def __init__(self, location): + message = "Failed to parse: %s" % location + HTTPError.__init__(self, message) + + self.location = location + + +class ResponseError(HTTPError): + "Used as a container for an error reason supplied in a MaxRetryError." + GENERIC_ERROR = 'too many error responses' + SPECIFIC_ERROR = 'too many {status_code} error responses' + + +class SecurityWarning(HTTPWarning): + "Warned when perfoming security reducing actions" + pass + + +class SubjectAltNameWarning(SecurityWarning): + "Warned when connecting to a host with a certificate missing a SAN." + pass + + +class InsecureRequestWarning(SecurityWarning): + "Warned when making an unverified HTTPS request." + pass + + +class SystemTimeWarning(SecurityWarning): + "Warned when system time is suspected to be wrong" + pass + + +class InsecurePlatformWarning(SecurityWarning): + "Warned when certain SSL configuration is not available on a platform." + pass + + +class SNIMissingWarning(HTTPWarning): + "Warned when making a HTTPS request without SNI available." + pass + + +class ResponseNotChunked(ProtocolError, ValueError): + "Response needs to be chunked in order to read it as chunks." + pass + + +class ProxySchemeUnknown(AssertionError, ValueError): + "ProxyManager does not support the supplied scheme" + # TODO(t-8ch): Stop inheriting from AssertionError in v2.0. + + def __init__(self, scheme): + message = "Not supported proxy scheme %s" % scheme + super(ProxySchemeUnknown, self).__init__(message) + + +class HeaderParsingError(HTTPError): + "Raised by assert_header_parsing, but we convert it to a log.warning statement." + def __init__(self, defects, unparsed_data): + message = '%s, unparsed data: %r' % (defects or 'Unknown', unparsed_data) + super(HeaderParsingError, self).__init__(message) diff --git a/resources/lib/libraries/requests/packages/urllib3/fields.py b/resources/lib/libraries/requests/packages/urllib3/fields.py new file mode 100644 index 00000000..c7d48113 --- /dev/null +++ b/resources/lib/libraries/requests/packages/urllib3/fields.py @@ -0,0 +1,178 @@ +from __future__ import absolute_import +import email.utils +import mimetypes + +from .packages import six + + +def guess_content_type(filename, default='application/octet-stream'): + """ + Guess the "Content-Type" of a file. + + :param filename: + The filename to guess the "Content-Type" of using :mod:`mimetypes`. + :param default: + If no "Content-Type" can be guessed, default to `default`. + """ + if filename: + return mimetypes.guess_type(filename)[0] or default + return default + + +def format_header_param(name, value): + """ + Helper function to format and quote a single header parameter. + + Particularly useful for header parameters which might contain + non-ASCII values, like file names. This follows RFC 2231, as + suggested by RFC 2388 Section 4.4. + + :param name: + The name of the parameter, a string expected to be ASCII only. + :param value: + The value of the parameter, provided as a unicode string. + """ + if not any(ch in value for ch in '"\\\r\n'): + result = '%s="%s"' % (name, value) + try: + result.encode('ascii') + except UnicodeEncodeError: + pass + else: + return result + if not six.PY3: # Python 2: + value = value.encode('utf-8') + value = email.utils.encode_rfc2231(value, 'utf-8') + value = '%s*=%s' % (name, value) + return value + + +class RequestField(object): + """ + A data container for request body parameters. + + :param name: + The name of this request field. + :param data: + The data/value body. + :param filename: + An optional filename of the request field. + :param headers: + An optional dict-like object of headers to initially use for the field. + """ + def __init__(self, name, data, filename=None, headers=None): + self._name = name + self._filename = filename + self.data = data + self.headers = {} + if headers: + self.headers = dict(headers) + + @classmethod + def from_tuples(cls, fieldname, value): + """ + A :class:`~urllib3.fields.RequestField` factory from old-style tuple parameters. + + Supports constructing :class:`~urllib3.fields.RequestField` from + parameter of key/value strings AND key/filetuple. A filetuple is a + (filename, data, MIME type) tuple where the MIME type is optional. + For example:: + + 'foo': 'bar', + 'fakefile': ('foofile.txt', 'contents of foofile'), + 'realfile': ('barfile.txt', open('realfile').read()), + 'typedfile': ('bazfile.bin', open('bazfile').read(), 'image/jpeg'), + 'nonamefile': 'contents of nonamefile field', + + Field names and filenames must be unicode. + """ + if isinstance(value, tuple): + if len(value) == 3: + filename, data, content_type = value + else: + filename, data = value + content_type = guess_content_type(filename) + else: + filename = None + content_type = None + data = value + + request_param = cls(fieldname, data, filename=filename) + request_param.make_multipart(content_type=content_type) + + return request_param + + def _render_part(self, name, value): + """ + Overridable helper function to format a single header parameter. + + :param name: + The name of the parameter, a string expected to be ASCII only. + :param value: + The value of the parameter, provided as a unicode string. + """ + return format_header_param(name, value) + + def _render_parts(self, header_parts): + """ + Helper function to format and quote a single header. + + Useful for single headers that are composed of multiple items. E.g., + 'Content-Disposition' fields. + + :param header_parts: + A sequence of (k, v) typles or a :class:`dict` of (k, v) to format + as `k1="v1"; k2="v2"; ...`. + """ + parts = [] + iterable = header_parts + if isinstance(header_parts, dict): + iterable = header_parts.items() + + for name, value in iterable: + if value: + parts.append(self._render_part(name, value)) + + return '; '.join(parts) + + def render_headers(self): + """ + Renders the headers for this request field. + """ + lines = [] + + sort_keys = ['Content-Disposition', 'Content-Type', 'Content-Location'] + for sort_key in sort_keys: + if self.headers.get(sort_key, False): + lines.append('%s: %s' % (sort_key, self.headers[sort_key])) + + for header_name, header_value in self.headers.items(): + if header_name not in sort_keys: + if header_value: + lines.append('%s: %s' % (header_name, header_value)) + + lines.append('\r\n') + return '\r\n'.join(lines) + + def make_multipart(self, content_disposition=None, content_type=None, + content_location=None): + """ + Makes this request field into a multipart request field. + + This method overrides "Content-Disposition", "Content-Type" and + "Content-Location" headers to the request parameter. + + :param content_type: + The 'Content-Type' of the request body. + :param content_location: + The 'Content-Location' of the request body. + + """ + self.headers['Content-Disposition'] = content_disposition or 'form-data' + self.headers['Content-Disposition'] += '; '.join([ + '', self._render_parts( + (('name', self._name), ('filename', self._filename)) + ) + ]) + self.headers['Content-Type'] = content_type + self.headers['Content-Location'] = content_location diff --git a/resources/lib/libraries/requests/packages/urllib3/filepost.py b/resources/lib/libraries/requests/packages/urllib3/filepost.py new file mode 100644 index 00000000..97a2843c --- /dev/null +++ b/resources/lib/libraries/requests/packages/urllib3/filepost.py @@ -0,0 +1,94 @@ +from __future__ import absolute_import +import codecs + +from uuid import uuid4 +from io import BytesIO + +from .packages import six +from .packages.six import b +from .fields import RequestField + +writer = codecs.lookup('utf-8')[3] + + +def choose_boundary(): + """ + Our embarassingly-simple replacement for mimetools.choose_boundary. + """ + return uuid4().hex + + +def iter_field_objects(fields): + """ + Iterate over fields. + + Supports list of (k, v) tuples and dicts, and lists of + :class:`~urllib3.fields.RequestField`. + + """ + if isinstance(fields, dict): + i = six.iteritems(fields) + else: + i = iter(fields) + + for field in i: + if isinstance(field, RequestField): + yield field + else: + yield RequestField.from_tuples(*field) + + +def iter_fields(fields): + """ + .. deprecated:: 1.6 + + Iterate over fields. + + The addition of :class:`~urllib3.fields.RequestField` makes this function + obsolete. Instead, use :func:`iter_field_objects`, which returns + :class:`~urllib3.fields.RequestField` objects. + + Supports list of (k, v) tuples and dicts. + """ + if isinstance(fields, dict): + return ((k, v) for k, v in six.iteritems(fields)) + + return ((k, v) for k, v in fields) + + +def encode_multipart_formdata(fields, boundary=None): + """ + Encode a dictionary of ``fields`` using the multipart/form-data MIME format. + + :param fields: + Dictionary of fields or list of (key, :class:`~urllib3.fields.RequestField`). + + :param boundary: + If not specified, then a random boundary will be generated using + :func:`mimetools.choose_boundary`. + """ + body = BytesIO() + if boundary is None: + boundary = choose_boundary() + + for field in iter_field_objects(fields): + body.write(b('--%s\r\n' % (boundary))) + + writer(body).write(field.render_headers()) + data = field.data + + if isinstance(data, int): + data = str(data) # Backwards compatibility + + if isinstance(data, six.text_type): + writer(body).write(data) + else: + body.write(data) + + body.write(b'\r\n') + + body.write(b('--%s--\r\n' % (boundary))) + + content_type = str('multipart/form-data; boundary=%s' % boundary) + + return body.getvalue(), content_type diff --git a/resources/lib/libraries/requests/packages/urllib3/packages/__init__.py b/resources/lib/libraries/requests/packages/urllib3/packages/__init__.py new file mode 100644 index 00000000..170e974c --- /dev/null +++ b/resources/lib/libraries/requests/packages/urllib3/packages/__init__.py @@ -0,0 +1,5 @@ +from __future__ import absolute_import + +from . import ssl_match_hostname + +__all__ = ('ssl_match_hostname', ) diff --git a/resources/lib/libraries/requests/packages/urllib3/packages/ordered_dict.py b/resources/lib/libraries/requests/packages/urllib3/packages/ordered_dict.py new file mode 100644 index 00000000..4479363c --- /dev/null +++ b/resources/lib/libraries/requests/packages/urllib3/packages/ordered_dict.py @@ -0,0 +1,259 @@ +# Backport of OrderedDict() class that runs on Python 2.4, 2.5, 2.6, 2.7 and pypy. +# Passes Python2.7's test suite and incorporates all the latest updates. +# Copyright 2009 Raymond Hettinger, released under the MIT License. +# http://code.activestate.com/recipes/576693/ +try: + from thread import get_ident as _get_ident +except ImportError: + from dummy_thread import get_ident as _get_ident + +try: + from _abcoll import KeysView, ValuesView, ItemsView +except ImportError: + pass + + +class OrderedDict(dict): + 'Dictionary that remembers insertion order' + # An inherited dict maps keys to values. + # The inherited dict provides __getitem__, __len__, __contains__, and get. + # The remaining methods are order-aware. + # Big-O running times for all methods are the same as for regular dictionaries. + + # The internal self.__map dictionary maps keys to links in a doubly linked list. + # The circular doubly linked list starts and ends with a sentinel element. + # The sentinel element never gets deleted (this simplifies the algorithm). + # Each link is stored as a list of length three: [PREV, NEXT, KEY]. + + def __init__(self, *args, **kwds): + '''Initialize an ordered dictionary. Signature is the same as for + regular dictionaries, but keyword arguments are not recommended + because their insertion order is arbitrary. + + ''' + if len(args) > 1: + raise TypeError('expected at most 1 arguments, got %d' % len(args)) + try: + self.__root + except AttributeError: + self.__root = root = [] # sentinel node + root[:] = [root, root, None] + self.__map = {} + self.__update(*args, **kwds) + + def __setitem__(self, key, value, dict_setitem=dict.__setitem__): + 'od.__setitem__(i, y) <==> od[i]=y' + # Setting a new item creates a new link which goes at the end of the linked + # list, and the inherited dictionary is updated with the new key/value pair. + if key not in self: + root = self.__root + last = root[0] + last[1] = root[0] = self.__map[key] = [last, root, key] + dict_setitem(self, key, value) + + def __delitem__(self, key, dict_delitem=dict.__delitem__): + 'od.__delitem__(y) <==> del od[y]' + # Deleting an existing item uses self.__map to find the link which is + # then removed by updating the links in the predecessor and successor nodes. + dict_delitem(self, key) + link_prev, link_next, key = self.__map.pop(key) + link_prev[1] = link_next + link_next[0] = link_prev + + def __iter__(self): + 'od.__iter__() <==> iter(od)' + root = self.__root + curr = root[1] + while curr is not root: + yield curr[2] + curr = curr[1] + + def __reversed__(self): + 'od.__reversed__() <==> reversed(od)' + root = self.__root + curr = root[0] + while curr is not root: + yield curr[2] + curr = curr[0] + + def clear(self): + 'od.clear() -> None. Remove all items from od.' + try: + for node in self.__map.itervalues(): + del node[:] + root = self.__root + root[:] = [root, root, None] + self.__map.clear() + except AttributeError: + pass + dict.clear(self) + + def popitem(self, last=True): + '''od.popitem() -> (k, v), return and remove a (key, value) pair. + Pairs are returned in LIFO order if last is true or FIFO order if false. + + ''' + if not self: + raise KeyError('dictionary is empty') + root = self.__root + if last: + link = root[0] + link_prev = link[0] + link_prev[1] = root + root[0] = link_prev + else: + link = root[1] + link_next = link[1] + root[1] = link_next + link_next[0] = root + key = link[2] + del self.__map[key] + value = dict.pop(self, key) + return key, value + + # -- the following methods do not depend on the internal structure -- + + def keys(self): + 'od.keys() -> list of keys in od' + return list(self) + + def values(self): + 'od.values() -> list of values in od' + return [self[key] for key in self] + + def items(self): + 'od.items() -> list of (key, value) pairs in od' + return [(key, self[key]) for key in self] + + def iterkeys(self): + 'od.iterkeys() -> an iterator over the keys in od' + return iter(self) + + def itervalues(self): + 'od.itervalues -> an iterator over the values in od' + for k in self: + yield self[k] + + def iteritems(self): + 'od.iteritems -> an iterator over the (key, value) items in od' + for k in self: + yield (k, self[k]) + + def update(*args, **kwds): + '''od.update(E, **F) -> None. Update od from dict/iterable E and F. + + If E is a dict instance, does: for k in E: od[k] = E[k] + If E has a .keys() method, does: for k in E.keys(): od[k] = E[k] + Or if E is an iterable of items, does: for k, v in E: od[k] = v + In either case, this is followed by: for k, v in F.items(): od[k] = v + + ''' + if len(args) > 2: + raise TypeError('update() takes at most 2 positional ' + 'arguments (%d given)' % (len(args),)) + elif not args: + raise TypeError('update() takes at least 1 argument (0 given)') + self = args[0] + # Make progressively weaker assumptions about "other" + other = () + if len(args) == 2: + other = args[1] + if isinstance(other, dict): + for key in other: + self[key] = other[key] + elif hasattr(other, 'keys'): + for key in other.keys(): + self[key] = other[key] + else: + for key, value in other: + self[key] = value + for key, value in kwds.items(): + self[key] = value + + __update = update # let subclasses override update without breaking __init__ + + __marker = object() + + def pop(self, key, default=__marker): + '''od.pop(k[,d]) -> v, remove specified key and return the corresponding value. + If key is not found, d is returned if given, otherwise KeyError is raised. + + ''' + if key in self: + result = self[key] + del self[key] + return result + if default is self.__marker: + raise KeyError(key) + return default + + def setdefault(self, key, default=None): + 'od.setdefault(k[,d]) -> od.get(k,d), also set od[k]=d if k not in od' + if key in self: + return self[key] + self[key] = default + return default + + def __repr__(self, _repr_running={}): + 'od.__repr__() <==> repr(od)' + call_key = id(self), _get_ident() + if call_key in _repr_running: + return '...' + _repr_running[call_key] = 1 + try: + if not self: + return '%s()' % (self.__class__.__name__,) + return '%s(%r)' % (self.__class__.__name__, self.items()) + finally: + del _repr_running[call_key] + + def __reduce__(self): + 'Return state information for pickling' + items = [[k, self[k]] for k in self] + inst_dict = vars(self).copy() + for k in vars(OrderedDict()): + inst_dict.pop(k, None) + if inst_dict: + return (self.__class__, (items,), inst_dict) + return self.__class__, (items,) + + def copy(self): + 'od.copy() -> a shallow copy of od' + return self.__class__(self) + + @classmethod + def fromkeys(cls, iterable, value=None): + '''OD.fromkeys(S[, v]) -> New ordered dictionary with keys from S + and values equal to v (which defaults to None). + + ''' + d = cls() + for key in iterable: + d[key] = value + return d + + def __eq__(self, other): + '''od.__eq__(y) <==> od==y. Comparison to another OD is order-sensitive + while comparison to a regular mapping is order-insensitive. + + ''' + if isinstance(other, OrderedDict): + return len(self)==len(other) and self.items() == other.items() + return dict.__eq__(self, other) + + def __ne__(self, other): + return not self == other + + # -- the following methods are only used in Python 2.7 -- + + def viewkeys(self): + "od.viewkeys() -> a set-like object providing a view on od's keys" + return KeysView(self) + + def viewvalues(self): + "od.viewvalues() -> an object providing a view on od's values" + return ValuesView(self) + + def viewitems(self): + "od.viewitems() -> a set-like object providing a view on od's items" + return ItemsView(self) diff --git a/resources/lib/libraries/requests/packages/urllib3/packages/six.py b/resources/lib/libraries/requests/packages/urllib3/packages/six.py new file mode 100644 index 00000000..27d80112 --- /dev/null +++ b/resources/lib/libraries/requests/packages/urllib3/packages/six.py @@ -0,0 +1,385 @@ +"""Utilities for writing code that runs on Python 2 and 3""" + +#Copyright (c) 2010-2011 Benjamin Peterson + +#Permission is hereby granted, free of charge, to any person obtaining a copy of +#this software and associated documentation files (the "Software"), to deal in +#the Software without restriction, including without limitation the rights to +#use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +#the Software, and to permit persons to whom the Software is furnished to do so, +#subject to the following conditions: + +#The above copyright notice and this permission notice shall be included in all +#copies or substantial portions of the Software. + +#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +#FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +#COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +#IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +#CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +import operator +import sys +import types + +__author__ = "Benjamin Peterson <benjamin@python.org>" +__version__ = "1.2.0" # Revision 41c74fef2ded + + +# True if we are running on Python 3. +PY3 = sys.version_info[0] == 3 + +if PY3: + string_types = str, + integer_types = int, + class_types = type, + text_type = str + binary_type = bytes + + MAXSIZE = sys.maxsize +else: + string_types = basestring, + integer_types = (int, long) + class_types = (type, types.ClassType) + text_type = unicode + binary_type = str + + if sys.platform.startswith("java"): + # Jython always uses 32 bits. + MAXSIZE = int((1 << 31) - 1) + else: + # It's possible to have sizeof(long) != sizeof(Py_ssize_t). + class X(object): + def __len__(self): + return 1 << 31 + try: + len(X()) + except OverflowError: + # 32-bit + MAXSIZE = int((1 << 31) - 1) + else: + # 64-bit + MAXSIZE = int((1 << 63) - 1) + del X + + +def _add_doc(func, doc): + """Add documentation to a function.""" + func.__doc__ = doc + + +def _import_module(name): + """Import module, returning the module after the last dot.""" + __import__(name) + return sys.modules[name] + + +class _LazyDescr(object): + + def __init__(self, name): + self.name = name + + def __get__(self, obj, tp): + result = self._resolve() + setattr(obj, self.name, result) + # This is a bit ugly, but it avoids running this again. + delattr(tp, self.name) + return result + + +class MovedModule(_LazyDescr): + + def __init__(self, name, old, new=None): + super(MovedModule, self).__init__(name) + if PY3: + if new is None: + new = name + self.mod = new + else: + self.mod = old + + def _resolve(self): + return _import_module(self.mod) + + +class MovedAttribute(_LazyDescr): + + def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None): + super(MovedAttribute, self).__init__(name) + if PY3: + if new_mod is None: + new_mod = name + self.mod = new_mod + if new_attr is None: + if old_attr is None: + new_attr = name + else: + new_attr = old_attr + self.attr = new_attr + else: + self.mod = old_mod + if old_attr is None: + old_attr = name + self.attr = old_attr + + def _resolve(self): + module = _import_module(self.mod) + return getattr(module, self.attr) + + + +class _MovedItems(types.ModuleType): + """Lazy loading of moved objects""" + + +_moved_attributes = [ + MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"), + MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"), + MovedAttribute("input", "__builtin__", "builtins", "raw_input", "input"), + MovedAttribute("map", "itertools", "builtins", "imap", "map"), + MovedAttribute("reload_module", "__builtin__", "imp", "reload"), + MovedAttribute("reduce", "__builtin__", "functools"), + MovedAttribute("StringIO", "StringIO", "io"), + MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"), + MovedAttribute("zip", "itertools", "builtins", "izip", "zip"), + + MovedModule("builtins", "__builtin__"), + MovedModule("configparser", "ConfigParser"), + MovedModule("copyreg", "copy_reg"), + MovedModule("http_cookiejar", "cookielib", "http.cookiejar"), + MovedModule("http_cookies", "Cookie", "http.cookies"), + MovedModule("html_entities", "htmlentitydefs", "html.entities"), + MovedModule("html_parser", "HTMLParser", "html.parser"), + MovedModule("http_client", "httplib", "http.client"), + MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"), + MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"), + MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"), + MovedModule("cPickle", "cPickle", "pickle"), + MovedModule("queue", "Queue"), + MovedModule("reprlib", "repr"), + MovedModule("socketserver", "SocketServer"), + MovedModule("tkinter", "Tkinter"), + MovedModule("tkinter_dialog", "Dialog", "tkinter.dialog"), + MovedModule("tkinter_filedialog", "FileDialog", "tkinter.filedialog"), + MovedModule("tkinter_scrolledtext", "ScrolledText", "tkinter.scrolledtext"), + MovedModule("tkinter_simpledialog", "SimpleDialog", "tkinter.simpledialog"), + MovedModule("tkinter_tix", "Tix", "tkinter.tix"), + MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"), + MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"), + MovedModule("tkinter_colorchooser", "tkColorChooser", + "tkinter.colorchooser"), + MovedModule("tkinter_commondialog", "tkCommonDialog", + "tkinter.commondialog"), + MovedModule("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"), + MovedModule("tkinter_font", "tkFont", "tkinter.font"), + MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"), + MovedModule("tkinter_tksimpledialog", "tkSimpleDialog", + "tkinter.simpledialog"), + MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"), + MovedModule("winreg", "_winreg"), +] +for attr in _moved_attributes: + setattr(_MovedItems, attr.name, attr) +del attr + +moves = sys.modules[__name__ + ".moves"] = _MovedItems("moves") + + +def add_move(move): + """Add an item to six.moves.""" + setattr(_MovedItems, move.name, move) + + +def remove_move(name): + """Remove item from six.moves.""" + try: + delattr(_MovedItems, name) + except AttributeError: + try: + del moves.__dict__[name] + except KeyError: + raise AttributeError("no such move, %r" % (name,)) + + +if PY3: + _meth_func = "__func__" + _meth_self = "__self__" + + _func_code = "__code__" + _func_defaults = "__defaults__" + + _iterkeys = "keys" + _itervalues = "values" + _iteritems = "items" +else: + _meth_func = "im_func" + _meth_self = "im_self" + + _func_code = "func_code" + _func_defaults = "func_defaults" + + _iterkeys = "iterkeys" + _itervalues = "itervalues" + _iteritems = "iteritems" + + +try: + advance_iterator = next +except NameError: + def advance_iterator(it): + return it.next() +next = advance_iterator + + +if PY3: + def get_unbound_function(unbound): + return unbound + + Iterator = object + + def callable(obj): + return any("__call__" in klass.__dict__ for klass in type(obj).__mro__) +else: + def get_unbound_function(unbound): + return unbound.im_func + + class Iterator(object): + + def next(self): + return type(self).__next__(self) + + callable = callable +_add_doc(get_unbound_function, + """Get the function out of a possibly unbound function""") + + +get_method_function = operator.attrgetter(_meth_func) +get_method_self = operator.attrgetter(_meth_self) +get_function_code = operator.attrgetter(_func_code) +get_function_defaults = operator.attrgetter(_func_defaults) + + +def iterkeys(d): + """Return an iterator over the keys of a dictionary.""" + return iter(getattr(d, _iterkeys)()) + +def itervalues(d): + """Return an iterator over the values of a dictionary.""" + return iter(getattr(d, _itervalues)()) + +def iteritems(d): + """Return an iterator over the (key, value) pairs of a dictionary.""" + return iter(getattr(d, _iteritems)()) + + +if PY3: + def b(s): + return s.encode("latin-1") + def u(s): + return s + if sys.version_info[1] <= 1: + def int2byte(i): + return bytes((i,)) + else: + # This is about 2x faster than the implementation above on 3.2+ + int2byte = operator.methodcaller("to_bytes", 1, "big") + import io + StringIO = io.StringIO + BytesIO = io.BytesIO +else: + def b(s): + return s + def u(s): + return unicode(s, "unicode_escape") + int2byte = chr + import StringIO + StringIO = BytesIO = StringIO.StringIO +_add_doc(b, """Byte literal""") +_add_doc(u, """Text literal""") + + +if PY3: + import builtins + exec_ = getattr(builtins, "exec") + + + def reraise(tp, value, tb=None): + if value.__traceback__ is not tb: + raise value.with_traceback(tb) + raise value + + + print_ = getattr(builtins, "print") + del builtins + +else: + def exec_(code, globs=None, locs=None): + """Execute code in a namespace.""" + if globs is None: + frame = sys._getframe(1) + globs = frame.f_globals + if locs is None: + locs = frame.f_locals + del frame + elif locs is None: + locs = globs + exec("""exec code in globs, locs""") + + + exec_("""def reraise(tp, value, tb=None): + raise tp, value, tb +""") + + + def print_(*args, **kwargs): + """The new-style print function.""" + fp = kwargs.pop("file", sys.stdout) + if fp is None: + return + def write(data): + if not isinstance(data, basestring): + data = str(data) + fp.write(data) + want_unicode = False + sep = kwargs.pop("sep", None) + if sep is not None: + if isinstance(sep, unicode): + want_unicode = True + elif not isinstance(sep, str): + raise TypeError("sep must be None or a string") + end = kwargs.pop("end", None) + if end is not None: + if isinstance(end, unicode): + want_unicode = True + elif not isinstance(end, str): + raise TypeError("end must be None or a string") + if kwargs: + raise TypeError("invalid keyword arguments to print()") + if not want_unicode: + for arg in args: + if isinstance(arg, unicode): + want_unicode = True + break + if want_unicode: + newline = unicode("\n") + space = unicode(" ") + else: + newline = "\n" + space = " " + if sep is None: + sep = space + if end is None: + end = newline + for i, arg in enumerate(args): + if i: + write(sep) + write(arg) + write(end) + +_add_doc(reraise, """Reraise an exception.""") + + +def with_metaclass(meta, base=object): + """Create a base class with a metaclass.""" + return meta("NewBase", (base,), {}) diff --git a/resources/lib/libraries/requests/packages/urllib3/packages/ssl_match_hostname/__init__.py b/resources/lib/libraries/requests/packages/urllib3/packages/ssl_match_hostname/__init__.py new file mode 100644 index 00000000..dd59a75f --- /dev/null +++ b/resources/lib/libraries/requests/packages/urllib3/packages/ssl_match_hostname/__init__.py @@ -0,0 +1,13 @@ +try: + # Python 3.2+ + from ssl import CertificateError, match_hostname +except ImportError: + try: + # Backport of the function from a pypi module + from backports.ssl_match_hostname import CertificateError, match_hostname + except ImportError: + # Our vendored copy + from ._implementation import CertificateError, match_hostname + +# Not needed, but documenting what we provide. +__all__ = ('CertificateError', 'match_hostname') diff --git a/resources/lib/libraries/requests/packages/urllib3/packages/ssl_match_hostname/_implementation.py b/resources/lib/libraries/requests/packages/urllib3/packages/ssl_match_hostname/_implementation.py new file mode 100644 index 00000000..52f42873 --- /dev/null +++ b/resources/lib/libraries/requests/packages/urllib3/packages/ssl_match_hostname/_implementation.py @@ -0,0 +1,105 @@ +"""The match_hostname() function from Python 3.3.3, essential when using SSL.""" + +# Note: This file is under the PSF license as the code comes from the python +# stdlib. http://docs.python.org/3/license.html + +import re + +__version__ = '3.4.0.2' + +class CertificateError(ValueError): + pass + + +def _dnsname_match(dn, hostname, max_wildcards=1): + """Matching according to RFC 6125, section 6.4.3 + + http://tools.ietf.org/html/rfc6125#section-6.4.3 + """ + pats = [] + if not dn: + return False + + # Ported from python3-syntax: + # leftmost, *remainder = dn.split(r'.') + parts = dn.split(r'.') + leftmost = parts[0] + remainder = parts[1:] + + wildcards = leftmost.count('*') + if wildcards > max_wildcards: + # Issue #17980: avoid denials of service by refusing more + # than one wildcard per fragment. A survey of established + # policy among SSL implementations showed it to be a + # reasonable choice. + raise CertificateError( + "too many wildcards in certificate DNS name: " + repr(dn)) + + # speed up common case w/o wildcards + if not wildcards: + return dn.lower() == hostname.lower() + + # RFC 6125, section 6.4.3, subitem 1. + # The client SHOULD NOT attempt to match a presented identifier in which + # the wildcard character comprises a label other than the left-most label. + if leftmost == '*': + # When '*' is a fragment by itself, it matches a non-empty dotless + # fragment. + pats.append('[^.]+') + elif leftmost.startswith('xn--') or hostname.startswith('xn--'): + # RFC 6125, section 6.4.3, subitem 3. + # The client SHOULD NOT attempt to match a presented identifier + # where the wildcard character is embedded within an A-label or + # U-label of an internationalized domain name. + pats.append(re.escape(leftmost)) + else: + # Otherwise, '*' matches any dotless string, e.g. www* + pats.append(re.escape(leftmost).replace(r'\*', '[^.]*')) + + # add the remaining fragments, ignore any wildcards + for frag in remainder: + pats.append(re.escape(frag)) + + pat = re.compile(r'\A' + r'\.'.join(pats) + r'\Z', re.IGNORECASE) + return pat.match(hostname) + + +def match_hostname(cert, hostname): + """Verify that *cert* (in decoded format as returned by + SSLSocket.getpeercert()) matches the *hostname*. RFC 2818 and RFC 6125 + rules are followed, but IP addresses are not accepted for *hostname*. + + CertificateError is raised on failure. On success, the function + returns nothing. + """ + if not cert: + raise ValueError("empty or no certificate") + dnsnames = [] + san = cert.get('subjectAltName', ()) + for key, value in san: + if key == 'DNS': + if _dnsname_match(value, hostname): + return + dnsnames.append(value) + if not dnsnames: + # The subject is only checked when there is no dNSName entry + # in subjectAltName + for sub in cert.get('subject', ()): + for key, value in sub: + # XXX according to RFC 2818, the most specific Common Name + # must be used. + if key == 'commonName': + if _dnsname_match(value, hostname): + return + dnsnames.append(value) + if len(dnsnames) > 1: + raise CertificateError("hostname %r " + "doesn't match either of %s" + % (hostname, ', '.join(map(repr, dnsnames)))) + elif len(dnsnames) == 1: + raise CertificateError("hostname %r " + "doesn't match %r" + % (hostname, dnsnames[0])) + else: + raise CertificateError("no appropriate commonName or " + "subjectAltName fields were found") diff --git a/resources/lib/libraries/requests/packages/urllib3/poolmanager.py b/resources/lib/libraries/requests/packages/urllib3/poolmanager.py new file mode 100644 index 00000000..f13e673d --- /dev/null +++ b/resources/lib/libraries/requests/packages/urllib3/poolmanager.py @@ -0,0 +1,281 @@ +from __future__ import absolute_import +import logging + +try: # Python 3 + from urllib.parse import urljoin +except ImportError: + from urlparse import urljoin + +from ._collections import RecentlyUsedContainer +from .connectionpool import HTTPConnectionPool, HTTPSConnectionPool +from .connectionpool import port_by_scheme +from .exceptions import LocationValueError, MaxRetryError, ProxySchemeUnknown +from .request import RequestMethods +from .util.url import parse_url +from .util.retry import Retry + + +__all__ = ['PoolManager', 'ProxyManager', 'proxy_from_url'] + + +pool_classes_by_scheme = { + 'http': HTTPConnectionPool, + 'https': HTTPSConnectionPool, +} + +log = logging.getLogger(__name__) + +SSL_KEYWORDS = ('key_file', 'cert_file', 'cert_reqs', 'ca_certs', + 'ssl_version', 'ca_cert_dir') + + +class PoolManager(RequestMethods): + """ + Allows for arbitrary requests while transparently keeping track of + necessary connection pools for you. + + :param num_pools: + Number of connection pools to cache before discarding the least + recently used pool. + + :param headers: + Headers to include with all requests, unless other headers are given + explicitly. + + :param \**connection_pool_kw: + Additional parameters are used to create fresh + :class:`urllib3.connectionpool.ConnectionPool` instances. + + Example:: + + >>> manager = PoolManager(num_pools=2) + >>> r = manager.request('GET', 'http://google.com/') + >>> r = manager.request('GET', 'http://google.com/mail') + >>> r = manager.request('GET', 'http://yahoo.com/') + >>> len(manager.pools) + 2 + + """ + + proxy = None + + def __init__(self, num_pools=10, headers=None, **connection_pool_kw): + RequestMethods.__init__(self, headers) + self.connection_pool_kw = connection_pool_kw + self.pools = RecentlyUsedContainer(num_pools, + dispose_func=lambda p: p.close()) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + self.clear() + # Return False to re-raise any potential exceptions + return False + + def _new_pool(self, scheme, host, port): + """ + Create a new :class:`ConnectionPool` based on host, port and scheme. + + This method is used to actually create the connection pools handed out + by :meth:`connection_from_url` and companion methods. It is intended + to be overridden for customization. + """ + pool_cls = pool_classes_by_scheme[scheme] + kwargs = self.connection_pool_kw + if scheme == 'http': + kwargs = self.connection_pool_kw.copy() + for kw in SSL_KEYWORDS: + kwargs.pop(kw, None) + + return pool_cls(host, port, **kwargs) + + def clear(self): + """ + Empty our store of pools and direct them all to close. + + This will not affect in-flight connections, but they will not be + re-used after completion. + """ + self.pools.clear() + + def connection_from_host(self, host, port=None, scheme='http'): + """ + Get a :class:`ConnectionPool` based on the host, port, and scheme. + + If ``port`` isn't given, it will be derived from the ``scheme`` using + ``urllib3.connectionpool.port_by_scheme``. + """ + + if not host: + raise LocationValueError("No host specified.") + + scheme = scheme or 'http' + port = port or port_by_scheme.get(scheme, 80) + pool_key = (scheme, host, port) + + with self.pools.lock: + # If the scheme, host, or port doesn't match existing open + # connections, open a new ConnectionPool. + pool = self.pools.get(pool_key) + if pool: + return pool + + # Make a fresh ConnectionPool of the desired type + pool = self._new_pool(scheme, host, port) + self.pools[pool_key] = pool + + return pool + + def connection_from_url(self, url): + """ + Similar to :func:`urllib3.connectionpool.connection_from_url` but + doesn't pass any additional parameters to the + :class:`urllib3.connectionpool.ConnectionPool` constructor. + + Additional parameters are taken from the :class:`.PoolManager` + constructor. + """ + u = parse_url(url) + return self.connection_from_host(u.host, port=u.port, scheme=u.scheme) + + def urlopen(self, method, url, redirect=True, **kw): + """ + Same as :meth:`urllib3.connectionpool.HTTPConnectionPool.urlopen` + with custom cross-host redirect logic and only sends the request-uri + portion of the ``url``. + + The given ``url`` parameter must be absolute, such that an appropriate + :class:`urllib3.connectionpool.ConnectionPool` can be chosen for it. + """ + u = parse_url(url) + conn = self.connection_from_host(u.host, port=u.port, scheme=u.scheme) + + kw['assert_same_host'] = False + kw['redirect'] = False + if 'headers' not in kw: + kw['headers'] = self.headers + + if self.proxy is not None and u.scheme == "http": + response = conn.urlopen(method, url, **kw) + else: + response = conn.urlopen(method, u.request_uri, **kw) + + redirect_location = redirect and response.get_redirect_location() + if not redirect_location: + return response + + # Support relative URLs for redirecting. + redirect_location = urljoin(url, redirect_location) + + # RFC 7231, Section 6.4.4 + if response.status == 303: + method = 'GET' + + retries = kw.get('retries') + if not isinstance(retries, Retry): + retries = Retry.from_int(retries, redirect=redirect) + + try: + retries = retries.increment(method, url, response=response, _pool=conn) + except MaxRetryError: + if retries.raise_on_redirect: + raise + return response + + kw['retries'] = retries + kw['redirect'] = redirect + + log.info("Redirecting %s -> %s" % (url, redirect_location)) + return self.urlopen(method, redirect_location, **kw) + + +class ProxyManager(PoolManager): + """ + Behaves just like :class:`PoolManager`, but sends all requests through + the defined proxy, using the CONNECT method for HTTPS URLs. + + :param proxy_url: + The URL of the proxy to be used. + + :param proxy_headers: + A dictionary contaning headers that will be sent to the proxy. In case + of HTTP they are being sent with each request, while in the + HTTPS/CONNECT case they are sent only once. Could be used for proxy + authentication. + + Example: + >>> proxy = urllib3.ProxyManager('http://localhost:3128/') + >>> r1 = proxy.request('GET', 'http://google.com/') + >>> r2 = proxy.request('GET', 'http://httpbin.org/') + >>> len(proxy.pools) + 1 + >>> r3 = proxy.request('GET', 'https://httpbin.org/') + >>> r4 = proxy.request('GET', 'https://twitter.com/') + >>> len(proxy.pools) + 3 + + """ + + def __init__(self, proxy_url, num_pools=10, headers=None, + proxy_headers=None, **connection_pool_kw): + + if isinstance(proxy_url, HTTPConnectionPool): + proxy_url = '%s://%s:%i' % (proxy_url.scheme, proxy_url.host, + proxy_url.port) + proxy = parse_url(proxy_url) + if not proxy.port: + port = port_by_scheme.get(proxy.scheme, 80) + proxy = proxy._replace(port=port) + + if proxy.scheme not in ("http", "https"): + raise ProxySchemeUnknown(proxy.scheme) + + self.proxy = proxy + self.proxy_headers = proxy_headers or {} + + connection_pool_kw['_proxy'] = self.proxy + connection_pool_kw['_proxy_headers'] = self.proxy_headers + + super(ProxyManager, self).__init__( + num_pools, headers, **connection_pool_kw) + + def connection_from_host(self, host, port=None, scheme='http'): + if scheme == "https": + return super(ProxyManager, self).connection_from_host( + host, port, scheme) + + return super(ProxyManager, self).connection_from_host( + self.proxy.host, self.proxy.port, self.proxy.scheme) + + def _set_proxy_headers(self, url, headers=None): + """ + Sets headers needed by proxies: specifically, the Accept and Host + headers. Only sets headers not provided by the user. + """ + headers_ = {'Accept': '*/*'} + + netloc = parse_url(url).netloc + if netloc: + headers_['Host'] = netloc + + if headers: + headers_.update(headers) + return headers_ + + def urlopen(self, method, url, redirect=True, **kw): + "Same as HTTP(S)ConnectionPool.urlopen, ``url`` must be absolute." + u = parse_url(url) + + if u.scheme == "http": + # For proxied HTTPS requests, httplib sets the necessary headers + # on the CONNECT to the proxy. For HTTP, we'll definitely + # need to set 'Host' at the very least. + headers = kw.get('headers', self.headers) + kw['headers'] = self._set_proxy_headers(url, headers) + + return super(ProxyManager, self).urlopen(method, url, redirect=redirect, **kw) + + +def proxy_from_url(url, **kw): + return ProxyManager(proxy_url=url, **kw) diff --git a/resources/lib/libraries/requests/packages/urllib3/request.py b/resources/lib/libraries/requests/packages/urllib3/request.py new file mode 100644 index 00000000..d5aa62d8 --- /dev/null +++ b/resources/lib/libraries/requests/packages/urllib3/request.py @@ -0,0 +1,151 @@ +from __future__ import absolute_import +try: + from urllib.parse import urlencode +except ImportError: + from urllib import urlencode + +from .filepost import encode_multipart_formdata + + +__all__ = ['RequestMethods'] + + +class RequestMethods(object): + """ + Convenience mixin for classes who implement a :meth:`urlopen` method, such + as :class:`~urllib3.connectionpool.HTTPConnectionPool` and + :class:`~urllib3.poolmanager.PoolManager`. + + Provides behavior for making common types of HTTP request methods and + decides which type of request field encoding to use. + + Specifically, + + :meth:`.request_encode_url` is for sending requests whose fields are + encoded in the URL (such as GET, HEAD, DELETE). + + :meth:`.request_encode_body` is for sending requests whose fields are + encoded in the *body* of the request using multipart or www-form-urlencoded + (such as for POST, PUT, PATCH). + + :meth:`.request` is for making any kind of request, it will look up the + appropriate encoding format and use one of the above two methods to make + the request. + + Initializer parameters: + + :param headers: + Headers to include with all requests, unless other headers are given + explicitly. + """ + + _encode_url_methods = set(['DELETE', 'GET', 'HEAD', 'OPTIONS']) + + def __init__(self, headers=None): + self.headers = headers or {} + + def urlopen(self, method, url, body=None, headers=None, + encode_multipart=True, multipart_boundary=None, + **kw): # Abstract + raise NotImplemented("Classes extending RequestMethods must implement " + "their own ``urlopen`` method.") + + def request(self, method, url, fields=None, headers=None, **urlopen_kw): + """ + Make a request using :meth:`urlopen` with the appropriate encoding of + ``fields`` based on the ``method`` used. + + This is a convenience method that requires the least amount of manual + effort. It can be used in most situations, while still having the + option to drop down to more specific methods when necessary, such as + :meth:`request_encode_url`, :meth:`request_encode_body`, + or even the lowest level :meth:`urlopen`. + """ + method = method.upper() + + if method in self._encode_url_methods: + return self.request_encode_url(method, url, fields=fields, + headers=headers, + **urlopen_kw) + else: + return self.request_encode_body(method, url, fields=fields, + headers=headers, + **urlopen_kw) + + def request_encode_url(self, method, url, fields=None, headers=None, + **urlopen_kw): + """ + Make a request using :meth:`urlopen` with the ``fields`` encoded in + the url. This is useful for request methods like GET, HEAD, DELETE, etc. + """ + if headers is None: + headers = self.headers + + extra_kw = {'headers': headers} + extra_kw.update(urlopen_kw) + + if fields: + url += '?' + urlencode(fields) + + return self.urlopen(method, url, **extra_kw) + + def request_encode_body(self, method, url, fields=None, headers=None, + encode_multipart=True, multipart_boundary=None, + **urlopen_kw): + """ + Make a request using :meth:`urlopen` with the ``fields`` encoded in + the body. This is useful for request methods like POST, PUT, PATCH, etc. + + When ``encode_multipart=True`` (default), then + :meth:`urllib3.filepost.encode_multipart_formdata` is used to encode + the payload with the appropriate content type. Otherwise + :meth:`urllib.urlencode` is used with the + 'application/x-www-form-urlencoded' content type. + + Multipart encoding must be used when posting files, and it's reasonably + safe to use it in other times too. However, it may break request + signing, such as with OAuth. + + Supports an optional ``fields`` parameter of key/value strings AND + key/filetuple. A filetuple is a (filename, data, MIME type) tuple where + the MIME type is optional. For example:: + + fields = { + 'foo': 'bar', + 'fakefile': ('foofile.txt', 'contents of foofile'), + 'realfile': ('barfile.txt', open('realfile').read()), + 'typedfile': ('bazfile.bin', open('bazfile').read(), + 'image/jpeg'), + 'nonamefile': 'contents of nonamefile field', + } + + When uploading a file, providing a filename (the first parameter of the + tuple) is optional but recommended to best mimick behavior of browsers. + + Note that if ``headers`` are supplied, the 'Content-Type' header will + be overwritten because it depends on the dynamic random boundary string + which is used to compose the body of the request. The random boundary + string can be explicitly set with the ``multipart_boundary`` parameter. + """ + if headers is None: + headers = self.headers + + extra_kw = {'headers': {}} + + if fields: + if 'body' in urlopen_kw: + raise TypeError( + "request got values for both 'fields' and 'body', can only specify one.") + + if encode_multipart: + body, content_type = encode_multipart_formdata(fields, boundary=multipart_boundary) + else: + body, content_type = urlencode(fields), 'application/x-www-form-urlencoded' + + extra_kw['body'] = body + extra_kw['headers'] = {'Content-Type': content_type} + + extra_kw['headers'].update(headers) + extra_kw.update(urlopen_kw) + + return self.urlopen(method, url, **extra_kw) diff --git a/resources/lib/libraries/requests/packages/urllib3/response.py b/resources/lib/libraries/requests/packages/urllib3/response.py new file mode 100644 index 00000000..8f2a1b5c --- /dev/null +++ b/resources/lib/libraries/requests/packages/urllib3/response.py @@ -0,0 +1,514 @@ +from __future__ import absolute_import +from contextlib import contextmanager +import zlib +import io +from socket import timeout as SocketTimeout +from socket import error as SocketError + +from ._collections import HTTPHeaderDict +from .exceptions import ( + ProtocolError, DecodeError, ReadTimeoutError, ResponseNotChunked +) +from .packages.six import string_types as basestring, binary_type, PY3 +from .packages.six.moves import http_client as httplib +from .connection import HTTPException, BaseSSLError +from .util.response import is_fp_closed, is_response_to_head + + +class DeflateDecoder(object): + + def __init__(self): + self._first_try = True + self._data = binary_type() + self._obj = zlib.decompressobj() + + def __getattr__(self, name): + return getattr(self._obj, name) + + def decompress(self, data): + if not data: + return data + + if not self._first_try: + return self._obj.decompress(data) + + self._data += data + try: + return self._obj.decompress(data) + except zlib.error: + self._first_try = False + self._obj = zlib.decompressobj(-zlib.MAX_WBITS) + try: + return self.decompress(self._data) + finally: + self._data = None + + +class GzipDecoder(object): + + def __init__(self): + self._obj = zlib.decompressobj(16 + zlib.MAX_WBITS) + + def __getattr__(self, name): + return getattr(self._obj, name) + + def decompress(self, data): + if not data: + return data + return self._obj.decompress(data) + + +def _get_decoder(mode): + if mode == 'gzip': + return GzipDecoder() + + return DeflateDecoder() + + +class HTTPResponse(io.IOBase): + """ + HTTP Response container. + + Backwards-compatible to httplib's HTTPResponse but the response ``body`` is + loaded and decoded on-demand when the ``data`` property is accessed. This + class is also compatible with the Python standard library's :mod:`io` + module, and can hence be treated as a readable object in the context of that + framework. + + Extra parameters for behaviour not present in httplib.HTTPResponse: + + :param preload_content: + If True, the response's body will be preloaded during construction. + + :param decode_content: + If True, attempts to decode specific content-encoding's based on headers + (like 'gzip' and 'deflate') will be skipped and raw data will be used + instead. + + :param original_response: + When this HTTPResponse wrapper is generated from an httplib.HTTPResponse + object, it's convenient to include the original for debug purposes. It's + otherwise unused. + """ + + CONTENT_DECODERS = ['gzip', 'deflate'] + REDIRECT_STATUSES = [301, 302, 303, 307, 308] + + def __init__(self, body='', headers=None, status=0, version=0, reason=None, + strict=0, preload_content=True, decode_content=True, + original_response=None, pool=None, connection=None): + + if isinstance(headers, HTTPHeaderDict): + self.headers = headers + else: + self.headers = HTTPHeaderDict(headers) + self.status = status + self.version = version + self.reason = reason + self.strict = strict + self.decode_content = decode_content + + self._decoder = None + self._body = None + self._fp = None + self._original_response = original_response + self._fp_bytes_read = 0 + + if body and isinstance(body, (basestring, binary_type)): + self._body = body + + self._pool = pool + self._connection = connection + + if hasattr(body, 'read'): + self._fp = body + + # Are we using the chunked-style of transfer encoding? + self.chunked = False + self.chunk_left = None + tr_enc = self.headers.get('transfer-encoding', '').lower() + # Don't incur the penalty of creating a list and then discarding it + encodings = (enc.strip() for enc in tr_enc.split(",")) + if "chunked" in encodings: + self.chunked = True + + # If requested, preload the body. + if preload_content and not self._body: + self._body = self.read(decode_content=decode_content) + + def get_redirect_location(self): + """ + Should we redirect and where to? + + :returns: Truthy redirect location string if we got a redirect status + code and valid location. ``None`` if redirect status and no + location. ``False`` if not a redirect status code. + """ + if self.status in self.REDIRECT_STATUSES: + return self.headers.get('location') + + return False + + def release_conn(self): + if not self._pool or not self._connection: + return + + self._pool._put_conn(self._connection) + self._connection = None + + @property + def data(self): + # For backwords-compat with earlier urllib3 0.4 and earlier. + if self._body: + return self._body + + if self._fp: + return self.read(cache_content=True) + + def tell(self): + """ + Obtain the number of bytes pulled over the wire so far. May differ from + the amount of content returned by :meth:``HTTPResponse.read`` if bytes + are encoded on the wire (e.g, compressed). + """ + return self._fp_bytes_read + + def _init_decoder(self): + """ + Set-up the _decoder attribute if necessar. + """ + # Note: content-encoding value should be case-insensitive, per RFC 7230 + # Section 3.2 + content_encoding = self.headers.get('content-encoding', '').lower() + if self._decoder is None and content_encoding in self.CONTENT_DECODERS: + self._decoder = _get_decoder(content_encoding) + + def _decode(self, data, decode_content, flush_decoder): + """ + Decode the data passed in and potentially flush the decoder. + """ + try: + if decode_content and self._decoder: + data = self._decoder.decompress(data) + except (IOError, zlib.error) as e: + content_encoding = self.headers.get('content-encoding', '').lower() + raise DecodeError( + "Received response with content-encoding: %s, but " + "failed to decode it." % content_encoding, e) + + if flush_decoder and decode_content: + data += self._flush_decoder() + + return data + + def _flush_decoder(self): + """ + Flushes the decoder. Should only be called if the decoder is actually + being used. + """ + if self._decoder: + buf = self._decoder.decompress(b'') + return buf + self._decoder.flush() + + return b'' + + @contextmanager + def _error_catcher(self): + """ + Catch low-level python exceptions, instead re-raising urllib3 + variants, so that low-level exceptions are not leaked in the + high-level api. + + On exit, release the connection back to the pool. + """ + try: + try: + yield + + except SocketTimeout: + # FIXME: Ideally we'd like to include the url in the ReadTimeoutError but + # there is yet no clean way to get at it from this context. + raise ReadTimeoutError(self._pool, None, 'Read timed out.') + + except BaseSSLError as e: + # FIXME: Is there a better way to differentiate between SSLErrors? + if 'read operation timed out' not in str(e): # Defensive: + # This shouldn't happen but just in case we're missing an edge + # case, let's avoid swallowing SSL errors. + raise + + raise ReadTimeoutError(self._pool, None, 'Read timed out.') + + except (HTTPException, SocketError) as e: + # This includes IncompleteRead. + raise ProtocolError('Connection broken: %r' % e, e) + + except Exception: + # The response may not be closed but we're not going to use it anymore + # so close it now to ensure that the connection is released back to the pool. + if self._original_response and not self._original_response.isclosed(): + self._original_response.close() + + # Closing the response may not actually be sufficient to close + # everything, so if we have a hold of the connection close that + # too. + if self._connection is not None: + self._connection.close() + + raise + finally: + if self._original_response and self._original_response.isclosed(): + self.release_conn() + + def read(self, amt=None, decode_content=None, cache_content=False): + """ + Similar to :meth:`httplib.HTTPResponse.read`, but with two additional + parameters: ``decode_content`` and ``cache_content``. + + :param amt: + How much of the content to read. If specified, caching is skipped + because it doesn't make sense to cache partial content as the full + response. + + :param decode_content: + If True, will attempt to decode the body based on the + 'content-encoding' header. + + :param cache_content: + If True, will save the returned data such that the same result is + returned despite of the state of the underlying file object. This + is useful if you want the ``.data`` property to continue working + after having ``.read()`` the file object. (Overridden if ``amt`` is + set.) + """ + self._init_decoder() + if decode_content is None: + decode_content = self.decode_content + + if self._fp is None: + return + + flush_decoder = False + data = None + + with self._error_catcher(): + if amt is None: + # cStringIO doesn't like amt=None + data = self._fp.read() + flush_decoder = True + else: + cache_content = False + data = self._fp.read(amt) + if amt != 0 and not data: # Platform-specific: Buggy versions of Python. + # Close the connection when no data is returned + # + # This is redundant to what httplib/http.client _should_ + # already do. However, versions of python released before + # December 15, 2012 (http://bugs.python.org/issue16298) do + # not properly close the connection in all cases. There is + # no harm in redundantly calling close. + self._fp.close() + flush_decoder = True + + if data: + self._fp_bytes_read += len(data) + + data = self._decode(data, decode_content, flush_decoder) + + if cache_content: + self._body = data + + return data + + def stream(self, amt=2**16, decode_content=None): + """ + A generator wrapper for the read() method. A call will block until + ``amt`` bytes have been read from the connection or until the + connection is closed. + + :param amt: + How much of the content to read. The generator will return up to + much data per iteration, but may return less. This is particularly + likely when using compressed data. However, the empty string will + never be returned. + + :param decode_content: + If True, will attempt to decode the body based on the + 'content-encoding' header. + """ + if self.chunked: + for line in self.read_chunked(amt, decode_content=decode_content): + yield line + else: + while not is_fp_closed(self._fp): + data = self.read(amt=amt, decode_content=decode_content) + + if data: + yield data + + @classmethod + def from_httplib(ResponseCls, r, **response_kw): + """ + Given an :class:`httplib.HTTPResponse` instance ``r``, return a + corresponding :class:`urllib3.response.HTTPResponse` object. + + Remaining parameters are passed to the HTTPResponse constructor, along + with ``original_response=r``. + """ + headers = r.msg + + if not isinstance(headers, HTTPHeaderDict): + if PY3: # Python 3 + headers = HTTPHeaderDict(headers.items()) + else: # Python 2 + headers = HTTPHeaderDict.from_httplib(headers) + + # HTTPResponse objects in Python 3 don't have a .strict attribute + strict = getattr(r, 'strict', 0) + resp = ResponseCls(body=r, + headers=headers, + status=r.status, + version=r.version, + reason=r.reason, + strict=strict, + original_response=r, + **response_kw) + return resp + + # Backwards-compatibility methods for httplib.HTTPResponse + def getheaders(self): + return self.headers + + def getheader(self, name, default=None): + return self.headers.get(name, default) + + # Overrides from io.IOBase + def close(self): + if not self.closed: + self._fp.close() + + @property + def closed(self): + if self._fp is None: + return True + elif hasattr(self._fp, 'closed'): + return self._fp.closed + elif hasattr(self._fp, 'isclosed'): # Python 2 + return self._fp.isclosed() + else: + return True + + def fileno(self): + if self._fp is None: + raise IOError("HTTPResponse has no file to get a fileno from") + elif hasattr(self._fp, "fileno"): + return self._fp.fileno() + else: + raise IOError("The file-like object this HTTPResponse is wrapped " + "around has no file descriptor") + + def flush(self): + if self._fp is not None and hasattr(self._fp, 'flush'): + return self._fp.flush() + + def readable(self): + # This method is required for `io` module compatibility. + return True + + def readinto(self, b): + # This method is required for `io` module compatibility. + temp = self.read(len(b)) + if len(temp) == 0: + return 0 + else: + b[:len(temp)] = temp + return len(temp) + + def _update_chunk_length(self): + # First, we'll figure out length of a chunk and then + # we'll try to read it from socket. + if self.chunk_left is not None: + return + line = self._fp.fp.readline() + line = line.split(b';', 1)[0] + try: + self.chunk_left = int(line, 16) + except ValueError: + # Invalid chunked protocol response, abort. + self.close() + raise httplib.IncompleteRead(line) + + def _handle_chunk(self, amt): + returned_chunk = None + if amt is None: + chunk = self._fp._safe_read(self.chunk_left) + returned_chunk = chunk + self._fp._safe_read(2) # Toss the CRLF at the end of the chunk. + self.chunk_left = None + elif amt < self.chunk_left: + value = self._fp._safe_read(amt) + self.chunk_left = self.chunk_left - amt + returned_chunk = value + elif amt == self.chunk_left: + value = self._fp._safe_read(amt) + self._fp._safe_read(2) # Toss the CRLF at the end of the chunk. + self.chunk_left = None + returned_chunk = value + else: # amt > self.chunk_left + returned_chunk = self._fp._safe_read(self.chunk_left) + self._fp._safe_read(2) # Toss the CRLF at the end of the chunk. + self.chunk_left = None + return returned_chunk + + def read_chunked(self, amt=None, decode_content=None): + """ + Similar to :meth:`HTTPResponse.read`, but with an additional + parameter: ``decode_content``. + + :param decode_content: + If True, will attempt to decode the body based on the + 'content-encoding' header. + """ + self._init_decoder() + # FIXME: Rewrite this method and make it a class with a better structured logic. + if not self.chunked: + raise ResponseNotChunked( + "Response is not chunked. " + "Header 'transfer-encoding: chunked' is missing.") + + # Don't bother reading the body of a HEAD request. + if self._original_response and is_response_to_head(self._original_response): + self._original_response.close() + return + + with self._error_catcher(): + while True: + self._update_chunk_length() + if self.chunk_left == 0: + break + chunk = self._handle_chunk(amt) + decoded = self._decode(chunk, decode_content=decode_content, + flush_decoder=False) + if decoded: + yield decoded + + if decode_content: + # On CPython and PyPy, we should never need to flush the + # decoder. However, on Jython we *might* need to, so + # lets defensively do it anyway. + decoded = self._flush_decoder() + if decoded: # Platform-specific: Jython. + yield decoded + + # Chunk content ends with \r\n: discard it. + while True: + line = self._fp.fp.readline() + if not line: + # Some sites may not end with '\r\n'. + break + if line == b'\r\n': + break + + # We read everything; close the "file". + if self._original_response: + self._original_response.close() diff --git a/resources/lib/libraries/requests/packages/urllib3/util/__init__.py b/resources/lib/libraries/requests/packages/urllib3/util/__init__.py new file mode 100644 index 00000000..c6c6243c --- /dev/null +++ b/resources/lib/libraries/requests/packages/urllib3/util/__init__.py @@ -0,0 +1,44 @@ +from __future__ import absolute_import +# For backwards compatibility, provide imports that used to be here. +from .connection import is_connection_dropped +from .request import make_headers +from .response import is_fp_closed +from .ssl_ import ( + SSLContext, + HAS_SNI, + assert_fingerprint, + resolve_cert_reqs, + resolve_ssl_version, + ssl_wrap_socket, +) +from .timeout import ( + current_time, + Timeout, +) + +from .retry import Retry +from .url import ( + get_host, + parse_url, + split_first, + Url, +) + +__all__ = ( + 'HAS_SNI', + 'SSLContext', + 'Retry', + 'Timeout', + 'Url', + 'assert_fingerprint', + 'current_time', + 'is_connection_dropped', + 'is_fp_closed', + 'get_host', + 'parse_url', + 'make_headers', + 'resolve_cert_reqs', + 'resolve_ssl_version', + 'split_first', + 'ssl_wrap_socket', +) diff --git a/resources/lib/libraries/requests/packages/urllib3/util/connection.py b/resources/lib/libraries/requests/packages/urllib3/util/connection.py new file mode 100644 index 00000000..01a4812f --- /dev/null +++ b/resources/lib/libraries/requests/packages/urllib3/util/connection.py @@ -0,0 +1,101 @@ +from __future__ import absolute_import +import socket +try: + from select import poll, POLLIN +except ImportError: # `poll` doesn't exist on OSX and other platforms + poll = False + try: + from select import select + except ImportError: # `select` doesn't exist on AppEngine. + select = False + + +def is_connection_dropped(conn): # Platform-specific + """ + Returns True if the connection is dropped and should be closed. + + :param conn: + :class:`httplib.HTTPConnection` object. + + Note: For platforms like AppEngine, this will always return ``False`` to + let the platform handle connection recycling transparently for us. + """ + sock = getattr(conn, 'sock', False) + if sock is False: # Platform-specific: AppEngine + return False + if sock is None: # Connection already closed (such as by httplib). + return True + + if not poll: + if not select: # Platform-specific: AppEngine + return False + + try: + return select([sock], [], [], 0.0)[0] + except socket.error: + return True + + # This version is better on platforms that support it. + p = poll() + p.register(sock, POLLIN) + for (fno, ev) in p.poll(0.0): + if fno == sock.fileno(): + # Either data is buffered (bad), or the connection is dropped. + return True + + +# This function is copied from socket.py in the Python 2.7 standard +# library test suite. Added to its signature is only `socket_options`. +def create_connection(address, timeout=socket._GLOBAL_DEFAULT_TIMEOUT, + source_address=None, socket_options=None): + """Connect to *address* and return the socket object. + + Convenience function. Connect to *address* (a 2-tuple ``(host, + port)``) and return the socket object. Passing the optional + *timeout* parameter will set the timeout on the socket instance + before attempting to connect. If no *timeout* is supplied, the + global default timeout setting returned by :func:`getdefaulttimeout` + is used. If *source_address* is set it must be a tuple of (host, port) + for the socket to bind as a source address before making the connection. + An host of '' or port 0 tells the OS to use the default. + """ + + host, port = address + if host.startswith('['): + host = host.strip('[]') + err = None + for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM): + af, socktype, proto, canonname, sa = res + sock = None + try: + sock = socket.socket(af, socktype, proto) + + # If provided, set socket level options before connecting. + # This is the only addition urllib3 makes to this function. + _set_socket_options(sock, socket_options) + + if timeout is not socket._GLOBAL_DEFAULT_TIMEOUT: + sock.settimeout(timeout) + if source_address: + sock.bind(source_address) + sock.connect(sa) + return sock + + except socket.error as e: + err = e + if sock is not None: + sock.close() + sock = None + + if err is not None: + raise err + + raise socket.error("getaddrinfo returns an empty list") + + +def _set_socket_options(sock, options): + if options is None: + return + + for opt in options: + sock.setsockopt(*opt) diff --git a/resources/lib/libraries/requests/packages/urllib3/util/request.py b/resources/lib/libraries/requests/packages/urllib3/util/request.py new file mode 100644 index 00000000..73779315 --- /dev/null +++ b/resources/lib/libraries/requests/packages/urllib3/util/request.py @@ -0,0 +1,72 @@ +from __future__ import absolute_import +from base64 import b64encode + +from ..packages.six import b + +ACCEPT_ENCODING = 'gzip,deflate' + + +def make_headers(keep_alive=None, accept_encoding=None, user_agent=None, + basic_auth=None, proxy_basic_auth=None, disable_cache=None): + """ + Shortcuts for generating request headers. + + :param keep_alive: + If ``True``, adds 'connection: keep-alive' header. + + :param accept_encoding: + Can be a boolean, list, or string. + ``True`` translates to 'gzip,deflate'. + List will get joined by comma. + String will be used as provided. + + :param user_agent: + String representing the user-agent you want, such as + "python-urllib3/0.6" + + :param basic_auth: + Colon-separated username:password string for 'authorization: basic ...' + auth header. + + :param proxy_basic_auth: + Colon-separated username:password string for 'proxy-authorization: basic ...' + auth header. + + :param disable_cache: + If ``True``, adds 'cache-control: no-cache' header. + + Example:: + + >>> make_headers(keep_alive=True, user_agent="Batman/1.0") + {'connection': 'keep-alive', 'user-agent': 'Batman/1.0'} + >>> make_headers(accept_encoding=True) + {'accept-encoding': 'gzip,deflate'} + """ + headers = {} + if accept_encoding: + if isinstance(accept_encoding, str): + pass + elif isinstance(accept_encoding, list): + accept_encoding = ','.join(accept_encoding) + else: + accept_encoding = ACCEPT_ENCODING + headers['accept-encoding'] = accept_encoding + + if user_agent: + headers['user-agent'] = user_agent + + if keep_alive: + headers['connection'] = 'keep-alive' + + if basic_auth: + headers['authorization'] = 'Basic ' + \ + b64encode(b(basic_auth)).decode('utf-8') + + if proxy_basic_auth: + headers['proxy-authorization'] = 'Basic ' + \ + b64encode(b(proxy_basic_auth)).decode('utf-8') + + if disable_cache: + headers['cache-control'] = 'no-cache' + + return headers diff --git a/resources/lib/libraries/requests/packages/urllib3/util/response.py b/resources/lib/libraries/requests/packages/urllib3/util/response.py new file mode 100644 index 00000000..bc723272 --- /dev/null +++ b/resources/lib/libraries/requests/packages/urllib3/util/response.py @@ -0,0 +1,74 @@ +from __future__ import absolute_import +from ..packages.six.moves import http_client as httplib + +from ..exceptions import HeaderParsingError + + +def is_fp_closed(obj): + """ + Checks whether a given file-like object is closed. + + :param obj: + The file-like object to check. + """ + + try: + # Check via the official file-like-object way. + return obj.closed + except AttributeError: + pass + + try: + # Check if the object is a container for another file-like object that + # gets released on exhaustion (e.g. HTTPResponse). + return obj.fp is None + except AttributeError: + pass + + raise ValueError("Unable to determine whether fp is closed.") + + +def assert_header_parsing(headers): + """ + Asserts whether all headers have been successfully parsed. + Extracts encountered errors from the result of parsing headers. + + Only works on Python 3. + + :param headers: Headers to verify. + :type headers: `httplib.HTTPMessage`. + + :raises urllib3.exceptions.HeaderParsingError: + If parsing errors are found. + """ + + # This will fail silently if we pass in the wrong kind of parameter. + # To make debugging easier add an explicit check. + if not isinstance(headers, httplib.HTTPMessage): + raise TypeError('expected httplib.Message, got {0}.'.format( + type(headers))) + + defects = getattr(headers, 'defects', None) + get_payload = getattr(headers, 'get_payload', None) + + unparsed_data = None + if get_payload: # Platform-specific: Python 3. + unparsed_data = get_payload() + + if defects or unparsed_data: + raise HeaderParsingError(defects=defects, unparsed_data=unparsed_data) + + +def is_response_to_head(response): + """ + Checks, wether a the request of a response has been a HEAD-request. + Handles the quirks of AppEngine. + + :param conn: + :type conn: :class:`httplib.HTTPResponse` + """ + # FIXME: Can we do this somehow without accessing private httplib _method? + method = response._method + if isinstance(method, int): # Platform-specific: Appengine + return method == 3 + return method.upper() == 'HEAD' diff --git a/resources/lib/libraries/requests/packages/urllib3/util/retry.py b/resources/lib/libraries/requests/packages/urllib3/util/retry.py new file mode 100644 index 00000000..03a01249 --- /dev/null +++ b/resources/lib/libraries/requests/packages/urllib3/util/retry.py @@ -0,0 +1,286 @@ +from __future__ import absolute_import +import time +import logging + +from ..exceptions import ( + ConnectTimeoutError, + MaxRetryError, + ProtocolError, + ReadTimeoutError, + ResponseError, +) +from ..packages import six + + +log = logging.getLogger(__name__) + + +class Retry(object): + """ Retry configuration. + + Each retry attempt will create a new Retry object with updated values, so + they can be safely reused. + + Retries can be defined as a default for a pool:: + + retries = Retry(connect=5, read=2, redirect=5) + http = PoolManager(retries=retries) + response = http.request('GET', 'http://example.com/') + + Or per-request (which overrides the default for the pool):: + + response = http.request('GET', 'http://example.com/', retries=Retry(10)) + + Retries can be disabled by passing ``False``:: + + response = http.request('GET', 'http://example.com/', retries=False) + + Errors will be wrapped in :class:`~urllib3.exceptions.MaxRetryError` unless + retries are disabled, in which case the causing exception will be raised. + + :param int total: + Total number of retries to allow. Takes precedence over other counts. + + Set to ``None`` to remove this constraint and fall back on other + counts. It's a good idea to set this to some sensibly-high value to + account for unexpected edge cases and avoid infinite retry loops. + + Set to ``0`` to fail on the first retry. + + Set to ``False`` to disable and imply ``raise_on_redirect=False``. + + :param int connect: + How many connection-related errors to retry on. + + These are errors raised before the request is sent to the remote server, + which we assume has not triggered the server to process the request. + + Set to ``0`` to fail on the first retry of this type. + + :param int read: + How many times to retry on read errors. + + These errors are raised after the request was sent to the server, so the + request may have side-effects. + + Set to ``0`` to fail on the first retry of this type. + + :param int redirect: + How many redirects to perform. Limit this to avoid infinite redirect + loops. + + A redirect is a HTTP response with a status code 301, 302, 303, 307 or + 308. + + Set to ``0`` to fail on the first retry of this type. + + Set to ``False`` to disable and imply ``raise_on_redirect=False``. + + :param iterable method_whitelist: + Set of uppercased HTTP method verbs that we should retry on. + + By default, we only retry on methods which are considered to be + indempotent (multiple requests with the same parameters end with the + same state). See :attr:`Retry.DEFAULT_METHOD_WHITELIST`. + + :param iterable status_forcelist: + A set of HTTP status codes that we should force a retry on. + + By default, this is disabled with ``None``. + + :param float backoff_factor: + A backoff factor to apply between attempts. urllib3 will sleep for:: + + {backoff factor} * (2 ^ ({number of total retries} - 1)) + + seconds. If the backoff_factor is 0.1, then :func:`.sleep` will sleep + for [0.1s, 0.2s, 0.4s, ...] between retries. It will never be longer + than :attr:`Retry.BACKOFF_MAX`. + + By default, backoff is disabled (set to 0). + + :param bool raise_on_redirect: Whether, if the number of redirects is + exhausted, to raise a MaxRetryError, or to return a response with a + response code in the 3xx range. + """ + + DEFAULT_METHOD_WHITELIST = frozenset([ + 'HEAD', 'GET', 'PUT', 'DELETE', 'OPTIONS', 'TRACE']) + + #: Maximum backoff time. + BACKOFF_MAX = 120 + + def __init__(self, total=10, connect=None, read=None, redirect=None, + method_whitelist=DEFAULT_METHOD_WHITELIST, status_forcelist=None, + backoff_factor=0, raise_on_redirect=True, _observed_errors=0): + + self.total = total + self.connect = connect + self.read = read + + if redirect is False or total is False: + redirect = 0 + raise_on_redirect = False + + self.redirect = redirect + self.status_forcelist = status_forcelist or set() + self.method_whitelist = method_whitelist + self.backoff_factor = backoff_factor + self.raise_on_redirect = raise_on_redirect + self._observed_errors = _observed_errors # TODO: use .history instead? + + def new(self, **kw): + params = dict( + total=self.total, + connect=self.connect, read=self.read, redirect=self.redirect, + method_whitelist=self.method_whitelist, + status_forcelist=self.status_forcelist, + backoff_factor=self.backoff_factor, + raise_on_redirect=self.raise_on_redirect, + _observed_errors=self._observed_errors, + ) + params.update(kw) + return type(self)(**params) + + @classmethod + def from_int(cls, retries, redirect=True, default=None): + """ Backwards-compatibility for the old retries format.""" + if retries is None: + retries = default if default is not None else cls.DEFAULT + + if isinstance(retries, Retry): + return retries + + redirect = bool(redirect) and None + new_retries = cls(retries, redirect=redirect) + log.debug("Converted retries value: %r -> %r" % (retries, new_retries)) + return new_retries + + def get_backoff_time(self): + """ Formula for computing the current backoff + + :rtype: float + """ + if self._observed_errors <= 1: + return 0 + + backoff_value = self.backoff_factor * (2 ** (self._observed_errors - 1)) + return min(self.BACKOFF_MAX, backoff_value) + + def sleep(self): + """ Sleep between retry attempts using an exponential backoff. + + By default, the backoff factor is 0 and this method will return + immediately. + """ + backoff = self.get_backoff_time() + if backoff <= 0: + return + time.sleep(backoff) + + def _is_connection_error(self, err): + """ Errors when we're fairly sure that the server did not receive the + request, so it should be safe to retry. + """ + return isinstance(err, ConnectTimeoutError) + + def _is_read_error(self, err): + """ Errors that occur after the request has been started, so we should + assume that the server began processing it. + """ + return isinstance(err, (ReadTimeoutError, ProtocolError)) + + def is_forced_retry(self, method, status_code): + """ Is this method/status code retryable? (Based on method/codes whitelists) + """ + if self.method_whitelist and method.upper() not in self.method_whitelist: + return False + + return self.status_forcelist and status_code in self.status_forcelist + + def is_exhausted(self): + """ Are we out of retries? """ + retry_counts = (self.total, self.connect, self.read, self.redirect) + retry_counts = list(filter(None, retry_counts)) + if not retry_counts: + return False + + return min(retry_counts) < 0 + + def increment(self, method=None, url=None, response=None, error=None, + _pool=None, _stacktrace=None): + """ Return a new Retry object with incremented retry counters. + + :param response: A response object, or None, if the server did not + return a response. + :type response: :class:`~urllib3.response.HTTPResponse` + :param Exception error: An error encountered during the request, or + None if the response was received successfully. + + :return: A new ``Retry`` object. + """ + if self.total is False and error: + # Disabled, indicate to re-raise the error. + raise six.reraise(type(error), error, _stacktrace) + + total = self.total + if total is not None: + total -= 1 + + _observed_errors = self._observed_errors + connect = self.connect + read = self.read + redirect = self.redirect + cause = 'unknown' + + if error and self._is_connection_error(error): + # Connect retry? + if connect is False: + raise six.reraise(type(error), error, _stacktrace) + elif connect is not None: + connect -= 1 + _observed_errors += 1 + + elif error and self._is_read_error(error): + # Read retry? + if read is False: + raise six.reraise(type(error), error, _stacktrace) + elif read is not None: + read -= 1 + _observed_errors += 1 + + elif response and response.get_redirect_location(): + # Redirect retry? + if redirect is not None: + redirect -= 1 + cause = 'too many redirects' + + else: + # Incrementing because of a server error like a 500 in + # status_forcelist and a the given method is in the whitelist + _observed_errors += 1 + cause = ResponseError.GENERIC_ERROR + if response and response.status: + cause = ResponseError.SPECIFIC_ERROR.format( + status_code=response.status) + + new_retry = self.new( + total=total, + connect=connect, read=read, redirect=redirect, + _observed_errors=_observed_errors) + + if new_retry.is_exhausted(): + raise MaxRetryError(_pool, url, error or ResponseError(cause)) + + log.debug("Incremented Retry for (url='%s'): %r" % (url, new_retry)) + + return new_retry + + def __repr__(self): + return ('{cls.__name__}(total={self.total}, connect={self.connect}, ' + 'read={self.read}, redirect={self.redirect})').format( + cls=type(self), self=self) + + +# For backwards compatibility (equivalent to pre-v1.9): +Retry.DEFAULT = Retry(3) diff --git a/resources/lib/libraries/requests/packages/urllib3/util/ssl_.py b/resources/lib/libraries/requests/packages/urllib3/util/ssl_.py new file mode 100644 index 00000000..67f83441 --- /dev/null +++ b/resources/lib/libraries/requests/packages/urllib3/util/ssl_.py @@ -0,0 +1,317 @@ +from __future__ import absolute_import +import errno +import warnings +import hmac + +from binascii import hexlify, unhexlify +from hashlib import md5, sha1, sha256 + +from ..exceptions import SSLError, InsecurePlatformWarning, SNIMissingWarning + + +SSLContext = None +HAS_SNI = False +create_default_context = None + +# Maps the length of a digest to a possible hash function producing this digest +HASHFUNC_MAP = { + 32: md5, + 40: sha1, + 64: sha256, +} + + +def _const_compare_digest_backport(a, b): + """ + Compare two digests of equal length in constant time. + + The digests must be of type str/bytes. + Returns True if the digests match, and False otherwise. + """ + result = abs(len(a) - len(b)) + for l, r in zip(bytearray(a), bytearray(b)): + result |= l ^ r + return result == 0 + + +_const_compare_digest = getattr(hmac, 'compare_digest', + _const_compare_digest_backport) + + +try: # Test for SSL features + import ssl + from ssl import wrap_socket, CERT_NONE, PROTOCOL_SSLv23 + from ssl import HAS_SNI # Has SNI? +except ImportError: + pass + + +try: + from ssl import OP_NO_SSLv2, OP_NO_SSLv3, OP_NO_COMPRESSION +except ImportError: + OP_NO_SSLv2, OP_NO_SSLv3 = 0x1000000, 0x2000000 + OP_NO_COMPRESSION = 0x20000 + +# A secure default. +# Sources for more information on TLS ciphers: +# +# - https://wiki.mozilla.org/Security/Server_Side_TLS +# - https://www.ssllabs.com/projects/best-practices/index.html +# - https://hynek.me/articles/hardening-your-web-servers-ssl-ciphers/ +# +# The general intent is: +# - Prefer cipher suites that offer perfect forward secrecy (DHE/ECDHE), +# - prefer ECDHE over DHE for better performance, +# - prefer any AES-GCM over any AES-CBC for better performance and security, +# - use 3DES as fallback which is secure but slow, +# - disable NULL authentication, MD5 MACs and DSS for security reasons. +DEFAULT_CIPHERS = ( + 'ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+HIGH:' + 'DH+HIGH:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+HIGH:RSA+3DES:!aNULL:' + '!eNULL:!MD5' +) + +try: + from ssl import SSLContext # Modern SSL? +except ImportError: + import sys + + class SSLContext(object): # Platform-specific: Python 2 & 3.1 + supports_set_ciphers = ((2, 7) <= sys.version_info < (3,) or + (3, 2) <= sys.version_info) + + def __init__(self, protocol_version): + self.protocol = protocol_version + # Use default values from a real SSLContext + self.check_hostname = False + self.verify_mode = ssl.CERT_NONE + self.ca_certs = None + self.options = 0 + self.certfile = None + self.keyfile = None + self.ciphers = None + + def load_cert_chain(self, certfile, keyfile): + self.certfile = certfile + self.keyfile = keyfile + + def load_verify_locations(self, cafile=None, capath=None): + self.ca_certs = cafile + + if capath is not None: + raise SSLError("CA directories not supported in older Pythons") + + def set_ciphers(self, cipher_suite): + if not self.supports_set_ciphers: + raise TypeError( + 'Your version of Python does not support setting ' + 'a custom cipher suite. Please upgrade to Python ' + '2.7, 3.2, or later if you need this functionality.' + ) + self.ciphers = cipher_suite + + def wrap_socket(self, socket, server_hostname=None): + warnings.warn( + 'A true SSLContext object is not available. This prevents ' + 'urllib3 from configuring SSL appropriately and may cause ' + 'certain SSL connections to fail. For more information, see ' + 'https://urllib3.readthedocs.org/en/latest/security.html' + '#insecureplatformwarning.', + InsecurePlatformWarning + ) + kwargs = { + 'keyfile': self.keyfile, + 'certfile': self.certfile, + 'ca_certs': self.ca_certs, + 'cert_reqs': self.verify_mode, + 'ssl_version': self.protocol, + } + if self.supports_set_ciphers: # Platform-specific: Python 2.7+ + return wrap_socket(socket, ciphers=self.ciphers, **kwargs) + else: # Platform-specific: Python 2.6 + return wrap_socket(socket, **kwargs) + + +def assert_fingerprint(cert, fingerprint): + """ + Checks if given fingerprint matches the supplied certificate. + + :param cert: + Certificate as bytes object. + :param fingerprint: + Fingerprint as string of hexdigits, can be interspersed by colons. + """ + + fingerprint = fingerprint.replace(':', '').lower() + digest_length = len(fingerprint) + hashfunc = HASHFUNC_MAP.get(digest_length) + if not hashfunc: + raise SSLError( + 'Fingerprint of invalid length: {0}'.format(fingerprint)) + + # We need encode() here for py32; works on py2 and p33. + fingerprint_bytes = unhexlify(fingerprint.encode()) + + cert_digest = hashfunc(cert).digest() + + if not _const_compare_digest(cert_digest, fingerprint_bytes): + raise SSLError('Fingerprints did not match. Expected "{0}", got "{1}".' + .format(fingerprint, hexlify(cert_digest))) + + +def resolve_cert_reqs(candidate): + """ + Resolves the argument to a numeric constant, which can be passed to + the wrap_socket function/method from the ssl module. + Defaults to :data:`ssl.CERT_NONE`. + If given a string it is assumed to be the name of the constant in the + :mod:`ssl` module or its abbrevation. + (So you can specify `REQUIRED` instead of `CERT_REQUIRED`. + If it's neither `None` nor a string we assume it is already the numeric + constant which can directly be passed to wrap_socket. + """ + if candidate is None: + return CERT_NONE + + if isinstance(candidate, str): + res = getattr(ssl, candidate, None) + if res is None: + res = getattr(ssl, 'CERT_' + candidate) + return res + + return candidate + + +def resolve_ssl_version(candidate): + """ + like resolve_cert_reqs + """ + if candidate is None: + return PROTOCOL_SSLv23 + + if isinstance(candidate, str): + res = getattr(ssl, candidate, None) + if res is None: + res = getattr(ssl, 'PROTOCOL_' + candidate) + return res + + return candidate + + +def create_urllib3_context(ssl_version=None, cert_reqs=None, + options=None, ciphers=None): + """All arguments have the same meaning as ``ssl_wrap_socket``. + + By default, this function does a lot of the same work that + ``ssl.create_default_context`` does on Python 3.4+. It: + + - Disables SSLv2, SSLv3, and compression + - Sets a restricted set of server ciphers + + If you wish to enable SSLv3, you can do:: + + from urllib3.util import ssl_ + context = ssl_.create_urllib3_context() + context.options &= ~ssl_.OP_NO_SSLv3 + + You can do the same to enable compression (substituting ``COMPRESSION`` + for ``SSLv3`` in the last line above). + + :param ssl_version: + The desired protocol version to use. This will default to + PROTOCOL_SSLv23 which will negotiate the highest protocol that both + the server and your installation of OpenSSL support. + :param cert_reqs: + Whether to require the certificate verification. This defaults to + ``ssl.CERT_REQUIRED``. + :param options: + Specific OpenSSL options. These default to ``ssl.OP_NO_SSLv2``, + ``ssl.OP_NO_SSLv3``, ``ssl.OP_NO_COMPRESSION``. + :param ciphers: + Which cipher suites to allow the server to select. + :returns: + Constructed SSLContext object with specified options + :rtype: SSLContext + """ + context = SSLContext(ssl_version or ssl.PROTOCOL_SSLv23) + + # Setting the default here, as we may have no ssl module on import + cert_reqs = ssl.CERT_REQUIRED if cert_reqs is None else cert_reqs + + if options is None: + options = 0 + # SSLv2 is easily broken and is considered harmful and dangerous + options |= OP_NO_SSLv2 + # SSLv3 has several problems and is now dangerous + options |= OP_NO_SSLv3 + # Disable compression to prevent CRIME attacks for OpenSSL 1.0+ + # (issue #309) + options |= OP_NO_COMPRESSION + + context.options |= options + + if getattr(context, 'supports_set_ciphers', True): # Platform-specific: Python 2.6 + context.set_ciphers(ciphers or DEFAULT_CIPHERS) + + context.verify_mode = cert_reqs + if getattr(context, 'check_hostname', None) is not None: # Platform-specific: Python 3.2 + # We do our own verification, including fingerprints and alternative + # hostnames. So disable it here + context.check_hostname = False + return context + + +def ssl_wrap_socket(sock, keyfile=None, certfile=None, cert_reqs=None, + ca_certs=None, server_hostname=None, + ssl_version=None, ciphers=None, ssl_context=None, + ca_cert_dir=None): + """ + All arguments except for server_hostname, ssl_context, and ca_cert_dir have + the same meaning as they do when using :func:`ssl.wrap_socket`. + + :param server_hostname: + When SNI is supported, the expected hostname of the certificate + :param ssl_context: + A pre-made :class:`SSLContext` object. If none is provided, one will + be created using :func:`create_urllib3_context`. + :param ciphers: + A string of ciphers we wish the client to support. This is not + supported on Python 2.6 as the ssl module does not support it. + :param ca_cert_dir: + A directory containing CA certificates in multiple separate files, as + supported by OpenSSL's -CApath flag or the capath argument to + SSLContext.load_verify_locations(). + """ + context = ssl_context + if context is None: + context = create_urllib3_context(ssl_version, cert_reqs, + ciphers=ciphers) + + if ca_certs or ca_cert_dir: + try: + context.load_verify_locations(ca_certs, ca_cert_dir) + except IOError as e: # Platform-specific: Python 2.6, 2.7, 3.2 + raise SSLError(e) + # Py33 raises FileNotFoundError which subclasses OSError + # These are not equivalent unless we check the errno attribute + except OSError as e: # Platform-specific: Python 3.3 and beyond + if e.errno == errno.ENOENT: + raise SSLError(e) + raise + + if certfile: + context.load_cert_chain(certfile, keyfile) + if HAS_SNI: # Platform-specific: OpenSSL with enabled SNI + return context.wrap_socket(sock, server_hostname=server_hostname) + + warnings.warn( + 'An HTTPS request has been made, but the SNI (Subject Name ' + 'Indication) extension to TLS is not available on this platform. ' + 'This may cause the server to present an incorrect TLS ' + 'certificate, which can cause validation failures. For more ' + 'information, see ' + 'https://urllib3.readthedocs.org/en/latest/security.html' + '#snimissingwarning.', + SNIMissingWarning + ) + return context.wrap_socket(sock) diff --git a/resources/lib/libraries/requests/packages/urllib3/util/timeout.py b/resources/lib/libraries/requests/packages/urllib3/util/timeout.py new file mode 100644 index 00000000..ff62f476 --- /dev/null +++ b/resources/lib/libraries/requests/packages/urllib3/util/timeout.py @@ -0,0 +1,242 @@ +from __future__ import absolute_import +# The default socket timeout, used by httplib to indicate that no timeout was +# specified by the user +from socket import _GLOBAL_DEFAULT_TIMEOUT +import time + +from ..exceptions import TimeoutStateError + +# A sentinel value to indicate that no timeout was specified by the user in +# urllib3 +_Default = object() + + +def current_time(): + """ + Retrieve the current time. This function is mocked out in unit testing. + """ + return time.time() + + +class Timeout(object): + """ Timeout configuration. + + Timeouts can be defined as a default for a pool:: + + timeout = Timeout(connect=2.0, read=7.0) + http = PoolManager(timeout=timeout) + response = http.request('GET', 'http://example.com/') + + Or per-request (which overrides the default for the pool):: + + response = http.request('GET', 'http://example.com/', timeout=Timeout(10)) + + Timeouts can be disabled by setting all the parameters to ``None``:: + + no_timeout = Timeout(connect=None, read=None) + response = http.request('GET', 'http://example.com/, timeout=no_timeout) + + + :param total: + This combines the connect and read timeouts into one; the read timeout + will be set to the time leftover from the connect attempt. In the + event that both a connect timeout and a total are specified, or a read + timeout and a total are specified, the shorter timeout will be applied. + + Defaults to None. + + :type total: integer, float, or None + + :param connect: + The maximum amount of time to wait for a connection attempt to a server + to succeed. Omitting the parameter will default the connect timeout to + the system default, probably `the global default timeout in socket.py + <http://hg.python.org/cpython/file/603b4d593758/Lib/socket.py#l535>`_. + None will set an infinite timeout for connection attempts. + + :type connect: integer, float, or None + + :param read: + The maximum amount of time to wait between consecutive + read operations for a response from the server. Omitting + the parameter will default the read timeout to the system + default, probably `the global default timeout in socket.py + <http://hg.python.org/cpython/file/603b4d593758/Lib/socket.py#l535>`_. + None will set an infinite timeout. + + :type read: integer, float, or None + + .. note:: + + Many factors can affect the total amount of time for urllib3 to return + an HTTP response. + + For example, Python's DNS resolver does not obey the timeout specified + on the socket. Other factors that can affect total request time include + high CPU load, high swap, the program running at a low priority level, + or other behaviors. + + In addition, the read and total timeouts only measure the time between + read operations on the socket connecting the client and the server, + not the total amount of time for the request to return a complete + response. For most requests, the timeout is raised because the server + has not sent the first byte in the specified time. This is not always + the case; if a server streams one byte every fifteen seconds, a timeout + of 20 seconds will not trigger, even though the request will take + several minutes to complete. + + If your goal is to cut off any request after a set amount of wall clock + time, consider having a second "watcher" thread to cut off a slow + request. + """ + + #: A sentinel object representing the default timeout value + DEFAULT_TIMEOUT = _GLOBAL_DEFAULT_TIMEOUT + + def __init__(self, total=None, connect=_Default, read=_Default): + self._connect = self._validate_timeout(connect, 'connect') + self._read = self._validate_timeout(read, 'read') + self.total = self._validate_timeout(total, 'total') + self._start_connect = None + + def __str__(self): + return '%s(connect=%r, read=%r, total=%r)' % ( + type(self).__name__, self._connect, self._read, self.total) + + @classmethod + def _validate_timeout(cls, value, name): + """ Check that a timeout attribute is valid. + + :param value: The timeout value to validate + :param name: The name of the timeout attribute to validate. This is + used to specify in error messages. + :return: The validated and casted version of the given value. + :raises ValueError: If the type is not an integer or a float, or if it + is a numeric value less than zero. + """ + if value is _Default: + return cls.DEFAULT_TIMEOUT + + if value is None or value is cls.DEFAULT_TIMEOUT: + return value + + try: + float(value) + except (TypeError, ValueError): + raise ValueError("Timeout value %s was %s, but it must be an " + "int or float." % (name, value)) + + try: + if value < 0: + raise ValueError("Attempted to set %s timeout to %s, but the " + "timeout cannot be set to a value less " + "than 0." % (name, value)) + except TypeError: # Python 3 + raise ValueError("Timeout value %s was %s, but it must be an " + "int or float." % (name, value)) + + return value + + @classmethod + def from_float(cls, timeout): + """ Create a new Timeout from a legacy timeout value. + + The timeout value used by httplib.py sets the same timeout on the + connect(), and recv() socket requests. This creates a :class:`Timeout` + object that sets the individual timeouts to the ``timeout`` value + passed to this function. + + :param timeout: The legacy timeout value. + :type timeout: integer, float, sentinel default object, or None + :return: Timeout object + :rtype: :class:`Timeout` + """ + return Timeout(read=timeout, connect=timeout) + + def clone(self): + """ Create a copy of the timeout object + + Timeout properties are stored per-pool but each request needs a fresh + Timeout object to ensure each one has its own start/stop configured. + + :return: a copy of the timeout object + :rtype: :class:`Timeout` + """ + # We can't use copy.deepcopy because that will also create a new object + # for _GLOBAL_DEFAULT_TIMEOUT, which socket.py uses as a sentinel to + # detect the user default. + return Timeout(connect=self._connect, read=self._read, + total=self.total) + + def start_connect(self): + """ Start the timeout clock, used during a connect() attempt + + :raises urllib3.exceptions.TimeoutStateError: if you attempt + to start a timer that has been started already. + """ + if self._start_connect is not None: + raise TimeoutStateError("Timeout timer has already been started.") + self._start_connect = current_time() + return self._start_connect + + def get_connect_duration(self): + """ Gets the time elapsed since the call to :meth:`start_connect`. + + :return: Elapsed time. + :rtype: float + :raises urllib3.exceptions.TimeoutStateError: if you attempt + to get duration for a timer that hasn't been started. + """ + if self._start_connect is None: + raise TimeoutStateError("Can't get connect duration for timer " + "that has not started.") + return current_time() - self._start_connect + + @property + def connect_timeout(self): + """ Get the value to use when setting a connection timeout. + + This will be a positive float or integer, the value None + (never timeout), or the default system timeout. + + :return: Connect timeout. + :rtype: int, float, :attr:`Timeout.DEFAULT_TIMEOUT` or None + """ + if self.total is None: + return self._connect + + if self._connect is None or self._connect is self.DEFAULT_TIMEOUT: + return self.total + + return min(self._connect, self.total) + + @property + def read_timeout(self): + """ Get the value for the read timeout. + + This assumes some time has elapsed in the connection timeout and + computes the read timeout appropriately. + + If self.total is set, the read timeout is dependent on the amount of + time taken by the connect timeout. If the connection time has not been + established, a :exc:`~urllib3.exceptions.TimeoutStateError` will be + raised. + + :return: Value to use for the read timeout. + :rtype: int, float, :attr:`Timeout.DEFAULT_TIMEOUT` or None + :raises urllib3.exceptions.TimeoutStateError: If :meth:`start_connect` + has not yet been called on this object. + """ + if (self.total is not None and + self.total is not self.DEFAULT_TIMEOUT and + self._read is not None and + self._read is not self.DEFAULT_TIMEOUT): + # In case the connect timeout has not yet been established. + if self._start_connect is None: + return self._read + return max(0, min(self.total - self.get_connect_duration(), + self._read)) + elif self.total is not None and self.total is not self.DEFAULT_TIMEOUT: + return max(0, self.total - self.get_connect_duration()) + else: + return self._read diff --git a/resources/lib/libraries/requests/packages/urllib3/util/url.py b/resources/lib/libraries/requests/packages/urllib3/util/url.py new file mode 100644 index 00000000..e996204a --- /dev/null +++ b/resources/lib/libraries/requests/packages/urllib3/util/url.py @@ -0,0 +1,217 @@ +from __future__ import absolute_import +from collections import namedtuple + +from ..exceptions import LocationParseError + + +url_attrs = ['scheme', 'auth', 'host', 'port', 'path', 'query', 'fragment'] + + +class Url(namedtuple('Url', url_attrs)): + """ + Datastructure for representing an HTTP URL. Used as a return value for + :func:`parse_url`. + """ + slots = () + + def __new__(cls, scheme=None, auth=None, host=None, port=None, path=None, + query=None, fragment=None): + if path and not path.startswith('/'): + path = '/' + path + return super(Url, cls).__new__(cls, scheme, auth, host, port, path, + query, fragment) + + @property + def hostname(self): + """For backwards-compatibility with urlparse. We're nice like that.""" + return self.host + + @property + def request_uri(self): + """Absolute path including the query string.""" + uri = self.path or '/' + + if self.query is not None: + uri += '?' + self.query + + return uri + + @property + def netloc(self): + """Network location including host and port""" + if self.port: + return '%s:%d' % (self.host, self.port) + return self.host + + @property + def url(self): + """ + Convert self into a url + + This function should more or less round-trip with :func:`.parse_url`. The + returned url may not be exactly the same as the url inputted to + :func:`.parse_url`, but it should be equivalent by the RFC (e.g., urls + with a blank port will have : removed). + + Example: :: + + >>> U = parse_url('http://google.com/mail/') + >>> U.url + 'http://google.com/mail/' + >>> Url('http', 'username:password', 'host.com', 80, + ... '/path', 'query', 'fragment').url + 'http://username:password@host.com:80/path?query#fragment' + """ + scheme, auth, host, port, path, query, fragment = self + url = '' + + # We use "is not None" we want things to happen with empty strings (or 0 port) + if scheme is not None: + url += scheme + '://' + if auth is not None: + url += auth + '@' + if host is not None: + url += host + if port is not None: + url += ':' + str(port) + if path is not None: + url += path + if query is not None: + url += '?' + query + if fragment is not None: + url += '#' + fragment + + return url + + def __str__(self): + return self.url + + +def split_first(s, delims): + """ + Given a string and an iterable of delimiters, split on the first found + delimiter. Return two split parts and the matched delimiter. + + If not found, then the first part is the full input string. + + Example:: + + >>> split_first('foo/bar?baz', '?/=') + ('foo', 'bar?baz', '/') + >>> split_first('foo/bar?baz', '123') + ('foo/bar?baz', '', None) + + Scales linearly with number of delims. Not ideal for large number of delims. + """ + min_idx = None + min_delim = None + for d in delims: + idx = s.find(d) + if idx < 0: + continue + + if min_idx is None or idx < min_idx: + min_idx = idx + min_delim = d + + if min_idx is None or min_idx < 0: + return s, '', None + + return s[:min_idx], s[min_idx + 1:], min_delim + + +def parse_url(url): + """ + Given a url, return a parsed :class:`.Url` namedtuple. Best-effort is + performed to parse incomplete urls. Fields not provided will be None. + + Partly backwards-compatible with :mod:`urlparse`. + + Example:: + + >>> parse_url('http://google.com/mail/') + Url(scheme='http', host='google.com', port=None, path='/mail/', ...) + >>> parse_url('google.com:80') + Url(scheme=None, host='google.com', port=80, path=None, ...) + >>> parse_url('/foo?bar') + Url(scheme=None, host=None, port=None, path='/foo', query='bar', ...) + """ + + # While this code has overlap with stdlib's urlparse, it is much + # simplified for our needs and less annoying. + # Additionally, this implementations does silly things to be optimal + # on CPython. + + if not url: + # Empty + return Url() + + scheme = None + auth = None + host = None + port = None + path = None + fragment = None + query = None + + # Scheme + if '://' in url: + scheme, url = url.split('://', 1) + + # Find the earliest Authority Terminator + # (http://tools.ietf.org/html/rfc3986#section-3.2) + url, path_, delim = split_first(url, ['/', '?', '#']) + + if delim: + # Reassemble the path + path = delim + path_ + + # Auth + if '@' in url: + # Last '@' denotes end of auth part + auth, url = url.rsplit('@', 1) + + # IPv6 + if url and url[0] == '[': + host, url = url.split(']', 1) + host += ']' + + # Port + if ':' in url: + _host, port = url.split(':', 1) + + if not host: + host = _host + + if port: + # If given, ports must be integers. + if not port.isdigit(): + raise LocationParseError(url) + port = int(port) + else: + # Blank ports are cool, too. (rfc3986#section-3.2.3) + port = None + + elif not host and url: + host = url + + if not path: + return Url(scheme, auth, host, port, path, query, fragment) + + # Fragment + if '#' in path: + path, fragment = path.split('#', 1) + + # Query + if '?' in path: + path, query = path.split('?', 1) + + return Url(scheme, auth, host, port, path, query, fragment) + + +def get_host(url): + """ + Deprecated. Use :func:`.parse_url` instead. + """ + p = parse_url(url) + return p.scheme or 'http', p.hostname, p.port diff --git a/resources/lib/libraries/requests/sessions.py b/resources/lib/libraries/requests/sessions.py new file mode 100644 index 00000000..9eaa36ae --- /dev/null +++ b/resources/lib/libraries/requests/sessions.py @@ -0,0 +1,680 @@ +# -*- coding: utf-8 -*- + +""" +requests.session +~~~~~~~~~~~~~~~~ + +This module provides a Session object to manage and persist settings across +requests (cookies, auth, proxies). + +""" +import os +from collections import Mapping +from datetime import datetime + +from .auth import _basic_auth_str +from .compat import cookielib, OrderedDict, urljoin, urlparse +from .cookies import ( + cookiejar_from_dict, extract_cookies_to_jar, RequestsCookieJar, merge_cookies) +from .models import Request, PreparedRequest, DEFAULT_REDIRECT_LIMIT +from .hooks import default_hooks, dispatch_hook +from .utils import to_key_val_list, default_headers, to_native_string +from .exceptions import ( + TooManyRedirects, InvalidSchema, ChunkedEncodingError, ContentDecodingError) +from .packages.urllib3._collections import RecentlyUsedContainer +from .structures import CaseInsensitiveDict + +from .adapters import HTTPAdapter + +from .utils import ( + requote_uri, get_environ_proxies, get_netrc_auth, should_bypass_proxies, + get_auth_from_url +) + +from .status_codes import codes + +# formerly defined here, reexposed here for backward compatibility +from .models import REDIRECT_STATI + +REDIRECT_CACHE_SIZE = 1000 + + +def merge_setting(request_setting, session_setting, dict_class=OrderedDict): + """ + Determines appropriate setting for a given request, taking into account the + explicit setting on that request, and the setting in the session. If a + setting is a dictionary, they will be merged together using `dict_class` + """ + + if session_setting is None: + return request_setting + + if request_setting is None: + return session_setting + + # Bypass if not a dictionary (e.g. verify) + if not ( + isinstance(session_setting, Mapping) and + isinstance(request_setting, Mapping) + ): + return request_setting + + merged_setting = dict_class(to_key_val_list(session_setting)) + merged_setting.update(to_key_val_list(request_setting)) + + # Remove keys that are set to None. Extract keys first to avoid altering + # the dictionary during iteration. + none_keys = [k for (k, v) in merged_setting.items() if v is None] + for key in none_keys: + del merged_setting[key] + + return merged_setting + + +def merge_hooks(request_hooks, session_hooks, dict_class=OrderedDict): + """ + Properly merges both requests and session hooks. + + This is necessary because when request_hooks == {'response': []}, the + merge breaks Session hooks entirely. + """ + if session_hooks is None or session_hooks.get('response') == []: + return request_hooks + + if request_hooks is None or request_hooks.get('response') == []: + return session_hooks + + return merge_setting(request_hooks, session_hooks, dict_class) + + +class SessionRedirectMixin(object): + def resolve_redirects(self, resp, req, stream=False, timeout=None, + verify=True, cert=None, proxies=None, **adapter_kwargs): + """Receives a Response. Returns a generator of Responses.""" + + i = 0 + hist = [] # keep track of history + + while resp.is_redirect: + prepared_request = req.copy() + + if i > 0: + # Update history and keep track of redirects. + hist.append(resp) + new_hist = list(hist) + resp.history = new_hist + + try: + resp.content # Consume socket so it can be released + except (ChunkedEncodingError, ContentDecodingError, RuntimeError): + resp.raw.read(decode_content=False) + + if i >= self.max_redirects: + raise TooManyRedirects('Exceeded %s redirects.' % self.max_redirects) + + # Release the connection back into the pool. + resp.close() + + url = resp.headers['location'] + method = req.method + + # Handle redirection without scheme (see: RFC 1808 Section 4) + if url.startswith('//'): + parsed_rurl = urlparse(resp.url) + url = '%s:%s' % (parsed_rurl.scheme, url) + + # The scheme should be lower case... + parsed = urlparse(url) + url = parsed.geturl() + + # Facilitate relative 'location' headers, as allowed by RFC 7231. + # (e.g. '/path/to/resource' instead of 'http://domain.tld/path/to/resource') + # Compliant with RFC3986, we percent encode the url. + if not parsed.netloc: + url = urljoin(resp.url, requote_uri(url)) + else: + url = requote_uri(url) + + prepared_request.url = to_native_string(url) + # Cache the url, unless it redirects to itself. + if resp.is_permanent_redirect and req.url != prepared_request.url: + self.redirect_cache[req.url] = prepared_request.url + + # http://tools.ietf.org/html/rfc7231#section-6.4.4 + if (resp.status_code == codes.see_other and + method != 'HEAD'): + method = 'GET' + + # Do what the browsers do, despite standards... + # First, turn 302s into GETs. + if resp.status_code == codes.found and method != 'HEAD': + method = 'GET' + + # Second, if a POST is responded to with a 301, turn it into a GET. + # This bizarre behaviour is explained in Issue 1704. + if resp.status_code == codes.moved and method == 'POST': + method = 'GET' + + prepared_request.method = method + + # https://github.com/kennethreitz/requests/issues/1084 + if resp.status_code not in (codes.temporary_redirect, codes.permanent_redirect): + if 'Content-Length' in prepared_request.headers: + del prepared_request.headers['Content-Length'] + + prepared_request.body = None + + headers = prepared_request.headers + try: + del headers['Cookie'] + except KeyError: + pass + + # Extract any cookies sent on the response to the cookiejar + # in the new request. Because we've mutated our copied prepared + # request, use the old one that we haven't yet touched. + extract_cookies_to_jar(prepared_request._cookies, req, resp.raw) + prepared_request._cookies.update(self.cookies) + prepared_request.prepare_cookies(prepared_request._cookies) + + # Rebuild auth and proxy information. + proxies = self.rebuild_proxies(prepared_request, proxies) + self.rebuild_auth(prepared_request, resp) + + # Override the original request. + req = prepared_request + + resp = self.send( + req, + stream=stream, + timeout=timeout, + verify=verify, + cert=cert, + proxies=proxies, + allow_redirects=False, + **adapter_kwargs + ) + + extract_cookies_to_jar(self.cookies, prepared_request, resp.raw) + + i += 1 + yield resp + + def rebuild_auth(self, prepared_request, response): + """ + When being redirected we may want to strip authentication from the + request to avoid leaking credentials. This method intelligently removes + and reapplies authentication where possible to avoid credential loss. + """ + headers = prepared_request.headers + url = prepared_request.url + + if 'Authorization' in headers: + # If we get redirected to a new host, we should strip out any + # authentication headers. + original_parsed = urlparse(response.request.url) + redirect_parsed = urlparse(url) + + if (original_parsed.hostname != redirect_parsed.hostname): + del headers['Authorization'] + + # .netrc might have more auth for us on our new host. + new_auth = get_netrc_auth(url) if self.trust_env else None + if new_auth is not None: + prepared_request.prepare_auth(new_auth) + + return + + def rebuild_proxies(self, prepared_request, proxies): + """ + This method re-evaluates the proxy configuration by considering the + environment variables. If we are redirected to a URL covered by + NO_PROXY, we strip the proxy configuration. Otherwise, we set missing + proxy keys for this URL (in case they were stripped by a previous + redirect). + + This method also replaces the Proxy-Authorization header where + necessary. + """ + headers = prepared_request.headers + url = prepared_request.url + scheme = urlparse(url).scheme + new_proxies = proxies.copy() if proxies is not None else {} + + if self.trust_env and not should_bypass_proxies(url): + environ_proxies = get_environ_proxies(url) + + proxy = environ_proxies.get(scheme) + + if proxy: + new_proxies.setdefault(scheme, environ_proxies[scheme]) + + if 'Proxy-Authorization' in headers: + del headers['Proxy-Authorization'] + + try: + username, password = get_auth_from_url(new_proxies[scheme]) + except KeyError: + username, password = None, None + + if username and password: + headers['Proxy-Authorization'] = _basic_auth_str(username, password) + + return new_proxies + + +class Session(SessionRedirectMixin): + """A Requests session. + + Provides cookie persistence, connection-pooling, and configuration. + + Basic Usage:: + + >>> import requests + >>> s = requests.Session() + >>> s.get('http://httpbin.org/get') + <Response [200]> + + Or as a context manager:: + + >>> with requests.Session() as s: + >>> s.get('http://httpbin.org/get') + <Response [200]> + """ + + __attrs__ = [ + 'headers', 'cookies', 'auth', 'proxies', 'hooks', 'params', 'verify', + 'cert', 'prefetch', 'adapters', 'stream', 'trust_env', + 'max_redirects', + ] + + def __init__(self): + + #: A case-insensitive dictionary of headers to be sent on each + #: :class:`Request <Request>` sent from this + #: :class:`Session <Session>`. + self.headers = default_headers() + + #: Default Authentication tuple or object to attach to + #: :class:`Request <Request>`. + self.auth = None + + #: Dictionary mapping protocol or protocol and host to the URL of the proxy + #: (e.g. {'http': 'foo.bar:3128', 'http://host.name': 'foo.bar:4012'}) to + #: be used on each :class:`Request <Request>`. + self.proxies = {} + + #: Event-handling hooks. + self.hooks = default_hooks() + + #: Dictionary of querystring data to attach to each + #: :class:`Request <Request>`. The dictionary values may be lists for + #: representing multivalued query parameters. + self.params = {} + + #: Stream response content default. + self.stream = False + + #: SSL Verification default. + self.verify = True + + #: SSL certificate default. + self.cert = None + + #: Maximum number of redirects allowed. If the request exceeds this + #: limit, a :class:`TooManyRedirects` exception is raised. + self.max_redirects = DEFAULT_REDIRECT_LIMIT + + #: Trust environment settings for proxy configuration, default + #: authentication and similar. + self.trust_env = True + + #: A CookieJar containing all currently outstanding cookies set on this + #: session. By default it is a + #: :class:`RequestsCookieJar <requests.cookies.RequestsCookieJar>`, but + #: may be any other ``cookielib.CookieJar`` compatible object. + self.cookies = cookiejar_from_dict({}) + + # Default connection adapters. + self.adapters = OrderedDict() + self.mount('https://', HTTPAdapter()) + self.mount('http://', HTTPAdapter()) + + # Only store 1000 redirects to prevent using infinite memory + self.redirect_cache = RecentlyUsedContainer(REDIRECT_CACHE_SIZE) + + def __enter__(self): + return self + + def __exit__(self, *args): + self.close() + + def prepare_request(self, request): + """Constructs a :class:`PreparedRequest <PreparedRequest>` for + transmission and returns it. The :class:`PreparedRequest` has settings + merged from the :class:`Request <Request>` instance and those of the + :class:`Session`. + + :param request: :class:`Request` instance to prepare with this + session's settings. + """ + cookies = request.cookies or {} + + # Bootstrap CookieJar. + if not isinstance(cookies, cookielib.CookieJar): + cookies = cookiejar_from_dict(cookies) + + # Merge with session cookies + merged_cookies = merge_cookies( + merge_cookies(RequestsCookieJar(), self.cookies), cookies) + + + # Set environment's basic authentication if not explicitly set. + auth = request.auth + if self.trust_env and not auth and not self.auth: + auth = get_netrc_auth(request.url) + + p = PreparedRequest() + p.prepare( + method=request.method.upper(), + url=request.url, + files=request.files, + data=request.data, + json=request.json, + headers=merge_setting(request.headers, self.headers, dict_class=CaseInsensitiveDict), + params=merge_setting(request.params, self.params), + auth=merge_setting(auth, self.auth), + cookies=merged_cookies, + hooks=merge_hooks(request.hooks, self.hooks), + ) + return p + + def request(self, method, url, + params=None, + data=None, + headers=None, + cookies=None, + files=None, + auth=None, + timeout=None, + allow_redirects=True, + proxies=None, + hooks=None, + stream=None, + verify=None, + cert=None, + json=None): + """Constructs a :class:`Request <Request>`, prepares it and sends it. + Returns :class:`Response <Response>` object. + + :param method: method for the new :class:`Request` object. + :param url: URL for the new :class:`Request` object. + :param params: (optional) Dictionary or bytes to be sent in the query + string for the :class:`Request`. + :param data: (optional) Dictionary, bytes, or file-like object to send + in the body of the :class:`Request`. + :param json: (optional) json to send in the body of the + :class:`Request`. + :param headers: (optional) Dictionary of HTTP Headers to send with the + :class:`Request`. + :param cookies: (optional) Dict or CookieJar object to send with the + :class:`Request`. + :param files: (optional) Dictionary of ``'filename': file-like-objects`` + for multipart encoding upload. + :param auth: (optional) Auth tuple or callable to enable + Basic/Digest/Custom HTTP Auth. + :param timeout: (optional) How long to wait for the server to send + data before giving up, as a float, or a :ref:`(connect timeout, + read timeout) <timeouts>` tuple. + :type timeout: float or tuple + :param allow_redirects: (optional) Set to True by default. + :type allow_redirects: bool + :param proxies: (optional) Dictionary mapping protocol or protocol and + hostname to the URL of the proxy. + :param stream: (optional) whether to immediately download the response + content. Defaults to ``False``. + :param verify: (optional) whether the SSL cert will be verified. + A CA_BUNDLE path can also be provided. Defaults to ``True``. + :param cert: (optional) if String, path to ssl client cert file (.pem). + If Tuple, ('cert', 'key') pair. + """ + # Create the Request. + req = Request( + method = method.upper(), + url = url, + headers = headers, + files = files, + data = data or {}, + json = json, + params = params or {}, + auth = auth, + cookies = cookies, + hooks = hooks, + ) + prep = self.prepare_request(req) + + proxies = proxies or {} + + settings = self.merge_environment_settings( + prep.url, proxies, stream, verify, cert + ) + + # Send the request. + send_kwargs = { + 'timeout': timeout, + 'allow_redirects': allow_redirects, + } + send_kwargs.update(settings) + resp = self.send(prep, **send_kwargs) + + return resp + + def get(self, url, **kwargs): + """Sends a GET request. Returns :class:`Response` object. + + :param url: URL for the new :class:`Request` object. + :param \*\*kwargs: Optional arguments that ``request`` takes. + """ + + kwargs.setdefault('allow_redirects', True) + return self.request('GET', url, **kwargs) + + def options(self, url, **kwargs): + """Sends a OPTIONS request. Returns :class:`Response` object. + + :param url: URL for the new :class:`Request` object. + :param \*\*kwargs: Optional arguments that ``request`` takes. + """ + + kwargs.setdefault('allow_redirects', True) + return self.request('OPTIONS', url, **kwargs) + + def head(self, url, **kwargs): + """Sends a HEAD request. Returns :class:`Response` object. + + :param url: URL for the new :class:`Request` object. + :param \*\*kwargs: Optional arguments that ``request`` takes. + """ + + kwargs.setdefault('allow_redirects', False) + return self.request('HEAD', url, **kwargs) + + def post(self, url, data=None, json=None, **kwargs): + """Sends a POST request. Returns :class:`Response` object. + + :param url: URL for the new :class:`Request` object. + :param data: (optional) Dictionary, bytes, or file-like object to send in the body of the :class:`Request`. + :param json: (optional) json to send in the body of the :class:`Request`. + :param \*\*kwargs: Optional arguments that ``request`` takes. + """ + + return self.request('POST', url, data=data, json=json, **kwargs) + + def put(self, url, data=None, **kwargs): + """Sends a PUT request. Returns :class:`Response` object. + + :param url: URL for the new :class:`Request` object. + :param data: (optional) Dictionary, bytes, or file-like object to send in the body of the :class:`Request`. + :param \*\*kwargs: Optional arguments that ``request`` takes. + """ + + return self.request('PUT', url, data=data, **kwargs) + + def patch(self, url, data=None, **kwargs): + """Sends a PATCH request. Returns :class:`Response` object. + + :param url: URL for the new :class:`Request` object. + :param data: (optional) Dictionary, bytes, or file-like object to send in the body of the :class:`Request`. + :param \*\*kwargs: Optional arguments that ``request`` takes. + """ + + return self.request('PATCH', url, data=data, **kwargs) + + def delete(self, url, **kwargs): + """Sends a DELETE request. Returns :class:`Response` object. + + :param url: URL for the new :class:`Request` object. + :param \*\*kwargs: Optional arguments that ``request`` takes. + """ + + return self.request('DELETE', url, **kwargs) + + def send(self, request, **kwargs): + """Send a given PreparedRequest.""" + # Set defaults that the hooks can utilize to ensure they always have + # the correct parameters to reproduce the previous request. + kwargs.setdefault('stream', self.stream) + kwargs.setdefault('verify', self.verify) + kwargs.setdefault('cert', self.cert) + kwargs.setdefault('proxies', self.proxies) + + # It's possible that users might accidentally send a Request object. + # Guard against that specific failure case. + if not isinstance(request, PreparedRequest): + raise ValueError('You can only send PreparedRequests.') + + checked_urls = set() + while request.url in self.redirect_cache: + checked_urls.add(request.url) + new_url = self.redirect_cache.get(request.url) + if new_url in checked_urls: + break + request.url = new_url + + # Set up variables needed for resolve_redirects and dispatching of hooks + allow_redirects = kwargs.pop('allow_redirects', True) + stream = kwargs.get('stream') + hooks = request.hooks + + # Get the appropriate adapter to use + adapter = self.get_adapter(url=request.url) + + # Start time (approximately) of the request + start = datetime.utcnow() + + # Send the request + r = adapter.send(request, **kwargs) + + # Total elapsed time of the request (approximately) + r.elapsed = datetime.utcnow() - start + + # Response manipulation hooks + r = dispatch_hook('response', hooks, r, **kwargs) + + # Persist cookies + if r.history: + + # If the hooks create history then we want those cookies too + for resp in r.history: + extract_cookies_to_jar(self.cookies, resp.request, resp.raw) + + extract_cookies_to_jar(self.cookies, request, r.raw) + + # Redirect resolving generator. + gen = self.resolve_redirects(r, request, **kwargs) + + # Resolve redirects if allowed. + history = [resp for resp in gen] if allow_redirects else [] + + # Shuffle things around if there's history. + if history: + # Insert the first (original) request at the start + history.insert(0, r) + # Get the last request made + r = history.pop() + r.history = history + + if not stream: + r.content + + return r + + def merge_environment_settings(self, url, proxies, stream, verify, cert): + """Check the environment and merge it with some settings.""" + # Gather clues from the surrounding environment. + if self.trust_env: + # Set environment's proxies. + env_proxies = get_environ_proxies(url) or {} + for (k, v) in env_proxies.items(): + proxies.setdefault(k, v) + + # Look for requests environment configuration and be compatible + # with cURL. + if verify is True or verify is None: + verify = (os.environ.get('REQUESTS_CA_BUNDLE') or + os.environ.get('CURL_CA_BUNDLE')) + + # Merge all the kwargs. + proxies = merge_setting(proxies, self.proxies) + stream = merge_setting(stream, self.stream) + verify = merge_setting(verify, self.verify) + cert = merge_setting(cert, self.cert) + + return {'verify': verify, 'proxies': proxies, 'stream': stream, + 'cert': cert} + + def get_adapter(self, url): + """Returns the appropriate connection adapter for the given URL.""" + for (prefix, adapter) in self.adapters.items(): + + if url.lower().startswith(prefix): + return adapter + + # Nothing matches :-/ + raise InvalidSchema("No connection adapters were found for '%s'" % url) + + def close(self): + """Closes all adapters and as such the session""" + for v in self.adapters.values(): + v.close() + + def mount(self, prefix, adapter): + """Registers a connection adapter to a prefix. + + Adapters are sorted in descending order by key length.""" + + self.adapters[prefix] = adapter + keys_to_move = [k for k in self.adapters if len(k) < len(prefix)] + + for key in keys_to_move: + self.adapters[key] = self.adapters.pop(key) + + def __getstate__(self): + state = dict((attr, getattr(self, attr, None)) for attr in self.__attrs__) + state['redirect_cache'] = dict(self.redirect_cache) + return state + + def __setstate__(self, state): + redirect_cache = state.pop('redirect_cache', {}) + for attr, value in state.items(): + setattr(self, attr, value) + + self.redirect_cache = RecentlyUsedContainer(REDIRECT_CACHE_SIZE) + for redirect, to in redirect_cache.items(): + self.redirect_cache[redirect] = to + + +def session(): + """Returns a :class:`Session` for context-management.""" + + return Session() diff --git a/resources/lib/libraries/requests/status_codes.py b/resources/lib/libraries/requests/status_codes.py new file mode 100644 index 00000000..a852574a --- /dev/null +++ b/resources/lib/libraries/requests/status_codes.py @@ -0,0 +1,90 @@ +# -*- coding: utf-8 -*- + +from .structures import LookupDict + +_codes = { + + # Informational. + 100: ('continue',), + 101: ('switching_protocols',), + 102: ('processing',), + 103: ('checkpoint',), + 122: ('uri_too_long', 'request_uri_too_long'), + 200: ('ok', 'okay', 'all_ok', 'all_okay', 'all_good', '\\o/', '✓'), + 201: ('created',), + 202: ('accepted',), + 203: ('non_authoritative_info', 'non_authoritative_information'), + 204: ('no_content',), + 205: ('reset_content', 'reset'), + 206: ('partial_content', 'partial'), + 207: ('multi_status', 'multiple_status', 'multi_stati', 'multiple_stati'), + 208: ('already_reported',), + 226: ('im_used',), + + # Redirection. + 300: ('multiple_choices',), + 301: ('moved_permanently', 'moved', '\\o-'), + 302: ('found',), + 303: ('see_other', 'other'), + 304: ('not_modified',), + 305: ('use_proxy',), + 306: ('switch_proxy',), + 307: ('temporary_redirect', 'temporary_moved', 'temporary'), + 308: ('permanent_redirect', + 'resume_incomplete', 'resume',), # These 2 to be removed in 3.0 + + # Client Error. + 400: ('bad_request', 'bad'), + 401: ('unauthorized',), + 402: ('payment_required', 'payment'), + 403: ('forbidden',), + 404: ('not_found', '-o-'), + 405: ('method_not_allowed', 'not_allowed'), + 406: ('not_acceptable',), + 407: ('proxy_authentication_required', 'proxy_auth', 'proxy_authentication'), + 408: ('request_timeout', 'timeout'), + 409: ('conflict',), + 410: ('gone',), + 411: ('length_required',), + 412: ('precondition_failed', 'precondition'), + 413: ('request_entity_too_large',), + 414: ('request_uri_too_large',), + 415: ('unsupported_media_type', 'unsupported_media', 'media_type'), + 416: ('requested_range_not_satisfiable', 'requested_range', 'range_not_satisfiable'), + 417: ('expectation_failed',), + 418: ('im_a_teapot', 'teapot', 'i_am_a_teapot'), + 422: ('unprocessable_entity', 'unprocessable'), + 423: ('locked',), + 424: ('failed_dependency', 'dependency'), + 425: ('unordered_collection', 'unordered'), + 426: ('upgrade_required', 'upgrade'), + 428: ('precondition_required', 'precondition'), + 429: ('too_many_requests', 'too_many'), + 431: ('header_fields_too_large', 'fields_too_large'), + 444: ('no_response', 'none'), + 449: ('retry_with', 'retry'), + 450: ('blocked_by_windows_parental_controls', 'parental_controls'), + 451: ('unavailable_for_legal_reasons', 'legal_reasons'), + 499: ('client_closed_request',), + + # Server Error. + 500: ('internal_server_error', 'server_error', '/o\\', '✗'), + 501: ('not_implemented',), + 502: ('bad_gateway',), + 503: ('service_unavailable', 'unavailable'), + 504: ('gateway_timeout',), + 505: ('http_version_not_supported', 'http_version'), + 506: ('variant_also_negotiates',), + 507: ('insufficient_storage',), + 509: ('bandwidth_limit_exceeded', 'bandwidth'), + 510: ('not_extended',), + 511: ('network_authentication_required', 'network_auth', 'network_authentication'), +} + +codes = LookupDict(name='status_codes') + +for code, titles in _codes.items(): + for title in titles: + setattr(codes, title, code) + if not title.startswith('\\'): + setattr(codes, title.upper(), code) diff --git a/resources/lib/libraries/requests/structures.py b/resources/lib/libraries/requests/structures.py new file mode 100644 index 00000000..3e5f2faa --- /dev/null +++ b/resources/lib/libraries/requests/structures.py @@ -0,0 +1,104 @@ +# -*- coding: utf-8 -*- + +""" +requests.structures +~~~~~~~~~~~~~~~~~~~ + +Data structures that power Requests. + +""" + +import collections + + +class CaseInsensitiveDict(collections.MutableMapping): + """ + A case-insensitive ``dict``-like object. + + Implements all methods and operations of + ``collections.MutableMapping`` as well as dict's ``copy``. Also + provides ``lower_items``. + + All keys are expected to be strings. The structure remembers the + case of the last key to be set, and ``iter(instance)``, + ``keys()``, ``items()``, ``iterkeys()``, and ``iteritems()`` + will contain case-sensitive keys. However, querying and contains + testing is case insensitive:: + + cid = CaseInsensitiveDict() + cid['Accept'] = 'application/json' + cid['aCCEPT'] == 'application/json' # True + list(cid) == ['Accept'] # True + + For example, ``headers['content-encoding']`` will return the + value of a ``'Content-Encoding'`` response header, regardless + of how the header name was originally stored. + + If the constructor, ``.update``, or equality comparison + operations are given keys that have equal ``.lower()``s, the + behavior is undefined. + + """ + def __init__(self, data=None, **kwargs): + self._store = dict() + if data is None: + data = {} + self.update(data, **kwargs) + + def __setitem__(self, key, value): + # Use the lowercased key for lookups, but store the actual + # key alongside the value. + self._store[key.lower()] = (key, value) + + def __getitem__(self, key): + return self._store[key.lower()][1] + + def __delitem__(self, key): + del self._store[key.lower()] + + def __iter__(self): + return (casedkey for casedkey, mappedvalue in self._store.values()) + + def __len__(self): + return len(self._store) + + def lower_items(self): + """Like iteritems(), but with all lowercase keys.""" + return ( + (lowerkey, keyval[1]) + for (lowerkey, keyval) + in self._store.items() + ) + + def __eq__(self, other): + if isinstance(other, collections.Mapping): + other = CaseInsensitiveDict(other) + else: + return NotImplemented + # Compare insensitively + return dict(self.lower_items()) == dict(other.lower_items()) + + # Copy is required + def copy(self): + return CaseInsensitiveDict(self._store.values()) + + def __repr__(self): + return str(dict(self.items())) + +class LookupDict(dict): + """Dictionary lookup object.""" + + def __init__(self, name=None): + self.name = name + super(LookupDict, self).__init__() + + def __repr__(self): + return '<lookup \'%s\'>' % (self.name) + + def __getitem__(self, key): + # We allow fall-through here, so values default to None + + return self.__dict__.get(key, None) + + def get(self, key, default=None): + return self.__dict__.get(key, default) diff --git a/resources/lib/libraries/requests/utils.py b/resources/lib/libraries/requests/utils.py new file mode 100644 index 00000000..c5c3fd01 --- /dev/null +++ b/resources/lib/libraries/requests/utils.py @@ -0,0 +1,721 @@ +# -*- coding: utf-8 -*- + +""" +requests.utils +~~~~~~~~~~~~~~ + +This module provides utility functions that are used within Requests +that are also useful for external consumption. + +""" + +import cgi +import codecs +import collections +import io +import os +import platform +import re +import sys +import socket +import struct +import warnings + +from . import __version__ +from . import certs +from .compat import parse_http_list as _parse_list_header +from .compat import (quote, urlparse, bytes, str, OrderedDict, unquote, is_py2, + builtin_str, getproxies, proxy_bypass, urlunparse, + basestring) +from .cookies import RequestsCookieJar, cookiejar_from_dict +from .structures import CaseInsensitiveDict +from .exceptions import InvalidURL, FileModeWarning + +_hush_pyflakes = (RequestsCookieJar,) + +NETRC_FILES = ('.netrc', '_netrc') + +DEFAULT_CA_BUNDLE_PATH = certs.where() + + +def dict_to_sequence(d): + """Returns an internal sequence dictionary update.""" + + if hasattr(d, 'items'): + d = d.items() + + return d + + +def super_len(o): + total_length = 0 + current_position = 0 + + if hasattr(o, '__len__'): + total_length = len(o) + + elif hasattr(o, 'len'): + total_length = o.len + + elif hasattr(o, 'getvalue'): + # e.g. BytesIO, cStringIO.StringIO + total_length = len(o.getvalue()) + + elif hasattr(o, 'fileno'): + try: + fileno = o.fileno() + except io.UnsupportedOperation: + pass + else: + total_length = os.fstat(fileno).st_size + + # Having used fstat to determine the file length, we need to + # confirm that this file was opened up in binary mode. + if 'b' not in o.mode: + warnings.warn(( + "Requests has determined the content-length for this " + "request using the binary size of the file: however, the " + "file has been opened in text mode (i.e. without the 'b' " + "flag in the mode). This may lead to an incorrect " + "content-length. In Requests 3.0, support will be removed " + "for files in text mode."), + FileModeWarning + ) + + if hasattr(o, 'tell'): + current_position = o.tell() + + return max(0, total_length - current_position) + + +def get_netrc_auth(url, raise_errors=False): + """Returns the Requests tuple auth for a given url from netrc.""" + + try: + from netrc import netrc, NetrcParseError + + netrc_path = None + + for f in NETRC_FILES: + try: + loc = os.path.expanduser('~/{0}'.format(f)) + except KeyError: + # os.path.expanduser can fail when $HOME is undefined and + # getpwuid fails. See http://bugs.python.org/issue20164 & + # https://github.com/kennethreitz/requests/issues/1846 + return + + if os.path.exists(loc): + netrc_path = loc + break + + # Abort early if there isn't one. + if netrc_path is None: + return + + ri = urlparse(url) + + # Strip port numbers from netloc. This weird `if...encode`` dance is + # used for Python 3.2, which doesn't support unicode literals. + splitstr = b':' + if isinstance(url, str): + splitstr = splitstr.decode('ascii') + host = ri.netloc.split(splitstr)[0] + + try: + _netrc = netrc(netrc_path).authenticators(host) + if _netrc: + # Return with login / password + login_i = (0 if _netrc[0] else 1) + return (_netrc[login_i], _netrc[2]) + except (NetrcParseError, IOError): + # If there was a parsing error or a permissions issue reading the file, + # we'll just skip netrc auth unless explicitly asked to raise errors. + if raise_errors: + raise + + # AppEngine hackiness. + except (ImportError, AttributeError): + pass + + +def guess_filename(obj): + """Tries to guess the filename of the given object.""" + name = getattr(obj, 'name', None) + if (name and isinstance(name, basestring) and name[0] != '<' and + name[-1] != '>'): + return os.path.basename(name) + + +def from_key_val_list(value): + """Take an object and test to see if it can be represented as a + dictionary. Unless it can not be represented as such, return an + OrderedDict, e.g., + + :: + + >>> from_key_val_list([('key', 'val')]) + OrderedDict([('key', 'val')]) + >>> from_key_val_list('string') + ValueError: need more than 1 value to unpack + >>> from_key_val_list({'key': 'val'}) + OrderedDict([('key', 'val')]) + """ + if value is None: + return None + + if isinstance(value, (str, bytes, bool, int)): + raise ValueError('cannot encode objects that are not 2-tuples') + + return OrderedDict(value) + + +def to_key_val_list(value): + """Take an object and test to see if it can be represented as a + dictionary. If it can be, return a list of tuples, e.g., + + :: + + >>> to_key_val_list([('key', 'val')]) + [('key', 'val')] + >>> to_key_val_list({'key': 'val'}) + [('key', 'val')] + >>> to_key_val_list('string') + ValueError: cannot encode objects that are not 2-tuples. + """ + if value is None: + return None + + if isinstance(value, (str, bytes, bool, int)): + raise ValueError('cannot encode objects that are not 2-tuples') + + if isinstance(value, collections.Mapping): + value = value.items() + + return list(value) + + +# From mitsuhiko/werkzeug (used with permission). +def parse_list_header(value): + """Parse lists as described by RFC 2068 Section 2. + + In particular, parse comma-separated lists where the elements of + the list may include quoted-strings. A quoted-string could + contain a comma. A non-quoted string could have quotes in the + middle. Quotes are removed automatically after parsing. + + It basically works like :func:`parse_set_header` just that items + may appear multiple times and case sensitivity is preserved. + + The return value is a standard :class:`list`: + + >>> parse_list_header('token, "quoted value"') + ['token', 'quoted value'] + + To create a header from the :class:`list` again, use the + :func:`dump_header` function. + + :param value: a string with a list header. + :return: :class:`list` + """ + result = [] + for item in _parse_list_header(value): + if item[:1] == item[-1:] == '"': + item = unquote_header_value(item[1:-1]) + result.append(item) + return result + + +# From mitsuhiko/werkzeug (used with permission). +def parse_dict_header(value): + """Parse lists of key, value pairs as described by RFC 2068 Section 2 and + convert them into a python dict: + + >>> d = parse_dict_header('foo="is a fish", bar="as well"') + >>> type(d) is dict + True + >>> sorted(d.items()) + [('bar', 'as well'), ('foo', 'is a fish')] + + If there is no value for a key it will be `None`: + + >>> parse_dict_header('key_without_value') + {'key_without_value': None} + + To create a header from the :class:`dict` again, use the + :func:`dump_header` function. + + :param value: a string with a dict header. + :return: :class:`dict` + """ + result = {} + for item in _parse_list_header(value): + if '=' not in item: + result[item] = None + continue + name, value = item.split('=', 1) + if value[:1] == value[-1:] == '"': + value = unquote_header_value(value[1:-1]) + result[name] = value + return result + + +# From mitsuhiko/werkzeug (used with permission). +def unquote_header_value(value, is_filename=False): + r"""Unquotes a header value. (Reversal of :func:`quote_header_value`). + This does not use the real unquoting but what browsers are actually + using for quoting. + + :param value: the header value to unquote. + """ + if value and value[0] == value[-1] == '"': + # this is not the real unquoting, but fixing this so that the + # RFC is met will result in bugs with internet explorer and + # probably some other browsers as well. IE for example is + # uploading files with "C:\foo\bar.txt" as filename + value = value[1:-1] + + # if this is a filename and the starting characters look like + # a UNC path, then just return the value without quotes. Using the + # replace sequence below on a UNC path has the effect of turning + # the leading double slash into a single slash and then + # _fix_ie_filename() doesn't work correctly. See #458. + if not is_filename or value[:2] != '\\\\': + return value.replace('\\\\', '\\').replace('\\"', '"') + return value + + +def dict_from_cookiejar(cj): + """Returns a key/value dictionary from a CookieJar. + + :param cj: CookieJar object to extract cookies from. + """ + + cookie_dict = {} + + for cookie in cj: + cookie_dict[cookie.name] = cookie.value + + return cookie_dict + + +def add_dict_to_cookiejar(cj, cookie_dict): + """Returns a CookieJar from a key/value dictionary. + + :param cj: CookieJar to insert cookies into. + :param cookie_dict: Dict of key/values to insert into CookieJar. + """ + + cj2 = cookiejar_from_dict(cookie_dict) + cj.update(cj2) + return cj + + +def get_encodings_from_content(content): + """Returns encodings from given content string. + + :param content: bytestring to extract encodings from. + """ + warnings.warn(( + 'In requests 3.0, get_encodings_from_content will be removed. For ' + 'more information, please see the discussion on issue #2266. (This' + ' warning should only appear once.)'), + DeprecationWarning) + + charset_re = re.compile(r'<meta.*?charset=["\']*(.+?)["\'>]', flags=re.I) + pragma_re = re.compile(r'<meta.*?content=["\']*;?charset=(.+?)["\'>]', flags=re.I) + xml_re = re.compile(r'^<\?xml.*?encoding=["\']*(.+?)["\'>]') + + return (charset_re.findall(content) + + pragma_re.findall(content) + + xml_re.findall(content)) + + +def get_encoding_from_headers(headers): + """Returns encodings from given HTTP Header Dict. + + :param headers: dictionary to extract encoding from. + """ + + content_type = headers.get('content-type') + + if not content_type: + return None + + content_type, params = cgi.parse_header(content_type) + + if 'charset' in params: + return params['charset'].strip("'\"") + + if 'text' in content_type: + return 'ISO-8859-1' + + +def stream_decode_response_unicode(iterator, r): + """Stream decodes a iterator.""" + + if r.encoding is None: + for item in iterator: + yield item + return + + decoder = codecs.getincrementaldecoder(r.encoding)(errors='replace') + for chunk in iterator: + rv = decoder.decode(chunk) + if rv: + yield rv + rv = decoder.decode(b'', final=True) + if rv: + yield rv + + +def iter_slices(string, slice_length): + """Iterate over slices of a string.""" + pos = 0 + while pos < len(string): + yield string[pos:pos + slice_length] + pos += slice_length + + +def get_unicode_from_response(r): + """Returns the requested content back in unicode. + + :param r: Response object to get unicode content from. + + Tried: + + 1. charset from content-type + 2. fall back and replace all unicode characters + + """ + warnings.warn(( + 'In requests 3.0, get_unicode_from_response will be removed. For ' + 'more information, please see the discussion on issue #2266. (This' + ' warning should only appear once.)'), + DeprecationWarning) + + tried_encodings = [] + + # Try charset from content-type + encoding = get_encoding_from_headers(r.headers) + + if encoding: + try: + return str(r.content, encoding) + except UnicodeError: + tried_encodings.append(encoding) + + # Fall back: + try: + return str(r.content, encoding, errors='replace') + except TypeError: + return r.content + + +# The unreserved URI characters (RFC 3986) +UNRESERVED_SET = frozenset( + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + + "0123456789-._~") + + +def unquote_unreserved(uri): + """Un-escape any percent-escape sequences in a URI that are unreserved + characters. This leaves all reserved, illegal and non-ASCII bytes encoded. + """ + parts = uri.split('%') + for i in range(1, len(parts)): + h = parts[i][0:2] + if len(h) == 2 and h.isalnum(): + try: + c = chr(int(h, 16)) + except ValueError: + raise InvalidURL("Invalid percent-escape sequence: '%s'" % h) + + if c in UNRESERVED_SET: + parts[i] = c + parts[i][2:] + else: + parts[i] = '%' + parts[i] + else: + parts[i] = '%' + parts[i] + return ''.join(parts) + + +def requote_uri(uri): + """Re-quote the given URI. + + This function passes the given URI through an unquote/quote cycle to + ensure that it is fully and consistently quoted. + """ + safe_with_percent = "!#$%&'()*+,/:;=?@[]~" + safe_without_percent = "!#$&'()*+,/:;=?@[]~" + try: + # Unquote only the unreserved characters + # Then quote only illegal characters (do not quote reserved, + # unreserved, or '%') + return quote(unquote_unreserved(uri), safe=safe_with_percent) + except InvalidURL: + # We couldn't unquote the given URI, so let's try quoting it, but + # there may be unquoted '%'s in the URI. We need to make sure they're + # properly quoted so they do not cause issues elsewhere. + return quote(uri, safe=safe_without_percent) + + +def address_in_network(ip, net): + """ + This function allows you to check if on IP belongs to a network subnet + Example: returns True if ip = 192.168.1.1 and net = 192.168.1.0/24 + returns False if ip = 192.168.1.1 and net = 192.168.100.0/24 + """ + ipaddr = struct.unpack('=L', socket.inet_aton(ip))[0] + netaddr, bits = net.split('/') + netmask = struct.unpack('=L', socket.inet_aton(dotted_netmask(int(bits))))[0] + network = struct.unpack('=L', socket.inet_aton(netaddr))[0] & netmask + return (ipaddr & netmask) == (network & netmask) + + +def dotted_netmask(mask): + """ + Converts mask from /xx format to xxx.xxx.xxx.xxx + Example: if mask is 24 function returns 255.255.255.0 + """ + bits = 0xffffffff ^ (1 << 32 - mask) - 1 + return socket.inet_ntoa(struct.pack('>I', bits)) + + +def is_ipv4_address(string_ip): + try: + socket.inet_aton(string_ip) + except socket.error: + return False + return True + + +def is_valid_cidr(string_network): + """Very simple check of the cidr format in no_proxy variable""" + if string_network.count('/') == 1: + try: + mask = int(string_network.split('/')[1]) + except ValueError: + return False + + if mask < 1 or mask > 32: + return False + + try: + socket.inet_aton(string_network.split('/')[0]) + except socket.error: + return False + else: + return False + return True + + +def should_bypass_proxies(url): + """ + Returns whether we should bypass proxies or not. + """ + get_proxy = lambda k: os.environ.get(k) or os.environ.get(k.upper()) + + # First check whether no_proxy is defined. If it is, check that the URL + # we're getting isn't in the no_proxy list. + no_proxy = get_proxy('no_proxy') + netloc = urlparse(url).netloc + + if no_proxy: + # We need to check whether we match here. We need to see if we match + # the end of the netloc, both with and without the port. + no_proxy = ( + host for host in no_proxy.replace(' ', '').split(',') if host + ) + + ip = netloc.split(':')[0] + if is_ipv4_address(ip): + for proxy_ip in no_proxy: + if is_valid_cidr(proxy_ip): + if address_in_network(ip, proxy_ip): + return True + else: + for host in no_proxy: + if netloc.endswith(host) or netloc.split(':')[0].endswith(host): + # The URL does match something in no_proxy, so we don't want + # to apply the proxies on this URL. + return True + + # If the system proxy settings indicate that this URL should be bypassed, + # don't proxy. + # The proxy_bypass function is incredibly buggy on OS X in early versions + # of Python 2.6, so allow this call to fail. Only catch the specific + # exceptions we've seen, though: this call failing in other ways can reveal + # legitimate problems. + try: + bypass = proxy_bypass(netloc) + except (TypeError, socket.gaierror): + bypass = False + + if bypass: + return True + + return False + +def get_environ_proxies(url): + """Return a dict of environment proxies.""" + if should_bypass_proxies(url): + return {} + else: + return getproxies() + +def select_proxy(url, proxies): + """Select a proxy for the url, if applicable. + + :param url: The url being for the request + :param proxies: A dictionary of schemes or schemes and hosts to proxy URLs + """ + proxies = proxies or {} + urlparts = urlparse(url) + proxy = proxies.get(urlparts.scheme+'://'+urlparts.hostname) + if proxy is None: + proxy = proxies.get(urlparts.scheme) + return proxy + +def default_user_agent(name="python-requests"): + """Return a string representing the default user agent.""" + return '%s/%s' % (name, __version__) + + +def default_headers(): + return CaseInsensitiveDict({ + 'User-Agent': default_user_agent(), + 'Accept-Encoding': ', '.join(('gzip', 'deflate')), + 'Accept': '*/*', + 'Connection': 'keep-alive', + }) + + +def parse_header_links(value): + """Return a dict of parsed link headers proxies. + + i.e. Link: <http:/.../front.jpeg>; rel=front; type="image/jpeg",<http://.../back.jpeg>; rel=back;type="image/jpeg" + + """ + + links = [] + + replace_chars = " '\"" + + for val in re.split(", *<", value): + try: + url, params = val.split(";", 1) + except ValueError: + url, params = val, '' + + link = {} + + link["url"] = url.strip("<> '\"") + + for param in params.split(";"): + try: + key, value = param.split("=") + except ValueError: + break + + link[key.strip(replace_chars)] = value.strip(replace_chars) + + links.append(link) + + return links + + +# Null bytes; no need to recreate these on each call to guess_json_utf +_null = '\x00'.encode('ascii') # encoding to ASCII for Python 3 +_null2 = _null * 2 +_null3 = _null * 3 + + +def guess_json_utf(data): + # JSON always starts with two ASCII characters, so detection is as + # easy as counting the nulls and from their location and count + # determine the encoding. Also detect a BOM, if present. + sample = data[:4] + if sample in (codecs.BOM_UTF32_LE, codecs.BOM32_BE): + return 'utf-32' # BOM included + if sample[:3] == codecs.BOM_UTF8: + return 'utf-8-sig' # BOM included, MS style (discouraged) + if sample[:2] in (codecs.BOM_UTF16_LE, codecs.BOM_UTF16_BE): + return 'utf-16' # BOM included + nullcount = sample.count(_null) + if nullcount == 0: + return 'utf-8' + if nullcount == 2: + if sample[::2] == _null2: # 1st and 3rd are null + return 'utf-16-be' + if sample[1::2] == _null2: # 2nd and 4th are null + return 'utf-16-le' + # Did not detect 2 valid UTF-16 ascii-range characters + if nullcount == 3: + if sample[:3] == _null3: + return 'utf-32-be' + if sample[1:] == _null3: + return 'utf-32-le' + # Did not detect a valid UTF-32 ascii-range character + return None + + +def prepend_scheme_if_needed(url, new_scheme): + '''Given a URL that may or may not have a scheme, prepend the given scheme. + Does not replace a present scheme with the one provided as an argument.''' + scheme, netloc, path, params, query, fragment = urlparse(url, new_scheme) + + # urlparse is a finicky beast, and sometimes decides that there isn't a + # netloc present. Assume that it's being over-cautious, and switch netloc + # and path if urlparse decided there was no netloc. + if not netloc: + netloc, path = path, netloc + + return urlunparse((scheme, netloc, path, params, query, fragment)) + + +def get_auth_from_url(url): + """Given a url with authentication components, extract them into a tuple of + username,password.""" + parsed = urlparse(url) + + try: + auth = (unquote(parsed.username), unquote(parsed.password)) + except (AttributeError, TypeError): + auth = ('', '') + + return auth + + +def to_native_string(string, encoding='ascii'): + """ + Given a string object, regardless of type, returns a representation of that + string in the native string type, encoding and decoding where necessary. + This assumes ASCII unless told otherwise. + """ + out = None + + if isinstance(string, builtin_str): + out = string + else: + if is_py2: + out = string.encode(encoding) + else: + out = string.decode(encoding) + + return out + + +def urldefragauth(url): + """ + Given a url remove the fragment and the authentication part + """ + scheme, netloc, path, params, query, fragment = urlparse(url) + + # see func:`prepend_scheme_if_needed` + if not netloc: + netloc, path = path, netloc + + netloc = netloc.rsplit('@', 1)[-1] + + return urlunparse((scheme, netloc, path, params, query, '')) diff --git a/resources/lib/library.py b/resources/lib/library.py new file mode 100644 index 00000000..34cde7ff --- /dev/null +++ b/resources/lib/library.py @@ -0,0 +1,628 @@ +# -*- coding: utf-8 -*- + +################################################################################################## + +import logging +import Queue +import threading +import sys +from datetime import datetime, timedelta + +import xbmc + +from objects import Movies, TVShows, MusicVideos, Music +from database import Database, emby_db, get_sync, save_sync +from full_sync import FullSync +from views import Views +from downloader import GetItemWorker +from helper import _, stop, settings, dialog, event, progress, LibraryException +from emby import Emby + +################################################################################################## + +LOG = logging.getLogger("EMBY."+__name__) +MEDIA = { + 'Movie': Movies, + 'BoxSet': Movies, + 'MusicVideo': MusicVideos, + 'Series': TVShows, + 'Season': TVShows, + 'Episode': TVShows, + 'MusicAlbum': Music, + 'MusicArtist': Music, + 'AlbumArtist': Music, + 'Audio': Music +} + +################################################################################################## + + + +class Library(threading.Thread): + + started = False + stop_thread = False + suspend = False + pending_refresh = False + + + def __init__(self, monitor): + + self.direct_path = settings('useDirectPaths') == "1" + self.monitor = monitor + self.server = Emby() + self.updated_queue = Queue.Queue() + self.userdata_queue = Queue.Queue() + self.removed_queue = Queue.Queue() + self.updated_output = self.__new_queues__() + self.userdata_output = self.__new_queues__() + self.removed_output = self.__new_queues__() + + self.emby_threads = [] + self.download_threads = [] + self.writer_threads = {'updated': [], 'userdata': [], 'removed': []} + self.database_lock = threading.Lock() + self.music_database_lock = threading.Lock() + + threading.Thread.__init__(self) + + def __new_queues__(self): + return { + 'Movie': Queue.Queue(), + 'BoxSet': Queue.Queue(), + 'MusicVideo': Queue.Queue(), + 'Series': Queue.Queue(), + 'Season': Queue.Queue(), + 'Episode': Queue.Queue(), + 'MusicAlbum': Queue.Queue(), + 'MusicArtist': Queue.Queue(), + 'AlbumArtist': Queue.Queue(), + 'Audio': Queue.Queue() + } + + def run(self): + + LOG.warn("--->[ library ]") + + if not self.startup(): + self.stop_client() + + while not self.stop_thread: + + try: + self.service() + except LibraryException as error: + break + except Exception as error: + LOG.exception(error) + + break + + if self.monitor.waitForAbort(2): + break + + LOG.warn("---<[ library ]") + + @stop() + def service(self): + + ''' If error is encountered, it will rerun this function. + Start new "daemon threads" to process library updates. + (actual daemon thread is not supported in Kodi) + ''' + active_queues = [] + + for threads in (self.download_threads, self.writer_threads['updated'], + self.writer_threads['userdata'], self.writer_threads['removed']): + for thread in threads: + if thread.is_done: + threads.remove(thread) + + for queue in ((self.updated_queue, self.updated_output), (self.userdata_queue, self.userdata_output)): + if queue[0].qsize() and len(self.download_threads) < 5: + + new_thread = GetItemWorker(self.server, queue[0], queue[1]) + new_thread.start() + LOG.info("-->[ q:download/%s ]", id(new_thread)) + + if self.removed_queue.qsize() and len(self.emby_threads) < 2: + + new_thread = SortWorker(self.removed_queue, self.removed_output) + new_thread.start() + LOG.info("-->[ q:sort/%s ]", id(new_thread)) + + for queues in self.updated_output: + queue = self.updated_output[queues] + + if queue.qsize() and len(self.writer_threads['updated']) < 4: + + if queues in ('Audio', 'MusicArtist', 'AlbumArtist', 'MusicAlbum'): + new_thread = UpdatedWorker(queue, self.music_database_lock, "music", self.server, self.direct_path) + else: + new_thread = UpdatedWorker(queue, self.database_lock, "video", self.server, self.direct_path) + + new_thread.start() + LOG.info("-->[ q:updated/%s/%s ]", queues, id(new_thread)) + self.writer_threads['updated'].append(new_thread) + self.pending_refresh = True + + for queues in self.userdata_output: + queue = self.userdata_output[queues] + + if queue.qsize() and len(self.writer_threads['userdata']) < 4: + + if queues in ('Audio', 'MusicArtist', 'AlbumArtist', 'MusicAlbum'): + new_thread = UserDataWorker(queue, self.music_database_lock, "music", self.server, self.direct_path) + else: + new_thread = UserDataWorker(queue, self.database_lock, "video", self.server, self.direct_path) + + new_thread.start() + LOG.info("-->[ q:userdata/%s/%s ]", queues, id(new_thread)) + self.writer_threads['userdata'].append(new_thread) + self.pending_refresh = True + + for queues in self.removed_output: + queue = self.removed_output[queues] + + if queue.qsize() and len(self.writer_threads['removed']) < 2: + + if queues in ('Audio', 'MusicArtist', 'AlbumArtist', 'MusicAlbum'): + new_thread = RemovedWorker(queue, self.music_database_lock, "music", self.server, self.direct_path) + else: + new_thread = RemovedWorker(queue, self.database_lock, "video", self.server, self.direct_path) + + new_thread.start() + LOG.info("-->[ q:removed/%s/%s ]", queues, id(new_thread)) + self.writer_threads['removed'].append(new_thread) + self.pending_refresh = True + + if (self.pending_refresh and not self.download_threads and not self.writer_threads['updated'] and + not self.writer_threads['userdata'] and not self.writer_threads['removed']): + self.pending_refresh = False + self.save_last_sync() + + if xbmc.getCondVisibility('Window.IsActive(home)'): + xbmc.executebuiltin('UpdateLibrary(video)') + else: + xbmc.executebuiltin('Container.Refresh') + + def stop_client(self): + self.stop_thread = True + + def startup(self): + + ''' Run at startup. Will check for the server plugin. + ''' + fast_sync = False + Views().get_views() + Views().get_nodes() + + try: + if settings('kodiCompanion.bool'): + for plugin in self.server['api'].get_plugins(): + if plugin['Name'] in ("Emby.Kodi Sync Queue", "Kodi companion"): + fast_sync = True + + break + else: + raise LibraryException('CompanionMissing') + + if settings('SyncInstallRunDone.bool'): + if fast_sync and not self.fast_sync(): + dialog("ok", heading="{emby}", line1=_(33128)) + + raise Exception("Failed to retrieve latest updates") + else: + FullSync(self) + + self.started = True + return True + + except LibraryException as error: + LOG.error(error.status) + + if error.status in 'SyncLibraryLater': + dialog("ok", heading="{emby}", line1=_(33129)) + settings('SyncInstallRunDone.bool', True) + + return True + + elif error.status == 'CompanionMissing': + + dialog("ok", heading="{emby}", line1=_(33099)) + settings('SyncInstallRunDone.bool', True) + + return True + + except Exception as error: + LOG.exception(error) + + return False + + def fast_sync(self): + + ''' Movie and userdata not provided by server yet. + ''' + last_sync = settings('LastIncrementalSync') + filters = ["tvshows", "boxsets", "musicvideos", "music", "movies"] + sync = get_sync() + LOG.info("--[ retrieve changes ] %s", last_sync) + + """ + for library in sync['Whitelist']: + + data = self.server['api'].get_date_modified(last_sync, library.replace('Mixed:', ""), "Series,Episode,BoxSet,Movie,MusicVideo,MusicArtist,MusicAlbum,Audio") + [self.updated_output[query['Type']].put(query) for query in data['Items']] + """ + try: + for media in filters: + result = self.server['api'].get_sync_queue(last_sync, ",".join([x for x in filters if x != media])) + self.updated(result['ItemsAdded']) + self.updated(result['ItemsUpdated']) + self.userdata(result['UserDataChanged']) + self.removed(result['ItemsRemoved']) + + """ + result = self.server['api'].get_sync_queue(last_sync) + self.userdata(result['UserDataChanged']) + self.removed(result['ItemsRemoved']) + + + filters.extend(["tvshows", "boxsets", "musicvideos", "music"]) + + # Get only movies. + result = self.server['api'].get_sync_queue(last_sync, ",".join(filters)) + self.updated(result['ItemsAdded']) + self.updated(result['ItemsUpdated']) + self.userdata(result['UserDataChanged']) + self.removed(result['ItemsRemoved']) + """ + + except Exception as error: + LOG.exception(error) + + return False + + return True + + def save_last_sync(self): + + time_now = datetime.utcnow() - timedelta(minutes=2) + last_sync = time_now.strftime('%Y-%m-%dT%H:%M:%Sz') + settings('LastIncrementalSync', value=last_sync) + LOG.info("--[ sync/%s ]", last_sync) + + def select_libraries(self, mode=None): + + ''' Select from libraries synced. Either update or repair libraries. + Send event back to service.py + ''' + mode = mode or 'SyncLibrary' + sync = get_sync() + libraries = [] + + with Database('emby') as embydb: + db = emby_db.EmbyDatabase(embydb.cursor) + + for library in sync['Whitelist']: + + name = db.get_view_name(library.replace('Mixed:', "")) + libraries.append({'Id': library, 'Name': name}) + + choices = [x['Name'] for x in libraries] + choices.insert(0, _(33121)) + selection = dialog("multi", _(33120), choices) + + if selection is None: + return + + if 0 in selection: + selection = list(range(1, len(libraries) + 1)) + + selected_libraries = [] + + for x in selection: + + library = libraries[x - 1] + selected_libraries.append(library['Id']) + + event(mode, {'Id': ','.join([libraries[x - 1]['Id'] for x in selection])}) + + def add_library(self, library_id): + + try: + FullSync(self, library_id) + except Exception as error: + LOG.exception(error) + + return False + + Views().get_nodes() + + return True + + @progress(_(33144)) + def remove_library(self, library_id, dialog): + + try: + with Database('emby') as embydb: + + db = emby_db.EmbyDatabase(embydb.cursor) + library = db.get_view(library_id) + items = db.get_item_by_media_folder(library_id) + media = 'music' if library[1] == 'music' else 'video' + + if items: + with self.music_database_lock if media == 'music' else self.database_lock: + with Database(media) as kodidb: + obj = MEDIA[items[0][1]](self.server, embydb, kodidb, self.direct_path)['Remove'] + + for item in items: + obj(item[0]) + + sync = get_sync() + + if library_id in sync['Whitelist']: + sync['Whitelist'].remove(library_id) + elif 'Mixed:%s' % library_id in sync['Whitelist']: + sync['Whitelist'].remove('Mixed:%s' % library_id) + + save_sync(sync) + Views().remove_library(library_id) + except Exception as error: + + LOG.exception(error) + dialog.close() + + return False + + Views().get_nodes() + + return True + + + def userdata(self, data): + + ''' Add item_id to userdata queue. + ''' + if not data: + return + + for item in data: + + if item in list(self.userdata_queue.queue): + continue + + self.userdata_queue.put(item['ItemId']) + + LOG.info("---[ userdata:%s ]", self.userdata_queue.qsize()) + + def updated(self, data): + + ''' Add item_id to updated queue. + ''' + if not data: + return + + for item in data: + + if item in list(self.updated_queue.queue): + continue + + self.updated_queue.put(item) + + LOG.info("---[ updated:%s ]", self.updated_queue.qsize()) + + def removed(self, data): + + ''' Add item_id to removed queue. + ''' + if not data: + return + + for item in data: + + if item in list(self.removed_queue.queue): + continue + + self.removed_queue.put(item) + + LOG.info("---[ removed:%s ]", self.removed_queue.qsize()) + + +class UpdatedWorker(threading.Thread): + + is_done = False + + def __init__(self, queue, lock, database, *args): + + self.queue = queue + self.lock = lock + self.database = Database(database) + self.args = args + threading.Thread.__init__(self) + + def run(self): + + with self.lock: + with self.database as kodidb: + with Database('emby') as embydb: + + while True: + + try: + item = self.queue.get(timeout=3) + except Queue.Empty: + + LOG.info("--<[ q:updated/%s ]", id(self)) + self.is_done = True + + break + + obj = MEDIA[item['Type']](self.args[0], embydb, kodidb, self.args[1])[item['Type']] + + try: + obj(item) + self.queue.task_done() + except LibraryException as error: + if error.status == 'StopCalled': + break + except Exception as error: + LOG.exception(error) + + if xbmc.Monitor().abortRequested(): + break + +class UserDataWorker(threading.Thread): + + is_done = False + + def __init__(self, queue, lock, database, *args): + + self.queue = queue + self.lock = lock + self.database = Database(database) + self.args = args + threading.Thread.__init__(self) + + def run(self): + + with self.lock: + with self.database as kodidb: + with Database('emby') as embydb: + + while True: + + try: + item = self.queue.get(timeout=3) + except Queue.Empty: + + LOG.info("--<[ q:userdata/%s ]", id(self)) + self.is_done = True + + break + + obj = MEDIA[item['Type']](self.args[0], embydb, kodidb, self.args[1])['UserData'] + + try: + obj(item) + self.queue.task_done() + except LibraryException as error: + if error.status == 'StopCalled': + break + except Exception as error: + LOG.exception(error) + + if xbmc.Monitor().abortRequested(): + break + +class SortWorker(threading.Thread): + + is_done = False + + def __init__(self, queue, output, *args): + + self.queue = queue + self.output = output + self.args = args + threading.Thread.__init__(self) + + def run(self): + + with Database('emby') as embydb: + database = emby_db.EmbyDatabase(embydb.cursor) + + while True: + + try: + item_id = self.queue.get(timeout=1) + except Queue.Empty: + + self.is_done = True + LOG.info("--<[ q:sort/%s ]", id(self)) + + return + + media = database.get_media_by_id(item_id) + + if media: + self.output[media].put({'Id': item_id, 'Type': media}) + else: + LOG.info("Could not find media %s in the emby database.", item_id) + + self.queue.task_done() + + if xbmc.Monitor().abortRequested(): + break + +class RemovedWorker(threading.Thread): + + is_done = False + + def __init__(self, queue, lock, database, *args): + + self.queue = queue + self.lock = lock + self.database = Database(database) + self.args = args + threading.Thread.__init__(self) + + def run(self): + + with self.lock: + with self.database as kodidb: + with Database('emby') as embydb: + + while True: + + try: + item = self.queue.get(timeout=3) + except Queue.Empty: + + LOG.info("--<[ q:removed/%s ]", id(self)) + self.is_done = True + + break + + obj = MEDIA[item['Type']](self.args[0], embydb, kodidb, self.args[1])['Remove'] + + try: + obj(item['Id']) + self.queue.task_done() + except LibraryException as error: + if error.status == 'StopCalled': + break + except Exception as error: + LOG.exception(error) + + if xbmc.Monitor().abortRequested(): + break + +class NotifyWorker(threading.Thread): + + is_done = False + + def __init__(self, queue): + self.queue = queue + + def run(self): + + while True: + + try: + item = self.queue.get(timeout=3) + except Queue.Empty: + + LOG.info("--<[ q:notify/%s ]", id(self)) + self.is_done = True + + break + + self.queue.task_done() + + if xbmc.Monitor().abortRequested(): + break + + if not self.pdialog and self.content_msg and self.new_time and (not xbmc.Player().isPlayingVideo() or xbmc.getCondVisibility('VideoPlayer.Content(livetv)')): + dialog("notification", heading="{emby}", message="%s %s" % (lang(33049), name), + icon="{emby}", time=self.new_time, sound=False) diff --git a/resources/lib/monitor.py b/resources/lib/monitor.py new file mode 100644 index 00000000..485fa33f --- /dev/null +++ b/resources/lib/monitor.py @@ -0,0 +1,399 @@ +# -*- coding: utf-8 -*- + +################################################################################################# + +import json +import logging +import threading +import sys + +import xbmc +import xbmcgui + +import connect +import downloader +import player +from client import get_device_id +from objects import Actions, PlaylistWorker, on_play, on_update, special_listener +from helper import _, settings, window, dialog, event, api, JSONRPC +from emby import Emby + +################################################################################################# + +LOG = logging.getLogger("EMBY."+__name__) + +################################################################################################# + + +class Monitor(xbmc.Monitor): + + servers = [] + + def __init__(self): + + self.player = player.Player() + self.device_id = get_device_id() + self.listener = Listener(self) + self.listener.start() + xbmc.Monitor.__init__(self) + + def onScanStarted(self, library): + LOG.info("-->[ kodi scan/%s ]", library) + + def onScanFinished(self, library): + LOG.info("--<[ kodi scan/%s ]", library) + + def onNotification(self, sender, method, data): + + if sender.lower() not in ('plugin.video.emby', 'xbmc'): + return + + if sender == 'plugin.video.emby': + method = method.split('.')[1] + + if method not in ('GetItem', 'ReportProgressRequested', 'ServerOnline', 'LoadServer', + 'GetServerAddress', 'GetPlaybackInfo', 'Browse', 'GetImages', 'GetToken', + 'PlayPlaylist', 'Play', 'GetIntros', 'GetAdditionalParts', 'RefreshItem', + 'FavoriteItem', 'DeleteItem', 'AddUser', 'GetSession', 'GetUsers', 'GetThemes', + 'GetTheme', 'Playstate', 'GeneralCommand'): + return + + data = json.loads(data)[0] + else: + if method not in ('Player.OnPlay', 'VideoLibrary.OnUpdate', 'System.OnSleep'): + return + + data = json.loads(data) + + if method != 'LoadServer' and data.get('ServerId') and data['ServerId'] not in self.servers: + + try: + connect.Connect().register(data['ServerId']) + self.server_instance(data['ServerId']) + except Exception as error: + + LOG.error(error) + dialog("ok", heading="{emby}", line1=_(33142)) + + return + + server = Emby(data.get('ServerId')) + + if method == 'GetItem': + + item = server['api'].get_item(data['Id']) + window('emby_%s.json' % data['VoidName'], item) + LOG.debug("--->[ beacon/emby_%s.json ] sent", data['VoidName']) + + elif method == 'GetAdditionalParts': + + item = server['api'].get_additional_parts(data['Id']) + window('emby_%s.json' % data['VoidName'], item) + LOG.debug("--->[ beacon/emby_%s.json ] sent", data['VoidName']) + + elif method == 'GetIntros': + + item = server['api'].get_intros(data['Id']) + window('emby_%s.json' % data['VoidName'], item) + LOG.debug("--->[ beacon/emby_%s.json ] sent", data['VoidName']) + + elif method == 'GetImages': + + item = server['api'].get_images(data['Id']) + window('emby_%s.json' % data['VoidName'], item) + LOG.debug("--->[ beacon/emby_%s.json ] sent", data['VoidName']) + + elif method == 'GetServerAddress': + + server_address = server['auth/server-address'] + window('emby_%s.json' % data['VoidName'], server_address) + LOG.debug("--->[ beacon/emby_%s.json ] sent", data['VoidName']) + + elif method == 'GetPlaybackInfo': + + sources = server['api'].get_play_info(data['Id'], data['Profile']) + window('emby_%s.json' % data['VoidName'], sources) + LOG.debug("--->[ beacon/emby_%s.json ] sent", data['VoidName']) + + elif method == 'GetLiveStream': + + sources = server['api'].get_play_info(data['Id'], data['PlaySessionId'], data['Token'], data['Profile']) + window('emby_%s.json' % data['VoidName'], sources) + LOG.debug("--->[ beacon/emby_%s.json ] sent", data['VoidName']) + + elif method == 'GetToken': + + token = server['auth/token'] + window('emby_%s.json' % data['VoidName'], token) + LOG.debug("--->[ beacon/emby_%s.json ] sent", data['VoidName']) + + elif method == 'GetSession': + + session = server['api'].get_device(self.device_id) + window('emby_%s.json' % data['VoidName'], session) + LOG.debug("--->[ beacon/emby_%s.json ] sent", data['VoidName']) + + elif method == 'GetUsers': + + users = server['api'].get_users(data.get('IsDisabled', True), data.get('IsHidden', True)) + window('emby_%s.json' % data['VoidName'], users) + LOG.debug("--->[ beacon/emby_%s.json ] sent", data['VoidName']) + + elif method == 'GetThemes': + + if data['Type'] == 'Video': + theme = server['api'].get_items_theme_video(data['Id']) + else: + theme = server['api'].get_items_theme_song(data['Id']) + + window('emby_%s.json' % data['VoidName'], theme) + LOG.debug("--->[ beacon/emby_%s.json ] sent", data['VoidName']) + + elif method == 'GetTheme': + + theme = server['api'].get_themes(data['Id']) + window('emby_%s.json' % data['VoidName'], theme) + LOG.debug("--->[ beacon/emby_%s.json ] sent", data['VoidName']) + + elif method == 'Browse': + + result = downloader.get_filtered_section(data.get('Id'), data.get('Media'), data.get('Limit'), + data.get('Recursive'), data.get('Sort'), data.get('SortOrder'), + data.get('Filters'), data.get('ServerId')) + window('emby_%s.json' % data['VoidName'], result) + LOG.debug("--->[ beacon/emby_%s.json ] sent", data['VoidName']) + + + elif method == 'RefreshItem': + server['api'].refresh_item(data['Id']) + + elif method == 'FavoriteItem': + server['api'].favorite(data['Id'], data['Favorite']) + + elif method == 'DeleteItem': + server['api'].delete_item(data['Id']) + + elif method == 'PlayPlaylist': + + server['api'].post_session(server['config/app.session'], "Playing", { + 'PlayCommand': "PlayNow", + 'ItemIds': data['Id'], + 'StartPositionTicks': 0 + }) + + elif method == 'Play': + + items = server['api'].get_items(data['ItemIds']) + PlaylistWorker(data.get('ServerId'), items['Items'], data['PlayCommand'] == 'PlayNow', + data.get('StartPositionTicks', 0), data.get('AudioStreamIndex'), + data.get('SubtitleStreamIndex')).start() + + elif method == 'ReportProgressRequested': + self.player.report_playback() + + elif method == 'Playstate': + self.playstate(data) + + elif method == 'GeneralCommand': + self.general_commands(data) + + elif method == 'LoadServer': + self.server_instance(data['ServerId']) + + elif method == 'AddUser': + server['api'].session_add_user(server['config/app.session'], data['Id'], data['Add']) + self.additional_users(server) + + elif method == 'System.OnSleep': + self.servers = [] + + elif method == 'Player.OnPlay': + on_play(data, server) + + elif method == 'VideoLibrary.OnUpdate': + on_update(data, server) + + def server_instance(self, server_id=None): + + server = Emby(server_id) + self.post_capabilities(server) + + if server_id is not None: + self.servers.append(server_id) + elif settings('additionalUsers'): + + users = settings('additionalUsers').split(',') + all_users = server['api'].get_users() + + for additional in users: + for user in all_users: + + if user['Name'].lower() in additional.decode('utf-8').lower(): + server['api'].session_add_user(server['config/app.session'], user['Id'], True) + + self.additional_users(server) + + def post_capabilities(self, server): + + server['api'].post_capabilities({ + 'PlayableMediaTypes': "Audio,Video", + 'SupportsMediaControl': True, + 'SupportedCommands': ( + "MoveUp,MoveDown,MoveLeft,MoveRight,Select," + "Back,ToggleContextMenu,ToggleFullscreen,ToggleOsdMenu," + "GoHome,PageUp,NextLetter,GoToSearch," + "GoToSettings,PageDown,PreviousLetter,TakeScreenshot," + "VolumeUp,VolumeDown,ToggleMute,SendString,DisplayMessage," + "SetAudioStreamIndex,SetSubtitleStreamIndex," + "SetRepeatMode," + "Mute,Unmute,SetVolume," + "Play,Playstate,PlayNext,PlayMediaSource" + ), + 'IconUrl': "https://raw.githubusercontent.com/MediaBrowser/plugin.video.emby/develop/kodi_icon.png", + }) + + session = server['api'].get_device(self.device_id) + server['config']['app.session'] = session[0]['Id'] + + def additional_users(self, server): + + ''' Setup additional users images. + ''' + for i in range(10): + window('EmbyAdditionalUserImage.%s' % i, clear=True) + + try: + session = server['api'].get_device(self.device_id) + except Exception as error: + LOG.error(error) + + return + + for index, user in enumerate(session[0]['AdditionalUsers']): + + info = server['api'].get_user(user['UserId']) + image = api.API(info, server['config/auth.server']).get_user_artwork(user['UserId']) + window('EmbyAdditionalUserImage.%s' % index, image) + window('EmbyAdditionalUserPosition.%s' % user['UserId'], str(index)) + + def playstate(self, data): + + ''' Emby playstate updates. + ''' + command = data['Command'] + actions = { + 'Stop': self.player.stop, + 'Unpause': self.player.pause, + 'Pause': self.player.pause, + 'PlayPause': self.player.pause, + 'NextTrack': self.player.playnext, + 'PreviousTrack': self.player.playprevious + } + if command == 'Seek': + + if self.player.isPlaying(): + + seektime = data['SeekPositionTicks'] / 10000000.0 + self.player.seekTime(seektime) + LOG.info("[ seek/%s ]", seektime) + + elif command in actions: + + actions[command]() + LOG.info("[ command/%s ]", command) + + def general_commands(self, data): + LOG.info(data) + command = data['Name'] + args = data['Arguments'] + + if command in ('Mute', 'Unmute', 'SetVolume', + 'SetSubtitleStreamIndex', 'SetAudioStreamIndex', 'SetRepeatMode'): + + if command == 'Mute': + xbmc.executebuiltin('Mute') + elif command == 'Unmute': + xbmc.executebuiltin('Mute') + elif command == 'SetVolume': + xbmc.executebuiltin('SetVolume(%s[,showvolumebar])' % args['Volume']) + elif command == 'SetRepeatMode': + xbmc.executebuiltin('xbmc.PlayerControl(%s)' % args['RepeatMode']) + elif command == 'SetAudioStreamIndex': + self.player.set_audio_subs(args['Index']) + elif command == 'SetSubtitleStreamIndex': + self.player.set_audio_subs(None, args['Index']) + + self.player.report_playback() + + elif command == 'DisplayMessage': + dialog("notification", heading=args['Header'], message=args['Text'], + icon="{emby}", time=int(settings('displayMessage'))*1000) + + elif command == 'SendString': + JSONRPC('Input.SendText').execute({'text': args['String'], 'done': False}) + + elif command == 'GoHome': + JSONRPC('GUI.ActivateWindow').execute({'window': "home"}) + + elif command == 'Guide': + JSONRPC('GUI.ActivateWindow').execute({'window': "tvguide"}) + + elif command in ('MoveUp', 'MoveDown', 'MoveRight', 'MoveLeft'): + actions = { + 'MoveUp': "Input.Up", + 'MoveDown': "Input.Down", + 'MoveRight': "Input.Right", + 'MoveLeft': "Input.Left" + } + JSONRPC(actions[command]).execute() + + else: + builtin = { + + 'ToggleFullscreen': 'Action(FullScreen)', + 'ToggleOsdMenu': 'Action(OSD)', + 'ToggleContextMenu': 'Action(ContextMenu)', + 'Select': 'Action(Select)', + 'Back': 'Action(back)', + 'PageUp': 'Action(PageUp)', + 'NextLetter': 'Action(NextLetter)', + 'GoToSearch': 'VideoLibrary.Search', + 'GoToSettings': 'ActivateWindow(Settings)', + 'PageDown': 'Action(PageDown)', + 'PreviousLetter': 'Action(PrevLetter)', + 'TakeScreenshot': 'TakeScreenshot', + 'ToggleMute': 'Mute', + 'VolumeUp': 'Action(VolumeUp)', + 'VolumeDown': 'Action(VolumeDown)', + } + if command in builtin: + xbmc.executebuiltin(builtin[command]) + + +class Listener(threading.Thread): + + stop_thread = False + + def __init__(self, monitor): + self.monitor = monitor + + threading.Thread.__init__(self) + + def run(self): + + ''' Detect the resume dialog for widgets. + Detect external players. + ''' + LOG.warn("--->[ listener ]") + + while not self.stop_thread: + special_listener() + + if self.monitor.waitForAbort(0.5): + # Abort was requested while waiting. We should exit + break + + LOG.warn("---<[ listener ]") + + def stop(self): + self.stop_thread = True diff --git a/resources/lib/musicutils.py b/resources/lib/musicutils.py deleted file mode 100644 index 2a02638d..00000000 --- a/resources/lib/musicutils.py +++ /dev/null @@ -1,289 +0,0 @@ -# -*- coding: utf-8 -*- - -################################################################################################# - -import logging -import os - -import xbmc -import xbmcaddon -import xbmcvfs - -from mutagen.flac import FLAC, Picture -from mutagen.id3 import ID3 -from mutagen import id3 -import base64 - -import read_embyserver as embyserver -from utils import window - -################################################################################################# - -log = logging.getLogger("EMBY."+__name__) - -################################################################################################# - -# Helper for the music library, intended to fix missing song ID3 tags on Emby - -def getRealFileName(filename, isTemp=False): - #get the filename path accessible by python if possible... - - if not xbmcvfs.exists(filename): - log.warn("File does not exist! %s" % filename) - return (False, "") - - #if we use os.path method on older python versions (sunch as some android builds), we need to pass arguments as string - if os.path.supports_unicode_filenames: - checkfile = filename - else: - checkfile = filename.encode("utf-8") - - # determine if our python module is able to access the file directly... - if os.path.exists(checkfile): - filename = filename - elif os.path.exists(checkfile.replace("smb://","\\\\").replace("/","\\")): - filename = filename.replace("smb://","\\\\").replace("/","\\") - else: - #file can not be accessed by python directly, we copy it for processing... - isTemp = True - if "/" in filename: filepart = filename.split("/")[-1] - else: filepart = filename.split("\\")[-1] - tempfile = "special://temp/"+filepart - xbmcvfs.copy(filename, tempfile) - filename = xbmc.translatePath(tempfile).decode("utf-8") - - return (isTemp,filename) - -def getEmbyRatingFromKodiRating(rating): - # Translation needed between Kodi/ID3 rating and emby likes/favourites: - # 3+ rating in ID3 = emby like - # 5+ rating in ID3 = emby favourite - # rating 0 = emby dislike - # rating 1-2 = emby no likes or dislikes (returns 1 in results) - favourite = False - deletelike = False - like = False - if (rating >= 3): like = True - if (rating == 0): like = False - if (rating == 1 or rating == 2): deletelike = True - if (rating >= 5): favourite = True - return(like, favourite, deletelike) - -def getAdditionalSongTags(embyid, emby_rating, API, kodicursor, emby_db, enableimportsongrating, enableexportsongrating, enableupdatesongrating): - - emby = embyserver.Read_EmbyServer() - - previous_values = None - filename = API.get_file_path() - rating = 0 - emby_rating = int(round(emby_rating, 0)) - - #get file rating and comment tag from file itself. - if enableimportsongrating: - file_rating, comment, hasEmbeddedCover = getSongTags(filename) - else: - file_rating = 0 - comment = "" - hasEmbeddedCover = False - - - emby_dbitem = emby_db.getItem_byId(embyid) - try: - kodiid = emby_dbitem[0] - except TypeError: - # Item is not in database. - currentvalue = None - else: - query = "SELECT rating FROM song WHERE idSong = ?" - kodicursor.execute(query, (kodiid,)) - try: - currentvalue = int(round(float(kodicursor.fetchone()[0]),0)) - except: currentvalue = None - - # Only proceed if we actually have a rating from the file - if file_rating is None and currentvalue: - return (currentvalue, comment, False) - elif file_rating is None and not currentvalue: - return (emby_rating, comment, False) - - log.info("getAdditionalSongTags --> embyid: %s - emby_rating: %s - file_rating: %s - current rating in kodidb: %s" %(embyid, emby_rating, file_rating, currentvalue)) - - updateFileRating = False - updateEmbyRating = False - - if currentvalue != None: - # we need to translate the emby values... - if emby_rating == 1 and currentvalue == 2: - emby_rating = 2 - if emby_rating == 3 and currentvalue == 4: - emby_rating = 4 - - #if updating rating into file is disabled, we ignore the rating in the file... - if not enableupdatesongrating: - file_rating = currentvalue - #if convert emby likes/favourites convert to song rating is disabled, we ignore the emby rating... - if not enableexportsongrating: - emby_rating = currentvalue - - if (emby_rating == file_rating) and (file_rating != currentvalue): - #the rating has been updated from kodi itself, update change to both emby ands file - rating = currentvalue - updateFileRating = True - updateEmbyRating = True - elif (emby_rating != currentvalue) and (file_rating == currentvalue): - #emby rating changed - update the file - rating = emby_rating - updateFileRating = True - elif (file_rating != currentvalue) and (emby_rating == currentvalue): - #file rating was updated, sync change to emby - rating = file_rating - updateEmbyRating = True - elif (emby_rating != currentvalue) and (file_rating != currentvalue): - #both ratings have changed (corner case) - the highest rating wins... - if emby_rating > file_rating: - rating = emby_rating - updateFileRating = True - else: - rating = file_rating - updateEmbyRating = True - else: - #nothing has changed, just return the current value - rating = currentvalue - else: - # no rating yet in DB - if enableimportsongrating: - #prefer the file rating - rating = file_rating - #determine if we should also send the rating to emby server - if enableexportsongrating: - if emby_rating == 1 and file_rating == 2: - emby_rating = 2 - if emby_rating == 3 and file_rating == 4: - emby_rating = 4 - if emby_rating != file_rating: - updateEmbyRating = True - - elif enableexportsongrating: - #set the initial rating to emby value - rating = emby_rating - - if updateFileRating and enableupdatesongrating: - updateRatingToFile(rating, filename) - - if updateEmbyRating and enableexportsongrating: - # sync details to emby server. Translation needed between ID3 rating and emby likes/favourites: - like, favourite, deletelike = getEmbyRatingFromKodiRating(rating) - window("ignore-update-%s" %embyid, "true") #set temp windows prop to ignore the update from webclient update - emby.updateUserRating(embyid, favourite) - - return (rating, comment, hasEmbeddedCover) - -def getSongTags(file): - # Get the actual ID3 tags for music songs as the server is lacking that info - rating = 0 - comment = "" - hasEmbeddedCover = False - - isTemp,filename = getRealFileName(file) - log.info( "getting song ID3 tags for " + filename) - - try: - ###### FLAC FILES ############# - if filename.lower().endswith(".flac"): - audio = FLAC(filename) - if audio.get("comment"): - comment = audio.get("comment")[0] - for pic in audio.pictures: - if pic.type == 3 and pic.data: - #the file has an embedded cover - hasEmbeddedCover = True - break - if audio.get("rating"): - rating = float(audio.get("rating")[0]) - #flac rating is 0-100 and needs to be converted to 0-5 range - if rating > 5: rating = (rating / 100) * 5 - - ###### MP3 FILES ############# - elif filename.lower().endswith(".mp3"): - audio = ID3(filename) - - if audio.get("APIC:Front Cover"): - if audio.get("APIC:Front Cover").data: - hasEmbeddedCover = True - - if audio.get("comment"): - comment = audio.get("comment")[0] - if audio.get("POPM:Windows Media Player 9 Series"): - if audio.get("POPM:Windows Media Player 9 Series").rating: - rating = float(audio.get("POPM:Windows Media Player 9 Series").rating) - #POPM rating is 0-255 and needs to be converted to 0-5 range - if rating > 5: rating = (rating / 255) * 5 - else: - log.info( "Not supported fileformat or unable to access file: %s" %(filename)) - - #the rating must be a round value - rating = int(round(rating,0)) - - except Exception as e: - #file in use ? - log.error("Exception in getSongTags %s" % e) - rating = None - - #remove tempfile if needed.... - if isTemp: xbmcvfs.delete(filename) - - return (rating, comment, hasEmbeddedCover) - -def updateRatingToFile(rating, file): - #update the rating from Emby to the file - - f = xbmcvfs.File(file) - org_size = f.size() - f.close() - - #create tempfile - if "/" in file: filepart = file.split("/")[-1] - else: filepart = file.split("\\")[-1] - tempfile = "special://temp/"+filepart - xbmcvfs.copy(file, tempfile) - tempfile = xbmc.translatePath(tempfile).decode("utf-8") - - log.info( "setting song rating: %s for filename: %s - using tempfile: %s" %(rating,file,tempfile)) - - if not tempfile: - return - - try: - if tempfile.lower().endswith(".flac"): - audio = FLAC(tempfile) - calcrating = int(round((float(rating) / 5) * 100, 0)) - audio["rating"] = str(calcrating) - audio.save() - elif tempfile.lower().endswith(".mp3"): - audio = ID3(tempfile) - calcrating = int(round((float(rating) / 5) * 255, 0)) - audio.add(id3.POPM(email="Windows Media Player 9 Series", rating=calcrating, count=1)) - audio.save() - else: - log.info( "Not supported fileformat: %s" %(tempfile)) - - #once we have succesfully written the flags we move the temp file to destination, otherwise not proceeding and just delete the temp - #safety check: we check the file size of the temp file before proceeding with overwite of original file - f = xbmcvfs.File(tempfile) - checksum_size = f.size() - f.close() - if checksum_size >= org_size: - xbmcvfs.delete(file) - xbmcvfs.copy(tempfile,file) - else: - log.info( "Checksum mismatch for filename: %s - using tempfile: %s - not proceeding with file overwite!" %(rating,file,tempfile)) - - #always delete the tempfile - xbmcvfs.delete(tempfile) - - except Exception as e: - #file in use ? - log.error("Exception in updateRatingToFile %s" % e) - - - \ No newline at end of file diff --git a/resources/lib/objects/__init__.py b/resources/lib/objects/__init__.py index 5219542a..0da06fa7 100644 --- a/resources/lib/objects/__init__.py +++ b/resources/lib/objects/__init__.py @@ -1,5 +1,12 @@ -# Dummy file to make this directory a package. +version = "1710760" + from movies import Movies from musicvideos import MusicVideos from tvshows import TVShows from music import Music +from obj import Objects +from actions import Actions +from actions import PlaylistWorker +from actions import on_play, on_update, special_listener + +Objects().mapping() diff --git a/resources/lib/objects/_common.py b/resources/lib/objects/_common.py deleted file mode 100644 index 122aa88b..00000000 --- a/resources/lib/objects/_common.py +++ /dev/null @@ -1,207 +0,0 @@ -# -*- coding: utf-8 -*- - -################################################################################################## - -import logging -import os -import sqlite3 - -import xbmc -import xbmcvfs - -import api -import artwork -import downloadutils -import read_embyserver as embyserver -#from ga_client import GoogleAnalytics -from utils import window, settings, dialog, language as lang, should_stop - -################################################################################################## - -log = logging.getLogger("EMBY."+__name__) -#ga = GoogleAnalytics() - -################################################################################################## - -def catch_except(errors=(Exception, ), default_value=False): -# Will wrap method with try/except and print parameters for easier debugging - def decorator(func): - def wrapper(*args, **kwargs): - try: - return func(*args, **kwargs) - except sqlite3.Error as error: - raise - except errors as error: - """ - if not (hasattr(error, 'quiet') and error.quiet): - errStrings = ga.formatException() - ga.sendEventData("Exception", errStrings[0], errStrings[1], True) - """ - log.exception(error) - log.error("function: %s \n args: %s \n kwargs: %s", - func.__name__, args, kwargs) - return default_value - - return wrapper - return decorator - - -class Items(object): - - pdialog = None - title = None - count = 0 - total = 0 - - - def __init__(self): - - self.artwork = artwork.Artwork() - self.emby = embyserver.Read_EmbyServer() - self.do_url = downloadutils.DownloadUtils().downloadUrl - self.should_stop = should_stop - - self.kodi_version = int(xbmc.getInfoLabel('System.BuildVersion')[:2]) - self.direct_path = settings('useDirectPaths') == "1" - self.content_msg = settings('newContent') == "true" - - @classmethod - def path_validation(cls, path): - # Verify if direct path is accessible or not - verify_path = path - if not os.path.supports_unicode_filenames: - verify_path = path.encode('utf-8') - - if window('emby_pathverified') != "true" and not xbmcvfs.exists(verify_path): - if dialog(type_="yesno", - heading="{emby}", - line1="%s %s. %s" % (lang(33047), path, lang(33048))): - - window('emby_shouldStop', value="true") - return False - - return True - - def content_pop(self, name): - # It's possible for the time to be 0. It should be considered disabled in this case. - if not self.pdialog and self.content_msg and self.new_time and (not xbmc.Player().isPlayingVideo() or xbmc.getCondVisibility('VideoPlayer.Content(livetv)')): - dialog(type_="notification", - heading="{emby}", - message="%s %s" % (lang(33049), name), - icon="{emby}", - time=self.new_time, - sound=False) - - def update_pdialog(self): - - if self.pdialog: - percentage = int((float(self.count) / float(self.total))*100) - self.pdialog.update(percentage, message=self.title) - - def add_all(self, item_type, items, view=None): - - if self.should_stop(): - return False - - total = items['TotalRecordCount'] if 'TotalRecordCount' in items else len(items) - items = items['Items'] if 'Items' in items else items - - if self.pdialog and view: - self.pdialog.update(heading="Processing %s / %s items" % (view['name'], total)) - - process = self._get_func(item_type, "added") - if view: - process(items, total, view) - else: - process(items, total) - - def process_all(self, item_type, action, items, total=None, view=None): - - log.debug("Processing %s: %s", action, items) - - process = self._get_func(item_type, action) - self.total = total or len(items) - - for item in items: - - if self.should_stop(): - return False - - if not process: - continue - - self.title = item.get('Name', "unknown") - self.update_pdialog() - - process(item) - self.count += 1 - - def remove_all(self, item_type, items): - - log.debug("Processing removal: %s", items) - - process = self._get_func(item_type, "remove") - for item in items: - process(item) - - def added(self, items, total=None, update=True): - # Generator for newly added content - if update: - self.total = total or len(items) - - for item in items: - - if self.should_stop(): - break - - self.title = item.get('Name', "unknown") - - self.update_pdialog() - yield item - - if update: - self.count += 1 - - def compare(self, item_type, items, compare_to, view=None): - - view_name = view['name'] if view else item_type - - update_list = self._compare_checksum(items, compare_to) - log.info("Update for %s: %s", view_name, update_list) - - if self.should_stop(): - return False - - emby_items = self.emby.getFullItems(update_list) - total = len(update_list) - - if self.pdialog: - self.pdialog.update(heading="Processing %s / %s items" % (view_name, total)) - - # Process additions and updates - if emby_items: - self.process_all(item_type, "update", emby_items, total, view) - # Process deletes - if compare_to: - self.remove_all(item_type, compare_to.keys()) - - return True - - def _compare_checksum(self, items, compare_to): - - update_list = list() - - for item in items: - - if self.should_stop(): - break - - item_id = item['Id'] - - if compare_to.get(item_id) != api.API(item).get_checksum(): - # Only update if item is not in Kodi or checksum is different - update_list.append(item_id) - - compare_to.pop(item_id, None) - - return update_list diff --git a/resources/lib/objects/_kodi_common.py b/resources/lib/objects/_kodi_common.py deleted file mode 100644 index 59ed5041..00000000 --- a/resources/lib/objects/_kodi_common.py +++ /dev/null @@ -1,813 +0,0 @@ -# -*- coding: utf-8 -*- - -################################################################################################## - -import logging - -import xbmc - -import artwork - -################################################################################################## - -log = logging.getLogger("EMBY."+__name__) - -################################################################################################## - - -class KodiItems(object): - - - def __init__(self): - - self.artwork = artwork.Artwork() - self.kodi_version = int(xbmc.getInfoLabel('System.BuildVersion')[:2]) - - def create_entry_path(self): - self.cursor.execute("select coalesce(max(idPath),0) from path") - kodi_id = self.cursor.fetchone()[0] + 1 - - return kodi_id - - def create_entry_file(self): - self.cursor.execute("select coalesce(max(idFile),0) from files") - kodi_id = self.cursor.fetchone()[0] + 1 - - return kodi_id - - def create_entry_person(self): - self.cursor.execute("select coalesce(max(actor_id),0) from actor") - kodi_id = self.cursor.fetchone()[0] + 1 - - return kodi_id - - def create_entry_genre(self): - self.cursor.execute("select coalesce(max(genre_id),0) from genre") - kodi_id = self.cursor.fetchone()[0] + 1 - - return kodi_id - - def create_entry_studio(self): - self.cursor.execute("select coalesce(max(studio_id),0) from studio") - kodi_id = self.cursor.fetchone()[0] + 1 - - return kodi_id - - def create_entry_bookmark(self): - self.cursor.execute("select coalesce(max(idBookmark),0) from bookmark") - kodi_id = self.cursor.fetchone()[0] + 1 - - return kodi_id - - def create_entry_tag(self): - self.cursor.execute("select coalesce(max(tag_id),0) from tag") - kodi_id = self.cursor.fetchone()[0] + 1 - - return kodi_id - - def add_path(self, path): - - path_id = self.get_path(path) - if path_id is None: - # Create a new entry - path_id = self.create_entry_path() - query = ( - ''' - INSERT INTO path(idPath, strPath) - - VALUES (?, ?) - ''' - ) - self.cursor.execute(query, (path_id, path)) - - return path_id - - def get_path(self, path): - - query = ' '.join(( - - "SELECT idPath", - "FROM path", - "WHERE strPath = ?" - )) - self.cursor.execute(query, (path,)) - try: - path_id = self.cursor.fetchone()[0] - except TypeError: - path_id = None - - return path_id - - def update_path(self, path_id, path, media_type, scraper): - - query = ' '.join(( - - "UPDATE path", - "SET strPath = ?, strContent = ?, strScraper = ?, noUpdate = ?", - "WHERE idPath = ?" - )) - self.cursor.execute(query, (path, media_type, scraper, 1, path_id)) - - def remove_path(self, path_id): - self.cursor.execute("DELETE FROM path WHERE idPath = ?", (path_id,)) - - def add_file(self, filename, path_id): - - query = ' '.join(( - - "SELECT idFile", - "FROM files", - "WHERE strFilename = ?", - "AND idPath = ?" - )) - self.cursor.execute(query, (filename, path_id,)) - try: - file_id = self.cursor.fetchone()[0] - except TypeError: - # Create a new entry - file_id = self.create_entry_file() - query = ( - ''' - INSERT INTO files(idFile, idPath, strFilename) - - VALUES (?, ?, ?) - ''' - ) - self.cursor.execute(query, (file_id, path_id, filename)) - - return file_id - - def update_file(self, file_id, filename, path_id, date_added): - - query = ' '.join(( - - "UPDATE files", - "SET idPath = ?, strFilename = ?, dateAdded = ?", - "WHERE idFile = ?" - )) - self.cursor.execute(query, (path_id, filename, date_added, file_id)) - - def remove_file(self, path, filename): - - path_id = self.get_path(path) - if path_id is not None: - - query = ' '.join(( - - "DELETE FROM files", - "WHERE idPath = ?", - "AND strFilename = ?" - )) - self.cursor.execute(query, (path_id, filename,)) - - def get_filename(self, file_id): - - query = ' '.join(( - - "SELECT strFilename", - "FROM files", - "WHERE idFile = ?" - )) - self.cursor.execute(query, (file_id,)) - try: - filename = self.cursor.fetchone()[0] - except TypeError: - filename = "" - - return filename - - def add_people(self, kodi_id, people, media_type): - - def add_thumbnail(person_id, person, type_): - - thumbnail = person['imageurl'] - if thumbnail: - - art = type_.lower() - if "writing" in art: - art = "writer" - - self.artwork.add_update_art(thumbnail, person_id, art, "thumb", self.cursor) - - def add_link(link_type, person_id, kodi_id, media_type): - - query = ( - "INSERT OR REPLACE INTO " + link_type + "(actor_id, media_id, media_type)" - "VALUES (?, ?, ?)" - ) - self.cursor.execute(query, (person_id, kodi_id, media_type)) - - cast_order = 1 - - if self.kodi_version > 14: - - for person in people: - - name = person['Name'] - type_ = person['Type'] - person_id = self._get_person(name) - - # Link person to content - if type_ == "Actor": - role = person.get('Role') - query = ( - ''' - INSERT OR REPLACE INTO actor_link( - actor_id, media_id, media_type, role, cast_order) - - VALUES (?, ?, ?, ?, ?) - ''' - ) - self.cursor.execute(query, (person_id, kodi_id, media_type, role, cast_order)) - cast_order += 1 - - elif type_ == "Director": - add_link("director_link", person_id, kodi_id, media_type) - - elif type_ in ("Writing", "Writer"): - add_link("writer_link", person_id, kodi_id, media_type) - - elif type_ == "Artist": - add_link("actor_link", person_id, kodi_id, media_type) - - add_thumbnail(person_id, person, type_) - else: - # TODO: Remove Helix code when Krypton is RC - for person in people: - name = person['Name'] - type_ = person['Type'] - - query = ' '.join(( - - "SELECT idActor", - "FROM actors", - "WHERE strActor = ?", - "COLLATE NOCASE" - )) - self.cursor.execute(query, (name,)) - - try: - person_id = self.cursor.fetchone()[0] - - except TypeError: - # Cast entry does not exists - self.cursor.execute("select coalesce(max(idActor),0) from actors") - person_id = self.cursor.fetchone()[0] + 1 - - query = "INSERT INTO actors(idActor, strActor) values(?, ?)" - self.cursor.execute(query, (person_id, name)) - log.debug("Add people to media, processing: %s", name) - - finally: - # Link person to content - if type_ == "Actor": - role = person.get('Role') - - if media_type == "movie": - query = ( - ''' - INSERT OR REPLACE INTO actorlinkmovie( - idActor, idMovie, strRole, iOrder) - - VALUES (?, ?, ?, ?) - ''' - ) - elif media_type == "tvshow": - query = ( - ''' - INSERT OR REPLACE INTO actorlinktvshow( - idActor, idShow, strRole, iOrder) - - VALUES (?, ?, ?, ?) - ''' - ) - elif media_type == "episode": - query = ( - ''' - INSERT OR REPLACE INTO actorlinkepisode( - idActor, idEpisode, strRole, iOrder) - - VALUES (?, ?, ?, ?) - ''' - ) - else: return # Item is invalid - - self.cursor.execute(query, (person_id, kodi_id, role, cast_order)) - cast_order += 1 - - elif type_ == "Director": - if media_type == "movie": - query = ( - ''' - INSERT OR REPLACE INTO directorlinkmovie(idDirector, idMovie) - VALUES (?, ?) - ''' - ) - elif media_type == "tvshow": - query = ( - ''' - INSERT OR REPLACE INTO directorlinktvshow(idDirector, idShow) - VALUES (?, ?) - ''' - ) - elif media_type == "musicvideo": - query = ( - ''' - INSERT OR REPLACE INTO directorlinkmusicvideo(idDirector, idMVideo) - VALUES (?, ?) - ''' - ) - elif media_type == "episode": - query = ( - ''' - INSERT OR REPLACE INTO directorlinkepisode(idDirector, idEpisode) - VALUES (?, ?) - ''' - ) - else: return # Item is invalid - - self.cursor.execute(query, (person_id, kodi_id)) - - elif type_ in ("Writing", "Writer"): - if media_type == "movie": - query = ( - ''' - INSERT OR REPLACE INTO writerlinkmovie(idWriter, idMovie) - VALUES (?, ?) - ''' - ) - elif media_type == "episode": - query = ( - ''' - INSERT OR REPLACE INTO writerlinkepisode(idWriter, idEpisode) - VALUES (?, ?) - ''' - ) - else: return # Item is invalid - - self.cursor.execute(query, (person_id, kodi_id)) - - elif type_ == "Artist": - query = ( - ''' - INSERT OR REPLACE INTO artistlinkmusicvideo(idArtist, idMVideo) - VALUES (?, ?) - ''' - ) - self.cursor.execute(query, (person_id, kodi_id)) - - add_thumbnail(person_id, person, type_) - - def _add_person(self, name): - - person_id = self.create_entry_person() - query = "INSERT INTO actor(actor_id, name) values(?, ?)" - self.cursor.execute(query, (person_id, name)) - log.debug("Add people to media, processing: %s", name) - - return person_id - - def _get_person(self, name): - - query = ' '.join(( - - "SELECT actor_id", - "FROM actor", - "WHERE name = ?", - "COLLATE NOCASE" - )) - self.cursor.execute(query, (name,)) - - try: - person_id = self.cursor.fetchone()[0] - except TypeError: - person_id = self._add_person(name) - - return person_id - - def add_genres(self, kodi_id, genres, media_type): - - if self.kodi_version > 14: - # Delete current genres for clean slate - query = ' '.join(( - - "DELETE FROM genre_link", - "WHERE media_id = ?", - "AND media_type = ?" - )) - self.cursor.execute(query, (kodi_id, media_type,)) - - # Add genres - for genre in genres: - - genre_id = self._get_genre(genre) - query = ( - ''' - INSERT OR REPLACE INTO genre_link( - genre_id, media_id, media_type) - - VALUES (?, ?, ?) - ''' - ) - self.cursor.execute(query, (genre_id, kodi_id, media_type)) - else: - # TODO: Remove Helix code when Krypton is RC - # Delete current genres for clean slate - if media_type == "movie": - self.cursor.execute("DELETE FROM genrelinkmovie WHERE idMovie = ?", (kodi_id,)) - elif media_type == "tvshow": - self.cursor.execute("DELETE FROM genrelinktvshow WHERE idShow = ?", (kodi_id,)) - elif media_type == "musicvideo": - self.cursor.execute("DELETE FROM genrelinkmusicvideo WHERE idMVideo = ?", (kodi_id,)) - - # Add genres - for genre in genres: - - query = ' '.join(( - - "SELECT idGenre", - "FROM genre", - "WHERE strGenre = ?", - "COLLATE NOCASE" - )) - self.cursor.execute(query, (genre,)) - - try: - genre_id = self.cursor.fetchone()[0] - - except TypeError: - # Create genre in database - self.cursor.execute("select coalesce(max(idGenre),0) from genre") - genre_id = self.cursor.fetchone()[0] + 1 - - query = "INSERT INTO genre(idGenre, strGenre) values(?, ?)" - self.cursor.execute(query, (genre_id, genre)) - log.debug("Add Genres to media, processing: %s", genre) - - finally: - # Assign genre to item - if media_type == "movie": - query = ( - ''' - INSERT OR REPLACE into genrelinkmovie(idGenre, idMovie) - VALUES (?, ?) - ''' - ) - elif media_type == "tvshow": - query = ( - ''' - INSERT OR REPLACE into genrelinktvshow(idGenre, idShow) - VALUES (?, ?) - ''' - ) - elif media_type == "musicvideo": - query = ( - ''' - INSERT OR REPLACE into genrelinkmusicvideo(idGenre, idMVideo) - VALUES (?, ?) - ''' - ) - else: return # Item is invalid - - self.cursor.execute(query, (genre_id, kodi_id)) - - def _add_genre(self, genre): - - genre_id = self.create_entry_genre() - query = "INSERT INTO genre(genre_id, name) values(?, ?)" - self.cursor.execute(query, (genre_id, genre)) - log.debug("Add Genres to media, processing: %s", genre) - - return genre_id - - def _get_genre(self, genre): - - query = ' '.join(( - - "SELECT genre_id", - "FROM genre", - "WHERE name = ?", - "COLLATE NOCASE" - )) - self.cursor.execute(query, (genre,)) - - try: - genre_id = self.cursor.fetchone()[0] - except TypeError: - genre_id = self._add_genre(genre) - - return genre_id - - def add_studios(self, kodi_id, studios, media_type): - - if self.kodi_version > 14: - - for studio in studios: - - studio_id = self._get_studio(studio) - query = ( - ''' - INSERT OR REPLACE INTO studio_link(studio_id, media_id, media_type) - VALUES (?, ?, ?) - ''') - self.cursor.execute(query, (studio_id, kodi_id, media_type)) - else: - # TODO: Remove Helix code when Krypton is RC - for studio in studios: - - query = ' '.join(( - - "SELECT idstudio", - "FROM studio", - "WHERE strstudio = ?", - "COLLATE NOCASE" - )) - self.cursor.execute(query, (studio,)) - try: - studio_id = self.cursor.fetchone()[0] - - except TypeError: - # Studio does not exists. - self.cursor.execute("select coalesce(max(idstudio),0) from studio") - studio_id = self.cursor.fetchone()[0] + 1 - - query = "INSERT INTO studio(idstudio, strstudio) values(?, ?)" - self.cursor.execute(query, (studio_id, studio)) - log.debug("Add Studios to media, processing: %s", studio) - - finally: # Assign studio to item - if media_type == "movie": - query = ( - ''' - INSERT OR REPLACE INTO studiolinkmovie(idstudio, idMovie) - VALUES (?, ?) - ''') - elif media_type == "musicvideo": - query = ( - ''' - INSERT OR REPLACE INTO studiolinkmusicvideo(idstudio, idMVideo) - VALUES (?, ?) - ''') - elif media_type == "tvshow": - query = ( - ''' - INSERT OR REPLACE INTO studiolinktvshow(idstudio, idShow) - VALUES (?, ?) - ''') - elif media_type == "episode": - query = ( - ''' - INSERT OR REPLACE INTO studiolinkepisode(idstudio, idEpisode) - VALUES (?, ?) - ''') - self.cursor.execute(query, (studio_id, kodi_id)) - - def _add_studio(self, studio): - - studio_id = self.create_entry_studio() - query = "INSERT INTO studio(studio_id, name) values(?, ?)" - self.cursor.execute(query, (studio_id, studio)) - log.debug("Add Studios to media, processing: %s", studio) - - return studio_id - - def _get_studio(self, studio): - - query = ' '.join(( - - "SELECT studio_id", - "FROM studio", - "WHERE name = ?", - "COLLATE NOCASE" - )) - self.cursor.execute(query, (studio,)) - try: - studio_id = self.cursor.fetchone()[0] - except TypeError: - studio_id = self._add_studio(studio) - - return studio_id - - def add_streams(self, file_id, streams, runtime): - # First remove any existing entries - self.cursor.execute("DELETE FROM streamdetails WHERE idFile = ?", (file_id,)) - if streams: - # Video details - for track in streams['video']: - query = ( - ''' - INSERT INTO streamdetails( - idFile, iStreamType, strVideoCodec, fVideoAspect, - iVideoWidth, iVideoHeight, iVideoDuration ,strStereoMode) - - VALUES (?, ?, ?, ?, ?, ?, ?, ?) - ''' - ) - self.cursor.execute(query, (file_id, 0, track['codec'], track['aspect'], - track['width'], track['height'], runtime, - track['video3DFormat'])) - # Audio details - for track in streams['audio']: - query = ( - ''' - INSERT INTO streamdetails( - idFile, iStreamType, strAudioCodec, iAudioChannels, strAudioLanguage) - - VALUES (?, ?, ?, ?, ?) - ''' - ) - self.cursor.execute(query, (file_id, 1, track['codec'], track['channels'], - track['language'])) - # Subtitles details - for track in streams['subtitle']: - query = ( - ''' - INSERT INTO streamdetails(idFile, iStreamType, strSubtitleLanguage) - VALUES (?, ?, ?) - ''' - ) - self.cursor.execute(query, (file_id, 2, track)) - - def add_playstate(self, file_id, resume, total, playcount, date_played): - - # Delete existing resume point - self.cursor.execute("DELETE FROM bookmark WHERE idFile = ?", (file_id,)) - # Set watched count - self.set_playcount(file_id, playcount, date_played) - - if resume: - bookmark_id = self.create_entry_bookmark() - query = ( - ''' - INSERT INTO bookmark( - idBookmark, idFile, timeInSeconds, totalTimeInSeconds, player, type) - - VALUES (?, ?, ?, ?, ?, ?) - ''' - ) - self.cursor.execute(query, (bookmark_id, file_id, resume, total, "DVDPlayer", 1)) - - def set_playcount(self, file_id, playcount, date_played): - - query = ' '.join(( - - "UPDATE files", - "SET playCount = ?, lastPlayed = ?", - "WHERE idFile = ?" - )) - self.cursor.execute(query, (playcount, date_played, file_id)) - - def add_tags(self, kodi_id, tags, media_type): - - if self.kodi_version > 14: - - query = ' '.join(( - - "DELETE FROM tag_link", - "WHERE media_id = ?", - "AND media_type = ?" - )) - self.cursor.execute(query, (kodi_id, media_type)) - - # Add tags - log.debug("Adding Tags: %s", tags) - for tag in tags: - tag_id = self.get_tag(kodi_id, tag, media_type) - else: - # TODO: Remove Helix code when Krypton is RC - query = ' '.join(( - - "DELETE FROM taglinks", - "WHERE idMedia = ?", - "AND media_type = ?" - )) - self.cursor.execute(query, (kodi_id, media_type)) - - # Add tags - log.debug("Adding Tags: %s", tags) - for tag in tags: - tag_id = self.get_tag_old(kodi_id, tag, media_type) - - def _add_tag(self, tag): - - tag_id = self.create_entry_tag() - query = "INSERT INTO tag(tag_id, name) values(?, ?)" - self.cursor.execute(query, (tag_id, tag)) - log.debug("Create tag_id: %s name: %s", tag_id, tag) - - return tag_id - - def get_tag(self, kodi_id, tag, media_type): - - if self.kodi_version > 14: - - query = ' '.join(( - - "SELECT tag_id", - "FROM tag", - "WHERE name = ?", - "COLLATE NOCASE" - )) - self.cursor.execute(query, (tag,)) - try: - tag_id = self.cursor.fetchone()[0] - except TypeError: - tag_id = self._add_tag(tag) - - query = ( - ''' - INSERT OR REPLACE INTO tag_link(tag_id, media_id, media_type) - VALUES (?, ?, ?) - ''' - ) - self.cursor.execute(query, (tag_id, kodi_id, media_type)) - else: - # TODO: Remove Helix code when Krypton is RC - tag_id = self.get_tag_old(kodi_id, tag, media_type) - - return tag_id - - def get_tag_old(self, kodi_id, tag, media_type): - # TODO: Remove Helix code when Krypton is RC - query = ' '.join(( - - "SELECT idTag", - "FROM tag", - "WHERE strTag = ?", - "COLLATE NOCASE" - )) - self.cursor.execute(query, (tag,)) - try: - tag_id = self.cursor.fetchone()[0] - - except TypeError: - # Create the tag - self.cursor.execute("select coalesce(max(idTag),0) from tag") - tag_id = self.cursor.fetchone()[0] + 1 - - query = "INSERT INTO tag(idTag, strTag) values(?, ?)" - self.cursor.execute(query, (tag_id, tag)) - log.debug("Create idTag: %s name: %s", tag_id, tag) - - finally: - # Assign tag to item - query = ( - ''' - INSERT OR REPLACE INTO taglinks( - idTag, idMedia, media_type) - - VALUES (?, ?, ?) - ''' - ) - self.cursor.execute(query, (tag_id, kodi_id, media_type)) - - return tag_id - - def remove_tag(self, kodi_id, tag, media_type): - - if self.kodi_version > 14: - - query = ' '.join(( - - "SELECT tag_id", - "FROM tag", - "WHERE name = ?", - "COLLATE NOCASE" - )) - self.cursor.execute(query, (tag,)) - try: - tag_id = self.cursor.fetchone()[0] - except TypeError: - return - else: - query = ' '.join(( - - "DELETE FROM tag_link", - "WHERE media_id = ?", - "AND media_type = ?", - "AND tag_id = ?" - )) - self.cursor.execute(query, (kodi_id, media_type, tag_id,)) - else: - # TODO: Remove Helix code when Krypton is RC - query = ' '.join(( - - "SELECT idTag", - "FROM tag", - "WHERE strTag = ?", - "COLLATE NOCASE" - )) - self.cursor.execute(query, (tag,)) - try: - tag_id = self.cursor.fetchone()[0] - except TypeError: - return - else: - query = ' '.join(( - - "DELETE FROM taglinks", - "WHERE idMedia = ?", - "AND media_type = ?", - "AND idTag = ?" - )) - self.cursor.execute(query, (kodi_id, media_type, tag_id,)) diff --git a/resources/lib/objects/_kodi_movies.py b/resources/lib/objects/_kodi_movies.py deleted file mode 100644 index 5e55787f..00000000 --- a/resources/lib/objects/_kodi_movies.py +++ /dev/null @@ -1,246 +0,0 @@ -# -*- coding: utf-8 -*- - -################################################################################################## - -import logging - -from _kodi_common import KodiItems - -################################################################################################## - -log = logging.getLogger("EMBY."+__name__) - -################################################################################################## - - -class KodiMovies(KodiItems): - - - def __init__(self, cursor): - self.cursor = cursor - - KodiItems.__init__(self) - - def create_entry_uniqueid(self): - self.cursor.execute("select coalesce(max(uniqueid_id),0) from uniqueid") - kodi_id = self.cursor.fetchone()[0] + 1 - - return kodi_id - - def create_entry_rating(self): - self.cursor.execute("select coalesce(max(rating_id),0) from rating") - kodi_id = self.cursor.fetchone()[0] + 1 - - return kodi_id - - def create_entry(self): - self.cursor.execute("select coalesce(max(idMovie),0) from movie") - kodi_id = self.cursor.fetchone()[0] + 1 - - return kodi_id - - def create_entry_set(self): - self.cursor.execute("select coalesce(max(idSet),0) from sets") - kodi_id = self.cursor.fetchone()[0] + 1 - - return kodi_id - - def create_entry_country(self): - self.cursor.execute("select coalesce(max(country_id),0) from country") - kodi_id = self.cursor.fetchone()[0] + 1 - - return kodi_id - - def get_movie(self, kodi_id): - - query = "SELECT * FROM movie WHERE idMovie = ?" - self.cursor.execute(query, (kodi_id,)) - try: - kodi_id = self.cursor.fetchone()[0] - except TypeError: - kodi_id = None - - return kodi_id - - def add_movie(self, *args): - # Create the movie entry - query = ( - ''' - INSERT INTO movie( - idMovie, idFile, c00, c01, c02, c03, c04, c05, c06, c07, - c09, c10, c11, c12, c14, c15, c16, c18, c19, c21, premiered) - - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - ''' - ) - self.cursor.execute(query, (args)) - - def update_movie(self, *args): - query = ' '.join(( - - "UPDATE movie", - "SET c00 = ?, c01 = ?, c02 = ?, c03 = ?, c04 = ?, c05 = ?, c06 = ?,", - "c07 = ?, c09 = ?, c10 = ?, c11 = ?, c12 = ?, c14 = ?, c15 = ?,", - "c16 = ?, c18 = ?, c19 = ?, c21 = ?, premiered = ?", - "WHERE idMovie = ?" - )) - self.cursor.execute(query, (args)) - - def remove_movie(self, kodi_id, file_id): - self.cursor.execute("DELETE FROM movie WHERE idMovie = ?", (kodi_id,)) - self.cursor.execute("DELETE FROM files WHERE idFile = ?", (file_id,)) - - def get_ratingid(self, media_id): - - query = "SELECT rating_id FROM rating WHERE media_type = ? AND media_id = ?" - self.cursor.execute(query, ("movie", media_id,)) - try: - ratingid = self.cursor.fetchone()[0] - except TypeError: - ratingid = None - - return ratingid - - def add_ratings(self, *args): - query = ( - ''' - INSERT INTO rating( - rating_id, media_id, media_type, rating_type, rating, votes) - - VALUES (?, ?, ?, ?, ?, ?) - ''' - ) - self.cursor.execute(query, (args)) - - def update_ratings(self, *args): - query = ' '.join(( - - "UPDATE rating", - "SET media_id = ?, media_type = ?, rating_type = ?, rating = ?, votes = ?", - "WHERE rating_id = ?" - )) - self.cursor.execute(query, (args)) - - def get_uniqueid(self, media_id): - - query = "SELECT uniqueid_id FROM uniqueid WHERE media_type = ? AND media_id = ?" - self.cursor.execute(query, ("movie", media_id,)) - try: - uniqueid = self.cursor.fetchone()[0] - except TypeError: - uniqueid = None - - return uniqueid - - def add_uniqueid(self, *args): - query = ( - ''' - INSERT INTO uniqueid( - uniqueid_id, media_id, media_type, value, type) - - VALUES (?, ?, ?, ?, ?) - ''' - ) - self.cursor.execute(query, (args)) - - def update_uniqueid(self, *args): - query = ' '.join(( - - "UPDATE uniqueid", - "SET media_id = ?, media_type = ?, value = ?, type = ?", - "WHERE uniqueid_id = ?" - )) - self.cursor.execute(query, (args)) - - def add_countries(self, kodi_id, countries): - - for country in countries: - country_id = self._get_country(country) - - query = ( - ''' - INSERT OR REPLACE INTO country_link(country_id, media_id, media_type) - VALUES (?, ?, ?) - ''' - ) - self.cursor.execute(query, (country_id, kodi_id, "movie")) - - def _add_country(self, country): - - country_id = self.create_entry_country() - query = "INSERT INTO country(country_id, name) values(?, ?)" - self.cursor.execute(query, (country_id, country)) - log.debug("Add country to media, processing: %s", country) - - return country_id - - def _get_country(self, country): - query = ' '.join(( - - "SELECT country_id", - "FROM country", - "WHERE name = ?", - "COLLATE NOCASE" - )) - self.cursor.execute(query, (country,)) - try: - country_id = self.cursor.fetchone()[0] - except TypeError: - country_id = self._add_country(country) - - return country_id - - def add_boxset(self, boxset): - query = ' '.join(( - - "SELECT idSet", - "FROM sets", - "WHERE strSet = ?", - "COLLATE NOCASE" - )) - self.cursor.execute(query, (boxset,)) - try: - set_id = self.cursor.fetchone()[0] - except TypeError: - set_id = self._add_boxset(boxset) - - return set_id - - def _add_boxset(self, boxset): - - set_id = self.create_entry_set() - query = "INSERT INTO sets(idSet, strSet) values(?, ?)" - self.cursor.execute(query, (set_id, boxset)) - log.debug("Adding boxset: %s", boxset) - - return set_id - - def update_boxset(self, set_id, boxset): - query = ' '.join(( - - "UPDATE sets", - "SET strSet = ?", - "WHERE idSet = ?" - )) - self.cursor.execute(query, (boxset, set_id,)) - - def set_boxset(self, set_id, movie_id): - query = ' '.join(( - - "UPDATE movie", - "SET idSet = ?", - "WHERE idMovie = ?" - )) - self.cursor.execute(query, (set_id, movie_id,)) - - def remove_from_boxset(self, movie_id): - query = ' '.join(( - - "UPDATE movie", - "SET idSet = null", - "WHERE idMovie = ?" - )) - self.cursor.execute(query, (movie_id,)) - - def remove_boxset(self, kodi_id): - self.cursor.execute("DELETE FROM sets WHERE idSet = ?", (kodi_id,)) diff --git a/resources/lib/objects/_kodi_music.py b/resources/lib/objects/_kodi_music.py deleted file mode 100644 index f3196a13..00000000 --- a/resources/lib/objects/_kodi_music.py +++ /dev/null @@ -1,455 +0,0 @@ -# -*- coding: utf-8 -*- - -################################################################################################## - -import logging - -from _kodi_common import KodiItems - -################################################################################################## - -log = logging.getLogger("EMBY."+__name__) - -################################################################################################## - - -class KodiMusic(KodiItems): - - - def __init__(self, cursor): - self.cursor = cursor - - KodiItems.__init__(self) - - def create_entry(self): - # Krypton has a dummy first entry - # idArtist: 1 strArtist: [Missing Tag] strMusicBrainzArtistID: Artist Tag Missing - self.cursor.execute("select coalesce(max(idArtist),1) from artist") - kodi_id = self.cursor.fetchone()[0] + 1 - - return kodi_id - - def create_entry_album(self): - self.cursor.execute("select coalesce(max(idAlbum),0) from album") - kodi_id = self.cursor.fetchone()[0] + 1 - - return kodi_id - - def create_entry_song(self): - self.cursor.execute("select coalesce(max(idSong),0) from song") - kodi_id = self.cursor.fetchone()[0] + 1 - - return kodi_id - - def create_entry_genre(self): - self.cursor.execute("select coalesce(max(idGenre),0) from genre") - kodi_id = self.cursor.fetchone()[0] + 1 - - return kodi_id - - def update_path(self, path_id, path): - - query = "UPDATE path SET strPath = ? WHERE idPath = ?" - self.cursor.execute(query, (path, path_id)) - - def add_role(self): - query = ( - ''' - INSERT OR REPLACE INTO role(idRole, strRole) - VALUES (?, ?) - ''' - ) - self.cursor.execute(query, (1, 'Composer')) - - def get_artist(self, name, musicbrainz, artist_id=None): - - query = ' '.join(( - - "SELECT idArtist, strArtist", - "FROM artist", - "WHERE strMusicBrainzArtistID = ?" - )) - self.cursor.execute(query, (musicbrainz,)) - try: - result = self.cursor.fetchone() - artist_id = result[0] - artist_name = result[1] - except TypeError: - artist_id = self._add_artist(name, musicbrainz, artist_id) - else: - if artist_name != name: - self.update_artist_name(artist_id, name) - - return artist_id - - def _add_artist(self, name, musicbrainz, artist_id=None): - - query = ' '.join(( - # Safety check, when musicbrainz does not exist - "SELECT idArtist", - "FROM artist", - "WHERE strArtist = ?", - "COLLATE NOCASE" - )) - self.cursor.execute(query, (name,)) - try: - artist_id = self.cursor.fetchone()[0] - except TypeError: - artist_id = artist_id or self.create_entry() - query = ( - ''' - INSERT INTO artist(idArtist, strArtist, strMusicBrainzArtistID) - VALUES (?, ?, ?) - ''' - ) - self.cursor.execute(query, (artist_id, name, musicbrainz)) - - return artist_id - - def update_artist_name(self, kodi_id, name): - - query = "UPDATE artist SET strArtist = ? WHERE idArtist = ?" - self.cursor.execute(query, (name, kodi_id,)) - - def update_artist_16(self, *args): - query = ' '.join(( - - "UPDATE artist", - "SET strGenres = ?, strBiography = ?, strImage = ?, strFanart = ?,", - "lastScraped = ?", - "WHERE idArtist = ?" - )) - self.cursor.execute(query, (args)) - - def update_artist(self, *args): - query = ' '.join(( - - "UPDATE artist", - "SET strGenres = ?, strBiography = ?, strImage = ?, strFanart = ?,", - "lastScraped = ?", - "WHERE idArtist = ?" - )) - self.cursor.execute(query, (args)) - - def update_artist_18(self, *args): - query = ' '.join(( - - "UPDATE artist", - "SET strGenres = ?, strBiography = ?, strImage = ?, strFanart = ?,", - "lastScraped = ?", - "WHERE idArtist = ?" - )) - self.cursor.execute(query, (args)) - - def link_artist(self, kodi_id, album_id, name): - query = ( - ''' - INSERT OR REPLACE INTO album_artist(idArtist, idAlbum, strArtist) - VALUES (?, ?, ?) - ''' - ) - self.cursor.execute(query, (kodi_id, album_id, name)) - - def add_discography(self, kodi_id, album, year): - query = ( - ''' - INSERT OR REPLACE INTO discography(idArtist, strAlbum, strYear) - VALUES (?, ?, ?) - ''' - ) - self.cursor.execute(query, (kodi_id, album, year)) - - def validate_artist(self, kodi_id): - - query = "SELECT * FROM artist WHERE idArtist = ?" - self.cursor.execute(query, (kodi_id,)) - try: - kodi_id = self.cursor.fetchone()[0] - except TypeError: - kodi_id = None - - return kodi_id - - def validate_album(self, kodi_id): - - query = "SELECT * FROM album WHERE idAlbum = ?" - self.cursor.execute(query, (kodi_id,)) - try: - kodi_id = self.cursor.fetchone()[0] - except TypeError: - kodi_id = None - - return kodi_id - - def validate_song(self, kodi_id): - - query = "SELECT * FROM song WHERE idSong = ?" - self.cursor.execute(query, (kodi_id,)) - try: - kodi_id = self.cursor.fetchone()[0] - except TypeError: - kodi_id = None - - return kodi_id - - def get_album(self, name, musicbrainz=None, album_id=None): - - if musicbrainz is not None: - query = ' '.join(( - - "SELECT idAlbum", - "FROM album", - "WHERE strMusicBrainzAlbumID = ?" - )) - self.cursor.execute(query, (musicbrainz,)) - else: - query = ' '.join(( - - "SELECT idAlbum", - "FROM album", - "WHERE strAlbum = ?" - )) - self.cursor.execute(query, (name,)) - - try: - album_id = self.cursor.fetchone()[0] - except TypeError: - album_id = self._add_album(name, musicbrainz, album_id) - - return album_id - - def _add_album(self, name, musicbrainz, album_id=None): - - album_id = album_id or self.create_entry_album() - query = ( - ''' - INSERT INTO album(idAlbum, strAlbum, strMusicBrainzAlbumID, strReleaseType) - VALUES (?, ?, ?, ?) - ''' - ) - self.cursor.execute(query, (album_id, name, musicbrainz, "album")) - - return album_id - - def update_album(self, *args): - query = ' '.join(( - - "UPDATE album", - "SET strArtists = ?, iYear = ?, strGenres = ?, strReview = ?, strImage = ?,", - "iUserrating = ?, lastScraped = ?, strReleaseType = ?", - "WHERE idAlbum = ?" - )) - self.cursor.execute(query, (args)) - - def update_album_18(self, *args): - query = ' '.join(( - - "UPDATE album", - "SET strArtistDisp = ?, iYear = ?, strGenres = ?, strReview = ?, strImage = ?,", - "iUserrating = ?, lastScraped = ?, strReleaseType = ?", - "WHERE idAlbum = ?" - )) - self.cursor.execute(query, (args)) - - def get_album_artist(self, album_id, artists): - - query = ' '.join(( - - "SELECT strArtists", - "FROM album", - "WHERE idAlbum = ?" - )) - self.cursor.execute(query, (album_id,)) - try: - curr_artists = self.cursor.fetchone()[0] - except TypeError: - return - - if curr_artists != artists: - self._update_album_artist(album_id, artists) - - def get_album_artist_18(self, album_id, artists): - - query = ' '.join(( - - "SELECT strArtistDisp", - "FROM album", - "WHERE idAlbum = ?" - )) - self.cursor.execute(query, (album_id,)) - try: - curr_artists = self.cursor.fetchone()[0] - except TypeError: - return - - if curr_artists != artists: - self._update_album_artist_18(album_id, artists) - - def _update_album_artist(self, album_id, artists): - - query = "UPDATE album SET strArtists = ? WHERE idAlbum = ?" - self.cursor.execute(query, (artists, album_id)) - - def _update_album_artist_18(self, album_id, artists): - - query = "UPDATE album SET strArtistDisp = ? WHERE idAlbum = ?" - self.cursor.execute(query, (artists, album_id)) - - def add_single(self, *args): - query = ( - ''' - INSERT INTO album(idAlbum, strGenres, iYear, strReleaseType) - - VALUES (?, ?, ?, ?) - ''' - ) - self.cursor.execute(query, (args)) - - def add_song(self, *args): - query = ( - ''' - INSERT INTO song( - idSong, idAlbum, idPath, strArtists, strGenres, strTitle, iTrack, - iDuration, iYear, strFileName, strMusicBrainzTrackID, iTimesPlayed, lastplayed, - rating) - - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - ''' - ) - self.cursor.execute(query, (args)) - - def add_song_18(self, *args): - query = ( - ''' - INSERT INTO song( - idSong, idAlbum, idPath, strArtistDisp, strGenres, strTitle, iTrack, - iDuration, iYear, strFileName, strMusicBrainzTrackID, iTimesPlayed, lastplayed, - rating) - - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - ''' - ) - self.cursor.execute(query, (args)) - - def update_song(self, *args): - query = ' '.join(( - - "UPDATE song", - "SET idAlbum = ?, strArtists = ?, strGenres = ?, strTitle = ?, iTrack = ?,", - "iDuration = ?, iYear = ?, strFilename = ?, iTimesPlayed = ?, lastplayed = ?,", - "rating = ?, comment = ?", - "WHERE idSong = ?" - )) - self.cursor.execute(query, (args)) - - def update_song_18(self, *args): - query = ' '.join(( - - "UPDATE song", - "SET idAlbum = ?, strArtistDisp = ?, strGenres = ?, strTitle = ?, iTrack = ?,", - "iDuration = ?, iYear = ?, strFilename = ?, iTimesPlayed = ?, lastplayed = ?,", - "rating = ?, comment = ?", - "WHERE idSong = ?" - )) - self.cursor.execute(query, (args)) - - def link_song_artist(self, kodi_id, song_id, index, artist): - - if self.kodi_version > 16: - query = ( - ''' - INSERT OR REPLACE INTO song_artist(idArtist, idSong, idRole, iOrder, strArtist) - VALUES (?, ?, ?, ?, ?) - ''' - ) - self.cursor.execute(query, (kodi_id, song_id, 1, index, artist)) - else: - query = ( - ''' - INSERT OR REPLACE INTO song_artist(idArtist, idSong, iOrder, strArtist) - VALUES (?, ?, ?, ?) - ''' - ) - self.cursor.execute(query, (kodi_id, song_id, index, artist)) - - def link_song_album(self, song_id, album_id, track, title, duration): - query = ( - ''' - INSERT OR REPLACE INTO albuminfosong( - idAlbumInfoSong, idAlbumInfo, iTrack, strTitle, iDuration) - - VALUES (?, ?, ?, ?, ?) - ''' - ) - self.cursor.execute(query, (song_id, album_id, track, title, duration)) - - def rate_song(self, kodi_id, playcount, rating, date_played): - - query = "UPDATE song SET iTimesPlayed = ?, lastplayed = ?, rating = ? WHERE idSong = ?" - self.cursor.execute(query, (playcount, date_played, rating, kodi_id)) - - def add_genres(self, kodi_id, genres, media_type): - - if media_type == "album": - # Delete current genres for clean slate - query = ' '.join(( - - "DELETE FROM album_genre", - "WHERE idAlbum = ?" - )) - self.cursor.execute(query, (kodi_id,)) - - for genre in genres: - - genre_id = self.get_genre(genre) - query = "INSERT OR REPLACE INTO album_genre(idGenre, idAlbum) values(?, ?)" - self.cursor.execute(query, (genre_id, kodi_id)) - - elif media_type == "song": - # Delete current genres for clean slate - query = ' '.join(( - - "DELETE FROM song_genre", - "WHERE idSong = ?" - )) - self.cursor.execute(query, (kodi_id,)) - - for genre in genres: - - genre_id = self.get_genre(genre) - query = "INSERT OR REPLACE INTO song_genre(idGenre, idSong) values(?, ?)" - self.cursor.execute(query, (genre_id, kodi_id)) - - def get_genre(self, genre): - - query = ' '.join(( - - "SELECT idGenre", - "FROM genre", - "WHERE strGenre = ?", - "COLLATE NOCASE" - )) - self.cursor.execute(query, (genre,)) - try: - genre_id = self.cursor.fetchone()[0] - except TypeError: - genre_id = self._add_genre(genre) - - return genre_id - - def _add_genre(self, genre): - - genre_id = self.create_entry_genre() - query = "INSERT INTO genre(idGenre, strGenre) values(?, ?)" - self.cursor.execute(query, (genre_id, genre)) - - return genre_id - - def remove_artist(self, kodi_id): - self.cursor.execute("DELETE FROM artist WHERE idArtist = ?", (kodi_id,)) - - def remove_album(self, kodi_id): - self.cursor.execute("DELETE FROM album WHERE idAlbum = ?", (kodi_id,)) - - def remove_song(self, kodi_id): - self.cursor.execute("DELETE FROM song WHERE idSong = ?", (kodi_id,)) diff --git a/resources/lib/objects/_kodi_musicvideos.py b/resources/lib/objects/_kodi_musicvideos.py deleted file mode 100644 index 404669d2..00000000 --- a/resources/lib/objects/_kodi_musicvideos.py +++ /dev/null @@ -1,90 +0,0 @@ -# -*- coding: utf-8 -*- - -################################################################################################## - -import logging - -from _kodi_common import KodiItems - -################################################################################################## - -log = logging.getLogger("EMBY."+__name__) - -################################################################################################## - - -class KodiMusicVideos(KodiItems): - - - def __init__(self, cursor): - self.cursor = cursor - - KodiItems.__init__(self) - - def create_entry(self): - self.cursor.execute("select coalesce(max(idMVideo),0) from musicvideo") - kodi_id = self.cursor.fetchone()[0] + 1 - - return kodi_id - - def get_musicvideo(self, kodi_id): - - query = "SELECT * FROM musicvideo WHERE idMVideo = ?" - self.cursor.execute(query, (kodi_id,)) - try: - kodi_id = self.cursor.fetchone()[0] - except TypeError: - kodi_id = None - - return kodi_id - - def add_musicvideo(self, *args): - - query = ( - ''' - INSERT INTO musicvideo( - idMVideo, idFile, c00, c04, c05, c06, c07, c08, c09, c10, c11, c12, premiered) - - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - ''' - ) - self.cursor.execute(query, (args)) - - def add_musicvideo_16(self, *args): - - query = ( - ''' - INSERT INTO musicvideo( - idMVideo, idFile, c00, c04, c05, c06, c07, c08, c09, c10, c11, c12) - - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - ''' - ) - self.cursor.execute(query, (args)) - - - def update_musicvideo(self, *args): - - query = ' '.join(( - - "UPDATE musicvideo", - "SET c00 = ?, c04 = ?, c05 = ?, c06 = ?, c07 = ?, c08 = ?, c09 = ?, c10 = ?,", - "c11 = ?, c12 = ?, premiered = ?" - "WHERE idMVideo = ?" - )) - self.cursor.execute(query, (args)) - - def update_musicvideo_16(self, *args): - - query = ' '.join(( - - "UPDATE musicvideo", - "SET c00 = ?, c04 = ?, c05 = ?, c06 = ?, c07 = ?, c08 = ?, c09 = ?, c10 = ?,", - "c11 = ?, c12 = ?" - "WHERE idMVideo = ?" - )) - self.cursor.execute(query, (args)) - - def remove_musicvideo(self, kodi_id, file_id): - self.cursor.execute("DELETE FROM musicvideo WHERE idMVideo = ?", (kodi_id,)) - self.cursor.execute("DELETE FROM files WHERE idFile = ?", (file_id,)) diff --git a/resources/lib/objects/_kodi_tvshows.py b/resources/lib/objects/_kodi_tvshows.py deleted file mode 100644 index bfdadac9..00000000 --- a/resources/lib/objects/_kodi_tvshows.py +++ /dev/null @@ -1,222 +0,0 @@ -# -*- coding: utf-8 -*- - -################################################################################################## - -import logging - -from _kodi_common import KodiItems - -################################################################################################## - -log = logging.getLogger("EMBY."+__name__) - -################################################################################################## - - -class KodiTVShows(KodiItems): - - - def __init__(self, cursor): - self.cursor = cursor - - KodiItems.__init__(self) - - def create_entry_uniqueid(self): - self.cursor.execute("select coalesce(max(uniqueid_id),0) from uniqueid") - kodi_id = self.cursor.fetchone()[0] + 1 - - return kodi_id - - def create_entry_rating(self): - self.cursor.execute("select coalesce(max(rating_id),0) from rating") - kodi_id = self.cursor.fetchone()[0] + 1 - - return kodi_id - - - def create_entry(self): - self.cursor.execute("select coalesce(max(idShow),0) from tvshow") - kodi_id = self.cursor.fetchone()[0] + 1 - - return kodi_id - - def create_entry_season(self): - self.cursor.execute("select coalesce(max(idSeason),0) from seasons") - kodi_id = self.cursor.fetchone()[0] + 1 - - return kodi_id - - def create_entry_episode(self): - self.cursor.execute("select coalesce(max(idEpisode),0) from episode") - kodi_id = self.cursor.fetchone()[0] + 1 - - return kodi_id - - def get_tvshow(self, kodi_id): - - query = "SELECT * FROM tvshow WHERE idShow = ?" - self.cursor.execute(query, (kodi_id,)) - try: - kodi_id = self.cursor.fetchone()[0] - except TypeError: - kodi_id = None - - return kodi_id - - def get_episode(self, kodi_id): - - query = "SELECT * FROM episode WHERE idEpisode = ?" - self.cursor.execute(query, (kodi_id,)) - try: - kodi_id = self.cursor.fetchone()[0] - except TypeError: - kodi_id = None - - return kodi_id - - def get_ratingid(self, media_type, media_id): - - query = "SELECT rating_id FROM rating WHERE media_type = ? AND media_id = ?" - self.cursor.execute(query, (media_type, media_id,)) - try: - ratingid = self.cursor.fetchone()[0] - except TypeError: - ratingid = None - - return ratingid - - def add_ratings(self, *args): - query = ( - ''' - INSERT INTO rating( - rating_id, media_id, media_type, rating_type, rating, votes) - - VALUES (?, ?, ?, ?, ?, ?) - ''' - ) - self.cursor.execute(query, (args)) - - def update_ratings(self, *args): - query = ' '.join(( - - "UPDATE rating", - "SET media_id = ?, media_type = ?, rating_type = ?, rating = ?, votes = ?", - "WHERE rating_id = ?" - )) - self.cursor.execute(query, (args)) - - def get_uniqueid(self, media_type, media_id): - - query = "SELECT uniqueid_id FROM uniqueid WHERE media_type = ? AND media_id = ?" - self.cursor.execute(query, (media_type, media_id,)) - try: - uniqueid = self.cursor.fetchone()[0] - except TypeError: - uniqueid = None - - return uniqueid - - def add_uniqueid(self, *args): - query = ( - ''' - INSERT INTO uniqueid(uniqueid_id, media_id, media_type, value, type) - VALUES (?, ?, ?, ?, ?) - ''' - ) - self.cursor.execute(query, (args)) - - def update_uniqueid(self, *args): - query = ' '.join(( - - "UPDATE uniqueid", - "SET media_id = ?, media_type = ?, value = ?, type = ?", - "WHERE uniqueid_id = ?" - )) - self.cursor.execute(query, (args)) - - def add_tvshow(self, *args): - - query = ( - ''' - INSERT INTO tvshow(idShow, c00, c01, c04, c05, c08, c09, c12, c13, c14, c15) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - ''' - ) - self.cursor.execute(query, (args)) - - def update_tvshow(self, *args): - - query = ' '.join(( - - "UPDATE tvshow", - "SET c00 = ?, c01 = ?, c04 = ?, c05 = ?, c08 = ?, c09 = ?,", - "c12 = ?, c13 = ?, c14 = ?, c15 = ?", - "WHERE idShow = ?" - )) - self.cursor.execute(query, (args)) - - def link_tvshow(self, show_id, path_id): - query = "INSERT OR REPLACE INTO tvshowlinkpath(idShow, idPath) values(?, ?)" - self.cursor.execute(query, (show_id, path_id)) - - def get_season(self, show_id, number, name=None): - - query = ' '.join(( - - "SELECT idSeason", - "FROM seasons", - "WHERE idShow = ?", - "AND season = ?" - )) - self.cursor.execute(query, (show_id, number,)) - try: - season_id = self.cursor.fetchone()[0] - except TypeError: - season_id = self._add_season(show_id, number) - - if name: - - query = "UPDATE seasons SET name = ? WHERE idSeason = ?" - self.cursor.execute(query, (name, season_id,)) - - return season_id - - def _add_season(self, show_id, number): - - season_id = self.create_entry_season() - query = "INSERT INTO seasons(idSeason, idShow, season) values(?, ?, ?)" - self.cursor.execute(query, (season_id, show_id, number)) - - return season_id - - def add_episode(self, *args): - query = ( - ''' - INSERT INTO episode( - idEpisode, idFile, c00, c01, c03, c04, c05, c09, c10, c12, c13, c14, - idShow, c15, c16, idSeason) - - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - ''' - ) - self.cursor.execute(query, (args)) - - def update_episode(self, *args): - query = ' '.join(( - - "UPDATE episode", - "SET c00 = ?, c01 = ?, c03 = ?, c04 = ?, c05 = ?, c09 = ?, c10 = ?,", - "c12 = ?, c13 = ?, c14 = ?, c15 = ?, c16 = ?, idSeason = ?, idShow = ?", - "WHERE idEpisode = ?" - )) - self.cursor.execute(query, (args)) - - def remove_tvshow(self, kodi_id): - self.cursor.execute("DELETE FROM tvshow WHERE idShow = ?", (kodi_id,)) - - def remove_season(self, kodi_id): - self.cursor.execute("DELETE FROM seasons WHERE idSeason = ?", (kodi_id,)) - - def remove_episode(self, kodi_id, file_id): - self.cursor.execute("DELETE FROM episode WHERE idEpisode = ?", (kodi_id,)) - self.cursor.execute("DELETE FROM files WHERE idFile = ?", (file_id,)) diff --git a/resources/lib/objects/actions.py b/resources/lib/objects/actions.py new file mode 100644 index 00000000..a31286b4 --- /dev/null +++ b/resources/lib/objects/actions.py @@ -0,0 +1,723 @@ +# -*- coding: utf-8 -*- + +################################################################################################# + +import json +import logging +import threading +import sys +from datetime import timedelta + +import xbmc +import xbmcgui +import xbmcplugin +import xbmcaddon + +import database +from downloader import TheVoid +from obj import Objects +from helper import _, playutils, api, window, settings, dialog, JSONRPC +from dialogs import resume +from emby import Emby +from utils import get_play_action + +################################################################################################# + +LOG = logging.getLogger("EMBY."+__name__) + +################################################################################################# + + +class Actions(object): + + def __init__(self, server_id=None): + + self.server_id = server_id or None + self.server = TheVoid('GetServerAddress', {'ServerId': self.server_id}).get() + self.stack = [] + + def get_playlist(self, item): + + if item['Type'] == 'Audio': + return xbmc.PlayList(xbmc.PLAYLIST_MUSIC) + + return xbmc.PlayList(xbmc.PLAYLIST_VIDEO) + + def play(self, item, db_id=None, transcode=False): + + ''' Play item based on if playback started from widget ot not. + To get everything to work together, play the first item in the stack with setResolvedUrl, + add the rest to the regular playlist. + ''' + listitem = xbmcgui.ListItem() + LOG.info("[ play/%s ] %s", item['Id'], item['Name']) + + playlist = self.get_playlist(item) + play = playutils.PlayUtils(item, transcode, self.server_id, self.server) + source = play.select_source(play.get_sources()) + play.set_external_subs(source, listitem) + + self.set_playlist(item, listitem, db_id, transcode) + index = max(playlist.getposition(), 0) + 1 # Can return -1 + force_play = False + + self.stack[0][1].setPath(self.stack[0][0]) + try: + if self.detect_widgets(item): + LOG.info(" [ play/widget ]") + + raise IndexError + + xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, self.stack[0][1]) + self.stack.pop(0) + except IndexError: + force_play = True + + for stack in self.stack: + + playlist.add(url=stack[0], listitem=stack[1], index=index) + index += 1 + + if force_play: + if len(sys.argv) > 1: xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, self.stack[0][1]) + xbmc.Player().play(playlist, windowed=False) + + def set_playlist(self, item, listitem, db_id=None, transcode=False): + + ''' Verify seektime, set intros, set main item and set additional parts. + Detect the seektime for video type content. + Verify the default video action set in Kodi for accurate resume behavior. + ''' + seektime = window('emby.resume.bool') + window('emby.resume', clear=True) + + if item['Type'] != 'Audio': + + if get_play_action() == "Resume": + seektime = True + + if transcode and not seektime: + resume = item['UserData'].get('PlaybackPositionTicks') + + if resume: + choice = self.resume_dialog(api.API(item, self.server).adjust_resume((resume or 0) / 10000000.0)) + + if choice is None: + return + + elif not choice: + seektime = False + + if settings('enableCinema.bool') and not seektime: + self._set_intros(item) + + self.set_listitem(item, listitem, db_id, seektime) + playutils.set_properties(item, item['PlaybackInfo']['Method'], self.server_id) + self.stack.append([item['PlaybackInfo']['Path'], listitem]) + + if item.get('PartCount'): + self._set_additional_parts(item['Id']) + + def _set_intros(self, item): + + ''' if we have any play them when the movie/show is not being resumed. + ''' + intros = TheVoid('GetIntros', {'ServerId': self.server_id, 'Id': item['Id']}).get() + + if intros['Items']: + enabled = True + + if settings('askCinema') == "true": + + resp = dialog("yesno", heading="{emby}", line1=_(33016)) + if not resp: + + enabled = False + LOG.info("Skip trailers.") + + if enabled: + for intro in intros['Items']: + + listitem = xbmcgui.ListItem() + LOG.info("[ intro/%s ] %s", intro['Id'], intro['Name']) + + play = playutils.PlayUtils(intro, False, self.server_id, self.server) + source = play.select_source(play.get_sources()) + self.set_listitem(intro, listitem) + listitem.setPath(intro['PlaybackInfo']['Path']) + playutils.set_properties(intro, intro['PlaybackInfo']['Method'], self.server_id) + + self.stack.append([intro['PlaybackInfo']['Path'], listitem]) + + window('emby.skip.%s' % intro['Id'], value="true") + + def _set_additional_parts(self, item_id): + + ''' Create listitems and add them to the stack of playlist. + ''' + parts = TheVoid('GetAdditionalParts', {'ServerId': self.server_id, 'Id': item_id}).get() + + for part in parts['Items']: + + listitem = xbmcgui.ListItem() + LOG.info("[ part/%s ] %s", part['Id'], part['Name']) + + play = playutils.PlayUtils(part, False, self.server_id, self.server) + source = play.select_source(play.get_sources()) + play.set_external_subs(source, listitem) + self.set_listitem(part, listitem) + listitem.setPath(part['PlaybackInfo']['Path']) + playutils.set_properties(part, part['PlaybackInfo']['Method'], self.server_id) + + self.stack.append([part['PlaybackInfo']['Path'], listitem]) + + def play_playlist(self, items, clear=True, seektime=None, audio=None, subtitle=None): + + ''' Play a list of items. Creates a new playlist. + ''' + playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO) + started = False + + if clear: + playlist.clear() + index = 0 + else: + index = max(playlist.getposition(), 0) + 1 # Can return -1 + + for order, item in enumerate(items): + + listitem = xbmcgui.ListItem() + LOG.info("[ playlist/%s ] %s", item['Id'], item['Name']) + + play = playutils.PlayUtils(item, False, self.server_id, self.server) + source = play.select_source(play.get_sources()) + play.set_external_subs(source, listitem) + + if order == 0: # First item + + item['PlaybackInfo']['AudioStreamIndex'] = audio or item['PlaybackInfo']['AudioStreamIndex'] + item['PlaybackInfo']['SubtitleStreamIndex'] = subtitle or item['PlaybackInfo'].get('SubtitleStreamIndex') + + self.set_listitem(item, listitem, None, True if order == 0 and seektime else False) + listitem.setPath(item['PlaybackInfo']['Path']) + playutils.set_properties(item, item['PlaybackInfo']['Method'], self.server_id) + + playlist.add(item['PlaybackInfo']['Path'], listitem, index) + index += 1 + + if not started and clear: + + started = True + xbmc.Player().play(playlist) + + def set_listitem(self, item, listitem, db_id=None, seektime=None): + + objects = Objects() + API = api.API(item, self.server) + + if item['Type'] in ('MusicArtist', 'MusicAlbum', 'Audio'): + + obj = objects.map(item, 'BrowseAudio') + obj['DbId'] = db_id + obj['Artwork'] = API.get_all_artwork(objects.map(item, 'ArtworkMusic'), True) + self.listitem_music(obj, listitem, item) + + elif item['Type'] in ('Photo', 'PhotoAlbum'): + + obj = objects.map(item, 'BrowsePhoto') + obj['Artwork'] = API.get_all_artwork(objects.map(item, 'Artwork')) + self.listitem_photo(obj, listitem, item) + + else: + obj = objects.map(item, 'BrowseVideo') + obj['DbId'] = db_id + obj['Artwork'] = API.get_all_artwork(objects.map(item, 'ArtworkParent'), True) + self.listitem_video(obj, listitem, item, seektime) + + listitem.setContentLookup(False) + + def listitem_video(self, obj, listitem, item, seektime=None): + + ''' Set listitem for video content. That also include streams. + ''' + API = api.API(item, self.server) + is_video = obj['MediaType'] == 'Video' + + obj['Genres'] = " / ".join(obj['Genres'] or []) + obj['Studios'] = [API.validate_studio(studio) for studio in (obj['Studios'] or [])] + obj['Studios'] = " / ".join(obj['Studios']) + obj['People'] = obj['People'] or [] + obj['Countries'] = " / ".join(obj['Countries'] or []) + obj['Directors'] = " / ".join(obj['Directors'] or []) + obj['Writers'] = " / ".join(obj['Writers'] or []) + obj['Plot'] = API.get_overview(obj['Plot']) + obj['ShortPlot'] = API.get_overview(obj['ShortPlot']) + obj['DateAdded'] = obj['DateAdded'].split('.')[0].replace('T', " ") + obj['Rating'] = obj['Rating'] or 0 + obj['FileDate'] = "%s.%s.%s" % tuple(reversed(obj['DateAdded'].split('T')[0].split('-'))) + obj['Runtime'] = round(float((obj['Runtime'] or 0) / 10000000.0), 6) + obj['Resume'] = API.adjust_resume((obj['Resume'] or 0) / 10000000.0) + obj['PlayCount'] = API.get_playcount(obj['Played'], obj['PlayCount']) or 0 + obj['Overlay'] = 7 if obj['Played'] else 6 + obj['Video'] = API.video_streams(obj['Video'] or [], obj['Container']) + obj['Audio'] = API.audio_streams(obj['Audio'] or []) + obj['Streams'] = API.media_streams(obj['Video'], obj['Audio'], obj['Subtitles']) + obj['Artwork']['Primary'] = obj['Artwork']['Primary'] or "special://home/addons/plugin.video.emby/icon.png" + obj['Artwork']['Thumb'] = obj['Artwork']['Thumb'] or "special://home/addons/plugin.video.emby/fanart.jpg" + obj['Artwork']['Backdrop'] = obj['Artwork']['Backdrop'] or ["special://home/addons/plugin.video.emby/fanart.jpg"] + obj['ChildCount'] = obj['ChildCount'] or 0 + obj['RecursiveCount'] = obj['RecursiveCount'] or 0 + obj['Unwatched'] = obj['Unwatched'] or 0 + + if obj['Premiere']: + obj['Premiere'] = obj['Premiere'].split('T')[0] + + if obj['DatePlayed']: + obj['DatePlayed'] = obj['DatePlayed'].split('.')[0].replace('T', " ") + + metadata = { + 'title': obj['Title'], + 'originaltitle': obj['Title'], + 'sorttitle': obj['SortTitle'], + 'country': obj['Countries'], + 'genre': obj['Genres'], + 'year': obj['Year'], + 'rating': obj['Rating'], + 'playcount': obj['PlayCount'], + 'overlay': obj['Overlay'], + 'director': obj['Directors'], + 'mpaa': obj['Mpaa'], + 'plot': obj['Plot'], + 'plotoutline': obj['ShortPlot'], + 'studio': obj['Studios'], + 'tagline': obj['Tagline'], + 'writer': obj['Writers'], + 'premiered': obj['Premiere'], + 'aired': obj['Premiere'], + 'votes': obj['Votes'], + 'dateadded': obj['DateAdded'], + 'date': obj['FileDate'], + 'dbid': obj['DbId'] + } + listitem.setCast(API.get_actors()) + listitem.setIconImage(obj['Artwork']['Thumb']) + listitem.setThumbnailImage(obj['Artwork']['Primary']) + self.set_artwork(obj['Artwork'], listitem, obj['Type']) + + if obj['Artwork']['Primary']: + listitem.setThumbnailImage(obj['Artwork']['Primary']) + + if not obj['Artwork']['Backdrop']: + listitem.setArt({'fanart': obj['Artwork']['Primary']}) + + if obj['Premiere']: + metadata['premieredate'] = obj['Premiere'] + metadata['date'] = obj['Premiere'] + + + if obj['Type'] == 'Episode': + metadata.update({ + 'mediatype': "episode", + 'tvshowtitle': obj['SeriesName'], + 'season': obj['Season'] or 0, + 'sortseason': obj['Season'] or 0, + 'episode': obj['Index'] or 0, + 'sortepisode': obj['Index'] or 0, + 'lastplayed': obj['DatePlayed'], + 'duration': obj['Runtime'] + }) + + elif obj['Type'] == 'Season': + metadata.update({ + 'mediatype': "season", + 'tvshowtitle': obj['SeriesName'], + 'season': obj['Index'] or 0, + 'sortseason': obj['Index'] or 0 + }) + listitem.setProperty('NumEpisodes', str(obj['RecursiveCount'])) + listitem.setProperty('WatchedEpisodes', str(obj['RecursiveCount'] - obj['Unwatched'])) + listitem.setProperty('UnWatchedEpisodes', str(obj['Unwatched'])) + listitem.setProperty('IsFolder', 'true') + + elif obj['Type'] == 'Series': + metadata.update({ + 'mediatype': "tvshow", + 'tvshowtitle': obj['Title'] + }) + listitem.setProperty('TotalSeasons', str(obj['ChildCount'])) + listitem.setProperty('TotalEpisodes', str(obj['RecursiveCount'])) + listitem.setProperty('WatchedEpisodes', str(obj['RecursiveCount'] - obj['Unwatched'])) + listitem.setProperty('UnWatchedEpisodes', str(obj['Unwatched'])) + listitem.setProperty('IsFolder', 'true') + + elif obj['Type'] == 'Movie': + metadata.update({ + 'mediatype': "movie", + 'imdbnumber': obj['UniqueId'], + 'lastplayed': obj['DatePlayed'], + 'duration': obj['Runtime'] + }) + + elif obj['Type'] == 'MusicVideo': + metadata.update({ + 'mediatype': "musicvideo", + 'album': obj['Album'], + 'artist': obj['Artists'], + 'lastplayed': obj['DatePlayed'], + 'duration': obj['Runtime'] + }) + + elif obj['Type'] == 'BoxSet': + metadata['mediatype'] = "set" + listitem.setProperty('IsFolder', 'true') + else: + metadata.update({ + 'mediatype': "video", + 'lastplayed': obj['DatePlayed'], + 'duration': obj['Runtime'] + }) + + + if is_video: + + listitem.setProperty('totaltime', str(obj['Runtime'])) + listitem.setProperty('IsPlayable', 'true') + listitem.setProperty('IsFolder', 'false') + + if obj['Resume'] and seektime != False: + listitem.setProperty('resumetime', str(obj['Resume'])) + listitem.setProperty('StartPercent', str(((obj['Resume']/obj['Runtime']) * 100) - 0.40)) + else: + listitem.setProperty('resumetime', '0') + + for track in obj['Streams']['video']: + listitem.addStreamInfo('video', { + 'duration': obj['Runtime'], + 'aspect': track['aspect'], + 'codec': track['codec'], + 'width': track['width'], + 'height': track['height'] + }) + + for track in obj['Streams']['audio']: + listitem.addStreamInfo('audio', {'codec': track['codec'], 'channels': track['channels']}) + + for track in obj['Streams']['subtitle']: + listitem.addStreamInfo('subtitle', {'language': track}) + + listitem.setLabel(obj['Title']) + listitem.setInfo('video', metadata) + listitem.setContentLookup(False) + + def listitem_music(self, obj, listitem, item): + API = api.API(item, self.server) + + obj['Runtime'] = round(float((obj['Runtime'] or 0) / 10000000.0), 6) + obj['PlayCount'] = API.get_playcount(obj['Played'], obj['PlayCount']) or 0 + obj['Rating'] = obj['Rating'] or 0 + + if obj['FileDate'] or obj['DatePlayed']: + obj['DatePlayed'] = (obj['DatePlayed'] or obj['FileDate']).split('.')[0].replace('T', " ") + + obj['FileDate'] = "%s.%s.%s" % tuple(reversed(obj['FileDate'].split('T')[0].split('-'))) + + metadata = { + 'title': obj['Title'], + 'genre': obj['Genre'], + 'year': obj['Year'], + 'album': obj['Album'], + 'artist': obj['Artists'], + 'rating': obj['Rating'], + 'comment': obj['Comment'], + 'date': obj['FileDate'] + } + self.set_artwork(obj['Artwork'], listitem, obj['Type']) + + if obj['Type'] == 'Audio': + metadata.update({ + 'mediatype': "song", + 'tracknumber': obj['Index'], + 'discnumber': obj['Disc'], + 'duration': obj['Runtime'], + 'playcount': obj['PlayCount'], + 'lastplayed': obj['DatePlayed'], + 'musicbrainztrackid': obj['UniqueId'] + }) + listitem.setProperty('IsPlayable', 'true') + listitem.setProperty('IsFolder', 'false') + + elif obj['Type'] == 'Album': + metadata.update({ + 'mediatype': "album", + 'musicbrainzalbumid': obj['UniqueId'] + }) + + elif obj['Type'] in ('Artist', 'MusicArtist'): + metadata.update({ + 'mediatype': "artist", + 'musicbrainzartistid': obj['UniqueId'] + }) + else: + metadata['mediatype'] = "music" + + listitem.setLabel(obj['Title']) + listitem.setInfo('music', metadata) + listitem.setContentLookup(False) + + def listitem_photo(self, obj, listitem, item): + API = api.API(item, self.server) + + obj['Overview'] = API.get_overview(obj['Overview']) + obj['FileDate'] = "%s.%s.%s" % tuple(reversed(obj['FileDate'].split('T')[0].split('-'))) + + metadata = { + 'title': obj['Title'] + } + listitem.setProperty('path', obj['Artwork']['Primary']) + listitem.setThumbnailImage(obj['Artwork']['Primary']) + listitem.setIconImage(obj['Artwork']['Primary'] or "special://home/addons/plugin.video.emby/icon.png") + listitem.setArt({'fanart': obj['Artwork']['Primary'] or "special://home/addons/plugin.video.emby/fanart.jpg"}) + + if obj['Type'] == 'Photo': + metadata.update({ + 'picturepath': obj['Artwork']['Primary'], + 'date': obj['FileDate'], + 'exif:width': str(obj.get('Width', 0)), + 'exif:height': str(obj.get('Height', 0)), + 'size': obj['Size'], + 'exif:cameramake': obj['CameraMake'], + 'exif:cameramodel': obj['CameraModel'], + 'exif:exposuretime': str(obj['ExposureTime']), + 'exif:focallength': str(obj['FocalLength']) + }) + listitem.setProperty('plot', obj['Overview']) + listitem.setProperty('IsFolder', 'false') + else: + if obj['Artwork']['Backdrop']: + listitem.setArt({'fanart': obj['Artwork']['Backdrop'][0]}) + + listitem.setProperty('IsFolder', 'true') + + listitem.setProperty('IsPlayable', 'false') + listitem.setLabel(obj['Title']) + listitem.setInfo('pictures', metadata) + listitem.setContentLookup(False) + + def set_artwork(self, artwork, listitem, media): + + if media == 'Episode': + + art = { + 'poster': "Series.Primary", + 'tvshow.poster': "Series.Primary", + 'clearart': "Art", + 'tvshow.clearart': "Art", + 'clearlogo': "Logo", + 'tvshow.clearlogo': "Logo", + 'discart': "Disc", + 'fanart_image': "Backdrop", + 'landscape': "Thumb", + 'tvshow.landscape': "Thumb", + 'thumb': "Primary", + 'fanart': "Backdrop" + } + elif media in ('Artist', 'Audio', 'MusicAlbum'): + + art = { + 'clearlogo': "Logo", + 'discart': "Disc", + 'fanart': "Backdrop", + 'fanart_image': "Backdrop", # in case + 'thumb': "Primary" + } + else: + art = { + 'poster': "Primary", + 'clearart': "Art", + 'clearlogo': "Logo", + 'discart': "Disc", + 'fanart_image': "Backdrop", + 'landscape': "Thumb", + 'thumb': "Primary", + 'fanart': "Backdrop" + } + + for k_art, e_art in art.items(): + + if e_art == "Backdrop": + self._set_art(listitem, k_art, artwork[e_art][0] if artwork[e_art] else " ") + else: + self._set_art(listitem, k_art, artwork.get(e_art, " ")) + + def _set_art(self, listitem, art, path): + LOG.debug(" [ art/%s ] %s", art, path) + + if art in ('fanart_image', 'small_poster', 'tiny_poster', + 'medium_landscape', 'medium_poster', 'small_fanartimage', + 'medium_fanartimage', 'fanart_noindicators', 'discart', + 'tvshow.poster'): + + listitem.setProperty(art, path) + else: + listitem.setArt({art: path}) + + def resume_dialog(self, seektime): + + ''' Base resume dialog based on Kodi settings. + ''' + LOG.info("Resume dialog called.") + XML_PATH = (xbmcaddon.Addon('plugin.video.emby').getAddonInfo('path'), "default", "1080i") + + dialog = resume.ResumeDialog("script-emby-resume.xml", *XML_PATH) + dialog.set_resume_point("Resume from %s" % str(timedelta(seconds=seektime)).split(".")[0]) + dialog.doModal() + + if dialog.is_selected(): + if not dialog.get_selected(): # Start from beginning selected. + return False + else: # User backed out + LOG.info("User exited without a selection.") + return + + return True + + def detect_widgets(self, item): + + kodi_version = xbmc.getInfoLabel('System.BuildVersion') + + if kodi_version and "Git:" in kodi_version and kodi_version.split('Git:')[1].split("-")[0] in ('20171119', 'a9a7a20'): + LOG.info("Build does not require workaround for widgets?") + + return False + + if (not xbmc.getCondVisibility('Window.IsMedia') and + ((item['Type'] == 'Audio' and not xbmc.getCondVisibility('Integer.IsGreater(Playlist.Length(music),1)')) or + not xbmc.getCondVisibility('Integer.IsGreater(Playlist.Length(video),1)'))): + + return True + + return False + + +class PlaylistWorker(threading.Thread): + + def __init__(self, server_id, items, *args): + + self.server_id = server_id + self.items = items + self.args = args + threading.Thread.__init__(self) + + def run(self): + Actions(self.server_id).play_playlist(self.items, *self.args) + + +def on_update(data, server): + + ''' Only for manually marking as watched/unwatched + ''' + try: + kodi_id = data['item']['id'] + media = data['item']['type'] + playcount = int(data['playcount']) + LOG.info(" [ update/%s ] kodi_id: %s media: %s", playcount, kodi_id, media) + except (KeyError, TypeError): + LOG.debug("Invalid playstate update") + + return + + item = database.get_item(kodi_id, media) + + if item: + + if not window('emby.skip.%s.bool' % item[0]): + server['api'].item_played(item[0], playcount) + + window('emby.skip.%s' % item[0], clear=True) + +def on_play(data, server): + + ''' Setup progress for emby playback. + ''' + player = xbmc.Player() + + try: + kodi_id = None + + if player.isPlayingVideo(): + + ''' Seems to misbehave when playback is not terminated prior to playing new content. + The kodi id remains that of the previous title. Maybe onPlay happens before + this information is updated. Added a failsafe further below. + ''' + item = player.getVideoInfoTag() + kodi_id = item.getDbId() + media = item.getMediaType() + + if kodi_id is None or int(kodi_id) == -1 or 'item' in data and 'id' in data['item'] and data['item']['id'] != kodi_id: + + item = data['item'] + kodi_id = item['id'] + media = item['type'] + + LOG.info(" [ play ] kodi_id: %s media: %s", kodi_id, media) + + except (KeyError, TypeError): + LOG.debug("Invalid playstate update") + + return + + if settings('useDirectPaths') == '1' or media == 'song': + item = database.get_item(kodi_id, media) + + if item: + + try: + file = player.getPlayingFile() + except Exception as error: + LOG.error(error) + + return + + item = server['api'].get_item(item[0]) + item['PlaybackInfo'] = {'Path': file} + playutils.set_properties(item, 'DirectStream' if settings('useDirectPaths') == '0' else 'DirectPlay') + +def special_listener(): + + ''' Corner cases that needs to be listened to. + This is run in a loop within monitor.py + ''' + player = xbmc.Player() + isPlaying = player.isPlaying() + count = int(window('emby.external_count') or 0) + + if (not isPlaying and xbmc.getCondVisibility('Window.IsVisible(DialogContextMenu.xml)') and + xbmc.getInfoLabel('Control.GetLabel(1002)') == xbmc.getLocalizedString(12021)): + + control = int(xbmcgui.Window(10106).getFocusId()) + + if control == 1002: # Start from beginning + + LOG.info("Resume dialog: Start from beginning selected.") + window('emby.resume', clear=True) + else: + LOG.info("Resume dialog: Resume selected.") + window('emby.resume.bool', True) + + elif isPlaying and not window('emby.external_check'): + time = player.getTime() + + if time > 1: # Not external player. + + window('emby.external_check', value="true") + window('emby.external_count', value="0") + elif count == 120: + + LOG.info("External player detected.") + window('emby.external.bool', True) + window('emby.external_check.bool', True) + window('emby.external_count', value="0") + + elif time == 0: + window('emby.external_count', value=str(count + 1)) diff --git a/resources/lib/objects/kodi/__init__.py b/resources/lib/objects/kodi/__init__.py new file mode 100644 index 00000000..86ada83f --- /dev/null +++ b/resources/lib/objects/kodi/__init__.py @@ -0,0 +1,6 @@ +from kodi import Kodi +from movies import Movies +from musicvideos import MusicVideos +from tvshows import TVShows +from music import Music +from artwork import Artwork diff --git a/resources/lib/objects/kodi/artwork.py b/resources/lib/objects/kodi/artwork.py new file mode 100644 index 00000000..4a2593fc --- /dev/null +++ b/resources/lib/objects/kodi/artwork.py @@ -0,0 +1,386 @@ +# -*- coding: utf-8 -*- + +################################################################################################# + +import logging +import urllib +import Queue +import threading + +import xbmc +import xbmcvfs + +import queries as QU +import queries_texture as QUTEX +from helper import window, settings +from libraries import requests + +################################################################################################## + +LOG = logging.getLogger("EMBY."+__name__) + +################################################################################################## + + +class Artwork(object): + + def __init__(self, cursor): + + self.cursor = cursor + self.enable_cache = settings('enableTextureCache.bool') + self.queue = Queue.Queue() + self.threads = [] + self.kodi = { + 'username': settings('webServerUser'), + 'password': settings('webServerPass'), + 'host': "localhost", + 'port': settings('webServerPort') + } + + + def update(self, image_url, kodi_id, media, image): + + ''' Update artwork in the video database. + Only cache artwork if it changed for the main backdrop, poster. + Delete current entry before updating with the new one. + Cache fanart and poster in Kodi texture cache. + ''' + if not image_url or image == 'poster' and media in ('song', 'artist', 'album'): + return + + cache = False + + try: + self.cursor.execute(QU.get_art, (kodi_id, media, image,)) + url = self.cursor.fetchone()[0] + except TypeError: + + cache = True + LOG.debug("ADD to kodi_id %s art: %s", kodi_id, image_url) + self.cursor.execute(QU.add_art, (kodi_id, media, image, image_url)) + else: + if url != image_url: + cache = True + + if image in ('fanart', 'poster'): + self.delete_cache(url) + + LOG.info("UPDATE to kodi_id %s art: %s", kodi_id, image_url) + self.cursor.execute(QU.update_art, (image_url, kodi_id, media, image)) + + if cache and image in ('fanart', 'poster'): + self.cache(image_url) + + def add(self, artwork, *args): + + ''' Add all artworks. + ''' + KODI = { + 'Primary': ['thumb', 'poster'], + 'Banner': "banner", + 'Logo': "clearlogo", + 'Art': "clearart", + 'Thumb': "landscape", + 'Disc': "discart", + 'Backdrop': "fanart" + } + + for art in KODI: + + if art == 'Backdrop': + self.cursor.execute(QU.get_backdrops, args + ("fanart%",)) + + if len(self.cursor.fetchall()) > len(artwork['Backdrop']): + self.cursor.execute(QU.delete_backdrops, args + ("fanart_",)) + + for index, backdrop in enumerate(artwork['Backdrop']): + + if index: + self.update(*(backdrop,) + args + ("%s%s" % ("fanart", index),)) + else: + self.update(*(backdrop,) + args + ("fanart",)) + + elif art == 'Primary': + for kodi_image in KODI['Primary']: + self.update(*(artwork['Primary'],) + args + (kodi_image,)) + + elif artwork.get(art): + self.update(*(artwork[art],) + args + (KODI[art],)) + + def delete(self, *args): + + ''' Delete artwork from kodi database and remove cache for backdrop/posters. + ''' + self.cursor.execute(QU.get_art_url, args) + + for row in self.cursor.fetchall(): + if row[1] in ('poster', 'fanart'): + self.delete_cache(row[0]) + + def cache(self, url): + + ''' Cache a single image to texture cache. + ''' + if not url or not self.enable_cache: + return + + url = self.double_urlencode(url) + self.queue.put(url) + self.add_worker() + + def double_urlencode(self, text): + + text = self.single_urlencode(text) + text = self.single_urlencode(text) + + return text + + def single_urlencode(self, text): + + ''' urlencode needs a utf-string. + return the result as unicode + ''' + text = urllib.urlencode({'blahblahblah': text.encode('utf-8')}) + text = text[13:] + + return text.decode('utf-8') + + def add_worker(self): + + for thread in self.threads: + if thread.is_done: + self.threads.remove(thread) + + if self.queue.qsize() and len(self.threads) < 3: + + new_thread = GetArtworkWorker(self.kodi, self.queue) + new_thread.start() + LOG.info("-->[ q:artwork/%s ]", id(new_thread)) + self.threads.append(new_thread) + + def delete_cache(self, url): + + ''' Delete cached artwork. + ''' + from database import Database + + with Database('texture') as texturedb: + + try: + texturedb.cursor.execute(QUTEX.get_cache, (url,)) + cached = texturedb.cursor.fetchone()[0] + except TypeError: + LOG.debug("Could not find cached url: %s", url) + else: + thumbnails = xbmc.translatePath("special://thumbnails/%s" % cached).decode('utf-8') + xbmcvfs.delete(thumbnails) + texturedb.cursor.execute(QUTEX.delete_cache, (url,)) + LOG.info("DELETE cached %s", cached) + + +class GetArtworkWorker(threading.Thread): + + is_done = False + + def __init__(self, kodi, queue): + + self.kodi = kodi + self.queue = queue + threading.Thread.__init__(self) + + def run(self): + + ''' Prepare the request. Request removes the urlencode which is required in this case. + Use a session allows to use a pool of connections. + ''' + with requests.Session() as s: + while True: + + try: + url = self.queue.get(timeout=2) + except Queue.Empty: + + self.is_done = True + LOG.info("--<[ q:artwork/%s ]", id(self)) + + return + + try: + req = requests.Request(method='HEAD', + url="http://%s:%s/image/image://%s" % (self.kodi['host'], self.kodi['port'], url), + auth=(self.kodi['username'], self.kodi['password'])) + prep = req.prepare() + prep.url = "http://%s:%s/image/image://%s" % (self.kodi['host'], self.kodi['port'], url) + s.send(prep, timeout=(0.01, 0.01)) + s.content # release the connection + except Exception: + pass + + self.queue.task_done() + + if xbmc.Monitor().abortRequested(): + break + + + + +""" + +# -*- coding: utf-8 -*- + +################################################################################################# + +import logging +import os +import urllib +from sqlite3 import OperationalError + +import xbmc +import xbmcgui +import xbmcvfs +import requests + +import resources.lib.image_cache_thread as image_cache_thread +from resources.lib.helper import _, window, settings, JSONRPC +from resources.lib.database import Database +from __objs__ import QU + +################################################################################################## + +log = logging.getLogger("EMBY."+__name__) + +################################################################################################## + + +class Artwork(object): + + xbmc_host = 'localhost' + xbmc_port = None + xbmc_username = None + xbmc_password = None + + image_cache_threads = [] + image_cache_limit = 0 + + + def __init__(self, server): + + self.server = server + self.enable_texture_cache = settings('enableTextureCache') == "true" + self.image_cache_limit = int(settings('imageCacheLimit')) * 5 + log.debug("image cache thread count: %s", self.image_cache_limit) + + if not self.xbmc_port and self.enable_texture_cache: + self._set_webserver_details() + + + def texture_cache_sync(self): + # This method will sync all Kodi artwork to textures13.db + # and cache them locally. This takes diskspace! + if not dialog(type_="yesno", + heading="{emby}", + line1=_(33042)): + return + + log.info("Doing Image Cache Sync") + + pdialog = xbmcgui.DialogProgress() + pdialog.create(_(29999), _(33043)) + + # ask to rest all existing or not + if dialog(type_="yesno", heading="{emby}", line1=_(33044)): + log.info("Resetting all cache data first") + self.delete_cache() + + # Cache all entries in video DB + self._cache_all_video_entries(pdialog) + # Cache all entries in music DB + self._cache_all_music_entries(pdialog) + + pdialog.update(100, "%s %s" % (_(33046), len(self.image_cache_threads))) + log.info("Waiting for all threads to exit") + + while len(self.image_cache_threads): + for thread in self.image_cache_threads: + if thread.is_finished: + self.image_cache_threads.remove(thread) + pdialog.update(100, "%s %s" % (_(33046), len(self.image_cache_threads))) + log.info("Waiting for all threads to exit: %s", len(self.image_cache_threads)) + xbmc.sleep(500) + + pdialog.close() + + @classmethod + def delete_cache(cls): + # Remove all existing textures first + path = xbmc.translatePath('special://thumbnails/').decode('utf-8') + if xbmcvfs.exists(path): + dirs, ignore_files = xbmcvfs.listdir(path) + for directory in dirs: + ignore_dirs, files = xbmcvfs.listdir(path + directory) + for file_ in files: + + if os.path.supports_unicode_filenames: + filename = os.path.join(path + directory.decode('utf-8'), + file_.decode('utf-8')) + else: + filename = os.path.join(path.encode('utf-8') + directory, file_) + + xbmcvfs.delete(filename) + log.debug("deleted: %s", filename) + + # remove all existing data from texture DB + with DatabaseConn('texture') as cursor_texture: + cursor_texture.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"') + rows = cursor_texture.fetchall() + for row in rows: + table_name = row[0] + if table_name != "version": + cursor_texture.execute("DELETE FROM " + table_name) + + def _cache_all_video_entries(self, pdialog): + + with Database('video') as cursor_video: + + cursor_video.execute("SELECT url FROM art WHERE media_type != 'actor'") # dont include actors + result = cursor_video.fetchall() + total = len(result) + log.info("Image cache sync about to process %s images", total) + cursor_video.close() + + count = 0 + for url in result: + + if pdialog.iscanceled(): + break + + percentage = int((float(count) / float(total))*100) + message = "%s of %s (%s)" % (count, total, len(self.image_cache_threads)) + pdialog.update(percentage, "%s %s" % (_(33045), message)) + self.cache_texture(url[0]) + count += 1 + + def _cache_all_music_entries(self, pdialog): + + with Database('music') as cursor_music: + + cursor_music.execute("SELECT url FROM art") + result = cursor_music.fetchall() + total = len(result) + + log.info("Image cache sync about to process %s images", total) + + count = 0 + for url in result: + + if pdialog.iscanceled(): + break + + percentage = int((float(count) / float(total))*100) + message = "%s of %s" % (count, total) + pdialog.update(percentage, "%s %s" % (_(33045), message)) + self.cache_texture(url[0]) + count += 1 + +""" + diff --git a/resources/lib/objects/kodi/kodi.py b/resources/lib/objects/kodi/kodi.py new file mode 100644 index 00000000..1d41627c --- /dev/null +++ b/resources/lib/objects/kodi/kodi.py @@ -0,0 +1,297 @@ +# -*- coding: utf-8 -*- + +################################################################################################## + +import logging + +import xbmc + +import artwork +import queries as QU +from helper import values + +################################################################################################## + +LOG = logging.getLogger("EMBY."+__name__) + +################################################################################################## + + +class Kodi(object): + + + def __init__(self): + self.artwork = artwork.Artwork(self.cursor) + + def create_entry_path(self): + self.cursor.execute(QU.create_path) + + return self.cursor.fetchone()[0] + 1 + + def create_entry_file(self): + self.cursor.execute(QU.create_file) + + return self.cursor.fetchone()[0] + 1 + + def create_entry_person(self): + self.cursor.execute(QU.create_person) + + return self.cursor.fetchone()[0] + 1 + + def create_entry_genre(self): + self.cursor.execute(QU.create_genre) + + return self.cursor.fetchone()[0] + 1 + + def create_entry_studio(self): + self.cursor.execute(QU.create_studio) + + return self.cursor.fetchone()[0] + 1 + + def create_entry_bookmark(self): + self.cursor.execute(QU.create_bookmark) + + return self.cursor.fetchone()[0] + 1 + + def create_entry_tag(self): + self.cursor.execute(QU.create_tag) + + return self.cursor.fetchone()[0] + 1 + + def add_path(self, *args): + path_id = self.get_path(*args) + + if path_id is None: + + path_id = self.create_entry_path() + self.cursor.execute(QU.add_path, (path_id,) + args) + + return path_id + + def get_path(self, *args): + + try: + self.cursor.execute(QU.get_path, args) + + return self.cursor.fetchone()[0] + except TypeError: + return + + def update_path(self, *args): + self.cursor.execute(QU.update_path, args) + + def remove_path(self, *args): + self.cursor.execute(QU.delete_path, args) + + def add_file(self, filename, path_id): + + try: + self.cursor.execute(QU.get_file, (filename, path_id,)) + file_id = self.cursor.fetchone()[0] + except TypeError: + + file_id = self.create_entry_file() + self.cursor.execute(QU.add_file, (file_id, path_id, filename)) + + return file_id + + def update_file(self, *args): + self.cursor.execute(QU.update_file, args) + + def remove_file(self, path, *args): + path_id = self.get_path(path) + + if path_id is not None: + self.cursor.execute(QU.delete_file_by_path, (path_id,) + args) + + def get_filename(self, *args): + + try: + self.cursor.execute(QU.get_filename, args) + + return self.cursor.fetchone()[0] + except TypeError: + return "" + + def add_people(self, people, *args): + + def add_thumbnail(person_id, person, person_type): + + if person['imageurl']: + + art = person_type.lower() + if "writing" in art: + art = "writer" + + self.artwork.update(person['imageurl'], person_id, art, "thumb") + + def add_link(link, person_id): + self.cursor.execute(QU.update_link.replace("{LinkType}", link), (person_id,) + args) + + cast_order = 1 + + for person in people: + person_id = self.get_person(person['Name']) + + if person['Type'] == 'Actor': + + role = person.get('Role') + self.cursor.execute(QU.update_actor, (person_id,) + args + (role, cast_order,)) + cast_order += 1 + + elif person['Type'] == 'Director': + add_link('director_link', person_id) + + elif person['Type'] == 'Writer': + add_link('writer_link', person_id) + + elif person['Type'] == 'Artist': + add_link('actor_link', person_id) + + add_thumbnail(person_id, person, person['Type']) + + def add_person(self, *args): + + person_id = self.create_entry_person() + self.cursor.execute(QU.add_person, (person_id,) + args) + + return person_id + + def get_person(self, *args): + + try: + self.cursor.execute(QU.get_person, args) + + return self.cursor.fetchone()[0] + except TypeError: + return self.add_person(*args) + + def add_genres(self, genres, *args): + + ''' Delete current genres first for clean slate. + ''' + self.cursor.execute(QU.delete_genres, args) + + for genre in genres: + self.cursor.execute(QU.update_genres, (self.get_genre(genre),) + args) + + def add_genre(self, *args): + + genre_id = self.create_entry_genre() + self.cursor.execute(QU.add_genre, (genre_id,) + args) + + return genre_id + + def get_genre(self, *args): + + try: + self.cursor.execute(QU.get_genre, args) + + return self.cursor.fetchone()[0] + except TypeError: + return self.add_genre(*args) + + def add_studios(self, studios, *args): + + for studio in studios: + + studio_id = self.get_studio(studio) + self.cursor.execute(QU.update_studios, (studio_id,) + args) + + def add_studio(self, *args): + + studio_id = self.create_entry_studio() + self.cursor.execute(QU.add_studio, (studio_id,) + args) + + return studio_id + + def get_studio(self, *args): + + try: + self.cursor.execute(QU.get_studio, args) + + return self.cursor.fetchone()[0] + except TypeError: + return self.add_studio(*args) + + def add_streams(self, file_id, streams, runtime): + + ''' First remove any existing entries + Then re-add video, audio and subtitles. + ''' + self.cursor.execute(QU.delete_streams, (file_id,)) + + if streams: + for track in streams['video']: + + track['FileId'] = file_id + track['Runtime'] = runtime + self.add_stream_video(*values(track, QU.add_stream_video_obj)) + + for track in streams['audio']: + + track['FileId'] = file_id + self.add_stream_audio(*values(track, QU.add_stream_audio_obj)) + + for track in streams['subtitle']: + self.add_stream_sub(*values({'language': track, 'FileId': file_id}, QU.add_stream_sub_obj)) + + def add_stream_video(self, *args): + self.cursor.execute(QU.add_stream_video, args) + + def add_stream_audio(self, *args): + self.cursor.execute(QU.add_stream_audio, args) + + def add_stream_sub(self, *args): + self.cursor.execute(QU.add_stream_sub, args) + + def add_playstate(self, file_id, playcount, date_played, resume, *args): + + ''' Delete the existing resume point. + Set the watched count. + ''' + self.cursor.execute(QU.delete_bookmark, (file_id,)) + self.set_playcount(playcount, date_played, file_id) + + if resume: + + bookmark_id = self.create_entry_bookmark() + self.cursor.execute(QU.add_bookmark, (bookmark_id, file_id, resume,) + args) + + def set_playcount(self, *args): + self.cursor.execute(QU.update_playcount, args) + + def add_tags(self, tags, *args): + self.cursor.execute(QU.delete_tags, args) + + for tag in tags: + tag_id = self.get_tag(tag, *args) + + def add_tag(self, *args): + + tag_id = self.create_entry_tag() + self.cursor.execute(QU.add_tag, (tag_id,) + args) + + return tag_id + + def get_tag(self, tag, *args): + + try: + self.cursor.execute(QU.get_tag, (tag,)) + tag_id = self.cursor.fetchone()[0] + except TypeError: + tag_id = self.add_tag(tag) + + self.cursor.execute(QU.update_tag, (tag_id,) + args) + + return tag_id + + def remove_tag(self, tag, *args): + + try: + self.cursor.execute(QU.get_tag, (tag,)) + tag_id = self.cursor.fetchone()[0] + except TypeError: + return + + self.cursor.execute(QU.delete_tag, (tag,) + args) diff --git a/resources/lib/objects/kodi/movies.py b/resources/lib/objects/kodi/movies.py new file mode 100644 index 00000000..e2600a75 --- /dev/null +++ b/resources/lib/objects/kodi/movies.py @@ -0,0 +1,149 @@ +# -*- coding: utf-8 -*- + +################################################################################################## + +import logging + +from kodi import Kodi +import queries as QU + +################################################################################################## + +LOG = logging.getLogger("EMBY."+__name__) + +################################################################################################## + + +class Movies(Kodi): + + + def __init__(self, cursor): + + self.cursor = cursor + Kodi.__init__(self) + + def create_entry_unique_id(self): + self.cursor.execute(QU.create_unique_id) + + return self.cursor.fetchone()[0] + 1 + + def create_entry_rating(self): + self.cursor.execute(QU.create_rating) + + return self.cursor.fetchone()[0] + 1 + + def create_entry(self): + self.cursor.execute(QU.create_movie) + + return self.cursor.fetchone()[0] + 1 + + def create_entry_set(self): + self.cursor.execute(QU.create_set) + + return self.cursor.fetchone()[0] + 1 + + def create_entry_country(self): + self.cursor.execute(QU.create_country) + + return self.cursor.fetchone()[0] + 1 + + def get(self, *args): + + try: + self.cursor.execute(QU.get_movie, args) + return self.cursor.fetchone()[0] + except TypeError: + return + + def add(self, *args): + self.cursor.execute(QU.add_movie, args) + + def update(self, *args): + self.cursor.execute(QU.update_movie, args) + + def delete(self, kodi_id, file_id): + + self.cursor.execute(QU.delete_movie, (kodi_id,)) + self.cursor.execute(QU.delete_file, (file_id,)) + + def get_rating_id(self, *args): + + try: + self.cursor.execute(QU.get_rating, args) + + return self.cursor.fetchone()[0] + except TypeError: + return None + + def add_ratings(self, *args): + + ''' Add ratings, rating type and votes. + ''' + self.cursor.execute(QU.add_rating, args) + + def update_ratings(self, *args): + + ''' Update rating by rating_id. + ''' + self.cursor.execute(QU.update_rating, args) + + def get_unique_id(self, *args): + + try: + self.cursor.execute(QU.get_unique_id, args) + + return self.cursor.fetchone()[0] + except TypeError: + return + + def add_unique_id(self, *args): + + ''' Add the provider id, imdb, tvdb. + ''' + self.cursor.execute(QU.add_unique_id, args) + + def update_unique_id(self, *args): + + ''' Update the provider id, imdb, tvdb. + ''' + self.cursor.execute(QU.update_unique_id, args) + + def add_countries(self, countries, *args): + + for country in countries: + self.cursor.execute(QU.update_country, (self.get_country(country),) + args) + + def add_country(self, *args): + + country_id = self.create_entry_country() + self.cursor.execute(QU.add_country, (country_id,) + args) + + return country_id + + def get_country(self, *args): + + try: + self.cursor.execute(QU.get_country, args) + + return self.cursor.fetchone()[0] + except TypeError: + return self.add_country(*args) + + def add_boxset(self, *args): + + set_id = self.create_entry_set() + self.cursor.execute(QU.add_set, (set_id,) + args) + + return set_id + + def update_boxset(self, *args): + self.cursor.execute(QU.update_set, args) + + def set_boxset(self, *args): + self.cursor.execute(QU.update_movie_set, args) + + def remove_from_boxset(self, *args): + self.cursor.execute(QU.delete_movie_set, args) + + def delete_boxset(self, *args): + self.cursor.execute(QU.delete_set, args) diff --git a/resources/lib/objects/kodi/music.py b/resources/lib/objects/kodi/music.py new file mode 100644 index 00000000..35514b72 --- /dev/null +++ b/resources/lib/objects/kodi/music.py @@ -0,0 +1,223 @@ +# -*- coding: utf-8 -*- + +################################################################################################## + +import logging + +import queries_music as QU +from kodi import Kodi + +################################################################################################## + +LOG = logging.getLogger("EMBY."+__name__) + +################################################################################################## + + +class Music(Kodi): + + + def __init__(self, cursor): + + self.cursor = cursor + Kodi.__init__(self) + + def create_entry(self): + + ''' Krypton has a dummy first entry + idArtist: 1 strArtist: [Missing Tag] strMusicBrainzArtistID: Artist Tag Missing + ''' + self.cursor.execute(QU.create_artist) + + return self.cursor.fetchone()[0] + 1 + + def create_entry_album(self): + self.cursor.execute(QU.create_album) + + return self.cursor.fetchone()[0] + 1 + + def create_entry_song(self): + self.cursor.execute(QU.create_song) + + return self.cursor.fetchone()[0] + 1 + + def create_entry_genre(self): + self.cursor.execute(QU.create_genre) + + return self.cursor.fetchone()[0] + 1 + + def update_path(self, *args): + self.cursor.execute(QU.update_path, args) + + def add_role(self, *args): + self.cursor.execute(QU.update_role, args) + + def get(self, artist_id, name, musicbrainz): + + ''' Get artist or create the entry. + ''' + try: + self.cursor.execute(QU.get_artist, (musicbrainz,)) + result = self.cursor.fetchone() + artist_id = result[0] + artist_name = result[1] + except TypeError: + artist_id = self.add_artist(artist_id, name, musicbrainz) + else: + if artist_name != name: + self.update_artist_name(artist_id, name) + + return artist_id + + def add_artist(self, artist_id, name, *args): + + ''' Safety check, when musicbrainz does not exist + ''' + try: + self.cursor.execute(QU.get_artist_by_name, (name,)) + artist_id = self.cursor.fetchone()[0] + except TypeError: + artist_id = artist_id or self.create_entry() + self.cursor.execute(QU.add_artist, (artist_id, name,) + args) + + return artist_id + + def update_artist_name(self, *args): + self.cursor.execute(QU.update_artist_name, args) + + def update(self, *args): + self.cursor.execute(QU.update_artist, args) + + def link(self, *args): + self.cursor.execute(QU.update_link, args) + + def add_discography(self, *args): + self.cursor.execute(QU.update_discography, args) + + def validate_artist(self, *args): + + try: + self.cursor.execute(QU.get_artist_by_id, args) + + return self.cursor.fetchone()[0] + except TypeError: + return + + def validate_album(self, *args): + + try: + self.cursor.execute(QU.get_album_by_id, args) + + return self.cursor.fetchone()[0] + except TypeError: + return + + def validate_song(self, *args): + + try: + self.cursor.execute(QU.get_song_by_id, args) + + return self.cursor.fetchone()[0] + except TypeError: + return + + def get_album(self, album_id, name, musicbrainz, *args): + + if musicbrainz is not None: + self.cursor.execute(QU.get_album, (musicbrainz,)) + else: + self.cursor.execute(QU.get_album_by_name, (name,)) + + try: + album_id = self.cursor.fetchone()[0] + except TypeError: + album_id = self.add_album(*(album_id, name, musicbrainz,) + args) + + return album_id + + def add_album(self, album_id, *args): + + album_id = album_id or self.create_entry_album() + self.cursor.execute(QU.add_album, (album_id,) + args) + + return album_id + + def update_album(self, *args): + self.cursor.execute(QU.update_album, args) + + def get_album_artist(self, album_id, artists): + + try: + self.cursor.execute(QU.get_album_artist, (album_id,)) + curr_artists = self.cursor.fetchone()[0] + except TypeError: + return + + if curr_artists != artists: + self.update_album_artist(artists, album_id) + + def update_album_artist(self, *args): + self.cursor.execute(QU.update_album_artist, args) + + def add_single(self, *args): + self.cursor.execute(QU.add_single, args) + + def add_song(self, *args): + self.cursor.execute(QU.add_song, args) + + def update_song(self, *args): + self.cursor.execute(QU.update_song, args) + + def link_song_artist(self, *args): + self.cursor.execute(QU.update_song_artist, args) + + def link_song_album(self, *args): + self.cursor.execute(QU.update_song_album, args) + + def rate_song(self, *args): + self.cursor.execute(QU.update_song_rating, args) + + def add_genres(self, kodi_id, genres, media): + + ''' Add genres, but delete current genres first. + ''' + if media == 'album': + self.cursor.execute(QU.delete_genres_album, (kodi_id,)) + + for genre in genres: + + genre_id = self.get_genre(genre) + self.cursor.execute(QU.update_genre_album, (genre_id, kodi_id)) + + elif media == 'song': + self.cursor.execute(QU.delete_genres_song, (kodi_id,)) + + for genre in genres: + + genre_id = self.get_genre(genre) + self.cursor.execute(QU.update_genre_song, (genre_id, kodi_id)) + + def get_genre(self, *args): + + try: + self.cursor.execute(QU.get_genre, args) + + return self.cursor.fetchone()[0] + except TypeError: + return self.add_genre(*args) + + def add_genre(self, *args): + + genre_id = self.create_entry_genre() + self.cursor.execute(QU.add_genre, (genre_id,) + args) + + return genre_id + + def delete(self, *args): + self.cursor.execute(QU.delete_artist, args) + + def delete_album(self, *args): + self.cursor.execute(QU.delete_album, args) + + def delete_song(self, *args): + self.cursor.execute(QU.delete_song, args) diff --git a/resources/lib/objects/kodi/musicvideos.py b/resources/lib/objects/kodi/musicvideos.py new file mode 100644 index 00000000..50b8e91d --- /dev/null +++ b/resources/lib/objects/kodi/musicvideos.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- + +################################################################################################## + +import logging + +import queries as QU +from kodi import Kodi + +################################################################################################## + +log = logging.getLogger("EMBY."+__name__) + +################################################################################################## + + +class MusicVideos(Kodi): + + + def __init__(self, cursor): + + self.cursor = cursor + Kodi.__init__(self) + + def create_entry(self): + self.cursor.execute(QU.create_musicvideo) + + return self.cursor.fetchone()[0] + 1 + + def get(self, *args): + + try: + self.cursor.execute(QU.get_musicvideo, args) + + return self.cursor.fetchone()[0] + except TypeError: + return + + def add(self, *args): + self.cursor.execute(QU.add_musicvideo, args) + + def update(self, *args): + self.cursor.execute(QU.update_musicvideo, args) + + def delete(self, kodi_id, file_id): + + self.cursor.execute(QU.delete_musicvideo, (kodi_id,)) + self.cursor.execute(QU.delete_file, (file_id,)) diff --git a/resources/lib/objects/kodi/queries.py b/resources/lib/objects/kodi/queries.py new file mode 100644 index 00000000..52d705a3 --- /dev/null +++ b/resources/lib/objects/kodi/queries.py @@ -0,0 +1,550 @@ + +''' Queries for the Kodi database. obj reflect key/value to retrieve from emby items. + Some functions require additional information, therefore obj do not always reflect + the Kodi database query values. +''' +create_path = """ SELECT coalesce(max(idPath), 0) + FROM path + """ +create_file = """ SELECT coalesce(max(idFile), 0) + FROM files + """ +create_person = """ SELECT coalesce(max(actor_id), 0) + FROM actor + """ +create_genre = """ SELECT coalesce(max(genre_id), 0) + FROM genre + """ +create_studio = """ SELECT coalesce(max(studio_id), 0) + FROM studio + """ +create_bookmark = """ SELECT coalesce(max(idBookmark), 0) + FROM bookmark + """ +create_tag = """ SELECT coalesce(max(tag_id), 0) + FROM tag + """ +create_unique_id = """ SELECT coalesce(max(uniqueid_id), 0) + FROM uniqueid + """ +create_rating = """ SELECT coalesce(max(rating_id), 0) + FROM rating + """ +create_movie = """ SELECT coalesce(max(idMovie), 0) + FROM movie + """ +create_set = """ SELECT coalesce(max(idSet), 0) + FROM sets + """ +create_country = """ SELECT coalesce(max(country_id), 0) + FROM country + """ +create_musicvideo = """ SELECT coalesce(max(idMVideo), 0) + FROM musicvideo + """ +create_tvshow = """ SELECT coalesce(max(idShow), 0) + FROM tvshow + """ +create_season = """ SELECT coalesce(max(idSeason), 0) + FROM seasons + """ +create_episode = """ SELECT coalesce(max(idEpisode), 0) + FROM episode + """ + + +get_path = """ SELECT idPath + FROM path + WHERE strPath = ? + """ +get_path_obj = [ "{Path}" + ] +get_file = """ SELECT idFile + FROM files + WHERE idPath = ? + AND strFilename = ? + """ +get_file_obj = [ "{FileId}" + ] +get_filename = """ SELECT strFilename + FROM files + WHERE idFile = ? + """ +get_person = """ SELECT actor_id + FROM actor + WHERE name = ? + COLLATE NOCASE + """ +get_genre = """ SELECT genre_id + FROM genre + WHERE name = ? + COLLATE NOCASE + """ +get_studio = """ SELECT studio_id + FROM studio + WHERE name = ? + COLLATE NOCASE + """ +get_tag = """ SELECT tag_id + FROM tag + WHERE name = ? + COLLATE NOCASE + """ +get_tag_movie_obj = [ "{MovieId}", "Favorite movies", "movie" + ] +get_tag_mvideo_obj = [ "{MvideoId}", "Favorite musicvideos", "musicvideo" + ] +get_tag_episode_obj = [ "{KodiId}", "Favorite tvshows", "tvshow" + ] +get_art = """ SELECT url + FROM art + WHERE media_id = ? + AND media_type = ? + AND type = ? + """ +get_movie = """ SELECT * + FROM movie + WHERE idMovie = ? + """ +get_movie_obj = [ "{MovieId}" + ] +get_rating = """ SELECT rating_id + FROM rating + WHERE media_type = ? + AND media_id = ? + """ +get_rating_movie_obj = [ "movie","{MovieId}" + ] +get_rating_episode_obj = [ "episode","{EpisodeId}" + ] +get_unique_id = """ SELECT uniqueid_id + FROM uniqueid + WHERE media_type = ? + AND media_id = ? + """ +get_unique_id_movie_obj = [ "movie","{MovieId}" + ] +get_unique_id_tvshow_obj = [ "tvshow","{ShowId}" + ] +get_unique_id_episode_obj = [ "episode","{EpisodeId}" + ] +get_country = """ SELECT country_id + FROM country + WHERE name = ? + COLLATE NOCASE + """ +get_set = """ SELECT idSet + FROM sets + WHERE strSet = ? + COLLATE NOCASE + """ +get_musicvideo = """ SELECT * + FROM musicvideo + WHERE idMVideo = ? + """ +get_musicvideo_obj = [ "{MvideoId}" + ] +get_tvshow = """ SELECT * + FROM tvshow + WHERE idShow = ? + """ +get_tvshow_obj = [ "{ShowId}" + ] +get_episode = """ SELECT * + FROM episode + WHERE idEpisode = ? + """ +get_episode_obj = [ "{EpisodeId}" + ] +get_season = """ SELECT idSeason + FROM seasons + WHERE idShow = ? + AND season = ? + """ +get_season_obj = [ "{Title}","{ShowId}","{Index}" + ] +get_season_special_obj = [ None,"{ShowId}",-1 + ] +get_season_episode_obj = [ None,"{ShowId}","{Season}" + ] +get_backdrops = """ SELECT url + FROM art + WHERE media_id = ? + AND media_type = ? + AND type LIKE ? + """ +get_art = """ SELECT url + FROM art + WHERE media_id = ? + AND media_type = ? + AND type = ? + """ +get_art_url = """ SELECT url, type + FROM art + WHERE media_id = ? + AND media_type = ? + """ +get_show_by_unique_id = """ SELECT idShow + FROM tvshow_view + WHERE uniqueid_value = ? + """ + +get_total_episodes = """ SELECT totalCount + FROM tvshowcounts + WHERE idShow = ? + """ +get_total_episodes_obj = [ "{ParentId}" + ] + + + +add_path = """ INSERT INTO path(idPath, strPath) + VALUES (?, ?) + """ +add_path_obj = [ "{Path}" + ] +add_file = """ INSERT INTO files(idFile, idPath, strFilename) + VALUES (?, ?, ?) + """ +add_file_obj = [ "{PathId}","{Filename}" + ] +add_person = """ INSERT INTO actor(actor_id, name) + VALUES (?, ?) + """ +add_people_movie_obj = [ "{People}","{MovieId}","movie" + ] +add_people_mvideo_obj = [ "{People}","{MvideoId}","musicvideo" + ] +add_people_tvshow_obj = [ "{People}","{ShowId}","tvshow" + ] +add_people_episode_obj = [ "{People}","{EpisodeId}","episode" + ] +add_actor_link = """ INSERT INTO actor_link(actor_id, media_id, media_type, role, cast_order) + VALUES (?, ?, ?, ?, ?) + """ +add_link = """ INSERT INTO {LinkType}(actor_id, media_id, media_type) + VALUES (?, ?, ?) + """ +add_genre = """ INSERT INTO genre(genre_id, name) + VALUES (?, ?) + """ +add_genres_movie_obj = [ "{Genres}","{MovieId}","movie" + ] +add_genres_mvideo_obj = [ "{Genres}","{MvideoId}","musicvideo" + ] +add_genres_tvshow_obj = [ "{Genres}","{ShowId}","tvshow" + ] +add_studio = """ INSERT INTO studio(studio_id, name) + VALUES (?, ?) + """ +add_studios_movie_obj = [ "{Studios}","{MovieId}","movie" + ] +add_studios_mvideo_obj = [ "{Studios}","{MvideoId}","musicvideo" + ] +add_studios_tvshow_obj = [ "{Studios}","{ShowId}","tvshow" + ] +add_bookmark = """ INSERT INTO bookmark(idBookmark, idFile, timeInSeconds, totalTimeInSeconds, player, type) + VALUES (?, ?, ?, ?, ?, ?) + """ +add_bookmark_obj = [ "{FileId}","{PlayCount}","{DatePlayed}","{Resume}","{Runtime}","DVDPlayer",1 + ] +add_streams_obj = [ "{FileId}","{Streams}","{Runtime}" + ] +add_stream_video = """ INSERT INTO streamdetails(idFile, iStreamType, strVideoCodec, fVideoAspect, iVideoWidth, + iVideoHeight, iVideoDuration, strStereoMode) + VALUES (?, ?, ?, ?, ?, ?, ?, ?) + """ +add_stream_video_obj = [ "{FileId}",0,"{codec}","{aspect}","{width}","{height}","{Runtime}","{3d}" + ] +add_stream_audio = """ INSERT INTO streamdetails(idFile, iStreamType, strAudioCodec, iAudioChannels, strAudioLanguage) + VALUES (?, ?, ?, ?, ?) + """ +add_stream_audio_obj = [ "{FileId}",1,"{codec}","{channels}","{language}" + ] +add_stream_sub = """ INSERT INTO streamdetails(idFile, iStreamType, strSubtitleLanguage) + VALUES (?, ?, ?) + """ +add_stream_sub_obj = [ "{FileId}",2,"{language}" + ] +add_tag = """ INSERT INTO tag(tag_id, name) + VALUES (?, ?) + """ +add_tags_movie_obj = [ "{Tags}","{MovieId}","movie" + ] +add_tags_mvideo_obj = [ "{Tags}","{MvideoId}","musicvideo" + ] +add_tags_tvshow_obj = [ "{Tags}","{ShowId}","tvshow" + ] +add_art = """ INSERT INTO art(media_id, media_type, type, url) + VALUES (?, ?, ?, ?) + """ +add_movie = """ INSERT INTO movie(idMovie, idFile, c00, c01, c02, c03, c04, c05, c06, c07, + c09, c10, c11, c12, c14, c15, c16, c18, c19, c21, premiered) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + """ +add_movie_obj = [ "{MovieId}","{FileId}","{Title}","{Plot}","{ShortPlot}","{Tagline}", + "{Votes}","{RatingId}","{Writers}","{Year}","{Unique}","{SortTitle}", + "{Runtime}","{Mpaa}","{Genre}","{Directors}","{Title}","{Studio}", + "{Trailer}","{Country}","{Year}" + ] +add_rating = """ INSERT INTO rating(rating_id, media_id, media_type, rating_type, rating, votes) + VALUES (?, ?, ?, ?, ?, ?) + """ +add_rating_movie_obj = [ "{RatingId}","{MovieId}","movie","default","{Rating}","{Votes}" + ] +add_rating_tvshow_obj = [ "{RatingId}","{ShowId}","tvshow","default","{Rating}","{Votes}" + ] +add_rating_episode_obj = [ "{RatingId}","{EpisodeId}","episode","default","{Rating}","{Votes}" + ] +add_unique_id = """ INSERT INTO uniqueid(uniqueid_id, media_id, media_type, value, type) + VALUES (?, ?, ?, ?, ?) + """ +add_unique_id_movie_obj = [ "{Unique}","{MovieId}","movie","{UniqueId}","{ProviderName}" + ] +add_unique_id_tvshow_obj = [ "{Unique}","{ShowId}","tvshow","{UniqueId}","{ProviderName}" + ] +add_unique_id_episode_obj = [ "{Unique}","{EpisodeId}","episode","{UniqueId}","{ProviderName}" + ] +add_country = """ INSERT INTO country(country_id, name) + VALUES (?, ?) + """ +add_set = """ INSERT INTO sets(idSet, strSet, strOverview) + VALUES (?, ?, ?) + """ +add_set_obj = [ "{Title}","{Overview}" + ] +add_musicvideo = """ INSERT INTO musicvideo(idMVideo,idFile, c00, c04, c05, c06, c07, c08, c09, c10, + c11, c12, premiered) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + """ +add_musicvideo_obj = [ "{MvideoId}","{FileId}","{Title}","{Runtime}","{Directors}","{Studio}","{Year}", + "{Plot}","{Album}","{Artists}","{Genre}","{Index}","{Premiere}" + ] +add_tvshow = """ INSERT INTO tvshow(idShow, c00, c01, c04, c05, c08, c09, c12, c13, c14, c15) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + """ +add_tvshow_obj = [ "{ShowId}","{Title}","{Plot}","{RatingId}","{Premiere}","{Genre}","{Title}", + "{Unique}","{Mpaa}","{Studio}","{SortTitle}" + ] +add_season = """ INSERT INTO seasons(idSeason, idShow, season) + VALUES (?, ?, ?) + """ +add_episode = """ INSERT INTO episode(idEpisode, idFile, c00, c01, c03, c04, c05, c09, c10, c12, c13, c14, + idShow, c15, c16, idSeason) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + """ +add_episode_obj = [ "{EpisodeId}","{FileId}","{Title}","{Plot}","{RatingId}","{Writers}","{Premiere}","{Runtime}", + "{Directors}","{Season}","{Index}","{Title}","{ShowId}","{AirsBeforeSeason}", + "{AirsBeforeEpisode}","{SeasonId}" + ] +add_art = """ INSERT INTO art(media_id, media_type, type, url) + VALUES (?, ?, ?, ?) + """ + + + +update_path = """ UPDATE path + SET strPath = ?, strContent = ?, strScraper = ?, noUpdate = ? + WHERE idPath = ? + """ +update_path_movie_obj = [ "{Path}","movies","metadata.local",1,"{PathId}" + ] +update_path_toptvshow_obj = [ "{TopLevel}","tvshows","metadata.local",1,"{TopPathId}" + ] +update_path_tvshow_obj = [ "{Path}",None,None,1,"{PathId}" + ] +update_path_episode_obj = [ "{Path}",None,None,1,"{PathId}" + ] +update_path_mvideo_obj = [ "{Path}","musicvideos","metadata.local",1,"{PathId}" + ] +update_file = """ UPDATE files + SET idPath = ?, strFilename = ?, dateAdded = ? + WHERE idFile = ? + """ +update_file_obj = [ "{PathId}","{Filename}","{DateAdded}","{FileId}" + ] +update_genres = """ INSERT OR REPLACE INTO genre_link(genre_id, media_id, media_type) + VALUES (?, ?, ?) + """ +update_studios = """ INSERT OR REPLACE INTO studio_link(studio_id, media_id, media_type) + VALUES (?, ?, ?) + """ +update_playcount = """ UPDATE files + SET playCount = ?, lastPlayed = ? + WHERE idFile = ? + """ +update_tag = """ INSERT OR REPLACE INTO tag_link(tag_id, media_id, media_type) + VALUES (?, ?, ?) + """ +update_art = """ UPDATE art + SET url = ? + WHERE media_id = ? + AND media_type = ? + AND type = ? + """ +update_actor = """ INSERT OR REPLACE INTO actor_link(actor_id, media_id, media_type, role, cast_order) + VALUES (?, ?, ?, ?, ?) + """ + +update_link = """ INSERT OR REPLACE INTO {LinkType}(actor_id, media_id, media_type) + VALUES (?, ?, ?) + """ +update_movie = """ UPDATE movie + SET c00 = ?, c01 = ?, c02 = ?, c03 = ?, c04 = ?, c05 = ?, c06 = ?, + c07 = ?, c09 = ?, c10 = ?, c11 = ?, c12 = ?, c14 = ?, c15 = ?, + c16 = ?, c18 = ?, c19 = ?, c21 = ?, premiered = ? + WHERE idMovie = ? + """ +update_movie_obj = [ "{Title}","{Plot}","{ShortPlot}","{Tagline}","{Votes}","{RatingId}", + "{Writers}","{Year}","{Unique}","{SortTitle}","{Runtime}", + "{Mpaa}","{Genre}","{Directors}","{Title}","{Studio}","{Trailer}", + "{Country}","{Year}","{MovieId}" + ] +update_rating = """ UPDATE rating + SET media_id = ?, media_type = ?, rating_type = ?, rating = ?, votes = ? + WHERE rating_id = ? + """ +update_rating_movie_obj = [ "{MovieId}","movie","default","{Rating}","{Votes}","{RatingId}" + ] +update_rating_tvshow_obj = [ "{ShowId}","tvshow","default","{Rating}","{Votes}","{RatingId}" + ] +update_rating_episode_obj = [ "{EpisodeId}","episode","default","{Rating}","{Votes}","{RatingId}" + ] +update_unique_id = """ UPDATE uniqueid + SET media_id = ?, media_type = ?, value = ?, type = ? + WHERE uniqueid_id = ? + """ +update_unique_id_movie_obj = [ "{MovieId}","movie","{UniqueId}","{ProviderName}","{Unique}" + ] +update_unique_id_tvshow_obj = [ "{ShowId}","tvshow","{UniqueId}","{ProviderName}","{Unique}" + ] +update_unique_id_episode_obj = [ "{EpisodeId}","episode","{UniqueId}","{ProviderName}","{Unique}" + ] +update_country = """ INSERT OR REPLACE INTO country_link(country_id, media_id, media_type) + VALUES (?, ?, ?) + """ +update_country_obj = [ "{Countries}","{MovieId}","movie" + ] +update_set = """ UPDATE sets + SET strSet = ?, strOverview = ? + WHERE idSet = ? + """ +update_set_obj = [ "{Title}", "{Overview}", "{SetId}" + ] +update_movie_set = """ UPDATE movie + SET idSet = ? + WHERE idMovie = ? + """ +update_movie_set_obj = [ "{SetId}","{MovieId}" + ] +update_musicvideo = """ UPDATE musicvideo + SET c00 = ?, c04 = ?, c05 = ?, c06 = ?, c07 = ?, c08 = ?, c09 = ?, c10 = ?, + c11 = ?, c12 = ?, premiered = ? + WHERE idMVideo = ? + """ +update_musicvideo_obj = [ "{Title}","{Runtime}","{Directors}","{Studio}","{Year}","{Plot}","{Album}", + "{Artists}","{Genre}","{Index}","{Premiere}","{MvideoId}" + ] +update_tvshow = """ UPDATE tvshow + SET c00 = ?, c01 = ?, c04 = ?, c05 = ?, c08 = ?, c09 = ?, + c12 = ?, c13 = ?, c14 = ?, c15 = ? + WHERE idShow = ? + """ +update_tvshow_obj = [ "{ShowId}","{Title}","{Plot}","{RatingId}","{Premiere}","{Genre}","{Title}", + "{Unique}","{Mpaa}","{Studio}","{SortTitle}" + ] +update_tvshow_link = """ INSERT OR REPLACE INTO tvshowlinkpath(idShow, idPath) + VALUES (?, ?) + """ +update_tvshow_link_obj = [ "{ShowId}","{PathId}" + ] +update_season = """ UPDATE seasons + SET name = ? + WHERE idSeason = ? + """ +update_episode = """ UPDATE episode + SET c00 = ?, c01 = ?, c03 = ?, c04 = ?, c05 = ?, c09 = ?, c10 = ?, + c12 = ?, c13 = ?, c14 = ?, c15 = ?, c16 = ?, idSeason = ?, idShow = ? + WHERE idEpisode = ? + """ +update_episode_obj = [ "{Title}","{Plot}","{RatingId}","{Writers}","{Premiere}","{Runtime}","{Directors}", + "{Season}","{Index}","{Title}","{AirsBeforeSeason}","{AirsBeforeEpisode}","{SeasonId}", + "{ShowId}","{EpisodeId}" + ] + + + +delete_path = """ DELETE FROM path + WHERE idPath = ? + """ +delete_path_obj = [ "{PathId}" + ] +delete_file = """ DELETE FROM files + WHERE idFile = ? + """ +delete_file_obj = [ "{Path}","{Filename}" + ] +delete_file_by_path = """ DELETE FROM files + WHERE idPath = ? + AND strFileName = ? + """ +delete_genres = """ DELETE FROM genre_link + WHERE media_id = ? + AND media_type = ? + """ +delete_bookmark = """ DELETE FROM bookmark + WHERE idFile = ? + """ +delete_streams = """ DELETE FROM streamdetails + WHERE idFile = ? + """ +delete_tags = """ DELETE FROM tag_link + WHERE media_id = ? + AND media_type = ? + """ +delete_tag = """ DELETE FROM tag_link + WHERE tag_id = ? + AND media_type = ? + AND media_id = ? + """ +delete_tag_movie_obj = [ "{MovieId}","Favorite movies","movie" + ] +delete_tag_mvideo_obj = [ "{MvideoId}","Favorite musicvideos","musicvideo" + ] +delete_tag_episode_obj = [ "{KodiId}","Favorite tvshows","tvshow" + ] +delete_movie = """ DELETE FROM movie + WHERE idMovie = ? + """ +delete_movie_obj = [ "{KodiId}","{FileId}" + ] +delete_set = """ DELETE FROM sets + WHERE idSet = ? + """ +delete_set_obj = [ "{KodiId}" + ] +delete_movie_set = """ UPDATE movie + SET idSet = null + WHERE idMovie = ? + """ +delete_movie_set_obj = [ "{MovieId}" + ] +delete_musicvideo = """ DELETE FROM musicvideo + WHERE idMVideo = ? + """ +delete_musicvideo_obj = [ "{MvideoId}", "{FileId}" + ] +delete_tvshow = """ DELETE FROM tvshow + WHERE idShow = ? + """ +delete_season = """ DELETE FROM seasons + WHERE idSeason = ? + """ +delete_episode = """ DELETE FROM episode + WHERE idEpisode = ? + """ +delete_backdrops = """ DELETE FROM art + WHERE media_id = ? + AND media_type = ? + AND type LIKE ? + """ diff --git a/resources/lib/objects/kodi/queries_music.py b/resources/lib/objects/kodi/queries_music.py new file mode 100644 index 00000000..eca4baa1 --- /dev/null +++ b/resources/lib/objects/kodi/queries_music.py @@ -0,0 +1,197 @@ + +create_artist = """ SELECT coalesce(max(idArtist), 1) + FROM artist + """ +create_album = """ SELECT coalesce(max(idAlbum), 0) + FROM album + """ +create_song = """ SELECT coalesce(max(idSong), 0) + FROM song + """ +create_genre = """ SELECT coalesce(max(idGenre), 0) + FROM genre + """ + + + +get_artist = """ SELECT idArtist, strArtist + FROM artist + WHERE strMusicBrainzArtistID = ? + """ +get_artist_obj = [ "{ArtistId}","{Name}","{UniqueId}" + ] +get_artist_by_name = """ SELECT idArtist + FROM artist + WHERE strArtist = ? + COLLATE NOCASE + """ +get_artist_by_id = """ SELECT * + FROM artist + WHERE idArtist = ? + """ +get_artist_by_id_obj = [ "{ArtistId}" + ] +get_album_by_id = """ SELECT * + FROM album + WHERE idAlbum = ? + """ +get_album_by_id_obj = [ "{AlbumId}" + ] +get_song_by_id = """ SELECT * + FROM song + WHERE idSong = ? + """ +get_song_by_id_obj = [ "{SongId}" + ] +get_album = """ SELECT idAlbum + FROM album + WHERE strMusicBrainzAlbumID = ? + """ +get_album_obj = [ "{AlbumId}","{Title}","{UniqueId}","album" + ] +get_album_by_name = """ SELECT idAlbum + FROM album + WHERE strAlbum = ? + """ +get_album_artist = """ SELECT strArtists + FROM album + WHERE idAlbum = ? + """ +get_album_artist_obj = [ "{AlbumId}","{strAlbumArtists}" + ] +get_genre = """ SELECT idGenre + FROM genre + WHERE strGenre = ? + COLLATE NOCASE + """ +get_total_episodes = """ SELECT totalCount + FROM tvshowcounts + WHERE idShow = ? + """ + + + +add_artist = """ INSERT INTO artist(idArtist, strArtist, strMusicBrainzArtistID) + VALUES (?, ?, ?) + """ +add_album = """ INSERT INTO album(idAlbum, strAlbum, strMusicBrainzAlbumID, strReleaseType) + VALUES (?, ?, ?, ?) + """ +add_single = """ INSERT INTO album(idAlbum, strGenres, iYear, strReleaseType) + VALUES (?, ?, ?, ?) + """ +add_single_obj = [ "{AlbumId}","{Genre}","{Year}","single" + ] +add_song = """ INSERT INTO song(idSong, idAlbum, idPath, strArtists, strGenres, strTitle, iTrack, + iDuration, iYear, strFileName, strMusicBrainzTrackID, iTimesPlayed, lastplayed, + rating, comment, dateAdded) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + """ +add_song_obj = [ "{SongId}","{AlbumId}","{PathId}","{Artists}","{Genre}","{Title}","{Index}", + "{Runtime}","{Year}","{Filename}","{UniqueId}","{PlayCount}","{DatePlayed}","{Rating}", + "{Comment}","{DateAdded}" + ] +add_genre = """ INSERT INTO genre(idGenre, strGenre) + VALUES (?, ?) + """ +add_genres_obj = [ "{AlbumId}","{Genres}","album" + ] + + + +update_path = """ UPDATE path + SET strPath = ? + WHERE idPath = ? + """ +update_path_obj = [ "{Path}","{PathId}" + ] +update_role = """ INSERT OR REPLACE INTO role(idRole, strRole) + VALUES (?, ?) + """ +update_role_obj = [ 1,"Composer" + ] +update_artist_name = """ UPDATE artist + SET strArtist = ? + WHERE idArtist = ? + """ +update_artist_name_obj = [ "{Name}","{ArtistId}" + ] +update_artist = """ UPDATE artist + SET strGenres = ?, strBiography = ?, strImage = ?, strFanart = ?, lastScraped = ? + WHERE idArtist = ? + """ +update_link = """ INSERT OR REPLACE INTO album_artist(idArtist, idAlbum, strArtist) + VALUES (?, ?, ?) + """ +update_link_obj = [ "{ArtistId}","{AlbumId}","{Name}" + ] +update_discography = """ INSERT OR REPLACE INTO discography(idArtist, strAlbum, strYear) + VALUES (?, ?, ?) + """ +update_discography_obj = [ "{ArtistId}","{Title}","{Year}" + ] +update_album = """ UPDATE album + SET strArtists = ?, iYear = ?, strGenres = ?, strReview = ?, strImage = ?, + iUserrating = ?, lastScraped = ?, strReleaseType = ? + WHERE idAlbum = ? + """ +update_album_obj = [ "{Artists}","{Year}","{Genre}","{Bio}","{Thumb}","{Rating}","{LastScraped}", + "album","{AlbumId}" + ] +update_album_artist = """ UPDATE album + SET strArtists = ? + WHERE idAlbum = ? + """ +update_song = """ UPDATE song + SET idAlbum = ?, strArtists = ?, strGenres = ?, strTitle = ?, iTrack = ?, + iDuration = ?, iYear = ?, strFilename = ?, iTimesPlayed = ?, lastplayed = ?, + rating = ?, comment = ?, dateAdded = ? + WHERE idSong = ? + """ +update_song_obj = [ "{AlbumId}","{Artists}","{Genre}","{Title}","{Index}","{Runtime}","{Year}", + "{Filename}","{PlayCount}","{DatePlayed}","{Rating}","{Comment}", + "{DateAdded}","{SongId}" + ] +update_song_artist = """ INSERT OR REPLACE INTO song_artist(idArtist, idSong, idRole, iOrder, strArtist) + VALUES (?, ?, ?, ?, ?) + """ +update_song_artist_obj = [ "{ArtistId}","{SongId}",1,"{Index}","{Name}" + ] +update_song_album = """ INSERT OR REPLACE INTO albuminfosong(idAlbumInfoSong, idAlbumInfo, iTrack, + strTitle, iDuration) + VALUES (?, ?, ?, ?, ?) + """ +update_song_album_obj = [ "{SongId}","{AlbumId}","{Index}","{Title}","{Runtime}" + ] +update_song_rating = """ UPDATE song + SET iTimesPlayed = ?, lastplayed = ?, rating = ? + WHERE idSong = ? + """ +update_song_rating_obj = [ "{PlayCount}","{DatePlayed}","{Rating}","{KodiId}" + ] +update_genre_album = """ INSERT OR REPLACE INTO album_genre(idGenre, idAlbum) + VALUES (?, ?) + """ +update_genre_song = """ INSERT OR REPLACE INTO song_genre(idGenre, idSong) + VALUES (?, ?) + """ +update_genre_song_obj = [ "{SongId}","{Genres}","song" + ] + + + +delete_genres_album = """ DELETE FROM album_genre + WHERE idAlbum = ? + """ +delete_genres_song = """ DELETE FROM song_genre + WHERE idSong = ? + """ +delete_artist = """ DELETE FROM artist + WHERE idArtist = ? + """ +delete_album = """ DELETE FROM album + WHERE idAlbum = ? + """ +delete_song = """ DELETE FROM song + WHERE idSong = ? + """ diff --git a/resources/lib/objects/kodi/queries_texture.py b/resources/lib/objects/kodi/queries_texture.py new file mode 100644 index 00000000..8f2235f8 --- /dev/null +++ b/resources/lib/objects/kodi/queries_texture.py @@ -0,0 +1,11 @@ + +get_cache = """ SELECT cachedurl + FROM texture + WHERE url = ? + """ + + + +delete_cache = """ DELETE FROM texture + WHERE url = ? + """ diff --git a/resources/lib/objects/kodi/tvshows.py b/resources/lib/objects/kodi/tvshows.py new file mode 100644 index 00000000..8da80f00 --- /dev/null +++ b/resources/lib/objects/kodi/tvshows.py @@ -0,0 +1,156 @@ +# -*- coding: utf-8 -*- + +################################################################################################## + +import logging + +import queries as QU +from kodi import Kodi + +################################################################################################## + +LOG = logging.getLogger("EMBY."+__name__) + +################################################################################################## + + +class TVShows(Kodi): + + + def __init__(self, cursor): + + self.cursor = cursor + Kodi.__init__(self) + + def create_entry_unique_id(self): + self.cursor.execute(QU.create_unique_id) + + return self.cursor.fetchone()[0] + 1 + + def create_entry_rating(self): + self.cursor.execute(QU.create_rating) + + return self.cursor.fetchone()[0] + 1 + + def create_entry(self): + self.cursor.execute(QU.create_tvshow) + + return self.cursor.fetchone()[0] + 1 + + def create_entry_season(self): + self.cursor.execute(QU.create_season) + + return self.cursor.fetchone()[0] + 1 + + def create_entry_episode(self): + self.cursor.execute(QU.create_episode) + + return self.cursor.fetchone()[0] + 1 + + def get(self, *args): + + try: + self.cursor.execute(QU.get_tvshow, args) + + return self.cursor.fetchone()[0] + except TypeError: + return + + def get_episode(self, *args): + + try: + self.cursor.execute(QU.get_episode, args) + + return self.cursor.fetchone()[0] + except TypeError: + return + + def get_rating_id(self, *args): + + try: + self.cursor.execute(QU.get_rating, args) + + return self.cursor.fetchone()[0] + except TypeError: + return + + def add_ratings(self, *args): + self.cursor.execute(QU.add_rating, args) + + def update_ratings(self, *args): + self.cursor.execute(QU.update_rating, args) + + def get_total_episodes(self, *args): + + try: + self.cursor.execute(QU.get_total_episodes, args) + + return self.cursor.fetchone()[0] + except TypeError: + return + + def get_unique_id(self, *args): + + try: + self.cursor.execute(QU.get_unique_id, args) + + return self.cursor.fetchone()[0] + except TypeError: + return + + def add_unique_id(self, *args): + self.cursor.execute(QU.add_unique_id, args) + + def update_unique_id(self, *args): + self.cursor.execute(QU.update_unique_id, args) + + def add(self, *args): + self.cursor.execute(QU.add_tvshow, args) + + def update(self, *args): + self.cursor.execute(QU.update_tvshow, args) + + def link(self, *args): + self.cursor.execute(QU.update_tvshow_link, args) + + def get_season(self, name, *args): + + self.cursor.execute(QU.get_season, args) + try: + season_id = self.cursor.fetchone()[0] + except TypeError: + season_id = self.add_season(*args) + + if name: + self.cursor.execute(QU.update_season, (name, season_id)) + + return season_id + + def add_season(self, *args): + + season_id = self.create_entry_season() + self.cursor.execute(QU.add_season, (season_id,) + args) + + return season_id + + def get_by_unique_id(self, *args): + self.cursor.execute(QU.get_show_by_unique_id, args) + + return self.cursor.fetchall() + + def add_episode(self, *args): + self.cursor.execute(QU.add_episode, args) + + def update_episode(self, *args): + self.cursor.execute(QU.update_episode, args) + + def delete_tvshow(self, *args): + self.cursor.execute(QU.delete_tvshow, args) + + def delete_season(self, *args): + self.cursor.execute(QU.delete_season, args) + + def delete_episode(self, kodi_id, file_id): + + self.cursor.execute(QU.delete_episode, (kodi_id,)) + self.cursor.execute(QU.delete_file, (file_id,)) diff --git a/resources/lib/objects/movies.py b/resources/lib/objects/movies.py index 77006ad1..0f803eb1 100644 --- a/resources/lib/objects/movies.py +++ b/resources/lib/objects/movies.py @@ -2,466 +2,346 @@ ################################################################################################## +import json import logging import urllib -import api -import embydb_functions as embydb -import _kodi_movies -from _common import Items, catch_except -from utils import window, settings, language as lang +import downloader as server +from obj import Objects +from kodi import Movies as KodiDb, queries as QU +from database import emby_db, queries as QUEM +from helper import api, catch, stop, validate, emby_item, library_check, values ################################################################################################## -log = logging.getLogger("EMBY."+__name__) +LOG = logging.getLogger("EMBY."+__name__) ################################################################################################## -class Movies(Items): +class Movies(KodiDb): + def __init__(self, server, embydb, videodb, direct_path): - def __init__(self, embycursor, kodicursor, pdialog=None): + self.server = server + self.emby = embydb + self.video = videodb + self.direct_path = direct_path - self.embycursor = embycursor - self.emby_db = embydb.Embydb_Functions(self.embycursor) - self.kodicursor = kodicursor - self.kodi_db = _kodi_movies.KodiMovies(self.kodicursor) - self.pdialog = pdialog + self.emby_db = emby_db.EmbyDatabase(embydb.cursor) + self.objects = Objects() - self.new_time = int(settings('newvideotime'))*1000 + KodiDb.__init__(self, videodb.cursor) - Items.__init__(self) + def __getitem__(self, key): - def _get_func(self, item_type, action): + if key == 'Movie': + return self.movie + elif key == 'BoxSet': + return self.boxset + elif key == 'UserData': + return self.userdata + elif key in 'Removed': + return self.remove - if item_type == "Movie": - actions = { - 'added': self.add_movies, - 'update': self.add_update, - 'userdata': self.updateUserdata, - 'remove': self.remove - } - elif item_type == "BoxSet": - actions = { - 'added': self.add_boxsets, - 'update': self.add_updateBoxset, - 'remove': self.remove - } - else: - log.info("Unsupported item_type: %s", item_type) - actions = {} - - return actions.get(action) - - def force_refresh_boxsets(self): - - if self.pdialog: - self.pdialog.update(heading=lang(29999), message=lang(33018)) - - boxsets = self.emby.getBoxset(dialog=self.pdialog) - self.add_all("BoxSet", boxsets) - - log.debug("Boxsets finished.") - return True - - def compare_all(self): - # Pull the list of movies and boxsets in Kodi - views = self.emby_db.getView_byType('movies') - views += self.emby_db.getView_byType('mixed') - log.info("Media folders: %s", views) - - # Process movies - for view in views: - - if self.should_stop(): - return False - - if not self.compare_movies(view): - return False - - # Process boxsets - if not self.compare_boxsets(): - return False - - return True - - def compare_movies(self, view): - - view_id = view['id'] - view_name = view['name'] - - if self.pdialog: - self.pdialog.update(heading=lang(29999), message="%s %s..." % (lang(33026), view_name)) + @stop() + @emby_item() + @library_check() + def movie(self, item, e_item, library): - movies = dict(self.emby_db.get_checksum_by_view("Movie", view_id)) - emby_movies = self.emby.getMovies(view_id, basic=True, dialog=self.pdialog) + ''' If item does not exist, entry will be added. + If item exists, entry will be updated. + ''' + API = api.API(item, self.server['auth/server-address']) + obj = self.objects.map(item, 'Movie') + update = True - return self.compare("Movie", emby_movies['Items'], movies, view) - - def compare_boxsets(self): - - if self.pdialog: - self.pdialog.update(heading=lang(29999), message=lang(33027)) - - boxsets = dict(self.emby_db.get_checksum('BoxSet')) - emby_boxsets = self.emby.getBoxset(dialog=self.pdialog) - - return self.compare("BoxSet", emby_boxsets['Items'], boxsets) - - def add_movies(self, items, total=None, view=None): - - for item in self.added(items, total): - if self.add_update(item, view): - self.content_pop(item.get('Name', "unknown")) - - def add_boxsets(self, items, total=None): - - for item in self.added(items, total): - self.add_updateBoxset(item) - - @catch_except() - def add_update(self, item, view=None): - # Process single movie - emby_db = self.emby_db - artwork = self.artwork - API = api.API(item) - - # If the item already exist in the local Kodi DB we'll perform a full item update - # If the item doesn't exist, we'll add it to the database - update_item = True - itemid = item['Id'] - emby_dbitem = emby_db.getItem_byId(itemid) try: - movieid = emby_dbitem[0] - fileid = emby_dbitem[1] - pathid = emby_dbitem[2] - log.info("movieid: %s fileid: %s pathid: %s", movieid, fileid, pathid) - - except TypeError: - update_item = False - log.debug("movieid: %s not found", itemid) - # movieid - movieid = self.kodi_db.create_entry() + obj['MovieId'] = e_item[0] + obj['FileId'] = e_item[1] + obj['PathId'] = e_item[2] + except TypeError as error: + update = False + LOG.debug("MovieId %s not found", obj['Id']) + obj['MovieId'] = self.create_entry() else: - if self.kodi_db.get_movie(movieid) is None: - # item is not found, let's recreate it. - update_item = False - log.info("movieid: %s missing from Kodi, repairing the entry", movieid) + if self.get(*values(obj, QU.get_movie_obj)) is None: - if not view: - # Get view tag from emby - viewtag, viewid = emby_db.getView_embyId(itemid) - log.debug("View tag found: %s", viewtag) + update = False + LOG.info("MovieId %s missing from kodi. repairing the entry.", obj['MovieId']) + + + obj['Path'] = API.get_file_path(obj['Path']) + obj['LibraryId'] = library['Id'] + obj['LibraryName'] = library['Name'] + obj['Genres'] = obj['Genres'] or [] + obj['Studios'] = [API.validate_studio(studio) for studio in (obj['Studios'] or [])] + obj['People'] = obj['People'] or [] + obj['Genre'] = " / ".join(obj['Genres']) + obj['Writers'] = " / ".join(obj['Writers'] or []) + obj['Directors'] = " / ".join(obj['Directors'] or []) + obj['Plot'] = API.get_overview(obj['Plot']) + obj['Resume'] = API.adjust_resume((obj['Resume'] or 0) / 10000000.0) + obj['Runtime'] = round(float((obj['Runtime'] or 0) / 10000000.0), 6) + obj['People'] = API.get_people_artwork(obj['People']) + obj['DateAdded'] = obj['DateAdded'].split('.')[0].replace('T', " ") + obj['DatePlayed'] = (obj['DatePlayed'] or obj['DateAdded']).split('.')[0].replace('T', " ") + obj['PlayCount'] = API.get_playcount(obj['Played'], obj['PlayCount']) + obj['Artwork'] = API.get_all_artwork(self.objects.map(item, 'Artwork')) + obj['Video'] = API.video_streams(obj['Video'] or [], obj['Container']) + obj['Audio'] = API.audio_streams(obj['Audio'] or []) + obj['Streams'] = API.media_streams(obj['Video'], obj['Audio'], obj['Subtitles']) + + self.get_path_filename(obj) + self.trailer(obj) + + if obj['Countries']: + self.add_countries(*values(obj, QU.update_country_obj)) + + tags = [] + tags.extend(obj['Tags'] or []) + tags.append(obj['LibraryName']) + + if obj['Favorite']: + tags.append('Favorite movies') + + obj['Tags'] = tags + + + if update: + self.movie_update(obj) else: - viewtag = view['name'] - viewid = view['id'] + self.movie_add(obj) - # fileId information - checksum = API.get_checksum() - dateadded = API.get_date_created() - userdata = API.get_userdata() - playcount = userdata['PlayCount'] - dateplayed = userdata['LastPlayedDate'] - # item details - people = API.get_people() - writer = " / ".join(people['Writer']) - director = " / ".join(people['Director']) - genres = item['Genres'] - title = item['Name'] - plot = API.get_overview() - shortplot = item.get('ShortOverview') - tagline = API.get_tagline() - votecount = item.get('VoteCount') - rating = item.get('CommunityRating') - year = item.get('ProductionYear') - imdb = API.get_provider('Imdb') - sorttitle = item['SortName'] - runtime = API.get_runtime() - mpaa = API.get_mpaa() - genre = " / ".join(genres) - country = API.get_country() - studios = API.get_studios() + self.update_path(*values(obj, QU.update_path_movie_obj)) + self.update_file(*values(obj, QU.update_file_obj)) + self.add_tags(*values(obj, QU.add_tags_movie_obj)) + self.add_genres(*values(obj, QU.add_genres_movie_obj)) + self.add_studios(*values(obj, QU.add_studios_movie_obj)) + self.add_playstate(*values(obj, QU.add_bookmark_obj)) + self.add_people(*values(obj, QU.add_people_movie_obj)) + self.add_streams(*values(obj, QU.add_streams_obj)) + self.artwork.add(obj['Artwork'], obj['MovieId'], "movie") + + def movie_add(self, obj): + + ''' Add object to kodi. + ''' + obj['RatingId'] = self.create_entry_rating() + self.add_ratings(*values(obj, QU.add_rating_movie_obj)) + + obj['Unique'] = self.create_entry_unique_id() + self.add_unique_id(*values(obj, QU.add_unique_id_movie_obj)) + + obj['PathId'] = self.add_path(*values(obj, QU.add_path_obj)) + obj['FileId'] = self.add_file(*values(obj, QU.add_file_obj)) + + self.add(*values(obj, QU.add_movie_obj)) + self.emby_db.add_reference(*values(obj, QUEM.add_reference_movie_obj)) + LOG.info("ADD movie [%s/%s/%s] %s: %s", obj['PathId'], obj['FileId'], obj['MovieId'], obj['Id'], obj['Title']) + + def movie_update(self, obj): + + ''' Update object to kodi. + ''' + obj['RatingId'] = self.get_rating_id(*values(obj, QU.get_rating_movie_obj)) + self.update_ratings(*values(obj, QU.update_rating_movie_obj)) + + obj['Unique'] = self.get_unique_id(*values(obj, QU.get_unique_id_movie_obj)) + self.update_unique_id(*values(obj, QU.update_unique_id_movie_obj)) + + self.update(*values(obj, QU.update_movie_obj)) + self.emby_db.update_reference(*values(obj, QUEM.update_reference_obj)) + LOG.info("UPDATE movie [%s/%s/%s] %s: %s", obj['PathId'], obj['FileId'], obj['MovieId'], obj['Id'], obj['Title']) + + def trailer(self, obj): + try: - studio = studios[0] - except IndexError: - studio = None + if obj['LocalTrailer']: - if int(item.get('LocalTrailerCount', 0)) > 0: - # There's a local trailer - url = ( - "{server}/emby/Users/{UserId}/Items/%s/LocalTrailers?format=json" - % itemid - ) - try: - result = self.do_url(url) - trailer = "plugin://plugin.video.emby/trailer/?id=%s&mode=play" % result[0]['Id'] - except Exception as error: - log.info("Failed to process local trailer: " + str(error)) - trailer = None - else: - # Try to get the youtube trailer - try: - trailer = item['RemoteTrailers'][0]['Url'] - except (KeyError, IndexError): - trailer = None - else: - try: - trailer_id = trailer.rsplit('=', 1)[1] - except IndexError: - log.info("Failed to process trailer: %s", trailer) - trailer = None - else: - trailer = "plugin://plugin.video.youtube/play/?video_id=%s" % trailer_id + trailer = self.server['api'].get_local_trailers(obj['Id']) + obj['Trailer'] = "plugin://plugin.video.emby/trailer?id=%s&mode=play" % trailer[0]['Id'] + elif obj['Trailer']: + obj['Trailer'] = "plugin://plugin.video.youtube/play/?video_id=%s" % obj['Trailer'].rsplit('=', 1)[1] + except Exception as error: - ##### GET THE FILE AND PATH ##### - playurl = API.get_file_path() + LOG.error("Failed to get trailer: %s", error) + obj['Trailer'] = None - if "\\" in playurl: - # Local path - filename = playurl.rsplit("\\", 1)[1] - else: # Network share - filename = playurl.rsplit("/", 1)[1] + def get_path_filename(self, obj): + + ''' Get the path and filename and build it into protocol://path + ''' + obj['Filename'] = obj['Path'].rsplit('\\', 1)[1] if '\\' in obj['Path'] else obj['Path'].rsplit('/', 1)[1] if self.direct_path: - # Direct paths is set the Kodi way - if not self.path_validation(playurl): - return False - path = playurl.replace(filename, "") - window('emby_pathverified', value="true") + if not validate(obj['Path']): + raise Exception("Failed to validate path. User stopped.") + + obj['Path'] = obj['Path'].replace(obj['Filename'], "") + else: - # Set plugin path and media flags using real filename - path = "plugin://plugin.video.emby.movies/" + obj['Path'] = "plugin://plugin.video.emby.movies/" params = { - - 'filename': filename.encode('utf-8'), - 'id': itemid, - 'dbid': movieid, + 'filename': obj['Filename'].encode('utf-8'), + 'id': obj['Id'], + 'dbid': obj['MovieId'], 'mode': "play" } - filename = "%s?%s" % (path, urllib.urlencode(params)) + obj['Filename'] = "%s?%s" % (obj['Path'], urllib.urlencode(params)) - ##### UPDATE THE MOVIE ##### - if update_item: - log.info("UPDATE movie itemid: %s - Title: %s", itemid, title) + @stop() + @emby_item() + def boxset(self, item, e_item): + + ''' If item does not exist, entry will be added. + If item exists, entry will be updated. - # update ratings - ratingid = self.kodi_db.get_ratingid(movieid) - self.kodi_db.update_ratings(movieid, "movie", "default", rating, votecount, ratingid) + Process movies inside boxset. + Process removals from boxset. + ''' + API = api.API(item, self.server['auth/server-address']) + obj = self.objects.map(item, 'Boxset') - # update uniqueid - uniqueid = self.kodi_db.get_uniqueid(movieid) - self.kodi_db.update_uniqueid(movieid, "movie", imdb, "imdb", uniqueid) + obj['Overview'] = API.get_overview(obj['Overview']) - # Update the movie entry - self.kodi_db.update_movie(title, plot, shortplot, tagline, votecount, uniqueid, - writer, year, uniqueid, sorttitle, runtime, mpaa, genre, - director, title, studio, trailer, country, year, movieid) - - # Update the checksum in emby table - emby_db.updateReference(itemid, checksum) - - ##### OR ADD THE MOVIE ##### - else: - log.info("ADD movie itemid: %s - Title: %s", itemid, title) - - # Add ratings - ratingid = self.kodi_db.create_entry_rating() - self.kodi_db.add_ratings(ratingid, movieid, "movie", "default", rating, votecount) - - # Add uniqueid - uniqueid = self.kodi_db.create_entry_uniqueid() - self.kodi_db.add_uniqueid(uniqueid, movieid, "movie", imdb, "imdb") - - # Add path - pathid = self.kodi_db.add_path(path) - # Add the file - fileid = self.kodi_db.add_file(filename, pathid) - - # Create the movie entry - self.kodi_db.add_movie(movieid, fileid, title, plot, shortplot, tagline, - votecount, uniqueid, writer, year, uniqueid, sorttitle, - runtime, mpaa, genre, director, title, studio, trailer, - country, year) - - # Create the reference in emby table - emby_db.addReference(itemid, movieid, "Movie", "movie", fileid, pathid, None, - checksum, viewid) - - # Update the path - self.kodi_db.update_path(pathid, path, "movies", "metadata.local") - # Update the file - self.kodi_db.update_file(fileid, filename, pathid, dateadded) - - # Process countries - if 'ProductionLocations' in item: - self.kodi_db.add_countries(movieid, item['ProductionLocations']) - # Process cast - people = artwork.get_people_artwork(item['People']) - self.kodi_db.add_people(movieid, people, "movie") - # Process genres - self.kodi_db.add_genres(movieid, genres, "movie") - # Process artwork - artwork.add_artwork(artwork.get_all_artwork(item), movieid, "movie", self.kodicursor) - # Process stream details - streams = API.get_media_streams() - self.kodi_db.add_streams(fileid, streams, runtime) - # Process studios - self.kodi_db.add_studios(movieid, studios, "movie") - # Process tags: view, emby tags - tags = [viewtag] - tags.extend(item['Tags']) - if userdata['Favorite']: - tags.append("Favorite movies") - self.kodi_db.add_tags(movieid, tags, "movie") - # Process playstates - resume = API.adjust_resume(userdata['Resume']) - total = round(float(runtime), 6) - self.kodi_db.add_playstate(fileid, resume, total, playcount, dateplayed) - - return True - - def add_updateBoxset(self, boxset): - - emby = self.emby - emby_db = self.emby_db - artwork = self.artwork - API = api.API(boxset) - - boxsetid = boxset['Id'] - title = boxset['Name'] - checksum = API.get_checksum() - emby_dbitem = emby_db.getItem_byId(boxsetid) try: - setid = emby_dbitem[0] - self.kodi_db.update_boxset(setid, title) - except TypeError: - setid = self.kodi_db.add_boxset(title) + obj['SetId'] = e_item[0] + self.update_boxset(*values(obj, QU.update_set_obj)) + except TypeError as error: - # Process artwork - artwork.add_artwork(artwork.get_all_artwork(boxset), setid, "set", self.kodicursor) + LOG.debug("SetId %s not found", obj['Id']) + obj['SetId'] = self.add_boxset(*values(obj, QU.add_set_obj)) - # Process movies inside boxset - current_movies = emby_db.getItemId_byParentId(setid, "movie") - process = [] + self.boxset_current(obj) + obj['Artwork'] = API.get_all_artwork(self.objects.map(item, 'Artwork')) + + for movie in obj['Current']: + + temp_obj = dict(obj) + temp_obj['Movie'] = movie + temp_obj['MovieId'] = obj['Current'][temp_obj['Movie']] + self.remove_from_boxset(*values(temp_obj, QU.delete_movie_set_obj)) + self.emby_db.update_parent_id(*values(temp_obj, QUEM.delete_parent_boxset_obj)) + LOG.info("DELETE from boxset [%s] %s: %s", temp_obj['SetId'], temp_obj['Title'], temp_obj['MovieId']) + + self.artwork.add(obj['Artwork'], obj['SetId'], "set") + self.emby_db.add_reference(*values(obj, QUEM.add_reference_boxset_obj)) + LOG.info("UPDATE boxset [%s] %s", obj['SetId'], obj['Title']) + + def boxset_current(self, obj): + + ''' Add or removes movies based on the current movies found in the boxset. + ''' + obj['Current'] = [] try: - # Try to convert tuple to dictionary - current = dict(current_movies) + movies = dict(self.emby_db.get_item_id_by_parent_id(*values(obj, QUEM.get_item_id_by_parent_boxset_obj))) except ValueError: - current = {} + movies = {} - # Sort current titles - for current_movie in current: - process.append(current_movie) + for movie in movies: + obj['Current'].append(movie) - # New list to compare - for movie in emby.getMovies_byBoxset(boxsetid)['Items']: - itemid = movie['Id'] + for all_movies in server.get_movies_by_boxset(obj['Id']): + for movie in all_movies['Items']: + + temp_obj = dict(obj) + temp_obj['Title'] = movie['Name'] + temp_obj['Id'] = movie['Id'] - if not current.get(itemid): - # Assign boxset to movie - emby_dbitem = emby_db.getItem_byId(itemid) try: - movieid = emby_dbitem[0] + temp_obj['MovieId'] = self.emby_db.get_item_by_id(*values(temp_obj, QUEM.get_item_obj))[0] except TypeError: - log.info("Failed to add: %s to boxset", movie['Name']) + LOG.info("Failed to process %s to boxset.", temp_obj['Title']) + continue - log.info("New addition to boxset %s: %s", title, movie['Name']) - self.kodi_db.set_boxset(setid, movieid) - # Update emby reference - emby_db.updateParentId(itemid, setid) - else: - # Remove from process, because the item still belongs - process.remove(itemid) + if temp_obj['Id'] not in movies: - # Process removals from boxset - for movie in process: - movieid = current[movie] - log.info("Remove from boxset %s: %s", title, movieid) - self.kodi_db.remove_from_boxset(movieid) - # Update emby reference - emby_db.updateParentId(movie, None) + self.set_boxset(*values(temp_obj, QU.update_movie_set_obj)) + self.emby_db.update_parent_id(*values(temp_obj, QUEM.update_parent_movie_obj)) + LOG.info("ADD to boxset [%s/%s] %s: %s to boxset", temp_obj['SetId'], temp_obj['MovieId'], temp_obj['Title'], temp_obj['Id']) + else: + obj['Current'].remove(temp_obj['Id']) - # Update the reference in the emby table - emby_db.addReference(boxsetid, setid, "BoxSet", mediatype="set", checksum=checksum) + def boxsets_reset(self): - def updateUserdata(self, item): - # This updates: Favorite, LastPlayedDate, Playcount, PlaybackPositionTicks - # Poster with progress bar - emby_db = self.emby_db - API = api.API(item) + ''' Special function to remove all existing boxsets. + ''' + boxsets = self.emby_db.get_items_by_media('set') + for boxset in boxsets: + self.remove(boxset[0]) - # Get emby information - itemid = item['Id'] - checksum = API.get_checksum() - userdata = API.get_userdata() - runtime = API.get_runtime() + @stop() + @emby_item() + def userdata(self, item, e_item): + + ''' This updates: Favorite, LastPlayedDate, Playcount, PlaybackPositionTicks + Poster with progress bar + ''' + API = api.API(item, self.server['auth/server-address']) + obj = self.objects.map(item, 'MovieUserData') - # Get Kodi information - emby_dbitem = emby_db.getItem_byId(itemid) try: - movieid = emby_dbitem[0] - fileid = emby_dbitem[1] - log.info("Update playstate for movie: %s fileid: %s", item['Name'], fileid) + obj['MovieId'] = e_item[0] + obj['FileId'] = e_item[1] except TypeError: return - # Process favorite tags - if userdata['Favorite']: - self.kodi_db.get_tag(movieid, "Favorite movies", "movie") + obj['Resume'] = API.adjust_resume((obj['Resume'] or 0) / 10000000.0) + obj['Runtime'] = round(float((obj['Runtime'] or 0) / 10000000.0), 6) + obj['PlayCount'] = API.get_playcount(obj['Played'], obj['PlayCount']) + + if obj['DatePlayed']: + obj['DatePlayed'] = obj['DatePlayed'].split('.')[0].replace('T', " ") + + if obj['Favorite']: + self.get_tag(*values(obj, QU.get_tag_movie_obj)) else: - self.kodi_db.remove_tag(movieid, "Favorite movies", "movie") + self.remove_tag(*values(obj, QU.delete_tag_movie_obj)) - # Process playstates - playcount = userdata['PlayCount'] - dateplayed = userdata['LastPlayedDate'] - resume = API.adjust_resume(userdata['Resume']) - total = round(float(runtime), 6) + LOG.debug("New resume point %s: %s", obj['Id'], obj['Resume']) + self.add_playstate(*values(obj, QU.add_bookmark_obj)) + self.emby_db.update_reference(*values(obj, QUEM.update_reference_obj)) + LOG.info("USERDATA movie [%s/%s] %s: %s", obj['FileId'], obj['MovieId'], obj['Id'], obj['Title']) - log.debug("%s New resume point: %s", itemid, resume) + @stop() + @emby_item() + def remove(self, item_id, e_item): - self.kodi_db.add_playstate(fileid, resume, total, playcount, dateplayed) - emby_db.updateReference(itemid, checksum) + ''' Remove movieid, fileid, emby reference. + Remove artwork, boxset + ''' + obj = {'Id': item_id} - def remove(self, itemid): - # Remove movieid, fileid, emby reference - emby_db = self.emby_db - artwork = self.artwork - - emby_dbitem = emby_db.getItem_byId(itemid) try: - kodiid = emby_dbitem[0] - fileid = emby_dbitem[1] - mediatype = emby_dbitem[4] - log.info("Removing %sid: %s fileid: %s", mediatype, kodiid, fileid) + obj['KodiId'] = e_item[0] + obj['FileId'] = e_item[1] + obj['Media'] = e_item[4] except TypeError: return - # Remove the emby reference - emby_db.removeItem(itemid) - # Remove artwork - artwork.delete_artwork(kodiid, mediatype, self.kodicursor) + self.artwork.delete(obj['KodiId'], obj['Media']) - if mediatype == "movie": - self.kodi_db.remove_movie(kodiid, fileid) + if obj['Media'] == 'movie': + self.delete(*values(obj, QU.delete_movie_obj)) + elif obj['Media'] == 'set': - elif mediatype == "set": - # Delete kodi boxset - boxset_movies = emby_db.getItem_byParentId(kodiid, "movie") - for movie in boxset_movies: - embyid = movie[0] - movieid = movie[1] - self.kodi_db.remove_from_boxset(movieid) - # Update emby reference - emby_db.updateParentId(embyid, None) + for movie in self.emby_db.get_item_by_parent_id(*values(obj, QUEM.get_item_by_parent_movie_obj)): + + temp_obj = dict(obj) + temp_obj['MovieId'] = movie[1] + temp_obj['Movie'] = movie[0] + self.remove_from_boxset(*values(temp_obj, QU.delete_movie_set_obj)) + self.emby_db.update_parent_id(*values(temp_obj, QUEM.delete_parent_boxset_obj)) - self.kodi_db.remove_boxset(kodiid) + self.delete_boxset(*values(obj, QU.delete_set_obj)) - log.info("Deleted %s %s from kodi database", mediatype, itemid) + self.emby_db.remove_item(*values(obj, QUEM.delete_item_obj)) + LOG.info("DELETE %s [%s/%s] %s", obj['Media'], obj['FileId'], obj['KodiId'], obj['Id']) diff --git a/resources/lib/objects/music.py b/resources/lib/objects/music.py index 72986e17..c1b2a572 100644 --- a/resources/lib/objects/music.py +++ b/resources/lib/objects/music.py @@ -2,722 +2,523 @@ ################################################################################################## +import json +import datetime import logging -from datetime import datetime +import urllib -import api -import emby as mb -import embydb_functions as embydb -import musicutils -import _kodi_music -from _common import Items, catch_except -from utils import window, settings, language as lang +from obj import Objects +from kodi import Music as KodiDb, queries_music as QU +from database import emby_db, queries as QUEM +from helper import api, catch, stop, validate, emby_item, values, library_check ################################################################################################## -log = logging.getLogger("EMBY."+__name__) +LOG = logging.getLogger("EMBY."+__name__) ################################################################################################## -class Music(Items): +class Music(KodiDb): + def __init__(self, server, embydb, musicdb, direct_path): - def __init__(self, embycursor, kodicursor, pdialog=None): + self.server = server + self.emby = embydb + self.music = musicdb + self.direct_path = direct_path - self.embycursor = embycursor - self.emby_db = embydb.Embydb_Functions(self.embycursor) - self.kodicursor = kodicursor - self.kodi_db = _kodi_music.KodiMusic(self.kodicursor) - self.pdialog = pdialog + self.emby_db = emby_db.EmbyDatabase(embydb.cursor) + self.objects = Objects() - self.new_time = int(settings('newmusictime'))*1000 - self.directstream = settings('streamMusic') == "true" - self.enableimportsongrating = settings('enableImportSongRating') == "true" - self.enableexportsongrating = settings('enableExportSongRating') == "true" - self.enableupdatesongrating = settings('enableUpdateSongRating') == "true" - self.userid = window('emby_currUser') - self.server = window('emby_server%s' % self.userid) + KodiDb.__init__(self, musicdb.cursor) - Items.__init__(self) + def __getitem__(self, key): - def _get_func(self, item_type, action): + if key in ('MusicArtist', 'AlbumArtist'): + return self.artist + elif key == 'MusicAlbum': + return self.album + elif key == 'Audio': + return self.song + elif key == 'UserData': + return self.userdata + elif key in 'Removed': + return self.remove - if item_type == "MusicAlbum": - actions = { - 'added': self.add_albums, - 'update': self.add_updateAlbum, - 'userdata': self.updateUserdata, - 'remove': self.remove - } - elif item_type in ("MusicArtist", "AlbumArtist"): - actions = { - 'added': self.add_artists, - 'update': self.add_updateArtist, - 'remove': self.remove - } - elif item_type == "Audio": - actions = { - 'added': self.add_songs, - 'update': self.add_updateSong, - 'userdata': self.updateUserdata, - 'remove': self.remove - } - else: - log.info("Unsupported item_type: %s", item_type) - actions = {} + @stop() + @emby_item() + @library_check() + def artist(self, item, e_item, library, artist_type=None): - return actions.get(action) + ''' If item does not exist, entry will be added. + If item exists, entry will be updated. + ''' + API = api.API(item, self.server['auth/server-address']) + obj = self.objects.map(item, 'Artist') + update = True - def compare_all(self): - - # Process artists - self.compare_artists() - # Process albums - self.compare_albums() - # Process songs - self.compare_songs() - - return True - - def compare_artists(self): - - all_embyartistsIds = set() - update_list = list() - - if self.pdialog: - self.pdialog.update(heading=lang(29999), message="%s Artists..." % lang(33031)) - - artists = dict(self.emby_db.get_checksum('MusicArtist')) - album_artists = dict(self.emby_db.get_checksum('AlbumArtist')) - emby_artists = self.emby.getArtists(dialog=self.pdialog) - - for item in emby_artists['Items']: - - if self.should_stop(): - return False - - item_id = item['Id'] - API = api.API(item) - - all_embyartistsIds.add(item_id) - if item_id in artists: - if artists[item_id] != API.get_checksum(): - # Only update if artist is not in Kodi or checksum is different - update_list.append(item_id) - elif album_artists.get(item_id) != API.get_checksum(): - # Only update if artist is not in Kodi or checksum is different - update_list.append(item_id) - - #compare_to.pop(item_id, None) - - log.info("Update for Artist: %s", update_list) - - emby_items = self.emby.getFullItems(update_list) - total = len(update_list) - - if self.pdialog: - self.pdialog.update(heading="Processing Artists / %s items" % total) - - # Process additions and updates - if emby_items: - self.process_all("MusicArtist", "update", emby_items, total) - # Process removals - for artist in artists: - if artist not in all_embyartistsIds and artists[artist] is not None: - self.remove(artist) - - def compare_albums(self): - - if self.pdialog: - self.pdialog.update(heading=lang(29999), message="%s Albums..." % lang(33031)) - - albums = dict(self.emby_db.get_checksum('MusicAlbum')) - emby_albums = self.emby.getAlbums(basic=True, dialog=self.pdialog) - - return self.compare("MusicAlbum", emby_albums['Items'], albums) - - def compare_songs(self): - - if self.pdialog: - self.pdialog.update(heading=lang(29999), message="%s Songs..." % lang(33031)) - - songs = dict(self.emby_db.get_checksum('Audio')) - emby_songs = self.emby.getSongs(basic=True, dialog=self.pdialog) - - return self.compare("Audio", emby_songs['Items'], songs) - - def add_artists(self, items, total=None): - - for item in self.added(items, total): - if self.add_updateArtist(item): - # Add albums - for all_albums in mb.get_albums_by_artist(item['Id']): - self.add_albums(all_albums['Items']) - - def add_albums(self, items, total=None): - - update = True if not self.total else False - - for item in self.added(items, total, update): - self.title = "%s - %s" % (item.get('AlbumArtist', "unknown"), self.title) - - if self.add_updateAlbum(item): - # Add songs - for all_songs in mb.get_items(item['Id'], "Audio"): - self.add_songs(all_songs['Items']) - - def add_songs(self, items, total=None): - - update = True if not self.total else False - - for item in self.added(items, total, update): - self.title = "%s - %s" % (item.get('AlbumArtist', "unknown"), self.title) - - if self.add_updateSong(item): - self.content_pop(self.title) - - @catch_except() - def add_updateArtist(self, item, artisttype="MusicArtist"): - # Process a single artist - kodicursor = self.kodicursor - emby_db = self.emby_db - artwork = self.artwork - API = api.API(item) - - update_item = True - itemid = item['Id'] - emby_dbitem = emby_db.getItem_byId(itemid) try: - artistid = emby_dbitem[0] - except TypeError: - update_item = False - log.debug("artistid: %s not found", itemid) - artistid = None + obj['ArtistId'] = e_item[0] + except TypeError as error: + + update = False + obj['ArtistId'] = None + LOG.debug("ArtistId %s not found", obj['Id']) else: - if self.kodi_db.validate_artist(artistid) is None: - # item is not found, let's recreate it. - update_item = False - log.info("artistid: %s missing from Kodi, repairing the entry", artistid) + if self.validate_artist(*values(obj, QU.get_artist_by_id_obj)) is None: - ##### The artist details ##### - lastScraped = datetime.now().strftime('%Y-%m-%d %H:%M:%S') - dateadded = API.get_date_created() - checksum = API.get_checksum() + update = False + LOG.info("ArtistId %s missing from kodi. repairing the entry.", obj['ArtistId']) - name = item['Name'] - musicBrainzId = API.get_provider('MusicBrainzArtist') - genres = " / ".join(item.get('Genres')) - bio = API.get_overview() + obj['LibraryId'] = library['Id'] + obj['LibraryName'] = library['Name'] + obj['LastScraped'] = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') + obj['ArtistType'] = artist_type or "MusicArtist" + obj['Genre'] = " / ".join(obj['Genres'] or []) + obj['Bio'] = API.get_overview(obj['Bio']) + obj['Artwork'] = API.get_all_artwork(self.objects.map(item, 'ArtworkMusic'), True) + obj['Thumb'] = obj['Artwork']['Primary'] + obj['Backdrops'] = obj['Artwork']['Backdrop'] or "" - # Associate artwork - artworks = artwork.get_all_artwork(item, parent_info=True) - thumb = artworks['Primary'] - backdrops = artworks['Backdrop'] # List + if obj['Thumb']: + obj['Thumb'] = "<thumb>%s</thumb>" % obj['Thumb'] - if thumb: - thumb = "<thumb>%s</thumb>" % thumb - if backdrops: - fanart = "<fanart>%s</fanart>" % backdrops[0] + if obj['Backdrops']: + obj['Backdrops'] = "<fanart>%s</fanart>" % obj['Backdrops'][0] + + + if update: + self.artist_update(obj) else: - fanart = "" + self.artist_add(obj) - ##### UPDATE THE ARTIST ##### - if update_item: - log.info("UPDATE artist itemid: %s - Name: %s", itemid, name) - # Update the checksum in emby table - emby_db.updateReference(itemid, checksum) + self.update(obj['Genre'], obj['Bio'], obj['Thumb'], obj['Backdrops'], obj['LastScraped'], obj['ArtistId']) + self.artwork.add(obj['Artwork'], obj['ArtistId'], "artist") - ##### OR ADD THE ARTIST ##### - else: - log.info("ADD artist itemid: %s - Name: %s", itemid, name) - # safety checks: It looks like Emby supports the same artist multiple times. - # Kodi doesn't allow that. In case that happens we just merge the artist entries. - artistid = self.kodi_db.get_artist(name, musicBrainzId, artistid) - # Create the reference in emby table - emby_db.addReference(itemid, artistid, artisttype, "artist", checksum=checksum) - - # Process the artist - if self.kodi_version < 18: - self.kodi_db.update_artist(genres, bio, thumb, fanart, lastScraped, artistid) - else: - self.kodi_db.update_artist_18(genres, bio, thumb, fanart, lastScraped, artistid) - - # Update artwork - artwork.add_artwork(artworks, artistid, "artist", kodicursor) - - return True - - @catch_except() - def add_updateAlbum(self, item): - # Process a single artist - emby = self.emby - kodicursor = self.kodicursor - emby_db = self.emby_db - artwork = self.artwork - API = api.API(item) - - update_item = True - itemid = item['Id'] - emby_dbitem = emby_db.getItem_byId(itemid) - try: - albumid = emby_dbitem[0] - except TypeError: - update_item = False - log.debug("albumid: %s not found", itemid) - albumid = None - else: - if self.kodi_db.validate_album(albumid) is None: - # item is not found, let's recreate it. - update_item = False - log.info("albumid: %s missing from Kodi, repairing the entry", albumid) - - ##### The album details ##### - lastScraped = datetime.now().strftime('%Y-%m-%d %H:%M:%S') - dateadded = API.get_date_created() - userdata = API.get_userdata() - checksum = API.get_checksum() - - name = item['Name'] - musicBrainzId = API.get_provider('MusicBrainzAlbum') - year = item.get('ProductionYear') - genres = item.get('Genres') - genre = " / ".join(genres) - bio = API.get_overview() - rating = 0 - artists = item['AlbumArtists'] - artistname = [] - for artist in artists: - artistname.append(artist['Name']) - artistname = " / ".join(artistname) - - # Associate artwork - artworks = artwork.get_all_artwork(item, parent_info=True) - thumb = artworks['Primary'] - if thumb: - thumb = "<thumb>%s</thumb>" % thumb - - ##### UPDATE THE ALBUM ##### - if update_item: - log.info("UPDATE album itemid: %s - Name: %s", itemid, name) - # Update the checksum in emby table - emby_db.updateReference(itemid, checksum) - - ##### OR ADD THE ALBUM ##### - else: - log.info("ADD album itemid: %s - Name: %s", itemid, name) - # safety checks: It looks like Emby supports the same artist multiple times. - # Kodi doesn't allow that. In case that happens we just merge the artist entries. - albumid = self.kodi_db.get_album(name, musicBrainzId, albumid) - # Create the reference in emby table - emby_db.addReference(itemid, albumid, "MusicAlbum", "album", checksum=checksum) - - # Process the album info - if self.kodi_version < 18: - self.kodi_db.update_album(artistname, year, genre, bio, thumb, rating, lastScraped, - "album", albumid) - else: - self.kodi_db.update_album_18(artistname, year, genre, bio, thumb, rating, lastScraped, - "album", albumid) - - # Assign main artists to album - for artist in item['AlbumArtists']: - artistname = artist['Name'] - artistId = artist['Id'] - emby_dbartist = emby_db.getItem_byId(artistId) - try: - artistid = emby_dbartist[0] - except TypeError: - # Artist does not exist in emby database, create the reference - artist = emby.getItem(artistId) - self.add_updateArtist(artist, artisttype="AlbumArtist") - emby_dbartist = emby_db.getItem_byId(artistId) - artistid = emby_dbartist[0] - else: - # Best take this name over anything else. - self.kodi_db.update_artist_name(artistid, artistname) - - # Add artist to album - self.kodi_db.link_artist(artistid, albumid, artistname) - # Update emby reference with parentid - emby_db.updateParentId(artistId, albumid) - - for artist in item['ArtistItems']: - artistId = artist['Id'] - emby_dbartist = emby_db.getItem_byId(artistId) - try: - artistid = emby_dbartist[0] - except TypeError: - pass - else: - # Update discography - self.kodi_db.add_discography(artistid, name, year) - - # Add genres - if self.kodi_version < 18: - self.kodi_db.add_genres(albumid, genres, "album") - # Update artwork - artwork.add_artwork(artworks, albumid, "album", kodicursor) - - return True - - @catch_except() - def add_updateSong(self, item): - # Process single song - kodicursor = self.kodicursor - emby = self.emby - emby_db = self.emby_db - artwork = self.artwork - API = api.API(item) - - update_item = True - itemid = item['Id'] - emby_dbitem = emby_db.getItem_byId(itemid) - try: - songid = emby_dbitem[0] - pathid = emby_dbitem[2] - albumid = emby_dbitem[3] - except TypeError: - update_item = False - log.debug("songid: %s not found", itemid) - songid = self.kodi_db.create_entry_song() - else: - if self.kodi_db.validate_song(songid) is None: - # item is not found, let's recreate it. - update_item = False - log.info("songid: %s missing from Kodi, repairing the entry", songid) - - ##### The song details ##### - checksum = API.get_checksum() - dateadded = API.get_date_created() - userdata = API.get_userdata() - playcount = userdata['PlayCount'] - dateplayed = userdata['LastPlayedDate'] - - # item details - title = item['Name'] - musicBrainzId = API.get_provider('MusicBrainzTrackId') - genres = item.get('Genres') - genre = " / ".join(genres) - artists = " / ".join(item['Artists']) - tracknumber = item.get('IndexNumber', 0) - disc = item.get('ParentIndexNumber', 1) - if disc == 1: - track = tracknumber - else: - track = disc*2**16 + tracknumber - year = item.get('ProductionYear') - duration = API.get_runtime() - rating = 0 - - #if enabled, try to get the rating from file and/or emby - if not self.directstream: - rating, comment, hasEmbeddedCover = musicutils.getAdditionalSongTags(itemid, rating, API, kodicursor, emby_db, self.enableimportsongrating, self.enableexportsongrating, self.enableupdatesongrating) - else: - hasEmbeddedCover = False - comment = API.get_overview() - - - ##### GET THE FILE AND PATH ##### - if self.directstream: - path = "%s/emby/Audio/%s/" % (self.server, itemid) - filename = "stream.%s?static=true" % item['MediaSources'][0]['Container'] - else: - playurl = API.get_file_path() - - if "\\" in playurl: - # Local path - filename = playurl.rsplit("\\", 1)[1] - else: # Network share - filename = playurl.rsplit("/", 1)[1] - - # Direct paths is set the Kodi way - if not self.path_validation(playurl): - return False - - path = playurl.replace(filename, "") - window('emby_pathverified', value="true") - - ##### UPDATE THE SONG ##### - if update_item: - log.info("UPDATE song itemid: %s - Title: %s", itemid, title) - - # Update path - self.kodi_db.update_path(pathid, path) - - # Update the song entry - if self.kodi_version < 18: - self.kodi_db.update_song(albumid, artists, genre, title, track, duration, year, - filename, playcount, dateplayed, rating, comment, songid) - else: - self.kodi_db.update_song_18(albumid, artists, genre, title, track, duration, year, - filename, playcount, dateplayed, rating, comment, songid) - - # Update the checksum in emby table - emby_db.updateReference(itemid, checksum) - - ##### OR ADD THE SONG ##### - else: - log.info("ADD song itemid: %s - Title: %s", itemid, title) - - # Add path - pathid = self.kodi_db.add_path(path) - - try: - # Get the album - emby_dbalbum = emby_db.getItem_byId(item['AlbumId']) - albumid = emby_dbalbum[0] - except KeyError: - # Verify if there's an album associated. - album_name = item.get('Album') - if album_name: - log.info("Creating virtual music album for song: %s", itemid) - albumid = self.kodi_db.get_album(album_name, API.get_provider('MusicBrainzAlbum')) - emby_db.addReference("%salbum%s" % (itemid, albumid), albumid, "MusicAlbum_", "album") - else: - # No album Id associated to the song. - log.error("Song itemid: %s has no albumId associated", itemid) - return False - - except TypeError: - # No album found. Let's create it - log.info("Album database entry missing.") - emby_albumId = item['AlbumId'] - album = emby.getItem(emby_albumId) - self.add_updateAlbum(album) - emby_dbalbum = emby_db.getItem_byId(emby_albumId) - try: - albumid = emby_dbalbum[0] - log.info("Found albumid: %s", albumid) - except TypeError: - # No album found, create a single's album - log.info("Failed to add album. Creating singles.") - albumid = self.kodi_db.create_entry_album() - if self.kodi_version == 16: - self.kodi_db.add_single(albumid, genre, year, "single") - - elif self.kodi_version == 15: - self.kodi_db.add_single_15(albumid, genre, year, dateadded, "single") - - else: - # TODO: Remove Helix code when Krypton is RC - self.kodi_db.add_single_14(albumid, genre, year, dateadded) - - # Create the song entry - if self.kodi_version < 18: - self.kodi_db.add_song(songid, albumid, pathid, artists, genre, title, track, duration, - year, filename, musicBrainzId, playcount, dateplayed, rating) - else: - self.kodi_db.add_song_18(songid, albumid, pathid, artists, genre, title, track, duration, - year, filename, musicBrainzId, playcount, dateplayed, rating) - - # Create the reference in emby table - emby_db.addReference(itemid, songid, "Audio", "song", pathid=pathid, parentid=albumid, - checksum=checksum) - - # Link song to album - if self.kodi_version < 18: - self.kodi_db.link_song_album(songid, albumid, track, title, duration) + def artist_add(self, obj): - # Create default role - self.kodi_db.add_role() + ''' Add object to kodi. - # Link song to artists - for index, artist in enumerate(item['ArtistItems']): + safety checks: It looks like Emby supports the same artist multiple times. + Kodi doesn't allow that. In case that happens we just merge the artist entries. + ''' + obj['ArtistId'] = self.get(*values(obj, QU.get_artist_obj)) + self.emby_db.add_reference(*values(obj, QUEM.add_reference_artist_obj)) + LOG.info("ADD artist [%s] %s: %s", obj['ArtistId'], obj['Name'], obj['Id']) - artist_name = artist['Name'] - artist_eid = artist['Id'] - artist_edb = emby_db.getItem_byId(artist_eid) - try: - artistid = artist_edb[0] - except TypeError: - # Artist is missing from emby database, add it. - artist_full = emby.getItem(artist_eid) - self.add_updateArtist(artist_full) - artist_edb = emby_db.getItem_byId(artist_eid) - artistid = artist_edb[0] if artist_edb else None - except Exception: - artistid = None + def artist_update(self, obj): - if artistid: - # Link song to artist - self.kodi_db.link_song_artist(artistid, songid, index, artist_name) + ''' Update object to kodi. + ''' + self.emby_db.update_reference(*values(obj, QUEM.update_reference_obj)) + LOG.info("UPDATE artist [%s] %s: %s", obj['ArtistId'], obj['Name'], obj['Id']) - # Verify if album artist exists - album_artists = [] - for artist in item['AlbumArtists']: - artist_name = artist['Name'] - album_artists.append(artist_name) - artist_eid = artist['Id'] - artist_edb = emby_db.getItem_byId(artist_eid) - try: - artistid = artist_edb[0] - except TypeError: - # Artist is missing from emby database, add it. - artist_full = emby.getItem(artist_eid) - self.add_updateArtist(artist_full) - artist_edb = emby_db.getItem_byId(artist_eid) - artistid = artist_edb[0] - finally: - # Link artist to album - self.kodi_db.link_artist(artistid, albumid, artist_name) - # Update discography - if item.get('Album'): - self.kodi_db.add_discography(artistid, item['Album'], 0) + @stop() + @emby_item() + def album(self, item, e_item): - # Artist names - album_artists = " / ".join(album_artists) - if self.kodi_version < 18: - self.kodi_db.get_album_artist(albumid, album_artists) + ''' Update object to kodi. + ''' + API = api.API(item, self.server['auth/server-address']) + obj = self.objects.map(item, 'Album') + update = True + + try: + obj['AlbumId'] = e_item[0] + except TypeError as error: + + update = False + obj['AlbumId'] = None + LOG.debug("AlbumId %s not found", obj['Id']) else: - self.kodi_db.get_album_artist_18(albumid, album_artists) + if self.validate_album(*values(obj, QU.get_album_by_id_obj)) is None: - # Add genres - self.kodi_db.add_genres(songid, genres, "song") + update = False + LOG.info("AlbumId %s missing from kodi. repairing the entry.", obj['AlbumId']) - # Update artwork - allart = artwork.get_all_artwork(item, parent_info=True) - if hasEmbeddedCover: - allart["Primary"] = "image://music@" + artwork.single_urlencode(playurl) - artwork.add_artwork(allart, songid, "song", kodicursor) + obj['Rating'] = 0 + obj['LastScraped'] = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') + obj['Genres'] = obj['Genres'] or [] + obj['Genre'] = " / ".join(obj['Genres']) + obj['Bio'] = API.get_overview(obj['Bio']) + obj['Artists'] = " / ".join(obj['Artists'] or []) + obj['Artwork'] = API.get_all_artwork(self.objects.map(item, 'ArtworkMusic'), True) + obj['Thumb'] = obj['Artwork']['Primary'] - if item.get('AlbumId') is None: - # Update album artwork - artwork.add_artwork(allart, albumid, "album", kodicursor) - - return True - - def updateUserdata(self, item): - # This updates: Favorite, LastPlayedDate, Playcount, PlaybackPositionTicks - # Poster with progress bar - kodicursor = self.kodicursor - emby_db = self.emby_db - API = api.API(item) - - # Get emby information - itemid = item['Id'] - checksum = API.get_checksum() - userdata = API.get_userdata() - rating = 0 - - # Get Kodi information - emby_dbitem = emby_db.getItem_byId(itemid) - try: - kodiid = emby_dbitem[0] - mediatype = emby_dbitem[4] - log.info("Update playstate for %s: %s", mediatype, item['Name']) - except TypeError: - return - - if mediatype == "song": - - #should we ignore this item ? - #happens when userdata updated by ratings method - if window("ignore-update-%s" %itemid): - window("ignore-update-%s" %itemid,clear=True) - return - - # Process playstates - playcount = userdata['PlayCount'] - dateplayed = userdata['LastPlayedDate'] - - #process item ratings - rating, comment, hasEmbeddedCover = musicutils.getAdditionalSongTags(itemid, rating, API, kodicursor, emby_db, self.enableimportsongrating, self.enableexportsongrating, self.enableupdatesongrating) - self.kodi_db.rate_song(playcount, dateplayed, rating, kodiid) - - emby_db.updateReference(itemid, checksum) - - def remove(self, itemid): - # Remove kodiid, fileid, pathid, emby reference - emby_db = self.emby_db - - emby_dbitem = emby_db.getItem_byId(itemid) - try: - kodiid = emby_dbitem[0] - mediatype = emby_dbitem[4] - log.info("Removing %s kodiid: %s", mediatype, kodiid) - except TypeError: - return - - ##### PROCESS ITEM ##### - - # Remove the emby reference - emby_db.removeItem(itemid) + if obj['Thumb']: + obj['Thumb'] = "<thumb>%s</thumb>" % obj['Thumb'] - ##### IF SONG ##### + if update: + self.album_update(obj) + else: + self.album_add(obj) - if mediatype == "song": - # Delete song - self.removeSong(kodiid) - # This should only address single song scenario, where server doesn't actually - # create an album for the song. - emby_db.removeWildItem(itemid) - for item in emby_db.getItem_byWildId(itemid): + self.artist_link(obj) + self.artist_discography(obj) + self.update_album(*values(obj, QU.update_album_obj)) + self.add_genres(*values(obj, QU.add_genres_obj)) + self.artwork.add(obj['Artwork'], obj['AlbumId'], "album") - item_kid = item[0] - item_mediatype = item[1] + def album_add(self, obj): + + ''' Add object to kodi. - if item_mediatype == "album": - childs = emby_db.getItem_byParentId(item_kid, "song") - if not childs: - # Delete album - self.removeAlbum(item_kid) + safety checks: It looks like Emby supports the same artist multiple times. + Kodi doesn't allow that. In case that happens we just merge the artist entries. + ''' + obj['AlbumId'] = self.get_album(*values(obj, QU.get_album_obj)) + self.emby_db.add_reference(*values(obj, QUEM.add_reference_album_obj)) + LOG.info("ADD album [%s] %s: %s", obj['AlbumId'], obj['Title'], obj['Id']) - ##### IF ALBUM ##### + def album_update(self, obj): + + ''' Update object to kodi. + ''' + self.emby_db.update_reference(*values(obj, QUEM.update_reference_obj)) + LOG.info("UPDATE album [%s] %s: %s", obj['AlbumId'], obj['Title'], obj['Id']) - elif mediatype == "album": - # Delete songs, album - album_songs = emby_db.getItem_byParentId(kodiid, "song") - for song in album_songs: - self.removeSong(song[1]) + def artist_discography(self, obj): + + ''' Update the artist's discography. + ''' + for artist in obj['ArtistItems']: + + temp_obj = dict(obj) + temp_obj['Id'] = artist['Id'] + temp_obj['AlbumId'] = obj['Id'] + + try: + temp_obj['ArtistId'] = self.emby_db.get_item_by_id(*values(temp_obj, QUEM.get_item_obj))[0] + except TypeError: + continue else: - # Remove emby songs - emby_db.removeItems_byParentId(kodiid, "song") + self.add_discography(*values(temp_obj, QU.update_discography_obj)) - # Remove the album - self.removeAlbum(kodiid) + self.emby_db.update_parent_id(*values(temp_obj, QUEM.update_parent_album_obj)) - ##### IF ARTIST ##### + def artist_link(self, obj): - elif mediatype == "artist": - # Delete songs, album, artist - albums = emby_db.getItem_byParentId(kodiid, "album") - for album in albums: - albumid = album[1] - album_songs = emby_db.getItem_byParentId(albumid, "song") - for song in album_songs: - self.removeSong(song[1]) + ''' Assign main artists to album. + Artist does not exist in emby database, create the reference. + ''' + for artist in obj['AlbumArtists']: + + temp_obj = dict(obj) + temp_obj['Name'] = artist['Name'] + temp_obj['Id'] = artist['Id'] + + try: + temp_obj['ArtistId'] = self.emby_db.get_item_by_id(*values(temp_obj, QUEM.get_item_obj))[0] + except TypeError: + continue + """ + self.artist(self.server['api'].get_item(temp_obj['Id']), artist_type="AlbumArtist") + + try: + temp_obj['ArtistId'] = self.emby_db.get_item_by_id(*values(temp_obj, QUEM.get_item_obj))[0] + except TypeError: + continue + """ + else: + self.update_artist_name(*values(temp_obj, QU.update_artist_name_obj)) + + self.link(*values(temp_obj, QU.update_link_obj)) + + + @stop() + @emby_item() + def song(self, item, e_item): + + ''' Update object to kodi. + ''' + API = api.API(item, self.server['auth/server-address']) + obj = self.objects.map(item, 'Song') + update = True + + try: + obj['SongId'] = e_item[0] + obj['PathId'] = e_item[2] + obj['AlbumId'] = e_item[3] + except TypeError as error: + + update = False + obj['SongId'] = self.create_entry_song() + LOG.debug("SongId %s not found", obj['Id']) + else: + if self.validate_song(*values(obj, QU.get_song_by_id_obj)) is None: + + update = False + LOG.info("SongId %s missing from kodi. repairing the entry.", obj['SongId']) + + self.get_song_path_filename(obj, API) + + obj['Rating'] = 0 + obj['Genres'] = obj['Genres'] or [] + obj['PlayCount'] = API.get_playcount(obj['Played'], obj['PlayCount']) + obj['Runtime'] = (obj['Runtime'] or 0) / 10000000.0 + obj['Genre'] = " / ".join(obj['Genres']) + obj['Artists'] = " / ".join(obj['Artists'] or []) + obj['AlbumArtists'] = obj['AlbumArtists'] or [] + obj['Index'] = obj['Index'] or 0 + obj['Disc'] = obj['Disc'] or 1 + obj['EmbedCover'] = False + obj['Comment'] = API.get_overview(obj['Comment']) + obj['Artwork'] = API.get_all_artwork(self.objects.map(item, 'ArtworkMusic'), True) + + if obj['DateAdded']: + obj['DateAdded'] = obj['DateAdded'].split('.')[0].replace('T', " ") + + if obj['DateAdded'] or obj['DatePlayed']: + obj['DatePlayed'] = (obj['DatePlayed'] or obj['DateAdded']).split('.')[0].replace('T', " ") + + if obj['Disc'] != 1: + obj['Index'] = obj['Disc'] * 2 ** 16 + obj['Index'] + + + if update: + self.song_update(obj) + else: + self.song_add(obj) + + + self.link_song_album(*values(obj, QU.update_song_album_obj)) + self.add_role(*values(obj, QU.update_role_obj)) # defaultt role + self.song_artist_link(obj) + self.song_artist_discography(obj) + + obj['strAlbumArtists'] = " / ".join(obj['AlbumArtists']) + self.get_album_artist(*values(obj, QU.get_album_artist_obj)) + + self.add_genres(*values(obj, QU.update_genre_song_obj)) + self.artwork.add(obj['Artwork'], obj['SongId'], "song") + + if obj['SongAlbumId'] is None: + self.artwork.add(obj['Artwork'], obj['AlbumId'], "album") + + def song_add(self, obj): + + ''' Add object to kodi. + + Verify if there's an album associated. + If no album found, create a single's album + ''' + obj['PathId'] = self.add_path(obj['Path']) + + try: + obj['AlbumId'] = self.emby_db.get_item_by_id(*values(obj, QUEM.get_item_song_obj))[0] + except TypeError: + self.album(self.server['api'].get_item(obj['SongAlbumId'])) + + try: + obj['AlbumId'] = self.emby_db.get_item_by_id(*values(obj, QUEM.get_item_song_obj))[0] + except TypeError: + self.single(obj) + + self.add_song(*values(obj, QU.add_song_obj)) + self.emby_db.add_reference(*values(obj, QUEM.add_reference_song_obj)) + LOG.debug("ADD song [%s/%s/%s] %s: %s", obj['PathId'], obj['AlbumId'], obj['SongId'], obj['Id'], obj['Title']) + + def song_update(self, obj): + + ''' Update object to kodi. + ''' + self.update_path(*values(obj, QU.update_path_obj)) + + self.update_song(*values(obj, QU.update_song_obj)) + self.emby_db.update_reference(*values(obj, QUEM.update_reference_obj)) + LOG.info("UPDATE song [%s/%s/%s] %s: %s", obj['PathId'], obj['AlbumId'], obj['SongId'], obj['Id'], obj['Title']) + + def get_song_path_filename(self, obj, api): + + ''' Get the path and filename and build it into protocol://path + ''' + obj['Path'] = api.get_file_path(obj['Path']) + obj['Filename'] = obj['Path'].rsplit('\\', 1)[1] if '\\' in obj['Path'] else obj['Path'].rsplit('/', 1)[1] + + if self.direct_path: + + if not validate(obj['Path']): + raise Exception("Failed to validate path. User stopped.") + + obj['Path'] = obj['Path'].replace(obj['Filename'], "") + + else: + obj['Path'] = "%s/emby/Audio/%s/" % (self.server['auth/server-address'], obj['Id']) + obj['Filename'] = "stream.%s?static=true" % obj['Container'] + + def song_artist_discography(self, obj): + + ''' Update the artist's discography. + ''' + artists = [] + for artist in obj['AlbumArtists']: + + temp_obj = dict(obj) + temp_obj['Name'] = artist['Name'] + temp_obj['Id'] = artist['Id'] + + artists.append(temp_obj['Name']) + + try: + temp_obj['ArtistId'] = self.emby_db.get_item_by_id(*values(temp_obj, QUEM.get_item_obj))[0] + except TypeError: + self.artist(self.server['api'].get_item(temp_obj['Id'])) + + try: + temp_obj['ArtistId'] = self.emby_db.get_item_by_id(*values(temp_obj, QUEM.get_item_obj))[0] + except TypeError: + continue + + self.link(*values(temp_obj, QU.update_link_obj)) + + if obj['Album']: + + temp_obj['Title'] = obj['Album'] + temp_obj['Year'] = 0 + self.add_discography(*values(temp_obj, QU.update_discography_obj)) + + obj['AlbumArtists'] = artists + + def song_artist_link(self, obj): + + ''' Assign main artists to song. + Artist does not exist in emby database, create the reference. + ''' + for index, artist in enumerate(obj['ArtistItems']): + + temp_obj = dict(obj) + temp_obj['Name'] = artist['Name'] + temp_obj['Id'] = artist['Id'] + temp_obj['Index'] = index + + try: + temp_obj['ArtistId'] = self.emby_db.get_item_by_id(*values(temp_obj, QUEM.get_item_obj))[0] + except TypeError: + self.artist(self.server['api'].get_item(temp_obj['Id'])) + + try: + temp_obj['ArtistId'] = self.emby_db.get_item_by_id(*values(temp_obj, QUEM.get_item_obj))[0] + except TypeError: + continue + + self.link_song_artist(*values(temp_obj, QU.update_song_artist_obj)) + + def single(self, obj): + + obj['AlbumId'] = self.create_entry_album() + self.add_single(*values(obj, QU.add_single_obj)) + + + @stop() + @emby_item() + def userdata(self, item, e_item): + + ''' This updates: Favorite, LastPlayedDate, Playcount, PlaybackPositionTicks + Poster with progress bar + ''' + API = api.API(item, self.server['auth/server-address']) + obj = self.objects.map(item, 'SongUserData') + + try: + obj['KodiId'] = e_item[0] + obj['Media'] = e_item[4] + except TypeError: + return + + obj['Rating'] = 0 + + if obj['Media'] == 'song': + + if obj['DatePlayed']: + obj['DatePlayed'] = obj['DatePlayed'].split('.')[0].replace('T', " ") + + self.rate_song(*values(obj, QU.update_song_rating_obj)) + + self.emby_db.update_reference(*values(obj, QUEM.update_reference_obj)) + LOG.info("USERDATA %s [%s] %s: %s", obj['Media'], obj['KodiId'], obj['Id'], obj['Title']) + + @stop() + @emby_item() + def remove(self, item_id, e_item): + + ''' This updates: Favorite, LastPlayedDate, Playcount, PlaybackPositionTicks + Poster with progress bar + + This should address single song scenario, where server doesn't actually + create an album for the song. + ''' + obj = {'Id': item_id} + + try: + obj['KodiId'] = e_item[0] + obj['Media'] = e_item[4] + except TypeError: + return + + if obj['Media'] == 'song': + + self.remove_song(obj['KodiId'], obj['Id']) + self.emby_db.remove_wild_item(obj['id']) + + for item in self.emby_get_item_by_wild_id(*values(obj, QUEM.get_item_by_wild_obj)): + if item[1] == 'album': + + temp_obj = dict(obj) + temp_obj['ParentId'] = item[0] + + if not self.emby_db.get_item_by_parent_id(*values(temp_obj, QUEM.get_item_by_parent_song_obj)): + self.remove_album(temp_obj['ParentId'], obj['Id']) + + elif obj['Media'] == 'album': + obj['ParentId'] = obj['KodiId'] + + for song in self.emby_db.get_item_by_parent_id(*values(obj, QUEM.get_item_by_parent_song_obj)): + self.remove_song(song[1], obj['Id']) + else: + self.emby_db.remove_items_by_parent_id(*values(obj, QUEM.delete_item_by_parent_song_obj)) + + self.remove_album(obj['KodiId'], obj['Id']) + + elif obj['Media'] == 'artist': + obj['ParentId'] = obj['KodiId'] + + for album in self.emby_db.get_item_by_parent_id(*values(obj, QUEM.get_item_by_parent_album_obj)): + + temp_obj = dict(obj) + temp_obj['ParentId'] = album[1] + + for song in self.emby_db.get_item_by_parent_id(*values(temp_obj, QUEM.get_item_by_parent_song_obj)): + self.remove_song(song[1], obj['Id']) else: - # Remove emby song - emby_db.removeItems_byParentId(albumid, "song") - # Remove emby artist - emby_db.removeItems_byParentId(albumid, "artist") - # Remove kodi album - self.removeAlbum(albumid) + self.emby_db.remove_items_by_parent_id(*values(temp_obj, QUEM.delete_item_by_parent_song_obj)) + self.emby_db.remove_items_by_parent_id(*values(temp_obj, QUEM.delete_item_by_parent_artist_obj)) + self.remove_album(temp_obj['ParentId'], obj['Id']) else: - # Remove emby albums - emby_db.removeItems_byParentId(kodiid, "album") + self.emby_db.remove_items_by_parent_id(*values(obj, QUEM.delete_item_by_parent_album_obj)) - # Remove artist - self.removeArtist(kodiid) + self.remove_artist(obj['KodiId'], obj['Id']) - log.info("Deleted %s: %s from kodi database", mediatype, itemid) + self.emby_db.remove_item(*values(obj, QUEM.delete_item_obj)) - def removeSong(self, kodi_id): + def remove_artist(self, kodi_id, item_id): + + self.artwork.delete(kodi_id, "artist") + self.delete(kodi_id) + LOG.info("DELETE artist [%s] %s", kodi_id, item_id) - self.artwork.delete_artwork(kodi_id, "song", self.kodicursor) - self.kodi_db.remove_song(kodi_id) + def remove_album(self, kodi_id, item_id): + + self.artwork.delete(kodi_id, "album") + self.delete_album(kodi_id) + LOG.info("DELETE album [%s] %s", kodi_id, item_id) - def removeAlbum(self, kodi_id): - - self.artwork.delete_artwork(kodi_id, "album", self.kodicursor) - self.kodi_db.remove_album(kodi_id) - - def removeArtist(self, kodi_id): - - self.artwork.delete_artwork(kodi_id, "artist", self.kodicursor) - self.kodi_db.remove_artist(kodi_id) + def remove_song(self, kodi_id, item_id): + + self.artwork.delete(kodi_id, "song") + self.delete_song(kodi_id) + LOG.info("DELETE song [%s] %s", kodi_id, item_id) diff --git a/resources/lib/objects/musicvideos.py b/resources/lib/objects/musicvideos.py index f280af51..063ff0b6 100644 --- a/resources/lib/objects/musicvideos.py +++ b/resources/lib/objects/musicvideos.py @@ -2,321 +2,236 @@ ################################################################################################## -import logging -import urllib import datetime +import logging import re +import urllib -import api -import embydb_functions as embydb -import _kodi_musicvideos -from _common import Items, catch_except -from utils import window, settings, language as lang +from obj import Objects +from kodi import MusicVideos as KodiDb, queries as QU +from database import emby_db, queries as QUEM +from helper import api, catch, stop, validate, library_check, emby_item, values ################################################################################################## -log = logging.getLogger("EMBY."+__name__) +LOG = logging.getLogger("EMBY."+__name__) ################################################################################################## -class MusicVideos(Items): +class MusicVideos(KodiDb): + def __init__(self, server, embydb, videodb, direct_path): - def __init__(self, embycursor, kodicursor, pdialog=None): + self.server = server + self.emby = embydb + self.video = videodb + self.direct_path = direct_path - self.embycursor = embycursor - self.emby_db = embydb.Embydb_Functions(self.embycursor) - self.kodicursor = kodicursor - self.kodi_db = _kodi_musicvideos.KodiMusicVideos(self.kodicursor) - self.pdialog = pdialog + self.emby_db = emby_db.EmbyDatabase(embydb.cursor) + self.objects = Objects() - self.new_time = int(settings('newvideotime'))*1000 + KodiDb.__init__(self, videodb.cursor) - Items.__init__(self) + def __getitem__(self, key): - def _get_func(self, item_type, action): + if key == 'MusicVideo': + return self.musicvideo + elif key == 'UserData': + return self.userdata + elif key in 'Removed': + return self.remove - if item_type == "MusicVideo": - actions = { - 'added': self.add_mvideos, - 'update': self.add_update, - 'userdata': self.updateUserdata, - 'remove': self.remove - } - else: - log.info("Unsupported item_type: %s", item_type) - actions = {} + @stop() + @emby_item() + @library_check() + def musicvideo(self, item, e_item, library): - return actions.get(action) + ''' If item does not exist, entry will be added. + If item exists, entry will be updated. - def compare_all(self): - # Pull the list of musicvideos in Kodi - views = self.emby_db.getView_byType('musicvideos') - log.info("Media folders: %s", views) + If we don't get the track number from Emby, see if we can infer it + from the sortname attribute. + ''' + API = api.API(item, self.server['auth/server-address']) + obj = self.objects.map(item, 'MusicVideo') + update = True - for view in views: - - if self.should_stop(): - return False - - if not self.compare_mvideos(view): - return False - - return True - - def compare_mvideos(self, view): - - view_id = view['id'] - view_name = view['name'] - - if self.pdialog: - self.pdialog.update(heading=lang(29999), message="%s %s..." % (lang(33028), view_name)) - - mvideos = dict(self.emby_db.get_checksum_by_view('MusicVideo', view_id)) - emby_mvideos = self.emby.getMusicVideos(view_id, basic=True, dialog=self.pdialog) - - return self.compare("MusicVideo", emby_mvideos['Items'], mvideos, view) - - def add_mvideos(self, items, total=None, view=None): - - for item in self.added(items, total): - if self.add_update(item, view): - self.content_pop(item.get('Name', "unknown")) - - @catch_except() - def add_update(self, item, view=None): - # Process single music video - kodicursor = self.kodicursor - emby_db = self.emby_db - artwork = self.artwork - API = api.API(item) - - # If the item already exist in the local Kodi DB we'll perform a full item update - # If the item doesn't exist, we'll add it to the database - update_item = True - itemid = item['Id'] - emby_dbitem = emby_db.getItem_byId(itemid) try: - mvideoid = emby_dbitem[0] - fileid = emby_dbitem[1] - pathid = emby_dbitem[2] - log.info("mvideoid: %s fileid: %s pathid: %s", mvideoid, fileid, pathid) - - except TypeError: - update_item = False - log.debug("mvideoid: %s not found", itemid) - # mvideoid - mvideoid = self.kodi_db.create_entry() + obj['MvideoId'] = e_item[0] + obj['FileId'] = e_item[1] + obj['PathId'] = e_item[2] + except TypeError as error: + update = False + LOG.debug("MvideoId for %s not found", obj['Id']) + obj['MvideoId'] = self.create_entry() else: - if self.kodi_db.get_musicvideo(mvideoid) is None: - # item is not found, let's recreate it. - update_item = False - log.info("mvideoid: %s missing from Kodi, repairing the entry.", mvideoid) + if self.get(*values(obj, QU.get_musicvideo_obj)) is None: - if not view: - # Get view tag from emby - viewtag, viewid = emby_db.getView_embyId(itemid) - log.debug("View tag found: %s", viewtag) + update = False + LOG.info("MvideoId %s missing from kodi. repairing the entry.", obj['MvideoId']) + + obj['Path'] = API.get_file_path(obj['Path']) + obj['LibraryId'] = library['Id'] + obj['LibraryName'] = library['Name'] + obj['Genres'] = obj['Genres'] or [] + obj['ArtistItems'] = obj['ArtistItems'] or [] + obj['Studios'] = [API.validate_studio(studio) for studio in (obj['Studios'] or [])] + obj['Plot'] = API.get_overview(obj['Plot']) + obj['DateAdded'] = obj['DateAdded'].split('.')[0].replace('T', " ") + obj['DatePlayed'] = (obj['DatePlayed'] or obj['DateAdded']).split('.')[0].replace('T', " ") + obj['PlayCount'] = API.get_playcount(obj['Played'], obj['PlayCount']) + obj['Resume'] = API.adjust_resume((obj['Resume'] or 0) / 10000000.0) + obj['Runtime'] = round(float((obj['Runtime'] or 0) / 10000000.0), 6) + obj['Premiere'] = obj['Premiere'] or datetime.date(obj['Year'] or 2021, 1, 1) + obj['Genre'] = " / ".join(obj['Genres']) + obj['Studio'] = " / ".join(obj['Studios']) + obj['Artists'] = " / ".join(obj['Artists'] or []) + obj['Directors'] = " / ".join(obj['Directors'] or []) + obj['Video'] = API.video_streams(obj['Video'] or [], obj['Container']) + obj['Audio'] = API.audio_streams(obj['Audio'] or []) + obj['Streams'] = API.media_streams(obj['Video'], obj['Audio'], obj['Subtitles']) + obj['Artwork'] = API.get_all_artwork(self.objects.map(item, 'Artwork')) + + self.get_path_filename(obj) + + if obj['Premiere']: + obj['Premiere'] = str(obj['Premiere']).split('.')[0].replace('T', " ") + + for artist in obj['ArtistItems']: + artist['Type'] = "Artist" + + obj['People'] = obj['People'] or [] + obj['ArtistItems'] + obj['People'] = API.get_people_artwork(obj['People']) + + if obj['Index'] is None and obj['SortTitle'] is not None: + search = re.search(r'^\d+\s?', obj['SortTitle']) + + if search: + obj['Index'] = search.group() + + tags = [] + tags.extend(obj['Tags'] or []) + tags.append(obj['LibraryName']) + + if obj['Favorite']: + tags.append('Favorite musicvideos') + + obj['Tags'] = tags + + + if update: + self.musicvideo_update(obj) else: - viewtag = view['name'] - viewid = view['id'] + self.musicvideo_add(obj) - # fileId information - checksum = API.get_checksum() - dateadded = API.get_date_created() - userdata = API.get_userdata() - playcount = userdata['PlayCount'] - dateplayed = userdata['LastPlayedDate'] - # item details - runtime = API.get_runtime() - plot = API.get_overview() - title = item['Name'] - year = item.get('ProductionYear') - # Some kodi views/skins rely on the "premiered" field to display by year. - # So, if we don't get the premiere date from Emby, just set it to Jan 1st - # of the video's "year", so that Kodi has a year to work with. - premiered = item.get('PremiereDate') - if premiered is None and year is not None: - premiered = datetime.date(year, 1, 1) - genres = item['Genres'] - genre = " / ".join(genres) - studios = API.get_studios() - studio = " / ".join(studios) - artist = " / ".join(item.get('Artists')) - album = item.get('Album') - track = item.get('Track') - # If we don't get the track number from Emby, see if we can infer it - # from the sortname attribute. - if track is None: - sortname = item.get('SortName') - if sortname is not None: - search = re.search(r'^\d+\s?', sortname) - if search is not None: - track = search.group() - people = API.get_people() - director = " / ".join(people['Director']) + self.update_path(*values(obj, QU.update_path_mvideo_obj)) + self.update_file(*values(obj, QU.update_file_obj)) + self.add_tags(*values(obj, QU.add_tags_mvideo_obj)) + self.add_genres(*values(obj, QU.add_genres_mvideo_obj)) + self.add_studios(*values(obj, QU.add_studios_mvideo_obj)) + self.add_playstate(*values(obj, QU.add_bookmark_obj)) + self.add_people(*values(obj, QU.add_people_mvideo_obj)) + self.add_streams(*values(obj, QU.add_streams_obj)) + self.artwork.add(obj['Artwork'], obj['MvideoId'], "musicvideo") - ##### GET THE FILE AND PATH ##### - playurl = API.get_file_path() + def musicvideo_add(self, obj): + + ''' Add object to kodi. + ''' + obj['PathId'] = self.add_path(*values(obj, QU.add_path_obj)) + obj['FileId'] = self.add_file(*values(obj, QU.add_file_obj)) - if "\\" in playurl: - # Local path - filename = playurl.rsplit("\\", 1)[1] - else: # Network share - filename = playurl.rsplit("/", 1)[1] + self.add(*values(obj, QU.add_musicvideo_obj)) + self.emby_db.add_reference(*values(obj, QUEM.add_reference_mvideo_obj)) + LOG.info("ADD mvideo [%s/%s/%s] %s: %s", obj['PathId'], obj['FileId'], obj['MvideoId'], obj['Id'], obj['Title']) + + def musicvideo_update(self, obj): + + ''' Update object to kodi. + ''' + self.update(*values(obj, QU.update_musicvideo_obj)) + self.emby_db.update_reference(*values(obj, QUEM.update_reference_obj)) + LOG.info("UPDATE mvideo [%s/%s/%s] %s: %s", obj['PathId'], obj['FileId'], obj['MvideoId'], obj['Id'], obj['Title']) + + def get_path_filename(self, obj): + + ''' Get the path and filename and build it into protocol://path + ''' + obj['Filename'] = obj['Path'].rsplit('\\', 1)[1] if '\\' in obj['Path'] else obj['Path'].rsplit('/', 1)[1] if self.direct_path: - # Direct paths is set the Kodi way - if not self.path_validation(playurl): - return False - path = playurl.replace(filename, "") - window('emby_pathverified', value="true") + if not validate(obj['Path']): + raise Exception("Failed to validate path. User stopped.") + + obj['Path'] = obj['Path'].replace(obj['Filename'], "") + else: - # Set plugin path and media flags using real filename - path = "plugin://plugin.video.emby.musicvideos/" + obj['Path'] = "plugin://plugin.video.emby.musicvideos/" params = { - - 'filename': filename.encode('utf-8'), - 'id': itemid, - 'dbid': mvideoid, + 'filename': obj['Filename'].encode('utf-8'), + 'id': obj['Id'], + 'dbid': obj['MvideoId'], 'mode': "play" } - filename = "%s?%s" % (path, urllib.urlencode(params)) + obj['Filename'] = "%s?%s" % (obj['Path'], urllib.urlencode(params)) - ##### UPDATE THE MUSIC VIDEO ##### - if update_item: - log.info("UPDATE mvideo itemid: %s - Title: %s", itemid, title) + @stop() + @emby_item() + def userdata(self, item, e_item): + + ''' This updates: Favorite, LastPlayedDate, Playcount, PlaybackPositionTicks + Poster with progress bar + ''' + API = api.API(item, self.server['auth/server-address']) + obj = self.objects.map(item, 'MusicVideoUserData') - # Update the music video entry - if self.kodi_version > 16: #Krypton and later - self.kodi_db.update_musicvideo(title, runtime, director, studio, year, plot, album, - artist, genre, track, premiered, mvideoid) - else: - self.kodi_db.update_musicvideo_16(title, runtime, director, studio, year, plot, album, - artist, genre, track, mvideoid) - - # Update the checksum in emby table - emby_db.updateReference(itemid, checksum) - - ##### OR ADD THE MUSIC VIDEO ##### - else: - log.info("ADD mvideo itemid: %s - Title: %s", itemid, title) - - # Add path - pathid = self.kodi_db.add_path(path) - # Add the file - fileid = self.kodi_db.add_file(filename, pathid) - - # Create the musicvideo entry - if self.kodi_version > 16: #Krypton and later - self.kodi_db.add_musicvideo(mvideoid, fileid, title, runtime, director, studio, - year, plot, album, artist, genre, track, premiered) - else: - self.kodi_db.add_musicvideo_16(mvideoid, fileid, title, runtime, director, studio, - year, plot, album, artist, genre, track) - - # Create the reference in emby table - emby_db.addReference(itemid, mvideoid, "MusicVideo", "musicvideo", fileid, pathid, - checksum=checksum, mediafolderid=viewid) - - # Update the path - self.kodi_db.update_path(pathid, path, "musicvideos", "metadata.local") - # Update the file - self.kodi_db.update_file(fileid, filename, pathid, dateadded) - - # Process cast - people = item['People'] - artists = item['ArtistItems'] - for artist in artists: - artist['Type'] = "Artist" - people.extend(artists) - people = artwork.get_people_artwork(people) - self.kodi_db.add_people(mvideoid, people, "musicvideo") - # Process genres - self.kodi_db.add_genres(mvideoid, genres, "musicvideo") - # Process artwork - artwork.add_artwork(artwork.get_all_artwork(item), mvideoid, "musicvideo", kodicursor) - # Process stream details - streams = API.get_media_streams() - self.kodi_db.add_streams(fileid, streams, runtime) - # Process studios - self.kodi_db.add_studios(mvideoid, studios, "musicvideo") - # Process tags: view, emby tags - tags = [viewtag] - tags.extend(item['Tags']) - if userdata['Favorite']: - tags.append("Favorite musicvideos") - self.kodi_db.add_tags(mvideoid, tags, "musicvideo") - # Process playstates - resume = API.adjust_resume(userdata['Resume']) - total = round(float(runtime), 6) - self.kodi_db.add_playstate(fileid, resume, total, playcount, dateplayed) - - return True - - def updateUserdata(self, item): - # This updates: Favorite, LastPlayedDate, Playcount, PlaybackPositionTicks - # Poster with progress bar - emby_db = self.emby_db - API = api.API(item) - - # Get emby information - itemid = item['Id'] - checksum = API.get_checksum() - userdata = API.get_userdata() - runtime = API.get_runtime() - - # Get Kodi information - emby_dbitem = emby_db.getItem_byId(itemid) try: - mvideoid = emby_dbitem[0] - fileid = emby_dbitem[1] - log.info("Update playstate for musicvideo: %s fileid: %s", item['Name'], fileid) + obj['MvideoId'] = e_item[0] + obj['FileId'] = e_item[1] except TypeError: return - # Process favorite tags - if userdata['Favorite']: - self.kodi_db.get_tag(mvideoid, "Favorite musicvideos", "musicvideo") + obj['Resume'] = API.adjust_resume((obj['Resume'] or 0) / 10000000.0) + obj['Runtime'] = round(float((obj['Runtime'] or 0) / 10000000.0), 6) + obj['PlayCount'] = API.get_playcount(obj['Played'], obj['PlayCount']) + + if obj['DatePlayed']: + obj['DatePlayed'] = obj['DatePlayed'].split('.')[0].replace('T', " ") + + if obj['Favorite']: + self.get_tag(*values(obj, QU.get_tag_mvideo_obj)) else: - self.kodi_db.remove_tag(mvideoid, "Favorite musicvideos", "musicvideo") + self.remove_tag(*values(obj, QU.delete_tag_mvideo_obj)) - # Process playstates - playcount = userdata['PlayCount'] - dateplayed = userdata['LastPlayedDate'] - resume = API.adjust_resume(userdata['Resume']) - total = round(float(runtime), 6) + self.add_playstate(*values(obj, QU.add_bookmark_obj)) + self.emby_db.update_reference(*values(obj, QUEM.update_reference_obj)) + LOG.info("USERDATA mvideo [%s/%s] %s: %s", obj['FileId'], obj['MvideoId'], obj['Id'], obj['Title']) - self.kodi_db.add_playstate(fileid, resume, total, playcount, dateplayed) - emby_db.updateReference(itemid, checksum) + @stop() + @emby_item() + def remove(self, item_id, e_item): - def remove(self, itemid): - # Remove mvideoid, fileid, pathid, emby reference - emby_db = self.emby_db - kodicursor = self.kodicursor - artwork = self.artwork + ''' Remove mvideoid, fileid, pathid, emby reference. + ''' + obj = {'Id': item_id} - emby_dbitem = emby_db.getItem_byId(itemid) try: - mvideoid = emby_dbitem[0] - fileid = emby_dbitem[1] - pathid = emby_dbitem[2] - log.info("Removing mvideoid: %s fileid: %s pathid: %s", mvideoid, fileid, pathid) + obj['MvideoId'] = e_item[0] + obj['FileId'] = e_item[1] + obj['PathId'] = e_item[2] except TypeError: return - # Remove the emby reference - emby_db.removeItem(itemid) - # Remove artwork - artwork.delete_artwork(mvideoid, "musicvideo", self.kodicursor) + self.artwork.delete(obj['MvideoId'], "musicvideo") + self.delete(*values(obj, QU.delete_musicvideo_obj)) - self.kodi_db.remove_musicvideo(mvideoid, fileid) if self.direct_path: - self.kodi_db.remove_path(pathid) + self.remove_path(*values(obj, QU.delete_path_obj)) - log.info("Deleted musicvideo %s from kodi database", itemid) + self.emby_db.remove_item(*values(obj, QUEM.delete_item_obj)) + LOG.info("DELETE musicvideo %s [%s/%s] %s", obj['MvideoId'], obj['PathId'], obj['FileId'], obj['Id']) diff --git a/resources/lib/objects/obj.py b/resources/lib/objects/obj.py new file mode 100644 index 00000000..f0559468 --- /dev/null +++ b/resources/lib/objects/obj.py @@ -0,0 +1,161 @@ +# -*- coding: utf-8 -*- + +################################################################################################## + +import json +import logging +import os + +################################################################################################## + +LOG = logging.getLogger("EMBY."+__name__) + +################################################################################################## + + +class Objects(object): + + # Borg - multiple instances, shared state + _shared_state = {} + + def __init__(self): + + ''' Hold all persistent data here. + ''' + + self.__dict__ = self._shared_state + + def mapping(self): + + ''' Load objects mapping. + ''' + with open(os.path.join(os.path.dirname(__file__), 'obj_map.json')) as infile: + self.objects = json.load(infile) + + def map(self, item, mapping_name): + + ''' Syntax to traverse the item dictionary. + This of the query almost as a url. + + Item is the Emby item json object structure + + ",": each element will be used as a fallback until a value is found. + "?": split filters and key name from the query part, i.e. MediaSources/0?$Name + "$": lead the key name with $. Only one key value can be requested per element. + ":": indicates it's a list of elements [], i.e. MediaSources/0/MediaStreams:?$Name + MediaStreams is a list. + "/": indicates where to go directly + ''' + self.mapped_item = {} + + if not mapping_name: + raise Exception("execute mapping() first") + + mapping = self.objects[mapping_name] + + for key, value in mapping.iteritems(): + + self.mapped_item[key] = None + params = value.split(',') + + for param in params: + + obj = item + obj_param = param + obj_key = "" + obj_filters = {} + + if '?' in obj_param: + + if '$' in obj_param: + obj_param, obj_key = obj_param.rsplit('$', 1) + + obj_param, filters = obj_param.rsplit('?', 1) + + if filters: + for filter in filters.split('&'): + filter_key, filter_value = filter.split('=') + obj_filters[filter_key] = filter_value + + if ':' in obj_param: + result = [] + + for d in self.__recursiveloop__(obj, obj_param): + + if obj_filters and self.__filters__(d, obj_filters): + result.append(d) + elif not obj_filters: + result.append(d) + + obj = result + obj_filters = {} + + elif '/' in obj_param: + obj = self.__recursive__(obj, obj_param) + + elif obj is item: + obj = item.get(obj_param) + + if obj_filters and obj: + if not self.__filters__(obj, obj_filters): + obj = None + + if not obj and len(params) != params.index(param): + continue + + if obj_key: + obj = [d[obj_key] for d in obj if d.get(obj_key)] if type(obj) == list else obj.get(obj_key) + + self.mapped_item[key] = obj + break + + self.mapped_item['ProviderName'] = self.objects.get('%sProviderName' % mapping_name) + + if not mapping_name.startswith('Browse'): + self.mapped_item['Checksum'] = json.dumps(item['UserData']) + + return self.mapped_item + + def __recursiveloop__(self, obj, keys): + + first, rest = keys.split(':', 1) + obj = self.__recursive__(obj, first) + + if obj: + if rest: + for item in obj: + self.__recursiveloop__(item, rest) + else: + for item in obj: + yield item + + def __recursive__(self, obj, keys): + + for string in keys.split('/'): + + if not obj: + return + + obj = obj[int(string)] if string.isdigit() else obj.get(string) + + return obj + + def __filters__(self, obj, filters): + + result = False + + for key, value in filters.iteritems(): + + inverse = False + + if value.startswith('!'): + + inverse = True + value = value.split('!', 1)[1] + + if value.lower() == "null": + value = None + + result = obj.get(key) != value if inverse else obj.get(key) == value + + return result diff --git a/resources/lib/objects/obj_map.json b/resources/lib/objects/obj_map.json new file mode 100644 index 00000000..a2733906 --- /dev/null +++ b/resources/lib/objects/obj_map.json @@ -0,0 +1,328 @@ +{ + "video": "special://database/MyVideos107.db", + "music": "special://database/MyMusic60.db", + "texture": "special://database/Textures13.db", + "emby": "special://database/emby.db", + "MovieProviderName": "imdb", + "Movie": { + "Id": "Id", + "Title": "Name", + "SortTitle": "SortName", + "Path": "Path", + "Genres": "Genres", + "UniqueId": "ProviderIds/Imdb", + "Rating": "CommunityRating", + "Year": "ProductionYear", + "Votes": "VoteCount", + "Plot": "Overview", + "ShortPlot": "ShortOverview", + "People": "People", + "Writers": "People:?Type=Writer$Name", + "Directors": "People:?Type=Director$Name", + "Cast": "People:?Type=Actor$Name", + "Tagline": "Taglines/0", + "Mpaa": "OfficialRating", + "Country": "ProductionLocations/0", + "Countries": "ProductionLocations", + "Studios": "Studios:?$Name", + "Studio": "Studios/0/Name", + "Runtime": "RunTimeTicks,CumulativeRunTimeTicks", + "LocalTrailer": "LocalTrailerCount", + "Trailer": "RemoteTrailers/0/Url", + "DateAdded": "DateCreated", + "Played": "UserData/Played", + "PlayCount": "UserData/PlayCount", + "DatePlayed": "UserData/LastPlayedDate", + "Favorite": "UserData/IsFavorite", + "Resume": "UserData/PlaybackPositionTicks", + "Tags": "Tags", + "Subtitles": "MediaSources/0/MediaStreams:?Type=Subtitle$Language", + "Audio": "MediaSources/0/MediaStreams:?Type=Audio", + "Video": "MediaSources/0/MediaStreams:?Type=Video", + "Container": "MediaSources/0/Container" + }, + "MovieUserData": { + "Id": "Id", + "Title": "Name", + "Runtime": "RunTimeTicks,CumulativeRunTimeTicks", + "Resume": "UserData/PlaybackPositionTicks", + "Favorite": "UserData/IsFavorite", + "PlayCount": "UserData/PlayCount", + "DatePlayed": "UserData/LastPlayedDate", + "Played": "UserData/Played" + }, + "Boxset": { + "Id": "Id", + "Title": "Name", + "Overview": "Overview" + }, + "SeriesProviderName": "tvdb", + "Series": { + "Id": "Id", + "Title": "Name", + "SortTitle": "SortName", + "People": "People", + "Path": "Path", + "Genres": "Genres", + "Plot": "Overview", + "Rating": "CommunityRating", + "Year": "ProductionYear", + "Votes": "VoteCount", + "Premiere": "PremiereDate", + "UniqueId": "ProviderIds/Tvdb", + "Mpaa": "OfficialRating", + "Studios": "Studios:?$Name", + "Tags": "Tags", + "Favorite": "UserData/IsFavorite", + "RecursiveCount": "RecursiveItemCount" + }, + "Season": { + "Id": "Id", + "Index": "IndexNumber", + "SeriesId": "SeriesId", + "Location": "LocationType", + "Title": "Name" + }, + "EpisodeProviderName": "tvdb", + "Episode": { + "Id": "Id", + "Title": "Name", + "Path": "Path", + "Plot": "Overview", + "People": "People", + "Rating": "CommunityRating", + "Writers": "People:?Type=Writer$Name", + "Directors": "People:?Type=Director$Name", + "Runtime": "RunTimeTicks,CumulativeRunTimeTicks", + "Premiere": "PremiereDate", + "Votes": "VoteCount", + "UniqueId": "ProviderIds/Tvdb", + "SeriesId": "SeriesId", + "Season": "ParentIndexNumber", + "Index": "IndexNumber", + "AbsoluteNumber": "AbsoluteEpisodeNumber", + "AirsAfterSeason": "AirsAfterSeasonNumber", + "AirsBeforeSeason": "AirsBeforeSeasonNumber", + "AirsBeforeEpisode": "AirsBeforeEpisodeNumber", + "MultiEpisode": "IndexNumberEnd", + "Played": "UserData/Played", + "PlayCount": "UserData/PlayCount", + "DateAdded": "DateCreated", + "DatePlayed": "UserData/LastPlayedDate", + "Resume": "UserData/PlaybackPositionTicks", + "Subtitles": "MediaSources/0/MediaStreams:?Type=Subtitle$Language", + "Audio": "MediaSources/0/MediaStreams:?Type=Audio", + "Video": "MediaSources/0/MediaStreams:?Type=Video", + "Container": "MediaSources/0/Container", + "Location": "LocationType" + }, + "EpisodeUserData": { + "Id": "Id", + "Title": "Name", + "Runtime": "RunTimeTicks,CumulativeRunTimeTicks", + "Resume": "UserData/PlaybackPositionTicks", + "Favorite": "UserData/IsFavorite", + "PlayCount": "UserData/PlayCount", + "DatePlayed": "UserData/LastPlayedDate", + "DateAdded": "DateCreated", + "Played": "UserData/Played" + }, + "MusicVideo": { + "Id": "Id", + "Title": "Name", + "Path": "Path", + "DateAdded": "DateCreated", + "DatePlayed": "UserData/LastPlayedDate", + "PlayCount": "UserData/PlayCount", + "Resume": "UserData/PlaybackPositionTicks", + "SortTitle": "SortName", + "Runtime": "RunTimeTicks,CumulativeRunTimeTicks", + "Plot": "Overview", + "Year": "ProductionYear", + "Premiere": "PremiereDate", + "Genres": "Genres", + "Studios": "Studios?$Name", + "Artists": "Artists", + "ArtistItems": "ArtistItems", + "Album": "Album", + "Index": "Track", + "People": "People", + "Subtitles": "MediaSources/0/MediaStreams:?Type=Subtitle$Language", + "Audio": "MediaSources/0/MediaStreams:?Type=Audio", + "Video": "MediaSources/0/MediaStreams:?Type=Video", + "Container": "MediaSources/0/Container", + "Tags": "Tags", + "Played": "UserData/Played", + "Favorite": "UserData/IsFavorite", + "Directors": "People:?Type=Director$Name" + }, + "MusicVideoUserData": { + "Id": "Id", + "Runtime": "RunTimeTicks,CumulativeRunTimeTicks", + "Resume": "UserData/PlaybackPositionTicks", + "Favorite": "UserData/IsFavorite", + "PlayCount": "UserData/PlayCount", + "DatePlayed": "UserData/LastPlayedDate", + "Played": "UserData/Played" + }, + "Artist": { + "Id": "Id", + "Name": "Name", + "UniqueId": "ProviderIds/MusicBrainzArtist", + "Genres": "Genres", + "Bio": "Overview" + }, + "Album": { + "Id": "Id", + "Title": "Name", + "UniqueId": "ProviderIds/MusicBrainzAlbum", + "Year": "ProductionYear", + "Genres": "Genres", + "Bio": "Overview", + "AlbumArtists": "AlbumArtists", + "Artists": "AlbumArtists:?$Name", + "ArtistItems": "ArtistItems" + }, + "Song": { + "Id": "Id", + "Title": "Name", + "Path": "Path", + "DateAdded": "DateCreated", + "Played": "UserData/Played", + "PlayCount": "UserData/PlayCount", + "DatePlayed": "UserData/LastPlayedDate", + "UniqueId": "ProviderIds/MusicBrainzTrackId", + "Genres": "Genres", + "Artists": "Artists", + "Index": "IndexNumber", + "Disc": "ParentIndexNumber", + "Year": "ProductionYear", + "Runtime": "RunTimeTicks,CumulativeRunTimeTicks", + "Comment": "Overview", + "ArtistItems": "ArtistItems", + "AlbumArtists": "AlbumArtists", + "Album": "Album", + "SongAlbumId": "AlbumId", + "Container": "MediaSources/0/Container" + }, + "SongUserData": { + "Id": "Id", + "Title": "Name", + "PlayCount": "UserData/PlayCount", + "DatePlayed": "UserData/LastPlayedDate", + "DateAdded": "DateCreated" + }, + "Artwork": { + "Id": "Id", + "Tags": "ImageTags", + "BackdropTags": "BackdropImageTags" + }, + "ArtworkParent": { + "Id": "Id", + "Tags": "ImageTags", + "BackdropTags": "BackdropImageTags", + "ParentBackdropId": "ParentBackdropItemId", + "ParentBackdropTags": "ParentBackdropImageTags", + "ParentLogoId": "ParentLogoItemId", + "ParentLogoTag": "ParentLogoImageTag", + "ParentArtId": "ParentArtItemId", + "ParentArtTag": "ParentArtImageTag", + "ParentThumbId": "ParentThumbItemId", + "ParentThumbTag": "ParentThumbTag", + "SeriesTag": "SeriesPrimaryImageTag", + "SeriesId": "SeriesId" + }, + "ArtworkMusic": { + "Id": "Id", + "Tags": "ImageTags", + "BackdropTags": "BackdropImageTags", + "ParentBackdropId": "ParentBackdropItemId", + "ParentBackdropTags": "ParentBackdropImageTags", + "ParentLogoId": "ParentLogoItemId", + "ParentLogoTag": "ParentLogoImageTag", + "ParentArtId": "ParentArtItemId", + "ParentArtTag": "ParentArtImageTag", + "ParentThumbId": "ParentThumbItemId", + "ParentThumbTag": "ParentThumbTag", + "AlbumId": "AlbumId", + "AlbumTag": "AlbumPrimaryImageTag" + }, + "BrowseVideo": { + "Id": "Id", + "Title": "Name", + "Type": "Type", + "Plot": "Overview", + "Year": "ProductionYear", + "Writers": "People:?Type=Writer$Name", + "Directors": "People:?Type=Director$Name", + "Cast": "People:?Type=Actor$Name", + "Mpaa": "OfficialRating", + "Genres": "Genres", + "Studios": "Studios:?$Name,SeriesStudio", + "Premiere": "PremiereDate,DateCreated", + "Rating": "CommunityRating", + "Votes": "VoteCount", + "Season": "ParentIndexNumber", + "Index": "IndexNumber,AbsoluteEpisodeNumber", + "SeriesName": "SeriesName", + "Countries": "ProductionLocations", + "Played": "UserData/Played", + "People": "People", + "ShortPlot": "ShortOverview", + "Runtime": "RunTimeTicks,CumulativeRunTimeTicks", + "Tagline": "Taglines/0", + "UniqueId": "ProviderIds/Imdb", + "DatePlayed": "UserData/LastPlayedDate", + "Artists": "Artists", + "Album": "Album", + "Votes": "VoteCount", + "Path": "Path", + "LocalTrailer": "LocalTrailerCount", + "Trailer": "RemoteTrailers/0/Url", + "DateAdded": "DateCreated", + "SortTitle": "SortName", + "PlayCount": "UserData/PlayCount", + "Resume": "UserData/PlaybackPositionTicks", + "Subtitles": "MediaStreams:?Type=Subtitle$Language", + "Audio": "MediaStreams:?Type=Audio", + "Video": "MediaStreams:?Type=Video", + "Container": "Container", + "Unwatched": "UserData/UnplayedItemCount", + "ChildCount": "ChildCount", + "RecursiveCount": "RecursiveItemCount", + "MediaType": "MediaType" + }, + "BrowseAudio": { + "Id": "Id", + "Title": "Name", + "Type": "Type", + "Index": "IndexNumber", + "Disc": "ParentIndexNumber", + "Runtime": "RunTimeTicks,CumulativeRunTimeTicks", + "Year": "ProductionYear", + "Genre": "Genres/0", + "Album": "Album", + "Artists": "Artists/0", + "Rating": "CommunityRating", + "PlayCount": "UserData/PlayCount", + "DatePlayed": "UserData/LastPlayedDate", + "UniqueId": "ProviderIds/MusicBrainzTrackId,ProviderIds/MusicBrainzAlbum,ProviderIds/MusicBrainzArtist", + "Comment": "Overview", + "FileDate": "DateCreated", + "Played": "UserData/Played" + }, + "BrowsePhoto": { + "Id": "Id", + "Title": "Name", + "Type": "Type", + "FileDate": "DateCreated", + "Width": "Width", + "Height": "Height", + "Size": "Size", + "Overview": "Overview", + "CameraMake": "CameraMake", + "CameraModel": "CameraModel", + "ExposureTime": "ExposureTime", + "FocalLength": "FocalLength" + } +} \ No newline at end of file diff --git a/resources/lib/objects/tvshows.py b/resources/lib/objects/tvshows.py index ee4062e1..85700f10 100644 --- a/resources/lib/objects/tvshows.py +++ b/resources/lib/objects/tvshows.py @@ -2,853 +2,574 @@ ################################################################################################## +import json import logging from ntpath import dirname +import urllib -import api -import emby as mb -import embydb_functions as embydb -import _kodi_tvshows -from _common import Items, catch_except -from utils import window, settings, language as lang, urllib_path +from obj import Objects +from kodi import TVShows as KodiDb, queries as QU +import downloader as server +from database import emby_db, queries as QUEM +from helper import api, catch, stop, validate, emby_item, library_check, settings, values ################################################################################################## -log = logging.getLogger("EMBY."+__name__) +LOG = logging.getLogger("EMBY."+__name__) ################################################################################################## -class TVShows(Items): +class TVShows(KodiDb): + def __init__(self, server, embydb, videodb, direct_path): - def __init__(self, embycursor, kodicursor, pdialog=None): + self.server = server + self.emby = embydb + self.video = videodb + self.direct_path = direct_path - self.embycursor = embycursor - self.emby_db = embydb.Embydb_Functions(self.embycursor) - self.kodicursor = kodicursor - self.kodi_db = _kodi_tvshows.KodiTVShows(self.kodicursor) - self.pdialog = pdialog + self.emby_db = emby_db.EmbyDatabase(embydb.cursor) + self.objects = Objects() - self.new_time = int(settings('newvideotime'))*1000 + KodiDb.__init__(self, videodb.cursor) - Items.__init__(self) + def __getitem__(self, key): - def _get_func(self, item_type, action): + if key == 'Series': + return self.tvshow + elif key == 'Season': + return self.season + elif key == 'Episode': + return self.episode + elif key == 'UserData': + return self.userdata + elif key in 'Removed': + return self.remove - if item_type == "Series": - actions = { - 'added': self.add_shows, - 'update': self.add_update, - 'userdata': self.updateUserdata, - 'remove': self.remove - } - elif item_type == "Season": - actions = { - 'added': self.add_seasons, - 'update': self.add_updateSeason, - 'remove': self.remove - } - elif item_type == "Episode": - actions = { - 'added': self.add_episodes, - 'update': self.add_updateEpisode, - 'userdata': self.updateUserdata, - 'remove': self.remove - } - else: - log.info("Unsupported item_type: %s", item_type) - actions = {} + @stop() + @emby_item() + @library_check() + def tvshow(self, item, e_item, library): - return actions.get(action) + ''' If item does not exist, entry will be added. + If item exists, entry will be updated. - def compare_all(self): - # Pull the list of movies and boxsets in Kodi - import emby as mb + If the show is empty, try to remove it. + Process seasons. + Apply series pooling. + ''' + API = api.API(item, self.server['auth/server-address']) + obj = self.objects.map(item, 'Series') + update = True - pdialog = self.pdialog - views = self.emby_db.getView_byType('tvshows') - views += self.emby_db.getView_byType('mixed') - log.info("Media folders: %s", views) + if not settings('syncEmptyShows.bool') and not obj['RecursiveCount']: - # Pull the list of tvshows and episodes in Kodi - try: - all_koditvshows = dict(self.emby_db.get_checksum('Series')) - except ValueError: - all_koditvshows = {} + LOG.info("Skipping empty show %s: %s", obj['Title'], obj['Id']) + self.remove(obj['Id']) - log.info("all_koditvshows = %s", all_koditvshows) - - try: - all_kodiepisodes = dict(self.emby_db.get_checksum('Episode')) - except ValueError: - all_kodiepisodes = {} - - all_embytvshowsIds = set() - all_embyepisodesIds = set() - updatelist = [] - - # TODO: Review once series pooling is explicitely returned in api - for view in views: - - if self.should_stop(): - return False - - # Get items per view - viewId = view['id'] - viewName = view['name'] - - if pdialog: - pdialog.update( - heading=lang(29999), - message="%s %s..." % (lang(33029), viewName)) - - all_embytvshows = self.emby.getShows(viewId, basic=True, dialog=pdialog) - for embytvshow in all_embytvshows['Items']: - - if self.should_stop(): - return False - - API = api.API(embytvshow) - itemid = embytvshow['Id'] - all_embytvshowsIds.add(itemid) - - - if all_koditvshows.get(itemid) != API.get_checksum(): - # Only update if movie is not in Kodi or checksum is different - updatelist.append(itemid) - - log.info("TVShows to update for %s: %s", viewName, updatelist) - embytvshows = (items['Items'] for items in mb.get_item_list(updatelist, True)) - self.total = len(updatelist) - del updatelist[:] - - - if pdialog: - pdialog.update(heading="Processing %s / %s items" % (viewName, self.total)) - - self.count = 0 - for embytvshow in embytvshows: - # Process individual show - if self.should_stop(): - return False - - itemid = embytvshow['Id'] - title = embytvshow['Name'] - all_embytvshowsIds.add(itemid) - self.update_pdialog() - - self.add_update(embytvshow, view) - self.count += 1 - - else: - # Get all episodes in view - if pdialog: - pdialog.update( - heading=lang(29999), - message="%s %s..." % (lang(33030), viewName)) - - all_embyepisodes = self.emby.getEpisodes(viewId, basic=True, dialog=pdialog) - for embyepisode in all_embyepisodes['Items']: - - if self.should_stop(): - return False - - API = api.API(embyepisode) - itemid = embyepisode['Id'] - all_embyepisodesIds.add(itemid) - if "SeriesId" in embyepisode: - all_embytvshowsIds.add(embyepisode['SeriesId']) - - if all_kodiepisodes.get(itemid) != API.get_checksum(): - # Only update if movie is not in Kodi or checksum is different - updatelist.append(itemid) - - log.info("Episodes to update for %s: %s", viewName, updatelist) - embyepisodes = self.emby.getFullItems(updatelist) - self.total = len(updatelist) - del updatelist[:] - - self.count = 0 - for episode in embyepisodes: - - # Process individual episode - if self.should_stop(): - return False - self.title = "%s - %s" % (episode.get('SeriesName', "Unknown"), episode['Name']) - self.add_updateEpisode(episode) - self.count += 1 - - ##### PROCESS DELETES ##### - - log.info("all_embytvshowsIds = %s ", all_embytvshowsIds) - - for koditvshow in all_koditvshows: - if koditvshow not in all_embytvshowsIds: - self.remove(koditvshow) - - log.info("TVShows compare finished.") - - for kodiepisode in all_kodiepisodes: - if kodiepisode not in all_embyepisodesIds: - self.remove(kodiepisode) - - log.info("Episodes compare finished.") - - return True - - - def add_shows(self, items, total=None, view=None): - - for item in self.added(items, total): - if self.add_update(item, view): - # Add episodes - for all_episodes in mb.get_items(item['Id'], "Episode"): - self.add_episodes(all_episodes['Items']) - - def add_seasons(self, items, total=None, view=None): - - update = True if not self.total else False - - for item in self.added(items, total, update): - self.title = "%s - %s" % (item.get('SeriesName', "Unknown"), self.title) - - if self.add_updateSeason(item): - # Add episodes - for all_episodes in mb.get_items(item['Id'], "Episode"): - self.add_episodes(all_episodes['Items']) - - def add_episodes(self, items, total=None, view=None): - - update = True if not self.total else False - - for item in self.added(items, total, update): - self.title = "%s - %s" % (item.get('SeriesName', "Unknown"), self.title) - - if self.add_updateEpisode(item): - self.content_pop(self.title) - - @catch_except() - def add_update(self, item, view=None): - # Process single tvshow - kodicursor = self.kodicursor - emby = self.emby - emby_db = self.emby_db - artwork = self.artwork - API = api.API(item) - - # If the show is empty, try to remove it. - if settings('syncEmptyShows') == "false" and not item.get('RecursiveItemCount'): - log.info("Skipping empty show: %s", item.get('Name', item['Id'])) - return self.remove(item['Id']) - - # If the item already exist in the local Kodi DB we'll perform a full item update - # If the item doesn't exist, we'll add it to the database - update_item = True - force_episodes = False - itemid = item['Id'] - emby_dbitem = emby_db.getItem_byId(itemid) - - if view is None: - # Get view tag from emby - viewtag, viewid = emby_db.getView_embyId(itemid) - log.debug("View tag found: %s", viewtag) - else: - viewtag = view['name'] - viewid = view['id'] - - # fileId information - checksum = API.get_checksum() - userdata = API.get_userdata() - - # item details - genres = item['Genres'] - title = item['Name'] - plot = API.get_overview() - rating = item.get('CommunityRating') - votecount = item.get('VoteCount') - premieredate = API.get_premiere_date() - tvdb = API.get_provider('Tvdb') - sorttitle = item['SortName'] - mpaa = API.get_mpaa() - genre = " / ".join(genres) - studios = API.get_studios() - studio = " / ".join(studios) - - ##### GET THE FILE AND PATH ##### - playurl = API.get_file_path() - - if self.direct_path: - # Direct paths is set the Kodi way - if "\\" in playurl: - # Local path - path = "%s\\" % playurl - toplevelpath = "%s\\" % dirname(dirname(path)) - else: - # Network path - path = "%s/" % playurl - toplevelpath = "%s/" % dirname(dirname(path)) - - if not self.path_validation(path): - return False - - window('emby_pathverified', value="true") - else: - # Set plugin path - toplevelpath = "plugin://plugin.video.emby.tvshows/" - path = "%s%s/" % (toplevelpath, itemid) - - - try: - showid = emby_dbitem[0] - pathid = emby_dbitem[2] - log.info("showid: %s pathid: %s", showid, pathid) - - except TypeError: - update_item = False - showid = None - log.debug("showid: %s not found", itemid) - - if self.emby_db.get_view_grouped_series(viewid) and tvdb: - # search kodi db for same provider id - query = "SELECT idShow FROM tvshow_view WHERE uniqueid_value = ?" - kodicursor.execute(query, (tvdb,)) - - try: - temps_showid = kodicursor.fetchall() - except TypeError: pass - else: - for temp_showid in temps_showid: - emby_other = emby_db.getItem_byKodiId(temp_showid[0], "tvshow") - if emby_other and viewid == emby_other[2]: - log.info("Applying series pooling for: %s %s", itemid, title) - emby_other_item = emby_db.getItem_byId(emby_other[0]) - showid = emby_other_item[0] - pathid = self.kodi_db.add_path(path) - emby_db.addReference(itemid, showid, "Series", "tvshow", pathid=pathid, - checksum=checksum, mediafolderid=viewid) - return - - showid = self.kodi_db.create_entry() - - else: - # Verification the item is still in Kodi - if self.kodi_db.get_tvshow(showid) is None: - # item is not found, let's recreate it. - update_item = False - log.info("showid: %s missing from Kodi, repairing the entry", showid) - # Force re-add episodes after the show is re-created. - force_episodes = True - - - ##### UPDATE THE TVSHOW ##### - if update_item: - log.info("UPDATE tvshow itemid: %s - Title: %s", itemid, title) - - # update ratings - ratingid = self.kodi_db.get_ratingid("tvshow", showid) - self.kodi_db.update_ratings(showid, "tvshow", "default", rating, votecount,ratingid) - - # update uniqueid - uniqueid = self.kodi_db.get_uniqueid("tvshow", showid) - self.kodi_db.update_uniqueid(showid, "tvshow", tvdb, "tvdb", uniqueid) - - # Update the tvshow entry - self.kodi_db.update_tvshow(title, plot, uniqueid, premieredate, genre, title, - uniqueid, mpaa, studio, sorttitle, showid) - - # Update the checksum in emby table - emby_db.updateReference(itemid, checksum) - - ##### OR ADD THE TVSHOW ##### - else: - log.info("ADD tvshow itemid: %s - Title: %s", itemid, title) - - # add ratings - ratingid = self.kodi_db.create_entry_rating() - self.kodi_db.add_ratings(ratingid, showid, "tvshow", "default", rating, votecount) - - # add uniqueid - uniqueid = self.kodi_db.create_entry_uniqueid() - self.kodi_db.add_uniqueid(uniqueid, showid, "tvshow", tvdb, "tvdb") - - # Add top path - toppathid = self.kodi_db.add_path(toplevelpath) - self.kodi_db.update_path(toppathid, toplevelpath, "tvshows", "metadata.local") - - # Add path - pathid = self.kodi_db.add_path(path) - - # Create the tvshow entry - self.kodi_db.add_tvshow(showid, title, plot, uniqueid, premieredate, genre, - title, uniqueid, mpaa, studio, sorttitle) - - # Create the reference in emby table - emby_db.addReference(itemid, showid, "Series", "tvshow", pathid=pathid, - checksum=checksum, mediafolderid=viewid) - - - # Link the path - self.kodi_db.link_tvshow(showid, pathid) - - # Update the path - self.kodi_db.update_path(pathid, path, None, None) - - # Process cast - people = artwork.get_people_artwork(item['People']) - self.kodi_db.add_people(showid, people, "tvshow") - # Process genres - self.kodi_db.add_genres(showid, genres, "tvshow") - # Process artwork - artwork.add_artwork(artwork.get_all_artwork(item), showid, "tvshow", kodicursor) - # Process studios - self.kodi_db.add_studios(showid, studios, "tvshow") - # Process tags: view, emby tags - tags = [viewtag] - tags.extend(item['Tags']) - if userdata['Favorite']: - tags.append("Favorite tvshows") - self.kodi_db.add_tags(showid, tags, "tvshow") - # Process seasons - all_seasons = emby.getSeasons(itemid) - for season in all_seasons['Items']: - log.info("found season: %s", season) - self.add_updateSeason(season, showid=showid) - else: - # Finally, refresh the all season entry - seasonid = self.kodi_db.get_season(showid, -1) - # Process artwork - artwork.add_artwork(artwork.get_all_artwork(item), seasonid, "season", kodicursor) - - if force_episodes: - # We needed to recreate the show entry. Re-add episodes now. - log.info("Repairing episodes for showid: %s %s", showid, title) - all_episodes = emby.getEpisodesbyShow(itemid) - self.add_episodes(all_episodes['Items'], None) - - return True - - def add_updateSeason(self, item, showid=None): - - kodicursor = self.kodicursor - emby_db = self.emby_db - artwork = self.artwork - - seasonnum = item.get('IndexNumber', 1) - - if showid is None: - try: - seriesId = item['SeriesId'] - showid = emby_db.getItem_byId(seriesId)[0] - except KeyError: - return - except TypeError: - # Show is missing, update show instead. - show = self.emby.getItem(seriesId) - self.add_update(show) - return - - seasonid = self.kodi_db.get_season(showid, seasonnum, item['Name']) - - if item['LocationType'] != "Virtual": - # Create the reference in emby table - emby_db.addReference(item['Id'], seasonid, "Season", "season", parentid=showid) - - # Process artwork - artwork.add_artwork(artwork.get_all_artwork(item), seasonid, "season", kodicursor) - - log.info("Processed seasonid: %s index: %s", item['Id'], seasonnum) - return True - - @catch_except() - def add_updateEpisode(self, item): - # Process single episode - kodicursor = self.kodicursor - emby_db = self.emby_db - artwork = self.artwork - API = api.API(item) - - if item.get('LocationType') == "Virtual": # TODO: Filter via api instead - log.info("Skipping virtual episode: %s", item['Name']) - return - - # If the item already exist in the local Kodi DB we'll perform a full item update - # If the item doesn't exist, we'll add it to the database - update_item = True - itemid = item['Id'] - emby_dbitem = emby_db.getItem_byId(itemid) - try: - episodeid = emby_dbitem[0] - fileid = emby_dbitem[1] - pathid = emby_dbitem[2] - log.info("episodeid: %s fileid: %s pathid: %s", episodeid, fileid, pathid) - - except TypeError: - update_item = False - log.debug("episodeid: %s not found", itemid) - # episodeid - episodeid = self.kodi_db.create_entry_episode() - - else: - # Verification the item is still in Kodi - if self.kodi_db.get_episode(episodeid) is None: - # item is not found, let's recreate it. - update_item = False - log.info("episodeid: %s missing from Kodi, repairing the entry", episodeid) - - # fileId information - checksum = API.get_checksum() - dateadded = API.get_date_created() - userdata = API.get_userdata() - playcount = userdata['PlayCount'] - dateplayed = userdata['LastPlayedDate'] - - # item details - people = API.get_people() - writer = " / ".join(people['Writer']) - director = " / ".join(people['Director']) - title = item['Name'] - plot = API.get_overview() - rating = item.get('CommunityRating') - runtime = API.get_runtime() - premieredate = API.get_premiere_date() - - votecount = item.get('VoteCount') - tvdb = API.get_provider('Tvdb') - - # episode details - try: - seriesId = item['SeriesId'] - except KeyError: - # Missing seriesId, skip - log.error("Skipping: %s. SeriesId is missing.", itemid) return False - season = item.get('ParentIndexNumber') - episode = item.get('IndexNumber', -1) - - if season is None: - if item.get('AbsoluteEpisodeNumber'): - # Anime scenario - season = 1 - episode = item['AbsoluteEpisodeNumber'] - else: - season = -1 if "Specials" not in item['Path'] else 0 - - # Specials ordering within season - if item.get('AirsAfterSeasonNumber'): - airsBeforeSeason = item['AirsAfterSeasonNumber'] - airsBeforeEpisode = 4096 # Kodi default number for afterseason ordering - else: - airsBeforeSeason = item.get('AirsBeforeSeasonNumber') - airsBeforeEpisode = item.get('AirsBeforeEpisodeNumber') - - # Append multi episodes to title - if item.get('IndexNumberEnd'): - title = "| %02d | %s" % (item['IndexNumberEnd'], title) - - # Get season id - show = emby_db.getItem_byId(seriesId) try: - showid = show[0] - except TypeError: - # Show is missing from database - show = self.emby.getItem(seriesId) - self.add_update(show) - show = emby_db.getItem_byId(seriesId) + obj['ShowId'] = e_item[0] + obj['PathId'] = e_item[2] + except TypeError as error: + + update = False + LOG.debug("ShowId %s not found", obj['Id']) + obj['ShowId'] = self.create_entry() + else: + if self.get(*values(obj, QU.get_tvshow_obj)) is None: + + update = False + LOG.info("ShowId %s missing from kodi. repairing the entry.", obj['ShowId']) + + + obj['Path'] = API.get_file_path(obj['Path']) + obj['LibraryId'] = library['Id'] + obj['LibraryName'] = library['Name'] + obj['Genres'] = obj['Genres'] or [] + obj['People'] = obj['People'] or [] + obj['Studios'] = [API.validate_studio(studio) for studio in (obj['Studios'] or [])] + obj['Genre'] = " / ".join(obj['Genres']) + obj['People'] = API.get_people_artwork(obj['People']) + obj['Plot'] = API.get_overview(obj['Plot']) + obj['Studio'] = " / ".join(obj['Studios']) + obj['Artwork'] = API.get_all_artwork(self.objects.map(item, 'Artwork')) + + self.get_path_filename(obj) + + if obj['Premiere']: + obj['Premiere'] = str(obj['Premiere']).split('.')[0].replace('T', " ") + + tags = [] + tags.extend(obj['Tags'] or []) + tags.append(obj['LibraryName']) + + if obj['Favorite']: + tags.append('Favorite tvshows') + + obj['Tags'] = tags + + + if update: + self.tvshow_update(obj) + else: + self.tvshow_add(obj) + + + self.link(*values(obj, QU.update_tvshow_link_obj)) + self.update_path(*values(obj, QU.update_path_tvshow_obj)) + self.add_tags(*values(obj, QU.add_tags_tvshow_obj)) + self.add_people(*values(obj, QU.add_people_tvshow_obj)) + self.add_genres(*values(obj, QU.add_genres_tvshow_obj)) + self.add_studios(*values(obj, QU.add_studios_tvshow_obj)) + self.artwork.add(obj['Artwork'], obj['ShowId'], "tvshow") + + season_episodes = [] + + for season in self.server['api'].get_seasons(obj['Id'])['Items']: + self.season(season, obj['ShowId']) + + if season['SeriesId'] != obj['Id']: + obj['SeriesId'] = season['SeriesId'] + + try: + self.emby_db.get_item_by_id(*values(obj, QUEM.get_item_series_obj))[0] + except TypeError: + + self.emby_db.add_reference(*values(obj, QUEM.add_reference_pool_obj)) + season_episodes.append(season['Id']) + LOG.info("POOL %s [%s/%s]", obj['Title'], obj['Id'], obj['SeriesId']) + else: + season_id = self.get_season(*values(obj, QU.get_season_special_obj)) + self.artwork.add(obj['Artwork'], season_id, "season") + + for season in season_episodes: + for episodes in server.get_items(season, "Episode"): + + for episode in episodes['Items']: + self.episode(episode) + + def tvshow_add(self, obj): + + ''' Add object to kodi. + ''' + obj['RatingId'] = self.create_entry_rating() + self.add_ratings(*values(obj, QU.add_rating_tvshow_obj)) + + obj['Unique'] = self.create_entry_unique_id() + self.add_unique_id(*values(obj, QU.add_unique_id_tvshow_obj)) + + obj['TopPathId'] = self.add_path(obj['TopLevel']) + self.update_path(*values(obj, QU.update_path_toptvshow_obj)) + + obj['PathId'] = self.add_path(*values(obj, QU.get_path_obj)) + + self.add(*values(obj, QU.add_tvshow_obj)) + self.emby_db.add_reference(*values(obj, QUEM.add_reference_tvshow_obj)) + LOG.info("ADD tvshow [%s/%s/%s] %s: %s", obj['TopPathId'], obj['PathId'], obj['ShowId'], obj['Title'], obj['Id']) + + def tvshow_update(self, obj): + + ''' Update object to kodi. + ''' + obj['RatingId'] = self.get_rating_id(*values(obj, QU.get_unique_id_tvshow_obj)) + self.update_ratings(*values(obj, QU.update_rating_tvshow_obj)) + + obj['Unique'] = self.get_unique_id(*values(obj, QU.get_unique_id_tvshow_obj)) + self.update_unique_id(*values(obj, QU.update_unique_id_tvshow_obj)) + + self.update(*values(obj, QU.update_tvshow_obj)) + self.emby_db.update_reference(*values(obj, QUEM.update_reference_obj)) + LOG.info("UPDATE tvshow [%s/%s] %s: %s", obj['PathId'], obj['ShowId'], obj['Title'], obj['Id']) + + def get_path_filename(self, obj): + + ''' Get the path and build it into protocol://path + ''' + if self.direct_path: + + if '\\' in obj['Path']: + obj['Path'] = "%s\\" % obj['Path'] + obj['TopLevel'] = "%s\\" % dirname(dirname(obj['Path'])) + else: + obj['Path'] = "%s/" % obj['Path'] + obj['TopLevel'] = "%s/" % dirname(dirname(obj['Path'])) + + if not validate(obj['Path']): + raise Exception("Failed to validate path. User stopped.") + else: + obj['TopLevel'] = "plugin://plugin.video.emby.tvshows/" + obj['Path'] = "%s%s/" % (obj['TopLevel'], obj['Id']) + + + @stop() + def season(self, item, show_id=None): + + ''' If item does not exist, entry will be added. + If item exists, entry will be updated. + + If the show is empty, try to remove it. + ''' + API = api.API(item, self.server['auth/server-address']) + obj = self.objects.map(item, 'Season') + + obj['ShowId'] = show_id + + if obj['ShowId'] is None: + try: - showid = show[0] - except TypeError: - log.error("Skipping: %s. Unable to add series: %s", itemid, seriesId) + obj['ShowId'] = self.emby_db.get_item_by_id(*values(obj, QUEM.get_item_series_obj))[0] + except (KeyError, TypeError): + LOG.error("Unable to add series %s", obj['SeriesId']) + return False - seasonid = self.kodi_db.get_season(showid, season) + obj['SeasonId'] = self.get_season(*values(obj, QU.get_season_obj)) + obj['Artwork'] = API.get_all_artwork(self.objects.map(item, 'Artwork')) + + if obj['Location'] != "Virtual": + self.emby_db.add_reference(*values(obj, QUEM.add_reference_season_obj)) + + self.artwork.add(obj['Artwork'], obj['SeasonId'], "season") + LOG.info("UPDATE season [%s/%s] %s: %s", obj['ShowId'], obj['SeasonId'], obj['Title'] or obj['Index'], obj['Id']) - ##### GET THE FILE AND PATH ##### - playurl = API.get_file_path() + @stop() + @emby_item() + def episode(self, item, e_item): - if "\\" in playurl: - # Local path - filename = playurl.rsplit("\\", 1)[1] - else: # Network share - filename = playurl.rsplit("/", 1)[1] + ''' If item does not exist, entry will be added. + If item exists, entry will be updated. + + Create additional entry for widgets. + This is only required for plugin/episode. + ''' + API = api.API(item, self.server['auth/server-address']) + obj = self.objects.map(item, 'Episode') + update = True + + if obj['Location'] == "Virtual": + LOG.info("Skipping virtual episode %s: %s", obj['Title'], obj['Id']) + + return + + elif obj['SeriesId'] is None: + LOG.info("Skipping episode %s with missing SeriesId", obj['Id']) + + return + + try: + obj['EpisodeId'] = e_item[0] + obj['FileId'] = e_item[1] + obj['PathId'] = e_item[2] + except TypeError as error: + + update = False + LOG.debug("EpisodeId %s not found", obj['Id']) + obj['EpisodeId'] = self.create_entry_episode() + else: + if self.get_episode(*values(obj, QU.get_episode_obj)) is None: + + update = False + LOG.info("EpisodeId %s missing from kodi. repairing the entry.", obj['EpisodeId']) + + + obj['Path'] = API.get_file_path(obj['Path']) + obj['Index'] = obj['Index'] or -1 + obj['Writers'] = " / ".join(obj['Writers'] or []) + obj['Directors'] = " / ".join(obj['Directors'] or []) + obj['Plot'] = API.get_overview(obj['Plot']) + obj['Resume'] = API.adjust_resume((obj['Resume'] or 0) / 10000000.0) + obj['Runtime'] = round(float((obj['Runtime'] or 0) / 10000000.0), 6) + obj['People'] = API.get_people_artwork(obj['People'] or []) + obj['DateAdded'] = obj['DateAdded'].split('.')[0].replace('T', " ") + obj['DatePlayed'] = (obj['DatePlayed'] or obj['DateAdded']).split('.')[0].replace('T', " ") + obj['PlayCount'] = API.get_playcount(obj['Played'], obj['PlayCount']) + obj['Artwork'] = API.get_all_artwork(self.objects.map(item, 'Artwork')) + obj['Video'] = API.video_streams(obj['Video'] or [], obj['Container']) + obj['Audio'] = API.audio_streams(obj['Audio'] or []) + obj['Streams'] = API.media_streams(obj['Video'], obj['Audio'], obj['Subtitles']) + + self.get_episode_path_filename(obj) + + if obj['Premiere']: + obj['Premiere'] = obj['Premiere'].split('.')[0].replace('T', " ") + + if obj['Season'] is None: + if obj['AbsoluteNumber']: + + obj['Season'] = 1 + obj['Index'] = obj['AbsoluteNumber'] + else: + obj['Season'] = 0 + + if obj['AirsAfterSeason']: + + obj['AirsBeforeSeason'] = obj['AirsAfterSeason'] + obj['AirsBeforeEpisode'] = 4096 # Kodi default number for afterseason ordering + + if obj['MultiEpisode']: + obj['MultiEpisode'] = "| %02d | %s" % (obj['MultiEpisode'], obj['Title']) + + if not self.get_show_id(obj): + return False + + obj['SeasonId'] = self.get_season(*values(obj, QU.get_season_episode_obj)) + + + if update: + self.episode_update(obj) + else: + self.episode_add(obj) + + + self.update_path(*values(obj, QU.update_path_episode_obj)) + self.update_file(*values(obj, QU.update_file_obj)) + self.add_people(*values(obj, QU.add_people_episode_obj)) + self.add_streams(*values(obj, QU.add_streams_obj)) + self.add_playstate(*values(obj, QU.add_bookmark_obj)) + self.artwork.update(obj['Artwork']['Primary'], obj['EpisodeId'], "episode", "thumb") + + if not self.direct_path and obj['Resume']: + + temp_obj = dict(obj) + temp_obj['Path'] = "plugin://plugin.video.emby.tvshows/" + temp_obj['PathId'] = self.get_path(*values(temp_obj, QU.get_path_obj)) + temp_obj['FileId'] = self.add_file(*values(temp_obj, QU.add_file_obj)) + self.update_file(*values(temp_obj, QU.update_file_obj)) + self.add_playstate(*values(temp_obj, QU.add_bookmark_obj)) + + def episode_add(self, obj): + + ''' Add object to kodi. + ''' + obj['RatingId'] = self.create_entry_rating() + self.add_ratings(*values(obj, QU.add_rating_episode_obj)) + + obj['Unique'] = self.create_entry_unique_id() + self.add_unique_id(*values(obj, QU.add_unique_id_episode_obj)) + + obj['PathId'] = self.add_path(*values(obj, QU.add_path_obj)) + obj['FileId'] = self.add_file(*values(obj, QU.add_file_obj)) + + self.add_episode(*values(obj, QU.add_episode_obj)) + self.emby_db.add_reference(*values(obj, QUEM.add_reference_episode_obj)) + LOG.debug("ADD episode [%s/%s] %s: %s", obj['PathId'], obj['FileId'], obj['Id'], obj['Title']) + + def episode_update(self, obj): + + ''' Update object to kodi. + ''' + obj['RatingId'] = self.get_rating_id(*values(obj, QU.get_rating_episode_obj)) + self.update_ratings(*values(obj, QU.update_rating_episode_obj)) + + obj['Unique'] = self.get_unique_id(*values(obj, QU.get_unique_id_episode_obj)) + self.update_unique_id(*values(obj, QU.update_unique_id_episode_obj)) + + self.update_episode(*values(obj, QU.update_episode_obj)) + + self.emby_db.update_reference(*values(obj, QUEM.update_reference_obj)) + self.emby_db.update_parent_id(*values(obj, QUEM.update_parent_episode_obj)) + LOG.debug("UPDATE episode [%s/%s] %s: %s", obj['PathId'], obj['FileId'], obj['Id'], obj['Title']) + + def get_episode_path_filename(self, obj): + + ''' Get the path and build it into protocol://path + ''' + if '\\' in obj['Path']: + obj['Filename'] = obj['Path'].rsplit('\\', 1)[1] + else: + obj['Filename'] = obj['Path'].rsplit('/', 1)[1] if self.direct_path: - # Direct paths is set the Kodi way - if not self.path_validation(playurl): - return False - path = playurl.replace(filename, "") - window('emby_pathverified', value="true") + if not validate(obj['Path']): + raise Exception("Failed to validate path. User stopped.") + + obj['Path'] = obj['Path'].replace(obj['Filename'], "") else: - # Set plugin path and media flags using real filename - path = "plugin://plugin.video.emby.tvshows/%s/" % seriesId + obj['Path'] = "plugin://plugin.video.emby.tvshows/%s/" % obj['SeriesId'] params = { - - 'filename': filename.encode('utf-8'), - 'id': itemid, - 'dbid': episodeid, + 'filename': obj['Filename'].encode('utf-8'), + 'id': obj['Id'], + 'dbid': obj['EpisodeId'], 'mode': "play" } - filename = urllib_path(path, params) + obj['Filename'] = "%s?%s" % (obj['Path'], urllib.urlencode(params)) - ##### UPDATE THE EPISODE ##### - if update_item: - log.info("UPDATE episode itemid: %s - Title: %s", itemid, title) + def get_show_id(self, obj): + obj['ShowId'] = self.emby_db.get_item_by_id(*values(obj, QUEM.get_item_series_obj)) - # update ratings - ratingid = self.kodi_db.get_ratingid("episode", episodeid) - self.kodi_db.update_ratings(episodeid, "episode", "default", rating, votecount, ratingid) + if obj['ShowId'] is None: - # update uniqueid - uniqueid = self.kodi_db.get_uniqueid("episode", episodeid) - self.kodi_db.update_uniqueid(episodeid, "episode", tvdb, "tvdb", uniqueid) + try: + self.tvshow(self.server['api'].get_item(obj['SeriesId']), None) + obj['ShowId'] = self.emby_db.get_item_by_id(*values(obj, QUEM.get_item_series_obj))[0] + except (TypeError, KeyError): + LOG.error("Unable to add series %s", obj['SeriesId']) - # Update the episode entry - self.kodi_db.update_episode(title, plot, uniqueid, writer, premieredate, runtime, - director, season, episode, title, airsBeforeSeason, - airsBeforeEpisode, seasonid, showid, episodeid) - - # Update the checksum in emby table - emby_db.updateReference(itemid, checksum) - # Update parentid reference - emby_db.updateParentId(itemid, seasonid) - - ##### OR ADD THE EPISODE ##### + return False else: - log.info("ADD episode itemid: %s - Title: %s", itemid, title) - - # add ratings - ratingid = self.kodi_db.create_entry_rating() - self.kodi_db.add_ratings(ratingid, episodeid, "episode", "default", rating, votecount) - - # add uniqueid - uniqueid = self.kodi_db.create_entry_uniqueid() - self.kodi_db.add_uniqueid(uniqueid, episodeid, "episode", tvdb, "tvdb") - - # Add path - pathid = self.kodi_db.add_path(path) - # Add the file - fileid = self.kodi_db.add_file(filename, pathid) - - # Create the episode entry - self.kodi_db.add_episode(episodeid, fileid, title, plot, uniqueid, writer, - premieredate, runtime, director, season, episode, title, - showid, airsBeforeSeason, airsBeforeEpisode, seasonid) - - # Create the reference in emby table - emby_db.addReference(itemid, episodeid, "Episode", "episode", fileid, pathid, - seasonid, checksum) - - # Update the path - self.kodi_db.update_path(pathid, path, None, None) - # Update the file - self.kodi_db.update_file(fileid, filename, pathid, dateadded) - - # Process cast - people = artwork.get_people_artwork(item['People']) - self.kodi_db.add_people(episodeid, people, "episode") - # Process artwork - artworks = artwork.get_all_artwork(item) - artwork.add_update_art(artworks['Primary'], episodeid, "episode", "thumb", kodicursor) - # Process stream details - streams = API.get_media_streams() - self.kodi_db.add_streams(fileid, streams, runtime) - # Process playstates - resume = API.adjust_resume(userdata['Resume']) - total = round(float(runtime), 6) - self.kodi_db.add_playstate(fileid, resume, total, playcount, dateplayed) - if not self.direct_path and resume: - # Create additional entry for widgets. This is only required for plugin/episode. - temppathid = self.kodi_db.get_path("plugin://plugin.video.emby.tvshows/") - tempfileid = self.kodi_db.add_file(filename, temppathid) - self.kodi_db.update_file(tempfileid, filename, temppathid, dateadded) - self.kodi_db.add_playstate(tempfileid, resume, total, playcount, dateplayed) + obj['ShowId'] = obj['ShowId'][0] return True - def updateUserdata(self, item): - # This updates: Favorite, LastPlayedDate, Playcount, PlaybackPositionTicks - # Poster with progress bar - emby_db = self.emby_db - API = api.API(item) - # Get emby information - itemid = item['Id'] - checksum = API.get_checksum() - userdata = API.get_userdata() - runtime = API.get_runtime() - dateadded = API.get_date_created() + @stop() + @emby_item() + def userdata(self, item, e_item): + + ''' This updates: Favorite, LastPlayedDate, Playcount, PlaybackPositionTicks + Poster with progress bar + + Make sure there's no other bookmarks created by widget. + Create additional entry for widgets. This is only required for plugin/episode. + ''' + API = api.API(item, self.server['auth/server-address']) + obj = self.objects.map(item, 'EpisodeUserData') - # Get Kodi information - emby_dbitem = emby_db.getItem_byId(itemid) try: - kodiid = emby_dbitem[0] - fileid = emby_dbitem[1] - mediatype = emby_dbitem[4] - log.info("Update playstate for %s: %s fileid: %s", mediatype, item['Name'], fileid) + obj['KodiId'] = e_item[0] + obj['FileId'] = e_item[1] + obj['Media'] = e_item[4] except TypeError: return - # Process favorite tags - if mediatype == "tvshow": - if userdata['Favorite']: - self.kodi_db.get_tag(kodiid, "Favorite tvshows", "tvshow") + if obj['Media'] == "tvshow": + + if obj['Favorite']: + self.get_tag(*values(obj, QU.get_tag_episode_obj)) else: - self.kodi_db.remove_tag(kodiid, "Favorite tvshows", "tvshow") - elif mediatype == "episode": - # Process playstates - playcount = userdata['PlayCount'] - dateplayed = userdata['LastPlayedDate'] - resume = API.adjust_resume(userdata['Resume']) - total = round(float(runtime), 6) + self.remove_tag(*values(obj, QU.delete_tag_episode_obj)) - log.debug("%s New resume point: %s", itemid, resume) + elif obj['Media'] == "episode": + + obj['Resume'] = API.adjust_resume((obj['Resume'] or 0) / 10000000.0) + obj['Runtime'] = round(float((obj['Runtime'] or 0) / 10000000.0), 6) + obj['PlayCount'] = API.get_playcount(obj['Played'], obj['PlayCount']) - self.kodi_db.add_playstate(fileid, resume, total, playcount, dateplayed) - if not self.direct_path and not resume: - # Make sure there's no other bookmarks created by widget. - filename = self.kodi_db.get_filename(fileid) - self.kodi_db.remove_file("plugin://plugin.video.emby.tvshows/", filename) + if obj['DatePlayed']: + obj['DatePlayed'] = obj['DatePlayed'].split('.')[0].replace('T', " ") - if not self.direct_path and resume: - # Create additional entry for widgets. This is only required for plugin/episode. - filename = self.kodi_db.get_filename(fileid) - temppathid = self.kodi_db.get_path("plugin://plugin.video.emby.tvshows/") - tempfileid = self.kodi_db.add_file(filename, temppathid) - self.kodi_db.update_file(tempfileid, filename, temppathid, dateadded) - self.kodi_db.add_playstate(tempfileid, resume, total, playcount, dateplayed) + if obj['DateAdded']: + obj['DateAdded'] = obj['DateAdded'].split('.')[0].replace('T', " ") - emby_db.updateReference(itemid, checksum) + self.add_playstate(*values(obj, QU.add_bookmark_obj)) - def remove(self, itemid): - # Remove showid, fileid, pathid, emby reference - emby_db = self.emby_db - kodicursor = self.kodicursor + if not self.direct_path and not obj['Resume']: + + temp_obj = dict(obj) + temp_obj['Filename'] = self.get_filename(*values(temp_obj, QU.get_file_obj)) + temp_obj['Path'] = "plugin://plugin.video.emby.tvshows/" + self.remove_file(*values(temp_obj, QU.delete_file_obj)) + + elif not self.direct_path and obj['Resume']: + + temp_obj = dict(obj) + temp_obj['Filename'] = self.get_filename(*values(temp_obj, QU.get_file_obj)) + temp_obj['PathId'] = self.get_path("plugin://plugin.video.emby.tvshows/") + temp_obj['FileId'] = self.add_file(*values(temp_obj, QU.add_file_obj)) + self.update_file(*values(temp_obj, QU.update_file_obj)) + self.add_playstate(*values(temp_obj, QU.add_bookmark_obj)) + + self.emby_db.update_reference(*values(obj, QUEM.update_reference_obj)) + LOG.info("USERDATA %s [%s/%s] %s: %s", obj['Media'], obj['FileId'], obj['KodiId'], obj['Id'], obj['Title']) + + @stop() + @emby_item() + def remove(self, item_id, e_item): + + ''' Remove showid, fileid, pathid, emby reference. + There's no episodes left, delete show and any possible remaining seasons + ''' + obj = {'Id': item_id} - emby_dbitem = emby_db.getItem_byId(itemid) try: - kodiid = emby_dbitem[0] - fileid = emby_dbitem[1] - parentid = emby_dbitem[3] - mediatype = emby_dbitem[4] - log.info("Removing %s kodiid: %s fileid: %s", mediatype, kodiid, fileid) + obj['KodiId'] = e_item[0] + obj['FileId'] = e_item[1] + obj['ParentId'] = e_item[3] + obj['Media'] = e_item[4] except TypeError: return - ##### PROCESS ITEM ##### + if obj['Media'] == 'episode': - # Remove the emby reference - emby_db.removeItem(itemid) + temp_obj = dict(obj) + self.remove_episode(obj['KodiId'], obj['FileId'], obj['Id']) + season = self.emby_db.get_full_item_by_kodi_id(*values(obj, QUEM.delete_item_by_parent_season_obj)) - ##### IF EPISODE ##### - - if mediatype == "episode": - # Delete kodi episode and file, verify season and tvshow - self.removeEpisode(kodiid, fileid) - - # Season verification - season = emby_db.getItem_byKodiId(parentid, "season") try: - showid = season[1] + temp_obj['Id'] = season[0] + temp_obj['ParentId'] = season[1] except TypeError: return - season_episodes = emby_db.getItem_byParentId(parentid, "episode") - if not season_episodes: - self.removeSeason(parentid) - emby_db.removeItem(season[0]) + if not self.emby_db.get_item_by_parent_id(*values(obj, QUEM.get_item_by_parent_episode_obj)): - # Show verification - show = emby_db.getItem_byKodiId(showid, "tvshow") - query = ' '.join(( + self.remove_season(obj['ParentId'], obj['Id']) + self.emby_db.remove_item(*values(temp_obj, QUEM.delete_item_obj)) - "SELECT totalCount", - "FROM tvshowcounts", - "WHERE idShow = ?" - )) - kodicursor.execute(query, (showid,)) - result = kodicursor.fetchone() - if result and result[0] is None: - # There's no episodes left, delete show and any possible remaining seasons - seasons = emby_db.getItem_byParentId(showid, "season") - for season in seasons: - self.removeSeason(season[1]) + temp_obj['Id'] = self.emby_db.get_item_by_kodi_id(*values(temp_obj, QUEM.get_item_by_parent_tvshow_obj)) + + if not self.get_total_episodes(*values(temp_obj, QU.get_total_episodes_obj)): + + for season in self.emby_db.get_item_by_parent_id(*values(temp_obj, QUEM.get_item_by_parent_season_obj)): + self.remove_season(season[1], obj['Id']) else: - # Delete emby season entries - emby_db.removeItems_byParentId(showid, "season") - self.removeShow(showid) - emby_db.removeItem(show[0]) + self.emby_db.remove_items_by_parent_id(*values(temp_obj, QUEM.delete_item_by_parent_season_obj)) - ##### IF TVSHOW ##### + self.remove_tvshow(temp_obj['ParentId'], obj['Id']) + self.emby_db.remove_item(*values(temp_obj, QUEM.delete_item_obj)) - elif mediatype == "tvshow": - # Remove episodes, seasons, tvshow - seasons = emby_db.getItem_byParentId(kodiid, "season") - for season in seasons: - seasonid = season[1] - season_episodes = emby_db.getItem_byParentId(seasonid, "episode") - for episode in season_episodes: - self.removeEpisode(episode[1], episode[2]) + elif obj['Media'] == 'tvshow': + obj['ParentId'] = obj['KodiId'] + + for season in self.emby_db.get_item_by_parent_id(*values(obj, QUEM.get_item_by_parent_season_obj)): + + temp_obj = dict(obj) + temp_obj['ParentId'] = season[1] + + for episode in self.emby_db.get_item_by_parent_id(*values(temp_obj, QUEM.get_item_by_parent_episode_obj)): + self.remove_episode(episode[1], episode[2], obj['Id']) else: - # Remove emby episodes - emby_db.removeItems_byParentId(seasonid, "episode") + self.emby_db.remove_items_by_parent_id(*values(temp_obj, QUEM.delete_item_by_parent_episode_obj)) else: - # Remove emby seasons - emby_db.removeItems_byParentId(kodiid, "season") + self.emby_db.remove_items_by_parent_id(*values(obj, QUEM.delete_item_by_parent_season_obj)) - # Remove tvshow - self.removeShow(kodiid) + self.remove_tvshow(obj['KodiId'], obj['Id']) - ##### IF SEASON ##### + elif obj['Media'] == 'season': - elif mediatype == "season": - # Remove episodes, season, verify tvshow - season_episodes = emby_db.getItem_byParentId(kodiid, "episode") - for episode in season_episodes: - self.removeEpisode(episode[1], episode[2]) + for episode in self.emby_db.get_item_by_parent_id(*values(obj, QUEM.get_item_by_parent_episode_obj)): + self.remove_episode(episode[1], episode[2], obj['Id']) else: - # Remove emby episodes - emby_db.removeItems_byParentId(kodiid, "episode") + self.emby_db.remove_items_by_parent_id(*values(obj, QUEM.delete_item_by_parent_episode_obj)) - # Remove season - self.removeSeason(kodiid) + self.remove_season(obj['KodiId'], obj['Id']) - # Show verification - seasons = emby_db.getItem_byParentId(parentid, "season") - if not seasons: - # There's no seasons, delete the show - self.removeShow(parentid) - emby_db.removeItem_byKodiId(parentid, "tvshow") + if not self.emby_db.get_item_by_parent_id(*values(obj, QUEM.delete_item_by_parent_season_obj)): - log.info("Deleted %s: %s from kodi database", mediatype, itemid) + self.remove_show(obj['ParentId'], obj['Id']) + self.emby_db.remove_item_by_kodi_id(*values(obj, QUEM.delete_item_by_parent_tvshow_obj)) - def removeShow(self, kodiid): + self.emby_db.remove_item(*values(obj, QUEM.delete_item_obj)) - kodicursor = self.kodicursor - self.artwork.delete_artwork(kodiid, "tvshow", kodicursor) - self.kodi_db.remove_tvshow(kodiid) - log.debug("Removed tvshow: %s", kodiid) + def remove_tvshow(self, kodi_id, item_id): + + self.artwork.delete(kodi_id, "tvshow") + self.delete_tvshow(kodi_id) + LOG.debug("DELETE tvshow [%s] %s", kodi_id, item_id) - def removeSeason(self, kodiid): + def remove_season(self, kodi_id, item_id): - kodicursor = self.kodicursor + self.artwork.delete(kodi_id, "season") + self.delete_season(kodi_id) + LOG.info("DELETE season [%s] %s", kodi_id, item_id) - self.artwork.delete_artwork(kodiid, "season", kodicursor) - self.kodi_db.remove_season(kodiid) - log.debug("Removed season: %s", kodiid) + def remove_episode(self, kodi_id, file_id, item_id): - def removeEpisode(self, kodiid, fileid): - - kodicursor = self.kodicursor - - self.artwork.delete_artwork(kodiid, "episode", kodicursor) - self.kodi_db.remove_episode(kodiid, fileid) - log.debug("Removed episode: %s", kodiid) - - def add_tvshow(self, item, view): - # The only way to keep things together is to drill down, like in the webclient. - # If series pooling is true, they will share the showid, extra tvshow entries - # will be added to emby.db due to server events. - pass + self.artwork.delete(kodi_id, "episode") + self.delete_episode(kodi_id, file_id) + LOG.info("DELETE episode [%s/%s] %s", file_id, kodi_id, item_id) diff --git a/resources/lib/objects/utils.py b/resources/lib/objects/utils.py new file mode 100644 index 00000000..f952d631 --- /dev/null +++ b/resources/lib/objects/utils.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- + +################################################################################################# + +import logging + +from helper import JSONRPC + +################################################################################################# + +LOG = logging.getLogger("EMBY."+__name__) + +################################################################################################# + +def get_play_action(): + + ''' I could not figure out a way to listen to kodi setting changes? + For now, verify the play action every time play is called. + ''' + options = ['Choose', 'Play', 'Resume', 'Show information'] + result = JSONRPC('Settings.GetSettingValue').execute({'setting': "myvideos.selectaction"}) + try: + return options[result['result']['value']] + except Exception as error: + log.error("Returning play action due to error: %s", error) + + return options[1] + +def get_grouped_set(): + + ''' Get if boxsets should be grouped + ''' + result = JSONRPC('Settings.GetSettingValue').execute({'setting': "videolibrary.groupmoviesets"}) + try: + return result['result']['value'] + except Exception as error: + return False diff --git a/resources/lib/playbackutils.py b/resources/lib/playbackutils.py deleted file mode 100644 index cba874c4..00000000 --- a/resources/lib/playbackutils.py +++ /dev/null @@ -1,450 +0,0 @@ -# -*- coding: utf-8 -*- - -################################################################################################# - -import json -import logging -import requests -import os -import shutil -import sys -from datetime import timedelta - -import xbmc -import xbmcgui -import xbmcplugin -import xbmcaddon -import xbmcvfs - -import api -import artwork -import downloadutils -import playutils as putils -import playlist -import read_embyserver as embyserver -import shutil -import embydb_functions as embydb -from database import DatabaseConn -from dialogs import resume -from utils import window, settings, language as lang, JSONRPC - -################################################################################################# - -log = logging.getLogger("EMBY."+__name__) -KODI_V = int(xbmc.getInfoLabel('System.BuildVersion')[:2]) - -################################################################################################# - - -class PlaybackUtils(object): - - - def __init__(self, item=None, item_id=None): - - self.artwork = artwork.Artwork() - self.emby = embyserver.Read_EmbyServer() - - self.item = item or self.emby.getItem(item_id) - self.API = api.API(self.item) - - self.server = window('emby_server%s' % window('emby_currUser')) - - self.stack = [] - - if self.item['Type'] == "Audio": - self.playlist = xbmc.PlayList(xbmc.PLAYLIST_MUSIC) - else: - self.playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO) - - def _detect_widgets(self): - - kodi_version = xbmc.getInfoLabel('System.BuildVersion') - - if KODI_V == 18: - return False - - elif kodi_version and "Git:" in kodi_version and kodi_version.split('Git:')[1].split("-")[0] in ('20171119', 'a9a7a20'): - #TODO: To be reviewed once Leia is out. - log.info("Build does not require workaround for widgets?") - return False - ''' - if not xbmc.getCondVisibility('Window.IsMedia'): - log.info("Not Window.IsMedia") - - if self.item['Type'] == "Audio" and not xbmc.getCondVisibility('Integer.IsGreater(Playlist.Length(music),1)'): - log.info("Audio and not playlist") - - if not xbmc.getCondVisibility('Integer.IsGreater(Playlist.Length(video),1)'): - log.info("Not video playlist") - ''' - - if (not xbmc.getCondVisibility('Window.IsMedia') and - ((self.item['Type'] == "Audio" and not xbmc.getCondVisibility('Integer.IsGreater(Playlist.Length(music),1)')) or - not xbmc.getCondVisibility('Integer.IsGreater(Playlist.Length(video),1)'))): - - return True - - return False - - def play(self, item_id, dbid=None, force_transcode=False): - - listitem = xbmcgui.ListItem() - - log.info("Play called %s: %s", item_id, self.item['Name']) - - resume = window('emby.resume') - window('emby.resume', clear=True) - - play_url = putils.PlayUtils(self.item, listitem).get_play_url(force_transcode) - - if not play_url: - if play_url == False: # User backed-out of menu - self.playlist.clear() - return xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, listitem) - - - if self.item['Type'] != "Audio": - - ''' Detect the seektime for video type content. - Verify the default video action set in Kodi for accurate resume behavior. - ''' - if self.get_play_action() == "Resume": - resume = "true" - - if force_transcode: - seektime = self.API.get_userdata()['Resume'] - - if seektime and resume != "true": - resume = self.resume_dialog(seektime) - - if resume is None: - return xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, listitem) - elif not resume: - seektime = 0 - else: - seektime = self.API.adjust_resume(self.API.get_userdata()['Resume']) if resume == "true" else 0 - - if force_transcode: - log.info("Clear the playlist.") - self.playlist.clear() - - self.set_playlist(play_url, item_id, listitem, seektime, dbid) - - ##### SETUP PLAYBACK - - ''' To get everything to work together, play the first item in the stack with setResolvedUrl, - add the rest to the regular playlist. - ''' - - index = max(self.playlist.getposition(), 0) + 1 # Can return -1 - force_play = False - - ''' Krypton 17.6 broke StartOffset. Seems to be working in Leia. - For now, set up using StartPercent and adjust a bit to compensate. - TODO: Once Leia is fully supported, move back to StartOffset. - ''' - - if seektime: - seektime_percent = ((seektime/self.API.get_runtime()) * 100) - 0.40 - log.info("seektime detected (percent): %s", seektime_percent) - listitem.setProperty('StartPercent', str(seektime_percent)) - - # Prevent manually mark as watched in Kodi monitor - window('emby.skip.%s' % item_id, value="true") - - # Stack: [(url, listitem), (url, ...), ...] - self.stack[0][1].setPath(self.stack[0][0]) - try: - if self._detect_widgets(): - # widgets do not fill artwork correctly - log.info("Detected widget.") - raise IndexError - - xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, self.stack[0][1]) - self.stack.pop(0) # remove the first item we just started. - except IndexError: - log.info("Playback activated via the context menu or widgets.") - force_play = True - - for stack in self.stack: - self.playlist.add(url=stack[0], listitem=stack[1], index=index) - index += 1 - - if force_play: - if len(sys.argv) > 1: - xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, self.stack[0][1]) - - xbmc.Player().play(self.playlist, windowed=False) - - @classmethod - def get_play_action(cls): - - ''' I could not figure out a way to listen to kodi setting changes? - For now, verify the play action every time play is called. - ''' - options = ['Choose', 'Play', 'Resume', 'Show information'] - result = JSONRPC('Settings.GetSettingValue').execute({'setting': "myvideos.selectaction"}) - try: - return options[result['result']['value']] - except Exception as error: - log.error("Returning play action due to error: %s", error) - return options[1] - - def set_playlist(self, play_url, item_id, listitem, seektime=None, db_id=None): - - ##### CHECK FOR INTROS - - if settings('enableCinema') == "true" and not seektime: - self._set_intros(item_id) - - ##### ADD MAIN ITEM - - self.set_properties(play_url, listitem) - self.set_listitem(listitem, db_id) - self.stack.append([play_url, listitem]) - - ##### ADD ADDITIONAL PARTS - - if self.item.get('PartCount'): - self._set_additional_parts(item_id) - - def _set_intros(self, item_id): - # if we have any play them when the movie/show is not being resumed - intros = self.emby.get_intros(item_id) - - if intros['Items']: - enabled = True - - if settings('askCinema') == "true": - - resp = xbmcgui.Dialog().yesno("Emby for Kodi", lang(33016)) - if not resp: - # User selected to not play trailers - enabled = False - log.info("Skip trailers.") - - if enabled: - for intro in intros['Items']: - - listitem = xbmcgui.ListItem() - url = putils.PlayUtils(intro, listitem).get_play_url() - log.info("Adding Intro: %s", url) - - PlaybackUtils(intro).set_properties(url, listitem) - self.set_artwork(listitem, self.item['Type']) - self.set_listitem(listitem) - - self.stack.append([url, listitem]) - - window('emby.skip.%s' % self.item['Id'], value="true") - - def _set_additional_parts(self, item_id): - - parts = self.emby.get_additional_parts(item_id) - - for part in parts['Items']: - - listitem = xbmcgui.ListItem() - url = putils.PlayUtils(part, listitem).get_play_url() - log.info("Adding additional part: %s", url) - - # Set listitem and properties for each additional parts - pb = PlaybackUtils(part) - pb.set_properties(url, listitem) - - self.stack.append([url, listitem]) - - def set_listitem(self, listitem, dbid=None): - - people = self.API.get_people() - mediatype = self.item['Type'] - - metadata = { - 'title': self.item.get('Name', "Missing name"), - 'year': self.item.get('ProductionYear'), - 'plot': self.API.get_overview(), - 'director': people.get('Director'), - 'writer': people.get('Writer'), - 'mpaa': self.API.get_mpaa(), - 'genre': " / ".join(self.item['Genres']), - 'studio': " / ".join(self.API.get_studios()), - 'aired': self.API.get_premiere_date(), - 'rating': self.item.get('CommunityRating'), - 'votes': self.item.get('VoteCount'), - 'dbid': dbid or None - } - - if mediatype == "Episode": - # Only for tv shows - metadata['mediatype'] = "episode" - metadata['TVShowTitle'] = self.item.get('SeriesName', "") - metadata['season'] = self.item.get('ParentIndexNumber', -1) - metadata['episode'] = self.item.get('IndexNumber', -1) - - elif mediatype == "Movie": - metadata['mediatype'] = "movie" - - elif mediatype == "MusicVideo": - metadata['mediatype'] = "musicvideo" - - elif mediatype == "Audio": - metadata['mediatype'] = "song" - - else: - metadata['mediatype'] = "video" - - listitem.setProperty('IsPlayable', 'true') - listitem.setProperty('IsFolder', 'false') - listitem.setLabel(metadata['title']) - listitem.setInfo('music' if mediatype == "Audio" else 'video', infoLabels=metadata) - - def set_properties(self, url, listitem): - - # Set all properties necessary for plugin path playback - - item_id = self.item['Id'] - item_type = self.item['Type'] - - info = window('emby_%s.play.json' % url) - window('emby_%s.play.json' % url, clear=True) - - window('emby_%s.json' % url, { - - 'url': url, - 'runtime': str(self.item.get('RunTimeTicks')), - 'type': item_type, - 'id': item_id, - 'mediasource_id': info.get('mediasource_id', item_id), - 'refreshid': self.item.get('SeriesId') if item_type == "Episode" else item_id, - 'playmethod': info['playmethod'], - 'playsession_id': info['playsession_id'] - }) - - self.set_artwork(listitem, item_type) - listitem.setCast(self.API.get_actors()) - - def set_artwork(self, listitem, item_type): - - all_artwork = self.artwork.get_all_artwork(self.item, parent_info=True) - # Set artwork for listitem - if item_type == "Episode": - - art = { - 'poster': "Series.Primary", - 'tvshow.poster': "Series.Primary", - 'clearart': "Art", - 'tvshow.clearart': "Art", - 'clearlogo': "Logo", - 'tvshow.clearlogo': "Logo", - 'discart': "Disc", - 'fanart_image': "Backdrop", - 'landscape': "Thumb", - 'tvshow.landscape': "Thumb", - 'thumb': "Primary", - 'fanart': "Backdrop" - } - elif item_type in ("Artist", "Audio", "MusicAlbum"): - - art = { - 'clearlogo': "Logo", - 'discart': "Disc", - 'fanart': "Backdrop", - 'fanart_image': "Backdrop", # in case - 'thumb': "Primary" - } - else: - art = { - 'poster': "Primary", - 'clearart': "Art", - 'clearlogo': "Logo", - 'discart': "Disc", - 'fanart_image': "Backdrop", - 'landscape': "Thumb", - 'thumb': "Primary", - 'fanart': "Backdrop" - } - - for k_art, e_art in art.items(): - - if e_art == "Backdrop": - self._set_art(listitem, k_art, all_artwork[e_art][0] if all_artwork[e_art] else " ") - else: - self._set_art(listitem, k_art, all_artwork.get(e_art, " ")) - - def _set_art(self, listitem, art, path): - - if art in ('fanart_image', 'small_poster', 'tiny_poster', - 'medium_landscape', 'medium_poster', 'small_fanartimage', - 'medium_fanartimage', 'fanart_noindicators'): - - listitem.setProperty(art, path) - else: - listitem.setArt({art: path}) - - def resume_dialog(self, seektime): - - log.info("Resume dialog called.") - XML_PATH = (xbmcaddon.Addon('plugin.video.emby').getAddonInfo('path'), "default", "1080i") - - dialog = resume.ResumeDialog("script-emby-resume.xml", *XML_PATH) - dialog.set_resume_point("Resume from %s" % str(timedelta(seconds=seektime)).split(".")[0]) - dialog.doModal() - - if dialog.is_selected(): - if not dialog.get_selected(): # Start from beginning selected. - return False - else: # User backed out - log.info("User exited without a selection.") - return - - return True - - def play_all(self, item_ids, seektime=None, **kwargs): - - self.playlist.clear() - started = False - - index = max(self.playlist.getposition(), 0) + 1 # Can return -1 - for item_id in item_ids: - - listitem = xbmcgui.ListItem() - db_id = None - - item = self.emby.getItem(item_id) - play_url = putils.PlayUtils(item, listitem, **kwargs if item_ids.index(item_id) == 0 else {}).get_play_url() - - if not play_url: - log.info("Failed to retrieve playurl") - continue - - log.info("Playurl: %s", play_url) - - with DatabaseConn('emby') as cursor: - item_db = embydb.Embydb_Functions(cursor).getItem_byId(item_id) - db_id = item_db[0] if item_db else None - - pbutils = PlaybackUtils(item) - - if item_ids.index(item_id) == 0 and seektime: - seektime = seektime / 10000000.0 if seektime else None - log.info("Seektime detected: %s", self.API.adjust_resume(seektime)) - listitem.setProperty('startoffset', str(self.API.adjust_resume(seektime))) - - pbutils.set_playlist(play_url, item_id, listitem, seektime if item_ids.index(item_id) == 0 else None, db_id) - - for stack in pbutils.stack: - self.playlist.add(url=stack[0], listitem=stack[1], index=index) - index += 1 - - if not started: - started = True - - item = window('emby_%s.json' % play_url) - item['forcedaudio'] = kwargs.get('AudioStreamIndex') - item['forcedsubs'] = kwargs.get('SubtitleStreamIndex') - window('emby_%s.json' % play_url, value=item) - - player = xbmc.Player() - player.play(pbutils.playlist) - - return True diff --git a/resources/lib/player.py b/resources/lib/player.py index 8d5d8d2a..d53844c8 100644 --- a/resources/lib/player.py +++ b/resources/lib/player.py @@ -2,6 +2,380 @@ ################################################################################################# +import json +import logging +import os + +import xbmc +import xbmcvfs + +from helper import _, window, settings, dialog, JSONRPC +from emby import Emby + +################################################################################################# + +LOG = logging.getLogger("EMBY."+__name__) + +################################################################################################# + + +class Player(xbmc.Player): + + # Borg - multiple instances, shared state + _shared_state = {} + played = {} + + def __init__(self): + + self.__dict__ = self._shared_state + xbmc.Player.__init__(self) + + def onPlayBackStarted(self): + + ''' We may need to wait for info to be set in kodi monitor. + Accounts for scenario where Kodi starts playback and exits immediately. + ''' + count = 0 + monitor = xbmc.Monitor() + + try: + current_file = self.getPlayingFile() + except Exception: + + while count < 5: + try: + current_file = self.getPlayingFile() + count = 0 + break + except Exception: + count += 1 + + if monitor.waitForAbort(1): + return + else: + LOG.info('Cancel playback report') + + return + + items = window('emby_play.json') + item = None + + while not items: + + if monitor.waitForAbort(2): + return + + items = window('emby_play.json') + count += 1 + + if count == 20: + LOG.info("Could not find emby prop...") + + return + + for item in items: + if item['Path'] == current_file.decode('utf-8'): + items.pop(items.index(item)) + + break + else: + item = items[0] + items.pop(0) + + window('emby_play.json', items) + + self.set_item(current_file, item) + data = { + 'QueueableMediaTypes': "Video,Audio", + 'CanSeek': True, + 'ItemId': item['Id'], + 'MediaSourceId': item['MediaSourceId'], + 'PlayMethod': item['PlayMethod'], + 'VolumeLevel': item['Volume'], + 'PositionTicks': int(item['CurrentPosition'] * 10000000), + 'IsPaused': item['Paused'], + 'IsMuted': item['Muted'], + 'PlaySessionId': item['PlaySessionId'], + 'AudioStreamIndex': item['AudioStreamIndex'], + 'SubtitleStreamIndex': item['SubtitleStreamIndex'] + } + item['Server']['api'].session_playing(data) + self.set_audio_subs(item['AudioStreamIndex'], item['SubtitleStreamIndex']) + self.detect_audio_subs(item) + + window('emby.skip.%s.bool' % item['Id'], True) + + def set_item(self, file, item): + + ''' Set playback information. + ''' + try: + item['Runtime'] = int(item['Runtime']) + except ValueError: + try: + item['Runtime'] = int(self.getTotalTime()) + LOG.info("Runtime is missing, Kodi runtime: %s" % item['Runtime']) + except Exception: + item['Runtime'] = 0 + LOG.info("Runtime is missing, Using Zero") + + try: + seektime = self.getTime() + except Exception: # at this point we should be playing and if not then bail out + return + + result = JSONRPC('Application.GetProperties').execute({'properties': ["volume", "muted"]}) + result = result.get('result', {}) + volume = result.get('volume') + muted = result.get('muted') + + item.update({ + 'File': file, + 'CurrentPosition': int(seektime), + 'Muted': muted, + 'Volume': volume, + 'Server': Emby(item['ServerId']), + 'Paused': False + }) + + self.played[file] = item + LOG.info("-->[ play/%s ] %s", item['Id'], item) + + def set_audio_subs(self, audio=None, subtitle=None): + + ''' Only for after playback started + ''' + LOG.info("Setting audio: %s subs: %s", audio, subtitle) + current_file = self.getPlayingFile() + + if current_file in self.played: + + item = self.played[current_file] + mapping = item['SubsMapping'] + + if audio and len(self.getAvailableAudioStreams()) > 1: + self.setAudioStream(audio - 1) + + if subtitle is None: + return + + tracks = len(self.getAvailableAudioStreams()) + + if subtitle == -1: + self.showSubtitles(False) + + elif mapping: + for index in mapping: + + if mapping[index] == subtitle: + self.setSubtitleStream(int(index)) + + break + else: + self.setSubtitleStream(len(mapping) + subtitle - tracks - 1) + else: + self.setSubtitleStream(subtitle - tracks - 1) + + def detect_audio_subs(self, item): + + params = { + 'playerid': 1, + 'properties': ["currentsubtitle","currentaudiostream","subtitleenabled"] + } + result = JSONRPC('Player.GetProperties').execute(params) + result = result.get('result') + + try: # Audio tracks + audio = result['currentaudiostream']['index'] + except (KeyError, TypeError): + audio = 0 + + try: # Subtitles tracks + subs = result['currentsubtitle']['index'] + except (KeyError, TypeError): + subs = 0 + + try: # If subtitles are enabled + subs_enabled = result['subtitleenabled'] + except (KeyError, TypeError): + subs_enabled = False + + item['AudioStreamIndex'] = audio + 1 + + if not subs_enabled or not len(self.getAvailableSubtitleStreams()): + item['SubtitleStreamIndex'] = None + + return + + mapping = item['SubsMapping'] + tracks = len(self.getAvailableAudioStreams()) + + if mapping: + if str(subs) in mapping: + item['SubtitleStreamIndex'] = mapping[str(subs)] + else: + item['SubtitleStreamIndex'] = subs - len(mapping) + tracks + 1 + else: + item['SubtitleStreamIndex'] = subs + tracks + 1 + + def onPlayBackPaused(self): + current_file = self.getPlayingFile() + + if current_file in self.played: + + self.played[current_file]['Paused'] = True + self.report_playback() + LOG.debug("-->[ paused ]") + + def onPlayBackResumed(self): + current_file = self.getPlayingFile() + + if current_file in self.played: + + self.played[current_file]['Paused'] = False + self.report_playback() + LOG.debug("--<[ paused ]") + + def onPlayBackSeek(self, time, seekOffset): + current_file = self.getPlayingFile() + + if current_file in self.played: + + self.report_playback() + LOG.debug("--[ seek ]") + + def report_playback(self): + + ''' Report playback progress to emby server. + ''' + current_file = self.getPlayingFile() + + if current_file not in self.played: + return + + item = self.played[current_file] + result = JSONRPC('Application.GetProperties').execute({'properties': ["volume", "muted"]}) + result = result.get('result', {}) + item['Volume'] = result.get('volume') + item['Muted'] = result.get('muted') + item['CurrentPosition'] = int(self.getTime()) + self.detect_audio_subs(item) + + data = { + 'QueueableMediaTypes': "Video,Audio", + 'CanSeek': True, + 'ItemId': item['Id'], + 'MediaSourceId': item['MediaSourceId'], + 'PlayMethod': item['PlayMethod'], + 'VolumeLevel': item['Volume'], + 'PositionTicks': int(item['CurrentPosition'] * 10000000), + 'IsPaused': item['Paused'], + 'IsMuted': item['Muted'], + 'PlaySessionId': item['PlaySessionId'], + 'AudioStreamIndex': item['AudioStreamIndex'], + 'SubtitleStreamIndex': item['SubtitleStreamIndex'] + } + item['Server']['api'].session_progress(data) + + def onPlayBackStopped(self): + + ''' Will be called when user stops playing a file. + ''' + window('emby_play', clear=True) + self.stop_playback() + LOG.debug("--<[ playback ]") + + def onPlayBackEnded(self): + + ''' Will be called when kodi stops playing a file. + ''' + self.stop_playback() + LOG.debug("--<<[ playback ]") + + def stop_playback(self): + + ''' Stop all playback. Check for external player for positionticks. + ''' + if not self.played: + return + + LOG.info("Played info: %s", self.played) + + for file in self.played: + item = self.played[file] + + if item: + + if item['CurrentPosition'] and item['Runtime']: + + try: + if window('emby.external'): + window('emby.external', clear=True) + raise ValueError + + played = (item['CurrentPosition'] * 10000000) / int(item['Runtime']) + except ZeroDivisionError: # Runtime is 0. + played = 0 + except ValueError: + played = 100 + item['CurrentPosition'] = int(item['Runtime']) + + marker = float(settings('markPlayed')) / 100 + delete = False + + if item['Type'] == 'Episode' and settings('deleteTV.bool'): + delete = True + elif item['Type'] == 'Movie' and settings('deleteMovies.bool'): + delete = True + + if not settings('offerDelete.bool'): + delete = False + + if played >= marker and delete: + + if dialog("yesno", heading=_(30091), line1=_(33015), autoclose=120000): + item['Server']['api'].delete_item(item['Id']) + + data = { + 'ItemId': item['Id'], + 'MediaSourceId': item['MediaSourceId'], + 'PositionTicks': int(item['CurrentPosition'] * 10000000), + 'PlaySessionId': item['PlaySessionId'] + } + item['Server']['api'].session_stop(data) + + if item.get('LiveStreamId'): + item['Server']['api'].close_live_stream(item['LiveStreamId']) + + elif item['PlayMethod'] == 'Transcode': + + LOG.info("Transcoding for %s terminated.", item['Id']) + item['Server']['api'].close_transcode(item['DeviceId']) + + + path = xbmc.translatePath("special://profile/addon_data/plugin.video.emby/temp/").decode('utf-8') + + if xbmcvfs.exists(path): + dirs, files = xbmcvfs.listdir(path) + + for file in files: + xbmcvfs.delete(os.path.join(path, file.decode('utf-8'))) + + window('emby.external_check', clear=True) + + self.played.clear() + + + + + + + +""" +# -*- coding: utf-8 -*- + +################################################################################################# + import json import logging @@ -14,6 +388,7 @@ import downloadutils import read_embyserver as embyserver import websocket_client as wsc from utils import window, settings, language as lang, JSONRPC +from ga_client import GoogleAnalytics, log_error ################################################################################################# @@ -39,6 +414,7 @@ class Player(xbmc.Player): self.doUtils = downloadutils.DownloadUtils().downloadUrl self.emby = embyserver.Read_EmbyServer() self.ws = wsc.WebSocketClient() + self.xbmcplayer = xbmc.Player() log.debug("Starting playback monitor.") xbmc.Player.__init__(self) @@ -76,14 +452,14 @@ class Player(xbmc.Player): audio_tracks = len(player.getAvailableAudioStreams()) player.setSubtitleStream(subs_index - audio_tracks - 1) - #@log_error() + @log_error() def onPlayBackStarted(self): # Will be called when xbmc starts playing a file self.stopAll() # Get current file try: - currentFile = self.getPlayingFile() + currentFile = self.xbmcplayer.getPlayingFile() xbmc.sleep(300) except: currentFile = "" @@ -91,10 +467,10 @@ class Player(xbmc.Player): while not currentFile: xbmc.sleep(100) try: - currentFile = self.getPlayingFile() + currentFile = self.xbmcplayer.getPlayingFile() except: pass - if count == 10: # try 5 times + if count == 5: # try 5 times log.info("Cancelling playback report...") break else: count += 1 @@ -141,11 +517,11 @@ class Player(xbmc.Player): if window('emby_customPlaylist') == "true" and customseek: # Start at, when using custom playlist (play to Kodi from webclient) log.info("Seeking to: %s", customseek) - self.seekTime(int(customseek)/10000000.0) + self.xbmcplayer.seekTime(int(customseek)/10000000.0) window('emby_customPlaylist.seektime', clear=True) try: - seekTime = self.getTime() + seekTime = self.xbmcplayer.getTime() except: # at this point we should be playing and if not then bail out return @@ -244,7 +620,7 @@ class Player(xbmc.Player): runtime = int(runtime) except ValueError: try: - runtime = int(self.getTotalTime()) + runtime = int(self.xbmcplayer.getTotalTime()) log.info("Runtime is missing, Kodi runtime: %s" % runtime) except: runtime = 0 @@ -269,6 +645,10 @@ class Player(xbmc.Player): self.played_info[currentFile] = data log.info("ADDING_FILE: %s", self.played_info) + ga = GoogleAnalytics() + ga.sendEventData("PlayAction", item_type, play_method) + ga.sendScreenView(item_type) + def reportPlayback(self): log.debug("reportPlayback Called") @@ -375,31 +755,8 @@ class Player(xbmc.Player): log.debug("Report: %s", postdata) self.emby.progress_report(postdata) - #@log_error() - def onPlayBackPaused(self): - currentFile = self.currentFile - log.debug("PLAYBACK_PAUSED: %s" % currentFile) - - if self.played_info.get(currentFile): - - self.played_info[currentFile]['paused'] = True - self.played_info[currentFile]['currentPosition'] = self.getTime() - self.reportPlayback() - - #@log_error() - def onPlayBackResumed(self): - - currentFile = self.currentFile - log.debug("PLAYBACK_RESUMED: %s" % currentFile) - - if self.played_info.get(currentFile): - - self.played_info[currentFile]['paused'] = False - self.played_info[currentFile]['currentPosition'] = self.getTime() - self.reportPlayback() - - #@log_error() + @log_error() def onPlayBackSeek(self, time, seekOffset): # Make position when seeking a bit more accurate currentFile = self.currentFile @@ -408,27 +765,13 @@ class Player(xbmc.Player): if self.played_info.get(currentFile): position = None try: - position = self.getTime() + position = self.xbmcplayer.getTime() except: pass if position is not None: self.played_info[currentFile]['currentPosition'] = position self.reportPlayback() - - #@log_error() - def onPlayBackStopped(self): - # Will be called when user stops xbmc playing a file - log.debug("ONPLAYBACK_STOPPED") - window('emby_customPlaylist.seektime', clear=True) - self.stopAll() - - #@log_error() - def onPlayBackEnded(self): - # Will be called when xbmc stops playing a file - log.debug("ONPLAYBACK_ENDED") - window('emby_customPlaylist.seektime', clear=True) - self.stopAll() def stopAll(self): @@ -453,8 +796,6 @@ class Player(xbmc.Player): media_type = data['Type'] playMethod = data['playmethod'] - window('emby.skip.%s' % itemid, value="true") - self.stop_playback(data) if currentPosition and runtime: @@ -519,6 +860,9 @@ class Player(xbmc.Player): xbmcvfs.delete("%s%s" % (path, file)) self.played_info.clear() + + ga = GoogleAnalytics() + ga.sendEventData("PlayAction", "Stopped") def stop_playback(self, data): @@ -534,3 +878,5 @@ class Player(xbmc.Player): log.info("Transcoding for %s terminated." % data['item_id']) url = "{server}/emby/Videos/ActiveEncodings?DeviceId=%s" % self.clientInfo.get_device_id() self.doUtils(url, action_type="DELETE") + +""" diff --git a/resources/lib/playutils.py b/resources/lib/playutils.py deleted file mode 100644 index bf52fa04..00000000 --- a/resources/lib/playutils.py +++ /dev/null @@ -1,634 +0,0 @@ -# -*- coding: utf-8 -*- - -################################################################################################# - -import collections -import logging -import requests -import os -import urllib - -import xbmc -import xbmcgui -import xbmcvfs - -import clientinfo -import downloadutils -import read_embyserver as embyserver -from utils import window, settings, language as lang, urllib_path, create_id - -################################################################################################# - -log = logging.getLogger("EMBY."+__name__) - -################################################################################################# - - -class PlayUtils(): - - play_session_id = None - method = "DirectPlay" - force_transcode = False - - - def __init__(self, item, listitem, **kwargs): - - self.info = kwargs - - self.item = item - self.listitem = listitem - - self.clientInfo = clientinfo.ClientInfo() - self.emby = embyserver.Read_EmbyServer() - - self.server = window('emby_server%s' % window('emby_currUser')) - self.play_session_id = str(create_id()).replace("-", "") - self.token = downloadutils.DownloadUtils().get_token() - - def get_play_url(self, force_transcode=False): - - ''' New style to retrieve the best playback method based on sending - the profile to the server. Based on capabilities the correct path is returned, - including livestreams that need to be opened by the server - ''' - - self.force_transcode = force_transcode - info = self.get_playback_info() - url = False if info == False else None - - if info: - url = info['Path'].encode('utf-8') - window('emby_%s.play.json' % url, { - - 'playmethod': self.method, - 'playsession_id': self.play_session_id, - 'mediasource_id': info.get('Id') or self.item['Id'] - }) - - if 'RequiredHttpHeaders' in info and 'User-Agent' in info['RequiredHttpHeaders']: - self.listitem.setProperty('User-Agent', info['RequiredHttpHeaders']['User-Agent']) - - log.info("playback info: %s", info) - log.info("play method: %s play url: %s", self.method, url) - - return url - - def _is_media_selection(self): - - if self.item['MediaType'] != 'Video': - log.debug("MediaType not video detected.") - return False - - elif self.item['Type'] == 'TvChannel': - log.debug("TvChannel detected.") - return False - - elif len(self.item['MediaSources']) == 1 and self.item['MediaSources'][0]['Type'] == 'Placeholder': - log.debug("Placeholder detected.") - return False - - elif 'SourceType' in self.item and self.item['SourceType'] != 'Library': - log.debug("SourceType not library detected.") - return False - - return True - - def get_playback_info(self): - - # Get the playback info for the current item - - info = self.emby.get_playback_info(self.item['Id'], self.get_device_profile()) - media_sources = info['MediaSources'] - - # Select the mediasource - if not media_sources: - log.error('No media sources found: %s', info) - return - - selected_source = media_sources[0] - - if 'MediaSourceId' in self.info: - for source in media_sources: - if source['Id'] == self.info['MediaSourceId']: - selected_source = source - break - - elif not self._is_media_selection(): - log.info("Special media type detected. Skip source selection.") - - elif len(media_sources) > 1: - # Offer choices - sources = [] - for source in media_sources: - sources.append(source.get('Name', "na")) - - resp = xbmcgui.Dialog().select("Select the source", sources) - if resp > -1: - selected_source = media_sources[resp] - else: - log.info("No media source selected.") - return False - - return self.get_optimal_track(selected_source) - - def get_optimal_track(self, source): - - ''' Because we posted our deviceprofile to the server, only streams will be - returned that can actually be played by this client so no need to check bitrates etc. - ''' - - # Log filename, used by other addons eg subtitles which require the file name - window('embyfilename', value=self.get_direct_path(source).encode('utf-8')) - - if (not self.force_transcode and (self.is_strm(source) or self.is_h265(source) or (settings('playFromStream') == "false" and self.is_file_exists(source)))): - # Append external subtitles - if settings('enableExternalSubs') == "true": - self.set_external_subs(source, source['Path']) - else: - source['Path'] = self.get_http_path(source, True if not source['SupportsDirectStream'] else self.force_transcode) - - log.debug("get source: %s", source) - return source - - def is_file_exists(self, source): - - path = self.get_direct_path(source) - - if xbmcvfs.exists(path): # or ":" not in path: - log.info("Path exists.") - - self.method = "DirectPlay" - source['Path'] = path - - return True - - log.info("Failed to find file.") - return False - - def is_strm(self, source): - - if source['Container'] == "strm" or '.strm' in self.item['Path']: - log.info('Strm detected.') - - self.method = "DirectPlay" - source['Path'] = self.get_direct_path(source) - - return True - - return False - - def is_h265(self, source): - - if source['MediaStreams']: - force_transcode = False - - for stream in source['MediaStreams']: - if self._is_h265(stream) or self._is_high10(stream): - force_transcode = True - break - - if force_transcode: - source['Path'] = self.get_http_path(source, True) - return True - - return False - - @classmethod - def _is_h265(cls, stream): - - if stream['Type'] == "Video" and stream['Codec'] in ("hevc", "h265"): - if settings('transcode_h265') == "true": - log.info("Force transcode h265/hevc detected.") - return True - - return False - - @classmethod - def _is_high10(cls, stream): - - if stream.get('Profile') == "High 10": - if settings('transcodeHi10P') == "true": - log.info("Force transcode hi10p detected.") - return True - - return False - - def get_direct_path(self, source): - - path = source['Path'] - - if 'VideoType' in source: - if source['VideoType'] == "Dvd": - path = "%s/VIDEO_TS/VIDEO_TS.IFO" % path - elif source['VideoType'] == "BluRay": - path = "%s/BDMV/index.bdmv" % path - - # Assign network protocol - if path.startswith('\\\\'): - path = path.replace('\\\\', "smb://", 1) - path = path.replace('\\\\', "\\") - path = path.replace('\\', "/") - - if "://" in path: - # Protocol needs to be lowercase, otherwise weird things happen. - protocol = path.split('://')[0] - path = path.replace(protocol, protocol.lower()) - - return path - - def get_http_path(self, source, transcode=False): - - if transcode and settings('ignoreTranscode') and source['MediaStreams']: - # Specified by user should not be transcoded. - ignore_codecs = settings('ignoreTranscode').split(',') - - for stream in source['MediaStreams']: - if stream['Type'] == "Video" and stream['Codec'] in ignore_codecs: - log.info("Ignoring transcode for: %s", stream['Codec']) - transcode = False - break - - url = self.get_transcode_url(source) if transcode else self.get_direct_url(source) - url = self._append_http_url(source, url) - - return url - - def get_direct_url(self, source): - - self.method = "DirectStream" - - if self.item['Type'] == "Audio": - url = "%s/emby/Audio/%s/stream.%s?static=true" % (self.server, self.item['Id'], self.item['MediaSources'][0]['Container']) - else: - url = "%s/emby/Videos/%s/stream?static=true" % (self.server, self.item['Id']) - - # Append external subtitles - if settings('enableExternalSubs') == "true": - self.set_external_subs(source, url) - - return url - - def get_transcode_url(self, source): - - self.method = "Transcode" - - if 'TranscodingUrl' in source: - base, params = source['TranscodingUrl'].split("?") - url_parsed = params.split("&") - for i in url_parsed: - if 'AudioStreamIndex' in i or 'AudioBitrate' in i or 'PlaySessionId' in i or 'MediaSourceId' in i or 'api_key' in i: #handle manually - url_parsed.remove(i) - url = "%s/emby%s?%s" % (self.server, base.replace("stream", "master"), '&'.join(url_parsed)) - else: - item_id = self.item['Id'] - url = urllib_path("%s/emby/Videos/%s/master.m3u8" % (self.server, item_id), { - - 'VideoCodec': "h264", - 'AudioCodec': "ac3", - 'MaxAudioChannels': 6, - 'DeviceId': self.clientInfo.get_device_id(), - 'VideoBitrate': self.get_bitrate() * 1000 - }) - - # Select audio and subtitles - url += self.get_audio_subs(source) - - # Limit to 8 bit if user selected transcode Hi10P - if settings('transcodeHi10P') == "true": - url += "&MaxVideoBitDepth=8" - - # Adjust the video resolution - url += "&maxWidth=%s&maxHeight=%s" % (self.get_resolution()) - - return url - - def _append_http_url(self, source, url): - - url += "&MediaSourceId=%s" % source['Id'] - url += "&PlaySessionId=%s" % self.play_session_id - url += "&api_key=%s" % self.token - - return url - - def set_external_subs(self, source, play_url): - - subs = [] - mapping = {} - - item_id = self.item['Id'] - streams = source['MediaStreams'] - - if not source['MediaStreams']: - log.info("No media streams found.") - return - - temp = xbmc.translatePath("special://profile/addon_data/plugin.video.emby/temp/").decode('utf-8') - - ''' Since Emby returns all possible tracks together, sort them. - IsTextSubtitleStream if true, is available to download from server. - ''' - - kodi_index = 0 - for stream in streams: - - if stream['Type'] == "Subtitle" and stream['IsExternal'] and stream['IsTextSubtitleStream']: - index = stream['Index'] - - if 'Path' in stream and xbmcvfs.exists(self.get_direct_path(stream)): - url = None if self.method == "DirectPlay" else self.get_direct_path(stream) - elif 'DeliveryUrl' in stream: - url = self.server + "/emby" + stream['DeliveryUrl'] - else: - url = self.server + "/emby/Videos/%s/%s/Subtitles/%s/Stream.%s?api_key=%s" % (item_id, source['Id'], index, stream['Codec'], self.token) - - log.info("Subtitle url: %s", url) - if url is None: - continue - - if 'Language' in stream: - filename = "Stream.%s.%s" % (stream['Language'].encode('utf-8'), stream['Codec']) - try: - subs.append(self._download_external_subs(url, temp, filename)) - except Exception as error: - log.warn(error) - subs.append(url) - else: - subs.append(url) - - # Map external subtitles for player.py - mapping[kodi_index] = index - kodi_index += 1 - - window('emby_%s.indexMapping.json' % play_url.encode('utf-8'), value=mapping) - self.listitem.setSubtitles(subs) - - return - - @classmethod - def _download_external_subs(cls, src, dst, filename): - - if not xbmcvfs.exists(dst): - xbmcvfs.mkdir(dst) - - path = os.path.join(dst, filename) - - try: - response = requests.get(src, stream=True) - response.raise_for_status() - except Exception as e: - raise - else: - response.encoding = 'utf-8' - with open(path, 'wb') as f: - f.write(response.content) - del response - - return path - - def get_audio_subs(self, source): - - ''' For transcoding only - Present the list of audio/subs to select from, before playback starts. - Returns part of the url to append. - ''' - - prefs = "" - streams = source['MediaStreams'] - - audio_streams = collections.OrderedDict() - subs_streams = collections.OrderedDict() - - if streams: - - ''' Since Emby returns all possible tracks together, sort them. - IsTextSubtitleStream if true, is available to download from server. - ''' - - for stream in streams: - index = stream['Index'] - stream_type = stream['Type'] - - if stream_type == "Audio": - codec = stream['Codec'] - channel = stream.get('ChannelLayout', "") - - if 'Language' in stream: - track = "%s - %s - %s %s" % (index, stream['Language'], codec, channel) - else: - track = "%s - %s %s" % (index, codec, channel) - - audio_streams[track] = index - - elif stream_type == "Subtitle": - - if 'Language' in stream: - track = "%s - %s" % (index, stream['Language']) - else: - track = "%s - %s" % (index, stream['Codec']) - - if stream['IsDefault']: - track = "%s - Default" % track - if stream['IsForced']: - track = "%s - Forced" % track - - subs_streams[track] = index - - dialog = xbmcgui.Dialog() - skip_dialog = int(settings('skipDialogTranscode') or 0) - audio_selected = None - - if self.info.get('AudioStreamIndex'): - audio_selected = self.info['AudioStreamIndex'] - - elif skip_dialog in (0, 1): - if len(audio_streams) > 1: - selection = list(audio_streams.keys()) - resp = dialog.select(lang(33013), selection) - audio_selected = audio_streams[selection[resp]] if resp else source['DefaultAudioStreamIndex'] - else: # Only one choice - audio_selected = audio_streams[next(iter(audio_streams))] - else: - audio_selected = source['DefaultAudioStreamIndex'] - - prefs += "&AudioStreamIndex=%s" % audio_selected - prefs += "&AudioBitrate=384000" if streams[audio_selected].get('Channels', 0) > 2 else "&AudioBitrate=192000" - - if self.info.get('SubtitleStreamIndex'): - index = self.info['SubtitleStreamIndex'] - - if index: - server_settings = self.emby.get_server_transcoding_settings() - if server_settings['EnableSubtitleExtraction'] and streams[index]['SupportsExternalStream']: - self._get_subtitles(source, index) - else: - prefs += "&SubtitleStreamIndex=%s" % index - - elif skip_dialog in (0, 2) and len(subs_streams): - selection = list(['No subtitles']) + list(subs_streams.keys()) - resp = dialog.select(lang(33014), selection) - if resp: - index = subs_streams[selection[resp]] if resp > -1 else source.get('DefaultSubtitleStreamIndex') - if index is not None: - server_settings = self.emby.get_server_transcoding_settings() - if server_settings['EnableSubtitleExtraction'] and streams[index]['SupportsExternalStream']: - self._get_subtitles(source, index) - else: - prefs += "&SubtitleStreamIndex=%s" % index - - return prefs - - def _get_subtitles(self, source, index): - - url = [("%s/Videos/%s/%s/Subtitles/%s/Stream.srt" - % (self.server, self.item['Id'], source['Id'], index))] - - log.info("Set up subtitles: %s %s", index, url) - self.listitem.setSubtitles(url) - - def get_bitrate(self): - - ''' Get the addon video quality - Max bit rate supported by server: 2147483 (max signed 32bit integer) - ''' - - bitrate = { - - '0': 664, - '1': 996, - '2': 1320, - '3': 2000, - '4': 3200, - '5': 4700, - '6': 6200, - '7': 7700, - '8': 9200, - '9': 10700, - '10': 12200, - '11': 13700, - '12': 15200, - '13': 16700, - '14': 18200, - '15': 20000, - '16': 25000, - '17': 30000, - '18': 35000, - '16': 40000, - '17': 100000, - '18': 1000000 - } - return bitrate.get(settings('videoBitrate'), 2147483) - - def get_device_profile(self): - return { - - "Name": "Kodi", - "MaxStreamingBitrate": self.get_bitrate() * 1000, - "MusicStreamingTranscodingBitrate": 1280000, - "TimelineOffsetSeconds": 5, - - "TranscodingProfiles": [ - { - "Container": "mp3", - "AudioCodec": "mp3", - "Type": "Audio" - }, - { - "Container": "m3u8", - "AudioCodec": "ac3", - "VideoCodec": "h264", - "Type": "Video" - }, - { - "Container": "jpeg", - "Type": "Photo" - } - ], - - "DirectPlayProfiles": [ - { - "Type": "Video" - }, - { - "Type": "Audio" - }, - { - "Type": "Photo" - } - ], - - "ResponseProfiles": [], - "ContainerProfiles": [], - "CodecProfiles": [], - - "SubtitleProfiles": [ - { - "Format": "srt", - "Method": "External" - }, - { - "Format": "srt", - "Method": "Embed" - }, - { - "Format": "ass", - "Method": "External" - }, - { - "Format": "ass", - "Method": "Embed" - }, - { - "Format": "sub", - "Method": "Embed" - }, - { - "Format": "sub", - "Method": "External" - }, - { - "Format": "ssa", - "Method": "Embed" - }, - { - "Format": "ssa", - "Method": "External" - }, - { - "Format": "smi", - "Method": "Embed" - }, - { - "Format": "smi", - "Method": "External" - }, - { - "Format": "pgssub", - "Method": "Embed" - }, - { - "Format": "pgssub", - "Method": "External" - }, - { - "Format": "dvdsub", - "Method": "Embed" - }, - { - "Format": "dvdsub", - "Method": "External" - }, - { - "Format": "pgs", - "Method": "Embed" - }, - { - "Format": "pgs", - "Method": "External" - } - ] - } - - def get_resolution(self): - - window = xbmcgui.Window() - return window.getWidth(), window.getHeight() diff --git a/resources/lib/read_embyserver.py b/resources/lib/read_embyserver.py deleted file mode 100644 index 3b19492b..00000000 --- a/resources/lib/read_embyserver.py +++ /dev/null @@ -1,687 +0,0 @@ -# -*- coding: utf-8 -*- - -################################################################################################# - -import logging -import hashlib -import threading -import Queue - -import xbmc - -import downloadutils -import database -from utils import window, settings -from contextlib import closing - -################################################################################################# - -log = logging.getLogger("EMBY."+__name__) - -################################################################################################# - - -class DownloadThreader(threading.Thread): - - is_finished = False - - def __init__(self, queue, output): - - self.queue = queue - self.output = output - threading.Thread.__init__(self) - - def run(self): - - try: - query = self.queue.get() - except Queue.Empty: - self.is_finished = True - return - - try: - result = downloadutils.DownloadUtils().downloadUrl(query['url'], - parameters=query.get('params')) - if result: - self.output.extend(result['Items']) - except Exception as error: - log.error(error) - - self.queue.task_done() - self.is_finished = True - - -class Read_EmbyServer(): - - limitIndex = min(int(settings('limitIndex')), 50) - download_limit = int(settings('downloadThreads')) - download_threads = list() - - def __init__(self): - - self.doUtils = downloadutils.DownloadUtils() - self.userId = window('emby_currUser') - self.server = window('emby_server%s' % self.userId) - - def get_emby_url(self, handler): - return "{server}/emby/%s" % handler - - def _add_worker_thread(self, queue, output): - - while True: - for thread in self.download_threads: - if thread.is_finished: - self.download_threads.remove(thread) - - if window('emby_online') != "true": - # Something happened - log.error("Server is not online, don't start new download thread") - queue.task_done() - return False - - if len(self.download_threads) < self.download_limit: - # Start new "daemon thread" - actual daemon thread is not supported in Kodi - new_thread = DownloadThreader(queue, output) - - counter = 0 - worked = False - while counter < 10: - try: - new_thread.start() - worked = True - break - except: - counter = counter + 1 - xbmc.sleep(1000) - - if worked: - self.download_threads.append(new_thread) - return True - else: - return False - else: - log.info("Waiting for empty download spot: %s", len(self.download_threads)) - xbmc.sleep(100) - - - def split_list(self, itemlist, size): - # Split up list in pieces of size. Will generate a list of lists - return [itemlist[i:i+size] for i in range(0, len(itemlist), size)] - - def getItem(self, itemid): - # This will return the full item - item = self.doUtils.downloadUrl("{server}/emby/Users/{UserId}/Items/%s?format=json" % itemid) - return item - - def getItems(self, item_list): - - items = [] - queue = Queue.Queue() - - url = "{server}/emby/Users/{UserId}/Items?&format=json" - for item_ids in self.split_list(item_list, self.limitIndex): - # Will return basic information - params = { - - 'Ids': ",".join(item_ids), - 'Fields': "Etag" - } - queue.put({'url': url, 'params': params}) - if not self._add_worker_thread(queue, items): - break - - queue.join() - - return items - - def getFullItems(self, item_list): - - items = [] - queue = Queue.Queue() - - url = "{server}/emby/Users/{UserId}/Items?format=json" - for item_ids in self.split_list(item_list, self.limitIndex): - params = { - - "Ids": ",".join(item_ids), - "Fields": ( - - "Path,Genres,SortName,Studios,Writer,ProductionYear,Taglines," - "CommunityRating,OfficialRating,CumulativeRunTimeTicks," - "Metascore,AirTime,DateCreated,MediaStreams,People,Overview," - "CriticRating,CriticRatingSummary,Etag,ShortOverview,ProductionLocations," - "Tags,ProviderIds,ParentId,RemoteTrailers,SpecialEpisodeNumbers," - "MediaSources,VoteCount,ItemCounts" - ) - } - queue.put({'url': url, 'params': params}) - if not self._add_worker_thread(queue, items): - break - - queue.join() - - return items - - def getFilteredSection(self, parentid, itemtype=None, sortby="SortName", recursive=True, - limit=None, sortorder="Ascending", filter_type=""): - params = { - - 'ParentId': parentid, - 'IncludeItemTypes': itemtype, - 'CollapseBoxSetItems': False, - 'IsVirtualUnaired': False, - 'IsMissing': False, - 'Recursive': recursive, - 'Limit': limit, - 'SortBy': sortby, - 'SortOrder': sortorder, - 'Filters': filter_type, - 'Fields': ( - - "Path,Genres,SortName,Studios,Writer,ProductionYear,Taglines," - "CommunityRating,OfficialRating,CumulativeRunTimeTicks," - "Metascore,AirTime,DateCreated,MediaStreams,People,Overview," - "CriticRating,CriticRatingSummary,Etag,ShortOverview,ProductionLocations," - "Tags,ProviderIds,ParentId,RemoteTrailers,SpecialEpisodeNumbers,ItemCounts" - ) - } - return self.doUtils.downloadUrl("{server}/emby/Users/{UserId}/Items?format=json", parameters=params) - - def getTvChannels(self): - - params = { - - 'EnableImages': True, - 'Fields': ( - - "Path,Genres,SortName,Studios,Writer,ProductionYear,Taglines," - "CommunityRating,OfficialRating,CumulativeRunTimeTicks," - "Metascore,AirTime,DateCreated,MediaStreams,People,Overview," - "CriticRating,CriticRatingSummary,Etag,ShortOverview,ProductionLocations," - "Tags,ProviderIds,ParentId,RemoteTrailers,SpecialEpisodeNumbers,ItemCounts" - ) - } - url = "{server}/emby/LiveTv/Channels/?userid={UserId}&format=json" - return self.doUtils.downloadUrl(url, parameters=params) - - def getTvRecordings(self, groupid): - - if groupid == "root": - groupid = "" - - params = { - - 'GroupId': groupid, - 'EnableImages': True, - 'Fields': ( - - "Path,Genres,SortName,Studios,Writer,ProductionYear,Taglines," - "CommunityRating,OfficialRating,CumulativeRunTimeTicks," - "Metascore,AirTime,DateCreated,MediaStreams,People,Overview," - "CriticRating,CriticRatingSummary,Etag,ShortOverview,ProductionLocations," - "Tags,ProviderIds,ParentId,RemoteTrailers,SpecialEpisodeNumbers,ItemCounts" - ) - } - url = "{server}/emby/LiveTv/Recordings/?userid={UserId}&format=json" - return self.doUtils.downloadUrl(url, parameters=params) - - def getSection(self, parentid, itemtype=None, sortby="SortName", artist_id=None, basic=False, dialog=None): - - items = { - - 'Items': [], - 'TotalRecordCount': 0 - } - # Get total number of items - url = "{server}/emby/Users/{UserId}/Items?format=json" - params = { - - 'ParentId': parentid, - 'ArtistIds': artist_id, - 'IncludeItemTypes': itemtype, - 'LocationTypes': "FileSystem,Remote,Offline", - 'CollapseBoxSetItems': False, - 'IsVirtualUnaired': False, - 'IsMissing': False, - 'Recursive': True, - 'Limit': 1 - } - try: - result = self.doUtils.downloadUrl(url, parameters=params) - total = result['TotalRecordCount'] - items['TotalRecordCount'] = total - except Exception as error: # Failed to retrieve - log.debug("%s:%s Failed to retrieve the server response: %s", url, params, error) - else: - index = 0 - jump = self.limitIndex - queue = Queue.Queue() - - while index < total: - # Get items by chunk to increase retrieval speed at scale - params = { - - 'ParentId': parentid, - 'ArtistIds': artist_id, - 'IncludeItemTypes': itemtype, - 'CollapseBoxSetItems': False, - 'IsVirtualUnaired': False, - 'EnableTotalRecordCount': False, - 'LocationTypes': "FileSystem,Remote,Offline", - 'IsMissing': False, - 'Recursive': True, - 'StartIndex': index, - 'Limit': jump, - 'SortBy': sortby, - 'SortOrder': "Ascending", - } - if basic: - params['Fields'] = "Etag" - else: - params['Fields'] = ( - - "Path,Genres,SortName,Studios,Writer,ProductionYear,Taglines," - "CommunityRating,OfficialRating,CumulativeRunTimeTicks," - "Metascore,AirTime,DateCreated,MediaStreams,People,Overview," - "CriticRating,CriticRatingSummary,Etag,ShortOverview,ProductionLocations," - "Tags,ProviderIds,ParentId,RemoteTrailers,SpecialEpisodeNumbers," - "MediaSources,VoteCount,ItemCounts" - ) - queue.put({'url': url, 'params': params}) - if not self._add_worker_thread(queue, items['Items']): - break - - index += jump - - if dialog: - percentage = int((float(index) / float(total))*100) - dialog.update(percentage) - - queue.join() - if dialog: - dialog.update(100) - - return items - - def get_views(self, root=False): - - if not root: - url = "{server}/emby/Users/{UserId}/Views?format=json" - else: # Views ungrouped - url = "{server}/emby/Users/{UserId}/Items?Sortby=SortName&format=json" - - return self.doUtils.downloadUrl(url) - - def getViews(self, mediatype="", root=False, sortedlist=False): - # Build a list of user views - views = [] - mediatype = mediatype.lower() - - try: - items = self.get_views(root)['Items'] - except Exception as error: - log.debug("Error retrieving views for type: %s error:%s" % (mediatype, error)) - else: - for item in items: - - if item['Type'] in ("Channel", "PlaylistsFolder"): - # Filter view types - continue - - # 3/4/2016 OriginalCollectionType is added - itemtype = item.get('OriginalCollectionType', item.get('CollectionType', "mixed")) - - if item['Name'] not in ('Collections', 'Trailers', 'Playlists'): - - if sortedlist: - views.append({ - - 'name': item['Name'], - 'type': itemtype, - 'id': item['Id'] - }) - - elif (itemtype == mediatype or - (itemtype == "mixed" and mediatype in ("movies", "tvshows"))): - - views.append({ - - 'name': item['Name'], - 'type': itemtype, - 'id': item['Id'] - }) - - return views - - def verifyView(self, parentid, itemid): - - params = { - - 'ParentId': parentid, - 'CollapseBoxSetItems': False, - 'IsVirtualUnaired': False, - 'LocationTypes': "FileSystem,Remote,Offline", - 'IsMissing': False, - 'Recursive': True, - 'Ids': itemid - } - try: - result = self.doUtils.downloadUrl("{server}/emby/Users/{UserId}/Items?format=json", parameters=params) - total = result['TotalRecordCount'] - except Exception as error: - # Something happened to the connection - log.info("Error getting item count: " + str(error)) - return False - - return True if total else False - - def getMovies(self, parentId, basic=False, dialog=None): - return self.getSection(parentId, "Movie", basic=basic, dialog=dialog) - - def getBoxset(self, dialog=None): - return self.getSection(None, "BoxSet", dialog=dialog) - - def getMovies_byBoxset(self, boxsetid): - return self.getSection(boxsetid, "Movie") - - def getMusicVideos(self, parentId, basic=False, dialog=None): - return self.getSection(parentId, "MusicVideo", basic=basic, dialog=dialog) - - def getHomeVideos(self, parentId): - return self.getSection(parentId, "Video") - - def getShows(self, parentId, basic=False, dialog=None): - return self.getSection(parentId, "Series", basic=basic, dialog=dialog) - - def getSeasons(self, showId): - - items = { - - 'Items': [], - 'TotalRecordCount': 0 - } - - params = { - - 'IsVirtualUnaired': False, - 'Fields': "Etag" - } - url = "{server}/emby/Shows/%s/Seasons?UserId={UserId}&format=json" % showId - - try: - result = self.doUtils.downloadUrl(url, parameters=params) - except Exception as error: - log.info("Error getting Seasons form server: " + str(error)) - result = None - - if result is not None: - items = result - - return items - - def getEpisodes(self, parentId, basic=False, dialog=None): - return self.getSection(parentId, "Episode", basic=basic, dialog=dialog) - - def getEpisodesbyShow(self, showId): - return self.getSection(showId, "Episode") - - def getEpisodesbySeason(self, seasonId): - return self.getSection(seasonId, "Episode") - - def getArtists(self, parent_id=None, dialog=None): - - items = { - - 'Items': [], - 'TotalRecordCount': 0 - } - # Get total number of items - url = "{server}/emby/Artists?UserId={UserId}&format=json" - params = { - - 'ParentId': parent_id, - 'Recursive': True, - 'Limit': 1 - } - try: - result = self.doUtils.downloadUrl(url, parameters=params) - total = result['TotalRecordCount'] - items['TotalRecordCount'] = total - except Exception as error: # Failed to retrieve - log.debug("%s:%s Failed to retrieve the server response: %s", url, params, error) - else: - index = 0 - jump = self.limitIndex - queue = Queue.Queue() - - while index < total: - # Get items by chunk to increase retrieval speed at scale - params = { - - 'ParentId': parent_id, - 'Recursive': True, - 'IsVirtualUnaired': False, - 'EnableTotalRecordCount': False, - 'LocationTypes': "FileSystem,Remote,Offline", - 'IsMissing': False, - 'StartIndex': index, - 'Limit': jump, - 'SortBy': "SortName", - 'SortOrder': "Ascending", - 'Fields': ( - - "Etag,Genres,SortName,Studios,Writer,ProductionYear," - "CommunityRating,OfficialRating,CumulativeRunTimeTicks,Metascore," - "AirTime,DateCreated,MediaStreams,People,ProviderIds,Overview,ItemCounts" - ) - } - queue.put({'url': url, 'params': params}) - if not self._add_worker_thread(queue, items['Items']): - break - - index += jump - - if dialog: - percentage = int((float(index) / float(total))*100) - dialog.update(percentage) - - queue.join() - if dialog: - dialog.update(100) - - return items - - def getAlbums(self, basic=False, dialog=None): - return self.getSection(None, "MusicAlbum", sortby="DateCreated", basic=basic, dialog=dialog) - - def getAlbumsbyArtist(self, artistId): - return self.getSection(None, "MusicAlbum", sortby="DateCreated", artist_id=artistId) - - def getSongs(self, basic=False, dialog=None): - return self.getSection(None, "Audio", basic=basic, dialog=dialog) - - def getSongsbyAlbum(self, albumId): - return self.getSection(albumId, "Audio") - - def getAdditionalParts(self, itemId): - - items = { - - 'Items': [], - 'TotalRecordCount': 0 - } - url = "{server}/emby/Videos/%s/AdditionalParts?UserId={UserId}&format=json" % itemId - - try: - result = self.doUtils.downloadUrl(url) - except Exception as error: - log.info("Error getting additional parts form server: " + str(error)) - result = None - - if result is not None: - items = result - - return items - - def sortby_mediatype(self, itemids): - - sorted_items = {} - - # Sort items - items = self.getFullItems(itemids) - for item in items: - - mediatype = item.get('Type') - if mediatype: - sorted_items.setdefault(mediatype, []).append(item) - - return sorted_items - - def updateUserRating(self, itemid, favourite=None): - # Updates the user rating to Emby - doUtils = self.doUtils.downloadUrl - - if favourite: - url = "{server}/emby/Users/{UserId}/FavoriteItems/%s?format=json" % itemid - doUtils(url, action_type="POST") - elif not favourite: - url = "{server}/emby/Users/{UserId}/FavoriteItems/%s?format=json" % itemid - doUtils(url, action_type="DELETE") - else: - log.info("Error processing user rating.") - - log.info("Update user rating to emby for itemid: %s | favourite: %s" % (itemid, favourite)) - - def refreshItem(self, itemid): - - url = "{server}/emby/Items/%s/Refresh?format=json" % itemid - params = { - - 'Recursive': True, - 'ImageRefreshMode': "FullRefresh", - 'MetadataRefreshMode': "FullRefresh", - 'ReplaceAllImages': False, - 'ReplaceAllMetadata': True - - } - self.doUtils.downloadUrl(url, postBody=params, action_type="POST") - - def deleteItem(self, itemid): - - url = "{server}/emby/Items/%s?format=json" % itemid - self.doUtils.downloadUrl(url, action_type="DELETE") - - def getUsers(self, server): - - url = "%s/emby/Users/Public?format=json" % server - try: - users = self.doUtils.downloadUrl(url, authenticate=False) - except Exception as error: - log.info("Error getting users from server: %s", str(error)) - users = [] - - return users - - def loginUser(self, server, username, password=None): - - password = password or "" - url = "%s/emby/Users/AuthenticateByName?format=json" % server - data = {'username': username, 'password': hashlib.sha1(password).hexdigest()} - user = self.doUtils.downloadUrl(url, postBody=data, action_type="POST", authenticate=False) - - return user - - def get_single_item(self, media_type, parent_id): - - params = { - 'ParentId': parent_id, - 'Recursive': True, - 'Limit': 1, - 'IncludeItemTypes': media_type - } - url = self.get_emby_url('Users/{UserId}/Items?format=json') - return self.doUtils.downloadUrl(url, parameters=params) - - # NEW CODE ---------------------------------------------------- - - @classmethod - def _get_full_details(cls, params): - params.update({ - 'Fields': ( - - "Path,Genres,SortName,Studios,Writer,ProductionYear,Taglines," - "CommunityRating,OfficialRating,CumulativeRunTimeTicks," - "Metascore,AirTime,DateCreated,MediaStreams,People,Overview," - "CriticRating,CriticRatingSummary,Etag,ShortOverview,ProductionLocations," - "Tags,ProviderIds,ParentId,RemoteTrailers,SpecialEpisodeNumbers," - "MediaSources,VoteCount,ItemCounts" - ) - }) - return params - - def get_parent_child(self, parent_id, media_format=None): - - url = self.get_emby_url('Users/{UserId}/Items') - params = { - 'SortBy': "SortName", - 'SortOrder': "Ascending", - 'IncludeItemTypes': media_format, - 'Recursive': True, - 'Limit': 1, - 'ParentId': parent_id - } - result = self.doUtils.downloadUrl(url, parameters=params) - params['Limit'] = self.limitIndex - params = self._get_full_details(params) - - index = 0 - while index < result['TotalRecordCount']: - params['StartIndex'] = index - yield self.doUtils.downloadUrl(url, parameters=params) - - index += self.limitIndex - - def get_view_options(self, view_id): - - url = self.get_emby_url('Library/VirtualFolders') - for library in self.doUtils.downloadUrl(url): - if library['ItemId'] == view_id: - return library['LibraryOptions'] - - def get_server_transcoding_settings(self): - return self.doUtils.downloadUrl(self.get_emby_url('System/Configuration/encoding')) - - def get_intros(self, item_id): - return self.doUtils.downloadUrl(self.get_emby_url('Users/{UserId}/Items/%s/Intros' % item_id)) - - def get_additional_parts(self, item_id): - return self.doUtils.downloadUrl(self.get_emby_url('Videos/%s/AdditionalParts' % item_id)) - - def get_playback_info(self, item_id, profile, offset=0, audio=None, subtitles=None): - - url = self.get_emby_url('Items/%s/PlaybackInfo' % item_id) - return self.doUtils.downloadUrl(url, action_type="POST", postBody={ - - 'UserId': self.userId, - 'DeviceProfile': profile, - 'StartTimeTicks': offset, #TODO - 'AudioStreamIndex': audio, #TODO - 'SubtitleStreamIndex': subtitles, #TODO - 'MediaSourceId': None, - 'LiveStreamId': None - }) - - def stop_playback(self, item_id, position, playsession_id, mediasource_id=None): - - url = self.get_emby_url('Sessions/Playing/Stopped') - return self.doUtils.downloadUrl(url, action_type="POST", postBody={ - - 'ItemId': item_id, - 'MediaSourceId': mediasource_id or item_id, - 'PositionTicks': position, - 'PlaySessionId': playsession_id - }) - - def progress_report(self, data): - url = self.get_emby_url('Sessions/Playing/Progress') - return self.doUtils.downloadUrl(url, action_type="POST", postBody=data) diff --git a/resources/lib/service_entry.py b/resources/lib/service_entry.py deleted file mode 100644 index e7c1b805..00000000 --- a/resources/lib/service_entry.py +++ /dev/null @@ -1,319 +0,0 @@ -# -*- coding: utf-8 -*- - -################################################################################################# - -import logging -import sys -import time -import _strptime # Workaround for threads using datetime: _striptime is locked -from datetime import datetime -import platform - -import xbmc -import xbmcgui - -import userclient -import clientinfo -import initialsetup -import kodimonitor -import librarysync -import player -import websocket_client as wsc -from views import VideoNodes -from utils import window, settings, dialog, language as lang -#from ga_client import GoogleAnalytics -import hashlib - -################################################################################################# - -log = logging.getLogger("EMBY."+__name__) - -################################################################################################# - - -class Service(object): - - startup = False - server_online = True - capabitilities = False - warn_auth = True - - userclient_running = False - userclient_thread = None - websocket_running = False - websocket_thread = None - library_running = False - library_thread = None - - last_progress = datetime.today() - - def __init__(self): - - self.client_info = clientinfo.ClientInfo() - self.addon_name = self.client_info.get_addon_name() - log_level = settings('logLevel') - - # General settings which are used by other entrypoints - window('emby_logLevel', value=str(log_level)) - window('emby_kodiProfile', value=xbmc.translatePath('special://profile')) - window('emby_context', value="true" if settings('enableContext') == "true" else "") - window('emby_context_transcode', value="true" if settings('enableContextTranscode') == "true" else "") - - # Initial logging - log.warn("======== START %s ========", self.addon_name) - log.warn("Python Version: %s", sys.version) - log.warn("Platform: %s", self.client_info.get_platform()) - log.warn("KODI Version: %s", xbmc.getInfoLabel('System.BuildVersion')) - log.warn("%s Version: %s", self.addon_name, self.client_info.get_version()) - log.warn("Using plugin paths: %s", settings('useDirectPaths') == "0") - log.warn("Log Level: %s", log_level) - - # Reset window props for profile switch - properties = [ - - "emby_online", "emby_state.json", "emby_serverStatus", "emby_onWake", - "emby_syncRunning", "emby_dbCheck", "emby_kodiScan", - "emby_shouldStop", "emby_currUser", "emby_dbScan", "emby_sessionId", - "emby_initialScan", "emby_customplaylist", "emby_playbackProps", - "emby.external_check", "emby.external", "emby.resume", "emby.connected" - ] - for prop in properties: - window(prop, clear=True) - - # Clear video nodes properties - VideoNodes().clearProperties() - # assume offline mode - log.info("Loading existing views...") - librarysync.LibrarySync().offline_mode_views() - - # Set the minimum database version - window('emby_minDBVersion', value="1.1.63") - - - def service_entry_point(self): - - # Important: Threads depending on abortRequest will not trigger - # if profile switch happens more than once. - self.monitor = kodimonitor.KodiMonitor() - self.kodi_player = player.Player() - kodi_profile = xbmc.translatePath('special://profile') - - # Server auto-detect - initialsetup.InitialSetup().setup() - - # Initialize important threads - self.userclient_thread = userclient.UserClient() - user_client = self.userclient_thread - self.websocket_thread = wsc.WebSocketClient() - self.library_thread = librarysync.LibrarySync() - - while not self.monitor.abortRequested(): - - if window('emby_kodiProfile') != kodi_profile: - # Profile change happened, terminate this thread and others - log.info("Kodi profile was: %s and changed to: %s. Terminating old Emby thread.", - kodi_profile, window('emby_kodiProfile')) - exc = Exception("Kodi profile changed detected") - exc.quiet = True - raise exc - - # Before proceeding, need to make sure: - # 1. Server is online - # 2. User is set - # 3. User has access to the server - - if window('emby_online') == "true": - - # Emby server is online - # Verify if user is set and has access to the server - if user_client.get_user() is not None and user_client.get_access(): - - if self.kodi_player.isPlaying(): - self._report_progress() - - # If an item is playing - if not self.startup: - self.startup = self._startup() - - if not self.websocket_running: - # Start the Websocket Client - self.websocket_running = True - self.websocket_thread.start() - if not self.library_running: - # Start the syncing thread - self.library_running = True - self.library_thread.start() - if not self.capabitilities and user_client.post_capabilities(): - self.capabitilities = True - - if self.monitor.waitForAbort(15): - # Abort was requested while waiting. We should exit - break - else: - - if (user_client.get_user() is None) and self.warn_auth: - # Alert user is not authenticated and suppress future warning - self.warn_auth = False - log.info("Not authenticated yet.") - - # User access is restricted. - # Keep verifying until access is granted - # unless server goes offline or Kodi is shut down. - self._access_check() - else: - # Wait until Emby server is online - # or Kodi is shut down. - self._server_online_check() - - if self.monitor.waitForAbort(1): - # Abort was requested while waiting. We should exit - break - - ##### Emby thread is terminating. ##### - self.shutdown() - - def _startup(self): - - # Start up events - self.warn_auth = True - - username = self.userclient_thread.get_username() - if settings('connectMsg') == "true" and username: - # Get additional users - add_users = settings('additionalUsers') - if add_users: - add_users = ", "+", ".join(add_users.split(',')) - - dialog(type_="notification", - heading="{emby}", - message=("%s %s%s" - % (lang(33000), username.decode('utf-8'), - add_users.decode('utf-8'))), - icon="{emby}", - time=2000, - sound=False) - return True - - def _server_online_check(self): - # Set emby_online true/false property - user_client = self.userclient_thread - while not self.monitor.abortRequested(): - - if user_client.get_server() is None: - # No server info set in add-on settings - pass - - elif not user_client.verify_server(): - # Server is offline. - # Alert the user and suppress future warning - if self.server_online: - log.info("Server is offline") - window('emby_online', value="false") - - if settings('offlineMsg') == "true": - dialog(type_="notification", - heading=lang(33001), - message="%s %s" % (self.addon_name, lang(33002)), - icon="{emby}", - sound=False) - - self.server_online = False - - if self.monitor.waitForAbort(7): - # Abort was requested while waiting. - break - - elif window('emby_online') in ("sleep", "reset"): - # device going to sleep - if self.websocket_running: - self.websocket_thread.stop_client() - self.websocket_thread = wsc.WebSocketClient() - self.websocket_running = False - - if self.library_running: - self.library_thread.stopThread() - self.library_thread = librarysync.LibrarySync() - self.library_running = False - else: - # Server is online - if not self.server_online: - # Server was offline when Kodi started. - # Wait for server to be fully established. - if self.monitor.waitForAbort(5): - # Abort was requested while waiting. - break - # Alert the user that server is online. - dialog(type_="notification", - heading="{emby}", - message=("%s %s" - % (lang(33000), user_client.get_username().decode('utf-8'))), - icon="{emby}", - time=2000, - sound=False) - - self.server_online = True - self.capabitilities = False - window('emby_online', value="true") - log.info("Server is online and ready") - - # Start the userclient thread - if not self.userclient_running: - self.userclient_running = True - user_client.start() - - break - - if self.monitor.waitForAbort(1): - # Abort was requested while waiting. - break - - def _access_check(self): - # Keep verifying until access is granted - # unless server goes offline or Kodi is shut down. - while not self.userclient_thread.get_access(): - - if window('emby_online') != "true": - # Server went offline - break - - if self.monitor.waitForAbort(30): - # Abort was requested while waiting. We should exit - break - - def _report_progress(self): - # Update and report playback progress - kodi_player = self.kodi_player - try: - play_time = kodi_player.getTime() - filename = kodi_player.getPlayingFile() - # Update positionticks - if filename in kodi_player.played_info and play_time > 0: - kodi_player.played_info[filename]['currentPosition'] = play_time - - difference = datetime.today() - self.last_progress - difference_seconds = difference.seconds - - # Ping session every 4-5 minutes - if difference_seconds > 270 and window('emby_command') == "true": - window('emby_command', clear=True) - kodi_player.reportPlayback() - self.last_progress = datetime.today() - - except Exception as error: - log.exception(error) - - def shutdown(self): - - if self.monitor.special_monitor: - self.monitor.special_monitor.stop_monitor() - - if self.userclient_running: - self.userclient_thread.stop_client() - - if self.library_running: - self.library_thread.stopThread() - - if self.websocket_running: - self.websocket_thread.stop_client() - - log.warn("======== STOP %s ========", self.addon_name) diff --git a/resources/lib/setup.py b/resources/lib/setup.py new file mode 100644 index 00000000..7bde7bf3 --- /dev/null +++ b/resources/lib/setup.py @@ -0,0 +1,113 @@ +# -*- coding: utf-8 -*- + +################################################################################################# + +import logging + +import xbmc + +from helper import _, settings, dialog, JSONRPC + +################################################################################################# + +LOG = logging.getLogger("EMBY."+__name__) + +################################################################################################# + + +class Setup(object): + + def __init__(self): + + self.set_web_server() + self.setup() + + LOG.info("---<[ setup ]") + + def set_web_server(self): + + ''' Enable the webserver if not enabled. This is used to cache artwork. + Will only test once, if it fails, user will be notified only once. + ''' + if settings('enableTextureCache.bool'): + + get_setting = JSONRPC('Settings.GetSettingValue') + enabled = self.get_web_server() + + if not enabled: + + set_setting = JSONRPC('Settings.SetSetingValue') + set_setting.execute({'setting': "services.webserverport", 'value': 50325}) + set_setting.execute({'setting': "services.webserver", 'value': True}) + enabled = self.get_web_server() + + if not enabled: + + settings('enableTextureCache.bool', False) + dialog("ok", heading="{emby}", line1=_(33103)) + + return + + result = get_setting.execute({'setting': "services.webserverport"}) + settings('webServerPort', str(result['result']['value'] or "")) + result = get_setting.execute({'setting': "services.webserverusername"}) + settings('webServerUser', str(result['result']['value'] or "")) + result = get_setting.execute({'setting': "services.webserverpassword"}) + settings('webServerPass', str(result['result']['value'] or "")) + settings('useWebServer.bool', True) + + def get_web_server(self): + + result = JSONRPC('Settings.GetSettingValue').execute({'setting': "services.webserver"}) + + try: + return result['result']['value'] + except (KeyError, TypeError): + return False + + def setup(self): + + minimum = "3.0.23" + + if settings('MinimumSetup') == minimum: + return + + self._is_mode() + LOG.info("Add-on playback: %s", settings('useDirectPaths') == "0") + self._is_artwork_caching() + LOG.info("Artwork caching: %s", settings('enableTextureCache.bool')) + self._is_empty_shows() + LOG.info("Sync empty shows: %s", settings('syncEmptyShows.bool')) + + # Setup completed + settings('MinimumSetup', minimum) + + def _is_mode(self): + + ''' Setup playback mode. If native mode selected, check network credentials. + ''' + value = dialog("yesno", + heading=_('playback_mode'), + line1=_(33035), + nolabel=_('addon_mode'), + yeslabel=_('native_mode')) + + settings('useDirectPaths', value="1" if value else "0") + + if value: + dialog("ok", heading="{emby}", line1=_(33145)) + + def _is_artwork_caching(self): + + value = dialog("yesno", heading="{emby}", line1=_(33117)) + settings('enableTextureCache.bool', value) + + def _is_empty_shows(self): + + value = dialog("yesno", heading="{emby}", line1=_(33100)) + settings('syncEmptyShows.bool', value) + + def _is_music(self): + + value = dialog("yesno", heading="{emby}", line1=_(33039)) + settings('enableMusic.bool', value=value) diff --git a/resources/lib/userclient.py b/resources/lib/userclient.py deleted file mode 100644 index bae10bdb..00000000 --- a/resources/lib/userclient.py +++ /dev/null @@ -1,312 +0,0 @@ -# -*- coding: utf-8 -*- - -################################################################################################## - -import json -import logging -import threading - -import xbmc -import xbmcgui - -import artwork -import connectmanager -import downloadutils -import read_embyserver as embyserver -from utils import window, settings, language as lang - -################################################################################################## - -log = logging.getLogger("EMBY."+__name__) - -################################################################################################## - -class UserClient(threading.Thread): - - _shared_state = {} # Borg - - _stop_thread = False - _user = None - _server = None - - _auth = True - _has_access = True - - - def __init__(self): - - self.__dict__ = self._shared_state - - self.doutils = downloadutils.DownloadUtils() - self.emby = embyserver.Read_EmbyServer() - self.connectmanager = connectmanager.ConnectManager() - - threading.Thread.__init__(self) - - @classmethod - def get_username(cls): - return settings('username') or settings('connectUsername') or None - - def get_user(self, data=None): - - if data is not None: - self._user = data - self._set_user_server() - - return self._user - - def get_server_details(self): - return self._server - - @classmethod - def get_server(cls): - return settings('server') or None - - def verify_server(self): - - try: - url = "%s/emby/system/info/public?format=json" % self.get_server() # tried system/public but can't get reliable response from the server? - self.doutils.downloadUrl(url, authenticate=False) - return True - except Exception as error: - # Server connection failed - return False - - @classmethod - def get_ssl(cls): - - ''' - Returns boolean value or path to certificate - True: Verify ssl - False: Don't verify connection - ''' - return settings('sslverify') == "true" - - def get_access(self): - - if not self._has_access: - self._set_access() - - return self._has_access - - def _set_access(self): - - try: - self.doutils.downloadUrl("{server}/emby/Users?format=json") - except Exception as error: - if self._has_access and "restricted" in error: - self._has_access = False - log.info("access is restricted") - else: - if not self._has_access: - self._has_access = True - window('emby_serverStatus', clear=True) - log.info("access is granted") - xbmcgui.Dialog().notification(lang(29999), lang(33007)) - - @classmethod - def get_userid(cls): - - ###$ Begin migration $### - if settings('userId') == "": - settings('userId', value=settings('userId%s' % settings('username'))) - log.info("userid migration completed") - ###$ End migration $### - - return settings('userId') or None - - @classmethod - def get_token(cls): - - ###$ Begin migration $### - if settings('token') == "": - settings('token', value=settings('accessToken')) - log.info("token migration completed") - ###$ End migration $### - - return settings('token') or None - - def _set_user_server(self): - - user = self.doutils.downloadUrl("{server}/emby/Users/{UserId}?format=json") - settings('username', value=user['Name']) - self._user = user - window('emby.userinfo.json', user) - if "PrimaryImageTag" in self._user: - window('EmbyUserImage', - value=artwork.Artwork().get_user_artwork(self._user['Id'], 'Primary')) - - self._server = self.doutils.downloadUrl("{server}/emby/System/Configuration?format=json") - settings('markPlayed', value=str(self._server['MaxResumePct'])) - - def _authenticate(self): - - if not self.get_server() or not self.get_username(): - log.info('missing server or user information') - self._auth = False - - elif self.get_token(): - try: - self._load_user() - except Exception as error: - if "401" in error: - log.info("token is invalid") - self._reset_client() - else: - log.info("current user: %s", self.get_username()) - log.info("current userid: %s", self.get_userid()) - log.debug("current token: %s", self.get_token()) - return - - ##### AUTHENTICATE USER ##### - server = self.get_server() - username = self.get_username().decode('utf-8') - - try: - user = self.connectmanager.login_manual(server, username) - except RuntimeError: - window('emby_serverStatus', value="stop") - self._auth = False - return - else: - log.info("user: %s", user) - settings('username', value=user['User']['Name']) - settings('token', value=user['AccessToken']) - settings('userId', value=user['User']['Id']) - xbmcgui.Dialog().notification(lang(29999), - "%s %s!" % (lang(33000), username)) - self._load_user(authenticated=True) - window('emby_serverStatus', clear=True) - - def _load_user(self, authenticated=False): - - doutils = self.doutils - - userid = self.get_userid() - server = self.get_server() - token = self.get_token() - - # Set properties - # TODO: Remove old reference once code converted - window('emby_currUser', value=userid) - window('emby_server%s' % userid, value=server) - window('emby_accessToken%s' % userid, value=token) - - server_json = { - 'UserId': userid, - 'Server': server, - 'ServerId': settings('serverId'), - 'Token': token, - 'SSL': self.get_ssl() - } - # Set downloadutils.py values - doutils.set_session(**server_json) - - # Test the validity of the current token - if not authenticated: - try: - self.doutils.downloadUrl("{server}/emby/Users/{UserId}?format=json") - except Exception as error: - if "401" in error: - # Token is not longer valid - raise - - # verify user access - self._set_access() - # Start downloadutils.py session - doutils.start_session() - # Set _user and _server - self._set_user_server() - - def post_capabilities(self): - log.info("post capabilities") - return self.doutils.post_capabilities() - - def load_connect_servers(self): - # Set connect servers - if not settings('connectUsername'): - return - - servers = self.connectmanager.get_connect_servers() - added_servers = [] - for server in servers: - if server['Id'] != settings('serverId'): - # TODO: SSL setup - self.doutils.add_server(server, False) - added_servers.append(server['Id']) - - # Set properties - log.info(added_servers) - window('emby_servers.json', value=added_servers) - - def _reset_client(self): - - log.info("reset UserClient authentication") - - settings('accessToken', value="") - window('emby_accessToken', clear=True) - - log.info("user token revoked.") - - self._user = None - self.auth = None - - current_state = self.connectmanager.get_state() - for server in current_state['Servers']: - - if server['Id'] == settings('serverId'): - # Update token - server['AccessToken'] = None - self.connectmanager.update_token(server) - - def run(self): - - monitor = xbmc.Monitor() - - log.warn("----====# Starting UserClient #====----") - - while not self._stop_thread: - - status = window('emby_serverStatus') - if status: - # Verify the connection status to server - if status == "restricted": - # Parental control is restricting access - self._has_access = False - - elif status == "401": - # Unauthorized access, revoke token - window('emby_serverStatus', value="auth") - self._reset_client() - - if self._auth and self._user is None: - # Try to authenticate user - status = window('emby_serverStatus') - if not status or status == "auth": - # Set auth flag because we no longer need - # to authenticate the user - self._auth = False - self._authenticate() - - if not self._auth and self._user is None: - # If authenticate failed. - server = self.get_server() - username = self.get_username() - status = window('emby_serverStatus') - - # The status Stop is for when user cancelled password dialog. - if server and username and status != "stop": - # Only if there's information found to login - log.info("Server found: %s", server) - log.info("Username found: %s", username) - self._auth = True - - if monitor.waitForAbort(2): - # Abort was requested while waiting. We should exit - break - - self.doutils.stop_session() - log.warn("#====---- UserClient Stopped ----====#") - - def stop_client(self): - self._stop_thread = True diff --git a/resources/lib/views.py b/resources/lib/views.py index cec43b61..f38623fa 100644 --- a/resources/lib/views.py +++ b/resources/lib/views.py @@ -3,863 +3,752 @@ ################################################################################################# import logging -import shutil import os -import unicodedata +import shutil +import urllib import xml.etree.ElementTree as etree import xbmc -import xbmcaddon import xbmcvfs -import artwork -import read_embyserver as embyserver -import embydb_functions as embydb -from utils import window, language as lang, indent as xml_indent, urllib_path +import downloader as server +from database import Database, emby_db, get_sync, save_sync +from objects.kodi import kodi +from helper import _, api, indent, write_xml, window +from emby import Emby ################################################################################################# -log = logging.getLogger("EMBY."+__name__) -KODI = int(xbmc.getInfoLabel('System.BuildVersion')[:2]) +LOG = logging.getLogger("EMBY."+__name__) +NODES = { + 'tvshows': [ + ('all', None), + ('recent', _(30170)), + ('recentepisodes', _(30175)), + ('inprogress', _(30171)), + ('inprogressepisodes', _(30178)), + ('nextepisodes', _(30179)), + ('genres', 135), + ('random', _(30229)), + ('recommended', _(30230)) + ], + 'movies': [ + ('all', None), + ('recent', _(30174)), + ('inprogress', _(30177)), + ('unwatched', _(30189)), + ('sets', 20434), + ('genres', 135), + ('random', _(30229)), + ('recommended', _(30230)) + ], + 'musicvideos': [ + ('all', None), + ('recent', _(30256)), + ('inprogress', _(30257)), + ('unwatched', _(30258)) + ], + 'homevideos': [ + ('all', None), + ('recent', _(30251)), + ('recommended', _(30253)) + ], + 'photos': [ + ('all', None), + ('recent', _(30252)), + ('sets', _(30255)), + ('recommended', _(30254)) + ] +} ################################################################################################# -class Views(object): +def verify_kodi_defaults(): - media_types = { - 'movies': "Movie", - 'tvshows': "Series", - 'musicvideos': "MusicVideo", - 'homevideos': "Video", - 'music': "Audio", - 'photos': "Photo" - } + ''' Make sure we have the kodi default folder in place. + ''' + node_path = xbmc.translatePath("special://profile/library/video").decode('utf-8') - def __init__(self, emby_cursor, kodi_cursor): - self.emby_cursor = emby_cursor - self.kodi_cursor = kodi_cursor - - self.total_nodes = 0 - self.nodes = list() - self.playlists = list() - self.views = list() - self.sorted_views = list() - self.grouped_views = list() - - self.video_nodes = VideoNodes() - self.playlist = Playlist() - self.emby = embyserver.Read_EmbyServer() - self.emby_db = embydb.Embydb_Functions(emby_cursor) - self.artwork = artwork.Artwork() - - def _populate_views(self): - # Will get emby views and views in Kodi + if not xbmcvfs.exists(node_path): try: - grouped_views = self.emby.get_views() - except Exception as error: - log.info("Error getting views from server: " + str(error)) - grouped_views = None - - if grouped_views is not None and "Items" in grouped_views: - self.grouped_views = grouped_views['Items'] - else: - self.grouped_views = [] - - for view in self.emby.getViews(sortedlist=True): - self.views.append(view['name']) - if view['type'] == "music": - continue - - if view['type'] == "mixed": - self.sorted_views.append(view['name']) - self.sorted_views.append(view['name']) - - log.info("sorted views: %s", self.sorted_views) - self.total_nodes = len(self.sorted_views) - - def maintain(self): - # Compare views to emby - self._populate_views() - curr_views = self.emby_db.getViews() - # total nodes for window properties - self.video_nodes.clearProperties() - - for media_type in ('movies', 'tvshows', 'musicvideos', 'homevideos', 'music', 'photos'): - - self.nodes = list() # Prevent duplicate for nodes of the same type - self.playlists = list() # Prevent duplicate for playlists of the same type - # Get media folders to include mixed views as well - for folder in self.emby.getViews(media_type, root=True): - - view_id = folder['id'] - view_name = folder['name'] - view_type = folder['type'] - - if view_name not in self.views: - # Media folders are grouped into userview - view_name = self._get_grouped_view(media_type, view_id, view_name) - - try: # Make sure the view is in sorted views before proceeding - self.sorted_views.index(view_name) - except ValueError: - self.sorted_views.append(view_name) - - # Get current media folders from emby database and compare - if self.compare_view(media_type, view_id, view_name, view_type): - if view_id in curr_views: # View is still valid - curr_views.remove(view_id) - - # Add video nodes listings - self.add_single_nodes() - # Save total - window('Emby.nodes.total', str(self.total_nodes)) - # Remove any old referenced views - log.info("Removing views: %s", curr_views) - for view in curr_views: - self.remove_view(view) - - def _get_grouped_view(self, media_type, view_id, view_name): - # Get single item from view to compare - try: - result = self.emby.get_single_item(self.media_types[media_type], view_id) - item = result['Items'][0]['Id'] - except Exception as error: - log.info("Error getting single item form server: " + str(error)) - # Something is wrong. Keep the same folder name. - # Could be the view is empty or the connection - pass - else: - for view in self.grouped_views: - if view['Type'] == "UserView" and view.get('CollectionType') == media_type: - # Take the userview, and validate the item belong to the view - if self.emby.verifyView(view['Id'], item): - log.info("found corresponding view: %s %s", view['Name'], view['Id']) - view_name = view['Name'] - break - else: # Unable to find a match, add the name to our sorted_view list - log.info("couldn't find corresponding grouped view: %s", self.sorted_views) - - return view_name - - def add_view(self, media_type, view_id, view_name, view_type): - # Generate view, playlist and video node - log.info("creating view %s: %s", view_name, view_id) - tag_id = self.get_tag(view_name) - - self.add_playlist_node(media_type, view_id, view_name, view_type) - # Add view to emby database - group_series = self.is_grouped_series(view_id, view_type) - self.emby_db.addView(view_id, view_name, view_type, tag_id, group_series) - - def is_grouped_series(self, view_id, view_type): - - if window('emby.userinfo.json')['Policy']['IsAdministrator']: - try: - return self.emby.get_view_options(view_id)['EnableAutomaticSeriesGrouping'] if view_type == "tvshows" else None - except Exception as error: # Currently admin only api entrypoint - log.error(error) - return None - else: - return None - - def compare_view(self, media_type, view_id, view_name, view_type): - - curr_view = self.emby_db.getView_byId(view_id) - try: - curr_view_name = curr_view[0] - curr_view_type = curr_view[1] - curr_tag_id = curr_view[2] - except TypeError: - self.add_view(media_type, view_id, view_name, view_type) - return False - - # View is still valid - log.debug("Found viewid: %s viewname: %s viewtype: %s tagid: %s", - view_id, curr_view_name, curr_view_type, curr_tag_id) - - if curr_view_name != view_name: - # View was modified, update with latest info - log.info("viewid: %s new viewname: %s", view_id, view_name) - tag_id = self.get_tag(view_name) - # Update view with new info - self.emby_db.updateView(view_name, tag_id, view_id) - # Delete old playlists and video nodes - self.delete_playlist_node(media_type, curr_view_name, view_id, curr_view_type) - # Update items with new tag - self._update_items_tag(curr_view_type[:-1], view_id, curr_tag_id, tag_id) - - group_series = self.is_grouped_series(view_id, view_type) - self.emby_db.update_view_grouped_series(view_id, group_series) - # Verify existance of playlist and nodes - self.add_playlist_node(media_type, view_id, view_name, view_type) - return True - - def remove_view(self, view): - # Remove any items that belongs to the old view - items = self.emby_db.get_item_by_view(view) - items = [i[0] for i in items] # Convert list of tuple to list - # TODO: Triage not accessible from here yet - #self.triage_items("remove", items) - - def _update_items_tag(self, media_type, view_id, tag, new_tag): - items = self.emby_db.getItem_byView(view_id) - for item in items: - # Remove the "s" from viewtype for tags - self._update_tag(tag, new_tag, item[0], media_type) - - def get_tag(self, tag): - # This will create and return the tag_id - if KODI > 14: - # Kodi Isengard and up - query = ' '.join(( - - "SELECT tag_id", - "FROM tag", - "WHERE name = ?", - "COLLATE NOCASE" - )) - self.kodi_cursor.execute(query, (tag,)) - try: - tag_id = self.kodi_cursor.fetchone()[0] - except TypeError: - tag_id = self._add_tag(tag) - else:# TODO: Remove once Kodi Krypton is RC - query = ' '.join(( - - "SELECT idTag", - "FROM tag", - "WHERE strTag = ?", - "COLLATE NOCASE" - )) - self.kodi_cursor.execute(query, (tag,)) - try: - tag_id = self.kodi_cursor.fetchone()[0] - except TypeError: - self.kodi_cursor.execute("select coalesce(max(idTag),0) from tag") - tag_id = self.kodi_cursor.fetchone()[0] + 1 - - query = "INSERT INTO tag(idTag, strTag) values(?, ?)" - self.kodi_cursor.execute(query, (tag_id, tag)) - log.debug("Create idTag: %s name: %s", tag_id, tag) - - return tag_id - - def _add_tag(self, tag): - - self.kodi_cursor.execute("select coalesce(max(tag_id),0) from tag") - tag_id = self.kodi_cursor.fetchone()[0] + 1 - - query = "INSERT INTO tag(tag_id, name) values(?, ?)" - self.kodi_cursor.execute(query, (tag_id, tag)) - log.debug("Create tag_id: %s name: %s", tag_id, tag) - - return tag_id - - def _update_tag(self, tag, new_tag, kodi_id, media_type): - - log.debug("Updating: %s with %s for %s: %s", tag, new_tag, media_type, kodi_id) - - if KODI > 14: - # Kodi Isengard and up - try: - query = ' '.join(( - - "UPDATE tag_link", - "SET tag_id = ?", - "WHERE media_id = ?", - "AND media_type = ?", - "AND tag_id = ?" - )) - self.kodi_cursor.execute(query, (new_tag, kodi_id, media_type, tag,)) - except sqlite3.IntegrityError: - # The new tag we are going to apply already exists for this item - # delete current tag instead - query = ' '.join(( - - "DELETE FROM tag_link", - "WHERE media_id = ?", - "AND media_type = ?", - "AND tag_id = ?" - )) - self.kodi_cursor.execute(query, (kodi_id, media_type, tag,)) - else:# TODO: Remove once Kodi Krypton is RC - try: - query = ' '.join(( - - "UPDATE taglinks", - "SET idTag = ?", - "WHERE idMedia = ?", - "AND media_type = ?", - "AND idTag = ?" - )) - self.kodi_cursor.execute(query, (new_tag, kodi_id, media_type, tag,)) - except sqlite3.IntegrityError: - # The new tag we are going to apply already exists for this item - # delete current tag instead - query = ' '.join(( - - "DELETE FROM taglinks", - "WHERE idMedia = ?", - "AND media_type = ?", - "AND idTag = ?" - )) - self.kodi_cursor.execute(query, (kodi_id, media_type, tag,)) - - def add_playlist_node(self, media_type, view_id, view_name, view_type): - # Create playlist for the video library - if view_name not in self.playlists and media_type in ('movies', 'tvshows', 'musicvideos'): - self.playlist.process_playlist(media_type, view_id, view_name, view_type) - self.playlists.append(view_name) - # Create the video node - if view_name not in self.nodes and media_type not in ('musicvideos', 'music'): - index = self.sorted_views.index(view_name) - self.video_nodes.viewNode(index, view_name, media_type, view_type, view_id) - - if window('emby_online') == "true": - # Only pull artwork if server is online - art = self.artwork.get_all_artwork(self.emby.getItem(view_id)) - if art.get('Primary'): - window("Emby.nodes.%s.artwork" % index, art['Primary']) - - if view_type == "mixed": # Change the value - self.sorted_views[index] = "%ss" % view_name - - self.nodes.append(view_name) - self.total_nodes += 1 - - def delete_playlist_node(self, media_type, view_id, view_name, view_type): - - if media_type == "music": - return - - if self.emby_db.getView_byName(view_name) is None: - # The tag could be a combined view. Ensure there's no other tags - # with the same name before deleting playlist. - self.playlist.process_playlist(media_type, view_id, view_name, view_type, True) - # Delete video node - if media_type != "musicvideos": - self.video_nodes.viewNode(None, view_name, media_type, view_type, view_id, True) - - def add_single_nodes(self): - - singles = [ - ("Favorite movies", "movies", "favourites"), - ("Favorite tvshows", "tvshows", "favourites"), - ("Favorite episodes", "episodes", "favourites"), - #("channels", "movies", "channels") - ] - for args in singles: - self._single_node(self.total_nodes, *args) - - def _single_node(self, index, tag, media_type, view_type): - self.video_nodes.singleNode(index, tag, media_type, view_type) - self.total_nodes += 1 - - def offline_mode(self): - # Just reads from the db and populate views that way - # total nodes for window properties - self.video_nodes.clearProperties() - - for media_type in ('movies', 'tvshows', 'musicvideos', 'homevideos', 'music', 'photos'): - - self.nodes = list() # Prevent duplicate for nodes of the same type - self.playlists = list() # Prevent duplicate for playlists of the same type - - views = self.emby_db.getView_byType(media_type) - for view in views: - - try: # Make sure the view is in sorted views before proceeding - self.sorted_views.index(view['name']) - except ValueError: - self.sorted_views.append(view['name']) - - self.add_playlist_node(media_type, view['id'], view['name'], view['mediatype']) - - self.add_single_nodes() - window('Emby.nodes.total', str(self.total_nodes)) - - -class Playlist(object): - - def __init__(self): - pass - - def process_playlist(self, media_type, view_id, view_name, view_type, delete=False): - # Tagname is in unicode - actions: add or delete - tag = view_name.encode('utf-8') - path = xbmc.translatePath("special://profile/playlists/video/").decode('utf-8') - - if view_type == "mixed": - playlist_name = "%s - %s" % (tag, media_type) - xsp_path = os.path.join(path, "Emby %s - %s.xsp" % (view_id, media_type)) - else: - playlist_name = tag - xsp_path = os.path.join(path, "Emby %s.xsp" % view_id) - - # Only add the playlist if it doesn't exist - if xbmcvfs.exists(xsp_path): - if delete: - self._delete_playlist(xsp_path) - return - - elif not xbmcvfs.exists(path): - log.info("creating directory: %s", path) - xbmcvfs.mkdirs(path) - - self._add_playlist(tag, playlist_name, xsp_path, media_type) - - def _add_playlist(self, tag, name, path, media_type): - # Using write process since there's no guarantee the xml declaration works with etree - special_types = {'homevideos': "movies"} - log.info("writing playlist to: %s", path) - try: - f = xbmcvfs.File(path, 'w') - except: - log.info("failed to create playlist: %s", path) - else: - f.write( - '<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>\n' - '<smartplaylist type="%s">\n\t' - '<name>Emby %s</name>\n\t' - '<match>all</match>\n\t' - '<rule field="tag" operator="is">\n\t\t' - '<value>%s</value>\n\t' - '</rule>' - '</smartplaylist>' - % (special_types.get(media_type, media_type), name, tag)) - f.close() - log.info("successfully added playlist: %s", tag) - - @classmethod - def _delete_playlist(cls, path): - xbmcvfs.delete(path) - log.info("successfully removed playlist: %s", path) - - def delete_playlists(self): - # Clean up the playlists - path = xbmc.translatePath("special://profile/playlists/video/").decode('utf-8') - dirs, files = xbmcvfs.listdir(path) - for file in files: - if file.decode('utf-8').startswith('Emby'): - self._delete_playlist(os.path.join(path, file.decode('utf-8'))) - - -class VideoNodes(object): - - - def __init__(self): - pass - - def normalize_nodes(self, text): - # For video nodes - text = text.replace(":", "") - text = text.replace("/", "-") - text = text.replace("\\", "-") - text = text.replace("<", "") - text = text.replace(">", "") - text = text.replace("*", "") - text = text.replace("?", "") - text = text.replace('|', "") - text = text.replace('(', "") - text = text.replace(')', "") - text = text.strip() - # Remove dots from the last character as windows can not have directories - # with dots at the end - text = text.rstrip('.') - text = unicodedata.normalize('NFKD', unicode(text, 'utf-8')).encode('ascii', 'ignore') - - return text - - def commonRoot(self, order, label, tagname="", roottype=1): - - if roottype == 0: - # Index - root = etree.Element('node', attrib={'order': "%s" % order}) - elif roottype == 1: - # Filter - root = etree.Element('node', attrib={'order': "%s" % order, 'type': "filter"}) - etree.SubElement(root, 'match').text = "all" - # Add tag rule - rule = etree.SubElement(root, 'rule', attrib={'field': "tag", 'operator': "is"}) - etree.SubElement(rule, 'value').text = tagname - else: - # Folder - root = etree.Element('node', attrib={'order': "%s" % order, 'type': "folder"}) - - etree.SubElement(root, 'label').text = label - etree.SubElement(root, 'icon').text = "special://home/addons/plugin.video.emby/icon.png" - - return root - - def viewNode(self, indexnumber, tagname, mediatype, viewtype, viewid, delete=False): - - if viewtype == "mixed": - dirname = "%s - %s" % (viewid, mediatype) - else: - dirname = viewid - - nodepath = xbmc.translatePath( - "special://profile/library/video/emby/%s/" % dirname).decode('utf-8') - - if delete: - dirs, files = xbmcvfs.listdir(nodepath) - for file in files: - xbmcvfs.delete(nodepath + file) - - log.info("Sucessfully removed videonode: %s." % tagname) - return - - # Verify the video directory - path = xbmc.translatePath("special://profile/library/video/").decode('utf-8') - if not xbmcvfs.exists(path): - try: - shutil.copytree( - src=xbmc.translatePath("special://xbmc/system/library/video").decode('utf-8'), - dst=xbmc.translatePath("special://profile/library/video").decode('utf-8')) - except Exception as error: - log.error(error) - - xbmcvfs.mkdir(path) - - embypath = xbmc.translatePath("special://profile/library/video/emby/").decode('utf-8') - if not xbmcvfs.exists(embypath): - xbmcvfs.mkdir(embypath) - root = self.commonRoot(order=0, label="Emby", roottype=0) - try: - xml_indent(root) - except: pass - etree.ElementTree(root).write(os.path.join(embypath, "index.xml")) - - # Create the node directory - if not xbmcvfs.exists(nodepath) and not mediatype == "photos": - # We need to copy over the default items - xbmcvfs.mkdir(nodepath) - - # Create index entry - nodeXML = "%sindex.xml" % nodepath - # Set windows property - path = "library://video/emby/%s/" % dirname - for i in range(1, indexnumber): - # Verify to make sure we don't create duplicates - if window('Emby.nodes.%s.index' % i) == path: - return - - if mediatype == "photos": - path = "plugin://plugin.video.emby/?id=%s&mode=getsubfolders" % indexnumber - - window('Emby.nodes.%s.index' % indexnumber, value=path) - - # Root - if not mediatype == "photos": - if viewtype == "mixed": - specialtag = "%s - %s" % (tagname, mediatype) - root = self.commonRoot(order=0, label=specialtag, tagname=tagname, roottype=0) - else: - root = self.commonRoot(order=0, label=tagname, tagname=tagname, roottype=0) - try: - xml_indent(root) - except: pass - etree.ElementTree(root).write(nodeXML) - - nodetypes = { - - '1': "all", - '2': "recent", - '3': "recentepisodes", - '4': "inprogress", - '5': "inprogressepisodes", - '6': "unwatched", - '7': "nextepisodes", - '8': "sets", - '9': "genres", - '10': "random", - '11': "recommended", - } - mediatypes = { - # label according to nodetype per mediatype - 'movies': - { - '1': tagname, - '2': 30174, - '4': 30177, - '6': 30189, - '8': 20434, - '9': 135, - '10': 30229, - '11': 30230 - }, - - 'tvshows': - { - '1': tagname, - '2': 30170, - '3': 30175, - '4': 30171, - '5': 30178, - '7': 30179, - '9': 135, - '10': 30229, - '11': 30230 - }, - - 'homevideos': - { - '1': tagname, - '2': 30251, - '11': 30253 - }, - - 'photos': - { - '1': tagname, - '2': 30252, - '8': 30255, - '11': 30254 - }, - - 'musicvideos': - { - '1': tagname, - '2': 30256, - '4': 30257, - '6': 30258 - } - } - - nodes = mediatypes[mediatype] - for node in nodes: - - nodetype = nodetypes[node] - nodeXML = "%s%s.xml" % (nodepath, nodetype) - # Get label - stringid = nodes[node] - if node != "1": - label = lang(stringid) - if not label: - label = xbmc.getLocalizedString(stringid) - else: - label = stringid - - # Set window properties - if (mediatype == "homevideos" or mediatype == "photos") and nodetype == "all": - params = { - - 'id': tagname.encode('utf-8'), - 'mode': "browsecontent", - 'type': mediatype - } - path = urllib_path("plugin://plugin.video.emby/", params) - - elif (mediatype == "homevideos" or mediatype == "photos"): - params = { - - 'id': tagname.encode('utf-8'), - 'mode': "browsecontent", - 'type': mediatype, - 'folderid': nodetype - } - path = urllib_path("plugin://plugin.video.emby/", params) - - elif nodetype == "nextepisodes": - params = { - - 'id': tagname.encode('utf-8'), - 'mode': "nextup", - 'limit': 25 - } - path = urllib_path("plugin://plugin.video.emby/", params) - - else: - path = "library://video/emby/%s/%s.xml" % (viewid, nodetype) - - if mediatype == "photos": - windowpath = "ActivateWindow(Pictures,%s,return)" % path - else: - windowpath = "ActivateWindow(Videos,%s,return)" % path - - if nodetype == "all": - - if viewtype == "mixed": - templabel = "%s - %s" % (tagname, mediatype) - else: - templabel = label - - embynode = "Emby.nodes.%s" % indexnumber - window('%s.title' % embynode, value=templabel) - window('%s.path' % embynode, value=windowpath) - window('%s.content' % embynode, value=path) - window('%s.type' % embynode, value=mediatype) - else: - embynode = "Emby.nodes.%s.%s" % (indexnumber, nodetype) - window('%s.title' % embynode, value=label) - window('%s.path' % embynode, value=windowpath) - window('%s.content' % embynode, value=path) - - if mediatype == "photos": - # For photos, we do not create a node in videos but we do want the window props - # to be created. - # To do: add our photos nodes to kodi picture sources somehow - continue - - if xbmcvfs.exists(nodeXML): - # Don't recreate xml if already exists - continue - - # Create the root - if (nodetype == "nextepisodes" or mediatype == "homevideos"): - # Folder type with plugin path - root = self.commonRoot(order=node, label=label, tagname=tagname, roottype=2) - etree.SubElement(root, 'path').text = path - etree.SubElement(root, 'content').text = "episodes" - else: - root = self.commonRoot(order=node, label=label, tagname=tagname) - if nodetype in ('recentepisodes', 'inprogressepisodes'): - etree.SubElement(root, 'content').text = "episodes" - else: - etree.SubElement(root, 'content').text = mediatype - - limit = "25" - # Elements per nodetype - if nodetype == "all": - etree.SubElement(root, 'order', {'direction': "ascending"}).text = "sorttitle" - - elif nodetype == "recent": - etree.SubElement(root, 'order', {'direction': "descending"}).text = "dateadded" - etree.SubElement(root, 'limit').text = limit - rule = etree.SubElement(root, 'rule', {'field': "playcount", 'operator': "is"}) - etree.SubElement(rule, 'value').text = "0" - - elif nodetype == "inprogress": - etree.SubElement(root, 'rule', {'field': "inprogress", 'operator': "true"}) - etree.SubElement(root, 'limit').text = limit - - elif nodetype == "genres": - etree.SubElement(root, 'order', {'direction': "ascending"}).text = "sorttitle" - etree.SubElement(root, 'group').text = "genres" - - elif nodetype == "unwatched": - etree.SubElement(root, 'order', {'direction': "ascending"}).text = "sorttitle" - rule = etree.SubElement(root, "rule", {'field': "playcount", 'operator': "is"}) - etree.SubElement(rule, 'value').text = "0" - - elif nodetype == "sets": - etree.SubElement(root, 'order', {'direction': "ascending"}).text = "sorttitle" - etree.SubElement(root, 'group').text = "sets" - - elif nodetype == "random": - etree.SubElement(root, 'order', {'direction': "ascending"}).text = "random" - etree.SubElement(root, 'limit').text = limit - - elif nodetype == "recommended": - etree.SubElement(root, 'order', {'direction': "descending"}).text = "rating" - etree.SubElement(root, 'limit').text = limit - rule = etree.SubElement(root, 'rule', {'field': "playcount", 'operator': "is"}) - etree.SubElement(rule, 'value').text = "0" - rule2 = etree.SubElement(root, 'rule', - attrib={'field': "rating", 'operator': "greaterthan"}) - etree.SubElement(rule2, 'value').text = "7" - - elif nodetype == "recentepisodes": - # Kodi Isengard, Jarvis - etree.SubElement(root, 'order', {'direction': "descending"}).text = "dateadded" - etree.SubElement(root, 'limit').text = limit - rule = etree.SubElement(root, 'rule', {'field': "playcount", 'operator': "is"}) - etree.SubElement(rule, 'value').text = "0" - - elif nodetype == "inprogressepisodes": - # Kodi Isengard, Jarvis - etree.SubElement(root, 'limit').text = "25" - rule = etree.SubElement(root, 'rule', - attrib={'field': "inprogress", 'operator':"true"}) - - try: - xml_indent(root) - except: pass - etree.ElementTree(root).write(nodeXML) - - def singleNode(self, indexnumber, tagname, mediatype, itemtype): - - tagname = tagname.encode('utf-8') - cleantagname = self.normalize_nodes(tagname) - nodepath = xbmc.translatePath("special://profile/library/video/").decode('utf-8') - nodeXML = "%semby_%s.xml" % (nodepath, cleantagname) - path = "library://video/emby_%s.xml" % cleantagname - windowpath = "ActivateWindow(Videos,%s,return)" % path - - # Create the video node directory - if not xbmcvfs.exists(nodepath): - # We need to copy over the default items shutil.copytree( src=xbmc.translatePath("special://xbmc/system/library/video").decode('utf-8'), dst=xbmc.translatePath("special://profile/library/video").decode('utf-8')) - xbmcvfs.exists(path) + except Exception as error: + xbmcvfs.mkdir(node_path) - labels = { + for index, node in enumerate(['movies', 'tvshows', 'musicvideos']): + file = os.path.join(node_path, node, "index.xml") + xml = etree.parse(file).getroot() + xml.set('order', str(17 + index)) + indent(xml) + write_xml(etree.tostring(xml, 'UTF-8'), file) - 'Favorite movies': 30180, - 'Favorite tvshows': 30181, - 'Favorite episodes': 30182 + playlist_path = xbmc.translatePath("special://profile/playlists/video").decode('utf-8') + + if not xbmcvfs.exists(playlist_path): + xbmcvfs.mkdirs(playlist_path) + +class Views(object): + + sync = None + limit = 25 + + def __init__(self): + + self.sync = get_sync() + self.window = {} + self.server = Emby() + + def add_library(self, view): + + ''' Add entry to view table in emby database. + ''' + with Database('emby') as embydb: + emby_db.EmbyDatabase(embydb.cursor).add_view(view['Id'], view['Name'], view['Media']) + + def remove_library(self, view_id): + + ''' Remove entry from view table in emby database. + ''' + with Database('emby') as embydb: + emby_db.EmbyDatabase(embydb.cursor).remove_view(view_id) + + self.delete_playlist_by_id(view_id) + self.delete_node_by_id(view_id) + self.get_views() + + def get_views(self): + + ''' Get all views and the media folders that make up the views. + Add custom views that are not media folders but should still be added + ''' + media = { + 'movies': "Movie", + 'tvshows': "Series", + 'musicvideos': "MusicVideo" } - label = lang(labels[tagname]) - embynode = "Emby.nodes.%s" % indexnumber - window('%s.title' % embynode, value=label) - window('%s.path' % embynode, value=windowpath) - window('%s.content' % embynode, value=path) - window('%s.type' % embynode, value=itemtype) + try: + libraries = self.server['api'].get_media_folders()['Items'] + except Exception as error: + LOG.error("Unable to process libraries: %s", error) - if xbmcvfs.exists(nodeXML): - # Don't recreate xml if already exists return - if itemtype == "channels": - root = self.commonRoot(order=1, label=label, tagname=tagname, roottype=2) - etree.SubElement(root, 'path').text = "plugin://plugin.video.emby/?id=0&mode=channels" - elif itemtype == "favourites" and mediatype == "episodes": - root = self.commonRoot(order=1, label=label, tagname=tagname, roottype=2) - etree.SubElement(root, 'path').text = "plugin://plugin.video.emby/?id=%s&mode=browsecontent&type=%s&folderid=favepisodes" %(tagname, mediatype) - else: - root = self.commonRoot(order=1, label=label, tagname=tagname) - etree.SubElement(root, 'order', {'direction': "ascending"}).text = "sorttitle" + self.sync['SortedViews'] = [x['Id'] for x in libraries] - etree.SubElement(root, 'content').text = mediatype + for library in libraries: + + if library['Type'] == 'Channel': + library['Media'] = "channels" + else: + library['Media'] = library.get('OriginalCollectionType', library.get('CollectionType', "mixed")) + + self.add_library(library) + + save_sync(self.sync) + + def get_nodes(self): + + ''' Set up playlists, video nodes, window prop. + ''' + node_path = xbmc.translatePath("special://profile/library/video").decode('utf-8') + playlist_path = xbmc.translatePath("special://profile/playlists/video").decode('utf-8') + index = 0 + + with Database('emby') as embydb: + db = emby_db.EmbyDatabase(embydb.cursor) + + for library in self.sync['Whitelist']: + + library = library.replace('Mixed:', "") + view = db.get_view(library) + view = {'Id': library, 'Name': view[0], 'Tag': view[0], 'Media': view[1]} + + if view['Media'] == 'mixed': + for media in ('movies', 'tvshows'): + + temp_view = dict(view) + temp_view['Media'] = media + self.add_playlist(playlist_path, temp_view, True) + self.add_nodes(node_path, temp_view, True) + else: # Compensate for the duplicate. + index += 1 + else: + if view['Media'] in ('movies', 'tvshows', 'musicvideos'): + self.add_playlist(playlist_path, view) + + if view['Media'] not in ('music'): + self.add_nodes(node_path, view) + + index += 1 + + for single in [{'Name': _('fav_movies'), 'Tag': "Favorite movies", 'Media': "movies"}, + {'Name': _('fav_tvshows'), 'Tag': "Favorite tvshows", 'Media': "tvshows"}, + {'Name': _('fav_episodes'), 'Tag': "Favorite episodes", 'Media': "episodes"}]: + + self.add_single_node(node_path, index, "favorites", single) + index += 1 + + self.window_nodes() + + def add_playlist(self, path, view, mixed=False): + + ''' Create or update the xps file. + ''' + file = os.path.join(path, "emby%s%s.xsp" % (view['Media'], view['Id'])) + + try: + xml = etree.parse(file).getroot() + except Exception: + xml = etree.Element('smartplaylist', {'type': view['Media']}) + etree.SubElement(xml, 'name') + etree.SubElement(xml, 'match') + + name = xml.find('name') + name.text = view['Name'] if not mixed else "%s (%s)" % (view['Name'], view['Media']) + + match = xml.find('match') + match.text = "all" + + for rule in xml.findall('.//value'): + if rule.text == view['Tag']: + break + else: + rule = etree.SubElement(xml, 'rule', {'field': "tag", 'operator': "is"}) + etree.SubElement(rule, 'value').text = view['Tag'] + + indent(xml) + write_xml(etree.tostring(xml, 'UTF-8'), file) + + def add_nodes(self, path, view, mixed=False): + + ''' Create or update the video node file. + ''' + folder = os.path.join(path, "emby%s%s" % (view['Media'], view['Id'])) + + if not xbmcvfs.exists(folder): + xbmcvfs.mkdir(folder) + + self.node_index(folder, view, mixed) + + if view['Media'] == 'tvshows': + self.node_tvshow(folder, view) + else: + self.node(folder, view) + + def add_single_node(self, path, index, item_type, view): + + file = os.path.join(path, "emby_%s.xml" % view['Tag'].replace(" ", "")) try: - xml_indent(root) - except: pass - etree.ElementTree(root).write(nodeXML) + xml = etree.parse(file).getroot() + except Exception: + xml = self.node_root('folder' if item_type == 'favorites' and view['Media'] == 'episodes' else 'filter', index) + etree.SubElement(xml, 'label') + etree.SubElement(xml, 'match') + etree.SubElement(xml, 'content') - def deleteNodes(self): - # Clean up video nodes - path = xbmc.translatePath("special://profile/library/video/emby/").decode('utf-8') - if (xbmcvfs.exists(path)): - try: - shutil.rmtree(path) - except: - log.warn("Failed to delete directory: %s" % path) - # Old cleanup code kept for cleanup of old style nodes - path = xbmc.translatePath("special://profile/library/video/").decode('utf-8') - dirs, files = xbmcvfs.listdir(path) - for dir in dirs: - if dir.decode('utf-8').startswith('Emby'): - try: - shutil.rmtree("%s%s" % (path, dir.decode('utf-8'))) - except: - log.warn("Failed to delete directory: %s" % dir.decode('utf-8')) - for file in files: - if file.decode('utf-8').startswith('emby'): - try: - xbmcvfs.delete("%s%s" % (path, file.decode('utf-8'))) - except: - log.warn("Failed to delete file: %s" % file.decode('utf-8')) + label = xml.find('label') + label.text = view['Name'] - def clearProperties(self): + content = xml.find('content') + content.text = view['Media'] - log.info("Clearing nodes properties.") - embyprops = window('Emby.nodes.total') - propnames = [ + match = xml.find('match') + match.text = "all" + + if view['Media'] != 'episodes': + + for rule in xml.findall('.//value'): + if rule.text == view['Tag']: + break + else: + rule = etree.SubElement(xml, 'rule', {'field': "tag", 'operator': "is"}) + etree.SubElement(rule, 'value').text = view['Tag'] + + if item_type == 'favorites' and view['Media'] == 'episodes': + path = self.window_browse(view, 'FavEpisodes') + self.node_favepisodes(xml, path) + else: + self.node_all(xml) + + indent(xml) + write_xml(etree.tostring(xml, 'UTF-8'), file) + + def node_root(self, root, index): + + ''' Create the root element + ''' + if root == 'main': + element = etree.Element('node', {'order': str(index)}) + elif root == 'filter': + element = etree.Element('node', {'order': str(index), 'type': "filter"}) + else: + element = etree.Element('node', {'order': str(index), 'type': "folder"}) + + etree.SubElement(element, 'icon').text = "special://home/addons/plugin.video.emby/icon.png" + + return element + + def node_index(self, folder, view, mixed=False): + + file = os.path.join(folder, "index.xml") + index = self.sync['SortedViews'].index(view['Id']) + + try: + xml = etree.parse(file).getroot() + xml.set('order', str(index)) + except Exception: + xml = self.node_root('main', index) + etree.SubElement(xml, 'label') + + label = xml.find('label') + label.text = view['Name'] if not mixed else "%s (%s)" % (view['Name'], _(view['Media'])) + + indent(xml) + write_xml(etree.tostring(xml, 'UTF-8'), file) + + def node(self, folder, view): + + for node in NODES[view['Media']]: + + xml_name = node[0] + xml_label = node[1] or view['Name'].encode('utf-8') + file = os.path.join(folder, "%s.xml" % xml_name) + self.add_node(NODES[view['Media']].index(node), file, view, xml_name, xml_label) + + def node_tvshow(self, folder, view): + + for node in NODES[view['Media']]: + + xml_name = node[0] + xml_label = node[1] or view['Name'].encode('utf-8') + xml_index = NODES[view['Media']].index(node) + file = os.path.join(folder, "%s.xml" % xml_name) + + if xml_name == 'nextepisodes': + path = self.window_nextepisodes(view) + self.add_dynamic_node(xml_index, file, view, xml_name, xml_label, path) + else: + self.add_node(xml_index, file, view, xml_name, xml_label) + + def add_node(self, index, file, view, node, name): + + try: + xml = etree.parse(file).getroot() + except Exception: + xml = self.node_root('filter', index) + etree.SubElement(xml, 'label') + etree.SubElement(xml, 'match') + etree.SubElement(xml, 'content') + + label = xml.find('label') + label.text = str(name) if type(name) == int else name + + content = xml.find('content') + content.text = view['Media'] + + match = xml.find('match') + match.text = "all" + + for rule in xml.findall('.//value'): + if rule.text == view['Tag']: + break + else: + rule = etree.SubElement(xml, 'rule', {'field': "tag", 'operator': "is"}) + etree.SubElement(rule, 'value').text = view['Tag'] + + getattr(self, 'node_' + node)(xml) + indent(xml) + write_xml(etree.tostring(xml, 'UTF-8'), file) + + def add_dynamic_node(self, index, file, view, node, name, path): + + try: + xml = etree.parse(file).getroot() + except Exception: + xml = self.node_root('folder', index) + etree.SubElement(xml, 'label') + etree.SubElement(xml, 'content') + + label = xml.find('label') + label.text = name + + getattr(self, 'node_' + node)(xml, path) + indent(xml) + write_xml(etree.tostring(xml, 'UTF-8'), file) + + def node_all(self, root): + + for rule in root.findall('.//order'): + if rule.text == "sorttitle": + break + else: + etree.SubElement(root, 'order', {'direction': "ascending"}).text = "sorttitle" + + def node_nextepisodes(self, root, path): + + for rule in root.findall('.//path'): + rule.text = path + break + else: + etree.SubElement(root, 'path').text = path + + for rule in root.findall('.//content'): + rule.text = "episodes" + break + else: + etree.SubElement(root, 'content').text = "episodes" + + def node_recent(self, root): + + for rule in root.findall('.//order'): + if rule.text == "dateadded": + break + else: + etree.SubElement(root, 'order', {'direction': "descending"}).text = "dateadded" + + for rule in root.findall('.//limit'): + rule.text = str(self.limit) + break + else: + etree.SubElement(root, 'limit').text = str(self.limit) + + for rule in root.findall('.//rule'): + if rule.attrib['field'] == 'playcount': + rule.find('value').text = "0" + break + else: + rule = etree.SubElement(root, 'rule', {'field': "playcount", 'operator': "is"}) + etree.SubElement(rule, 'value').text = "0" + + def node_inprogress(self, root): + + for rule in root.findall('.//rule'): + if rule.attrib['field'] == 'inprogress': + break + else: + etree.SubElement(root, 'rule', {'field': "inprogress", 'operator': "true"}) + + for rule in root.findall('.//limit'): + rule.text = str(self.limit) + break + else: + etree.SubElement(root, 'limit').text = str(self.limit) + + def node_genres(self, root): + + for rule in root.findall('.//order'): + if rule.text == "sorttitle": + break + else: + etree.SubElement(root, 'order', {'direction': "ascending"}).text = "sorttitle" + + for rule in root.findall('.//group'): + rule.text = "genres" + break + else: + etree.SubElement(root, 'group').text = "genres" - "index","path","title","content", + def node_unwatched(self, root): + + for rule in root.findall('.//order'): + if rule.text == "sorttitle": + break + else: + etree.SubElement(root, 'order', {'direction': "ascending"}).text = "sorttitle" + + for rule in root.findall('.//rule'): + if rule.attrib['field'] == 'playcount': + rule.find('value').text = "0" + break + else: + rule = etree.SubElement(root, "rule", {'field': "playcount", 'operator': "is"}) + etree.SubElement(rule, 'value').text = "0" + + def node_sets(self, root): + + for rule in root.findall('.//order'): + if rule.text == "sorttitle": + break + else: + etree.SubElement(root, 'order', {'direction': "ascending"}).text = "sorttitle" + + for rule in root.findall('.//group'): + rule.text = "sets" + break + else: + etree.SubElement(root, 'group').text = "sets" + + def node_random(self, root): + + for rule in root.findall('.//order'): + if rule.text == "random": + break + else: + etree.SubElement(root, 'order', {'direction': "ascending"}).text = "random" + + for rule in root.findall('.//limit'): + rule.text = str(self.limit) + break + else: + etree.SubElement(root, 'limit').text = str(self.limit) + + def node_recommended(self, root): + + for rule in root.findall('.//order'): + if rule.text == "rating": + break + else: + etree.SubElement(root, 'order', {'direction': "descending"}).text = "rating" + + for rule in root.findall('.//limit'): + rule.text = str(self.limit) + break + else: + etree.SubElement(root, 'limit').text = str(self.limit) + + for rule in root.findall('.//rule'): + if rule.attrib['field'] == 'playcount': + rule.find('value').text = "0" + break + else: + rule = etree.SubElement(root, 'rule', {'field': "playcount", 'operator': "is"}) + etree.SubElement(rule, 'value').text = "0" + + for rule in root.findall('.//rule'): + if rule.attrib['field'] == 'rating': + rule.find('value').text = "7" + break + else: + rule = etree.SubElement(root, 'rule', {'field': "rating", 'operator': "greaterthan"}) + etree.SubElement(rule, 'value').text = "7" + + def node_recentepisodes(self, root): + + for rule in root.findall('.//order'): + if rule.text == "dateadded": + break + else: + etree.SubElement(root, 'order', {'direction': "descending"}).text = "dateadded" + + for rule in root.findall('.//limit'): + rule.text = str(self.limit) + break + else: + etree.SubElement(root, 'limit').text = str(self.limit) + + for rule in root.findall('.//rule'): + if rule.attrib['field'] == 'playcount': + rule.find('value').text = "0" + break + else: + rule = etree.SubElement(root, 'rule', {'field': "playcount", 'operator': "is"}) + etree.SubElement(rule, 'value').text = "0" + + content = root.find('content') + content.text = "episodes" + + def node_inprogressepisodes(self, root): + + for rule in root.findall('.//limit'): + rule.text = str(self.limit) + break + else: + etree.SubElement(root, 'limit').text = str(self.limit) + + for rule in root.findall('.//rule'): + if rule.attrib['field'] == 'inprogress': + break + else: + etree.SubElement(root, 'rule', {'field': "inprogress", 'operator':"true"}) + + content = root.find('content') + content.text = "episodes" + + def node_favepisodes(self, root, path): + + for rule in root.findall('.//path'): + rule.text = path + break + else: + etree.SubElement(root, 'path').text = path + + for rule in root.findall('.//content'): + rule.text = "episodes" + break + else: + etree.SubElement(root, 'content').text = "episodes" + + + def order_media_folders(self, folders): + + ''' Returns a list of sorted media folders based on the Emby views. + Insert them in SortedViews and remove Views that are not in media folders. + ''' + if not folders: + return folders + + sorted_views = list(self.sync['SortedViews']) + unordered = [x[0] for x in folders] + grouped = [x for x in unordered if x not in sorted_views] + + for library in grouped: + sorted_views.append(library) + + sorted_folders = [x for x in sorted_views if x in unordered] + + return [folders[unordered.index(x)] for x in sorted_folders] + + def window_nodes(self): + + ''' Just read from the database and populate based on SortedViews + Setup the window properties that reflect the emby server views and more. + ''' + self.window_clear() + + with Database('emby') as embydb: + libraries = emby_db.EmbyDatabase(embydb.cursor).get_views() + + libraries = self.order_media_folders(libraries or []) + index = 0 + + for library in (libraries or []): + view = {'Id': library[0], 'Name': library[1], 'Tag': library[1], 'Media': library[2]} + + if library[0] in self.sync['Whitelist']: # Synced libraries + + if view['Media'] in ('movies', 'tvshows', 'musicvideos', 'mixed'): + for node in NODES[view['Media']]: + + if view['Media'] == 'mixed': + for media in ('movies', 'tvshows'): + + temp_view = dict(view) + temp_view['Media'] = media + temp_view['Name'] = "%s (%s)" % (view['Name'], _(media)) + self.window_node(index, temp_view, *node) + else: # Add one to compensate for the duplicate. + index += 1 + else: + self.window_node(index, view, *node) + + elif view['Media'] == 'music': + self.window_node(index, view, 'music') + else: # Dynamic entry + self.window_node(index, view, 'browse') + + index += 1 + + for single in [{'Name': _('fav_movies'), 'Tag': "Favorite movies", 'Media': "movies"}, + {'Name': _('fav_tvshows'), 'Tag': "Favorite tvshows", 'Media': "tvshows"}, + {'Name': _('fav_episodes'), 'Tag': "Favorite episodes", 'Media': "episodes"}]: + + self.window_single_node(index, "favorites", single) + index += 1 + + window('Emby.nodes.total', str(index)) + + def window_node(self, index, view, node=None, node_label=None): + + ''' Leads to another listing of nodes. + ''' + if view['Media'] in ('homevideos', 'photos'): + path = self.window_browse(view, None if node in ('all', 'browse') else node) + elif node == 'nextepisodes': + path = self.window_nextepisodes(view) + elif node == 'music': + path = self.window_music(view) + elif node == 'browse': + path = self.window_browse(view) + else: + path = self.window_path(view, node) + + if node == 'music': + window_path = "ActivateWindow(Music,%s,return)" % path + elif node in ('browse', 'homevideos', 'photos'): + window_path = path + else: + window_path = "ActivateWindow(Videos,%s,return)" % path + + if node in ('all', 'music'): + + window_prop = "Emby.nodes.%s" % index + window('%s.index' % window_prop, path.replace('all.xml', "")) # dir + window('%s.title' % window_prop, view['Name']) + window('%s.content' % window_prop, path) + + elif node == 'browse': + + window_prop = "Emby.nodes.%s" % index + window('%s.title' % window_prop, view['Name']) + else: + window_prop = "Emby.nodes.%s.%s" % (index, node) + window('%s.title' % window_prop, str(node_label) or view['Name']) + window('%s.content' % window_prop, path) + + window('%s.id' % window_prop, view['Id']) + window('%s.path' % window_prop, window_path) + window('%s.type' % window_prop, view['Media']) + + if self.server['connected']: + + artwork = api.API(None, self.server['auth/server-address']).get_artwork(view['Id'], 'Primary') + window('%s.artwork' % window_prop, artwork) + + def window_single_node(self, index, item_type, view): + + ''' Single destination node. + ''' + path = "library://video/emby_%s.xml" % view['Tag'].replace(" ", "") + window_path = "ActivateWindow(Videos,%s,return)" % path + + window_prop = "Emby.nodes.%s" % index + window('%s.title' % window_prop, view['Name']) + window('%s.path' % window_prop, window_path) + window('%s.content' % window_prop, path) + window('%s.type' % window_prop, item_type) + + def window_path(self, view, node): + return "library://video/emby%s%s/%s.xml" % (view['Media'], view['Id'], node) + + def window_music(self, view): + return "library://music/" + + def window_nextepisodes(self, view): + + params = { + 'id': view['Id'], + 'mode': "nextepisodes", + 'limit': self.limit + } + return "%s?%s" % ("plugin://plugin.video.emby", urllib.urlencode(params)) + + def window_browse(self, view, node=None): + + params = { + 'mode': "browse", + 'type': view['Media'] + } + + if view.get('Id'): + params['id'] = view['Id'] + + if node: + params['folder'] = node + + return "%s?%s" % ("plugin://plugin.video.emby", urllib.urlencode(params)) + + def window_clear(self): + + ''' Clearing window prop setup for Views. + ''' + total = int(window('Emby.nodes.total') or 0) + props = [ + + "index","id","path","title","content","type" "inprogress.content","inprogress.title", "inprogress.content","inprogress.path", "nextepisodes.title","nextepisodes.content", @@ -870,9 +759,77 @@ class VideoNodes(object): "recentepisodes.path","inprogressepisodes.title", "inprogressepisodes.content","inprogressepisodes.path" ] + for i in range(total): + for prop in props: + window('Emby.nodes.%s.%s' % (str(i), prop), clear=True) - if embyprops: - totalnodes = int(embyprops) - for i in range(totalnodes): - for prop in propnames: - window('Emby.nodes.%s.%s' % (str(i), prop), clear=True) + def delete_playlist(self, path): + + xbmcvfs.delete(path) + LOG.info("DELETE playlist %s", path) + + def delete_playlists(self): + + ''' Remove all emby playlists. + ''' + path = xbmc.translatePath("special://profile/playlists/video/").decode('utf-8') + _, files = xbmcvfs.listdir(path) + for file in files: + if file.decode('utf-8').startswith('emby'): + self.delete_playlist(os.path.join(path, file.decode('utf-8'))) + + def delete_playlist_by_id(self, view_id): + + ''' Remove playlist based based on view_id. + ''' + path = xbmc.translatePath("special://profile/playlists/video/").decode('utf-8') + _, files = xbmcvfs.listdir(path) + for file in files: + file = file.decode('utf-8') + + if file.startswith('emby') and file.endswith('%s.xsp' % view_id): + self.delete_playlist(os.path.join(path, file.decode('utf-8'))) + + def delete_node(self, path): + + xbmcvfs.delete(path) + LOG.info("DELETE node %s", path) + + def delete_nodes(self): + + ''' Remove node and children files. + ''' + path = xbmc.translatePath("special://profile/library/video/").decode('utf-8') + dirs, files = xbmcvfs.listdir(path) + + for file in files: + + if file.startswith('emby'): + self.delete_node(os.path.join(path, file.decode('utf-8'))) + + for directory in dirs: + + if directory.startswith('emby'): + _, files = xbmcvfs.listdir(os.path.join(path, directory.decode('utf-8'))) + + for file in files: + self.delete_node(os.path.join(path, directory.decode('utf-8'), file.decode('utf-8'))) + + xbmcvfs.rmdir(os.path.join(path, directory.decode('utf-8'))) + + def delete_node_by_id(self, view_id): + + ''' Remove node and children files based on view_id. + ''' + path = xbmc.translatePath("special://profile/library/video/").decode('utf-8') + dirs, files = xbmcvfs.listdir(path) + + for directory in dirs: + + if directory.startswith('emby') and directory.endswith(view_id): + _, files = xbmcvfs.listdir(os.path.join(path, directory.decode('utf-8'))) + + for file in files: + self.delete_node(os.path.join(path, directory.decode('utf-8'), file.decode('utf-8'))) + + xbmcvfs.rmdir(os.path.join(path, directory.decode('utf-8'))) diff --git a/resources/lib/websocket_client.py b/resources/lib/websocket_client.py deleted file mode 100644 index 866d87db..00000000 --- a/resources/lib/websocket_client.py +++ /dev/null @@ -1,359 +0,0 @@ -# -*- coding: utf-8 -*- - -################################################################################################# - -import json -import logging -import threading -import websocket - -import xbmc - -import clientinfo -import downloadutils -import librarysync -import playlist -import userclient -import playbackutils -from utils import window, settings, dialog, language as lang, JSONRPC -#from ga_client import log_error - -################################################################################################## - -log = logging.getLogger("EMBY."+__name__) - -################################################################################################## - - -class WebSocketClient(threading.Thread): - - _shared_state = {} - - _client = None - _stop_websocket = False - - - def __init__(self): - - self.__dict__ = self._shared_state - self.monitor = xbmc.Monitor() - - self.doutils = downloadutils.DownloadUtils() - self.client_info = clientinfo.ClientInfo() - self.device_id = self.client_info.get_device_id() - self.library_sync = librarysync.LibrarySync() - - threading.Thread.__init__(self) - - #@log_error() - def on_message(self, ws, message): - - result = json.loads(message) - message_type = result['MessageType'] - - if message_type not in ('NotificationAdded', 'SessionEnded', 'RestartRequired', - 'PackageInstalling'): - # Mute certain events - log.info("Message: %s", message) - - if message_type == 'Play': - # A remote control play command has been sent from the server. - data = result['Data'] - self._play(data) - - elif message_type == 'Playstate': - # A remote control update playstate command has been sent from the server. - data = result['Data'] - self._playstate(data) - - elif message_type == "UserDataChanged": - # A user changed their personal rating for an item, or their playstate was updated - data = result['Data'] - userdata_list = data['UserDataList'] - self.library_sync.triage_items("userdata", userdata_list) - - elif message_type == "LibraryChanged": - data = result['Data'] - self._library_changed(data) - - elif message_type == "GeneralCommand": - data = result['Data'] - self._general_commands(data) - - elif message_type == "ServerRestarting": - self._server_restarting() - - elif message_type == "UserConfigurationUpdated": - # Update user data set in userclient - data = result['Data'] - userclient.UserClient().get_user(data) - self.library_sync.refresh_views = True - - elif message_type == "ServerShuttingDown": - # Server went offline - window('emby_online', value="false") - - @classmethod - def _play(cls, data): - - ''' {"Id":"e2c106a953bfc0d9c191a49cda6561de", - "ItemIds":["52f0c57e2133e1c11d36c59edcd835fc"], - "PlayCommand":"PlayNow","ControllingUserId":"d40000000000000000000000000000000", - "SubtitleStreamIndex":3,"AudioStreamIndex":1, - "MediaSourceId":"ba83c549ac5c0e4180ae33ebdf813c51"}} - ''' - - item_ids = data['ItemIds'] - kwargs = { - - 'SubtitleStreamIndex': data.get('SubtitleStreamIndex'), - 'AudioStreamIndex': data.get('AudioStreamIndex'), - 'MediaSourceId': data.get('MediaSourceId') - } - command = data['PlayCommand'] - - playlist_ = playlist.Playlist() - - if command == 'PlayNow': - if playbackutils.PlaybackUtils(None, item_ids[0]).play_all(item_ids, data.get('StartPositionTicks', 0), **kwargs): - dialog(type_="notification", - heading="{emby}", - message="%s %s" % (len(item_ids), lang(33004)), - icon="{emby}", - sound=False) - - elif command == 'PlayNext': - new_playlist = playlist_.modify_playlist(item_ids) - dialog(type_="notification", - heading="{emby}", - message="%s %s" % (len(item_ids), lang(33005)), - icon="{emby}", - sound=False) - player = xbmc.Player() - if not player.isPlaying(): - # Only start the playlist if nothing is playing - player.play(new_playlist) - - @classmethod - def _playstate(cls, data): - - command = data['Command'] - player = xbmc.Player() - - actions = { - - 'Stop': player.stop, - 'Unpause': player.pause, - 'Pause': player.pause, - 'PlayPause': player.pause, - 'NextTrack': player.playnext, - 'PreviousTrack': player.playprevious - } - if command == 'Seek': - - if player.isPlaying(): - seek_to = data['SeekPositionTicks'] - seek_time = seek_to / 10000000.0 - player.seekTime(seek_time) - log.info("Seek to %s", seek_time) - - elif command in actions: - actions[command]() - log.info("Command: %s completed", command) - - else: - log.info("Unknown command: %s", command) - return - - def _library_changed(self, data): - - process_list = { - - 'added': data['ItemsAdded'], - 'update': data['ItemsUpdated'], - 'remove': data['ItemsRemoved'] - } - for action in process_list: - self.library_sync.triage_items(action, process_list[action]) - - @classmethod - def _general_commands(cls, data): - - command = data['Name'] - arguments = data['Arguments'] - - if command in ('Mute', 'Unmute', 'SetVolume', - 'SetSubtitleStreamIndex', 'SetAudioStreamIndex', 'SetRepeatMode'): - - player = xbmc.Player() - # These commands need to be reported back - if command == 'Mute': - xbmc.executebuiltin('Mute') - - elif command == 'Unmute': - xbmc.executebuiltin('Mute') - - elif command == 'SetVolume': - volume = arguments['Volume'] - xbmc.executebuiltin('SetVolume(%s[,showvolumebar])' % volume) - - elif command == 'SetAudioStreamIndex': - index = int(arguments['Index']) - player.setAudioStream(index - 1) - - elif command == 'SetRepeatMode': - mode = arguments['RepeatMode'] - xbmc.executebuiltin('xbmc.PlayerControl(%s)' % mode) - - elif command == 'SetSubtitleStreamIndex': - emby_index = int(arguments['Index']) - current_file = player.getPlayingFile() - mapping = window('emby_%s.indexMapping.json' % current_file) - - if emby_index == -1: - player.showSubtitles(False) - - elif mapping: - external_index = json.loads(mapping) - # If there's external subtitles added via playbackutils - for index in external_index: - if external_index[index] == emby_index: - player.setSubtitleStream(int(index)) - break - else: - # User selected internal subtitles - external = len(external_index) - audio_tracks = len(player.getAvailableAudioStreams()) - player.setSubtitleStream(external + emby_index - audio_tracks - 1) - else: - # Emby merges audio and subtitle index together - audio_tracks = len(player.getAvailableAudioStreams()) - player.setSubtitleStream(emby_index - audio_tracks - 1) - - # Let service know - window('emby_command', value="true") - - elif command == 'DisplayMessage': - - header = arguments['Header'] - text = arguments['Text'] - dialog(type_="notification", - heading=header, - message=text, - icon="{emby}", - time=int(settings('displayMessage'))*1000) - - elif command == 'SendString': - - params = { - - 'text': arguments['String'], - 'done': False - } - JSONRPC('Input.SendText').execute(params) - - elif command in ('MoveUp', 'MoveDown', 'MoveRight', 'MoveLeft'): - # Commands that should wake up display - actions = { - - 'MoveUp': "Input.Up", - 'MoveDown': "Input.Down", - 'MoveRight': "Input.Right", - 'MoveLeft': "Input.Left" - } - JSONRPC(actions[command]).execute() - - elif command == 'GoHome': - JSONRPC('GUI.ActivateWindow').execute({'window': "home"}) - - elif command == "Guide": - JSONRPC('GUI.ActivateWindow').execute({'window': "tvguide"}) - - else: - builtin = { - - 'ToggleFullscreen': 'Action(FullScreen)', - 'ToggleOsdMenu': 'Action(OSD)', - 'ToggleContextMenu': 'Action(ContextMenu)', - 'Select': 'Action(Select)', - 'Back': 'Action(back)', - 'PageUp': 'Action(PageUp)', - 'NextLetter': 'Action(NextLetter)', - 'GoToSearch': 'VideoLibrary.Search', - 'GoToSettings': 'ActivateWindow(Settings)', - 'PageDown': 'Action(PageDown)', - 'PreviousLetter': 'Action(PrevLetter)', - 'TakeScreenshot': 'TakeScreenshot', - 'ToggleMute': 'Mute', - 'VolumeUp': 'Action(VolumeUp)', - 'VolumeDown': 'Action(VolumeDown)', - } - if command in builtin: - xbmc.executebuiltin(builtin[command]) - - @classmethod - def _server_restarting(cls): - - if settings('supressRestartMsg') == "true": - dialog(type_="notification", - heading="{emby}", - message=lang(33006), - icon="{emby}") - window('emby_online', value="false") - - def on_close(self, ws): - log.debug("closed") - - def on_open(self, ws): - log.debug("open") - - def on_error(self, ws, error): - - if "10061" in str(error): - # Server is offline - pass - else: - log.debug("Error: %s", error) - - def run(self): - - # websocket.enableTrace(True) - user_id = window('emby_currUser') - server = window('emby_server%s' % user_id) - token = window('emby_accessToken%s' % user_id) - # Get the appropriate prefix for the websocket - if "https" in server: - server = server.replace('https', "wss") - else: - server = server.replace('http', "ws") - - websocket_url = "%s/embywebsocket?api_key=%s&deviceId=%s" % (server, token, self.device_id) - log.info("websocket url: %s", websocket_url) - - self._client = websocket.WebSocketApp(websocket_url, - on_message=self.on_message, - on_error=self.on_error, - on_close=self.on_close) - self._client.on_open = self.on_open - log.warn("----===## Starting WebSocketClient ##===----") - - while not self.monitor.abortRequested(): - - if window('emby_online') == "true": - self._client.run_forever(ping_interval=10) - - if self._stop_websocket: - break - - if self.monitor.waitForAbort(120): - # Abort was requested, exit - break - - log.warn("##===---- WebSocketClient Stopped ----===##") - - def stop_client(self): - - self._stop_websocket = True - if self._client is not None: - self._client.close() - log.info("Stopping thread") diff --git a/resources/settings.xml b/resources/settings.xml index 833503c0..e6e294ea 100644 --- a/resources/settings.xml +++ b/resources/settings.xml @@ -1,94 +1,96 @@ <?xml version="1.0" encoding="utf-8" standalone="yes"?> <settings> - <category label="30014"><!-- Emby --> - <setting id="idMethod" label="Login method" type="enum" values="Manual|Emby Connect" default="0" /> - <!-- Manual address --> - <setting id="username" label="30024" type="text" default="" visible="eq(-1,0)" /> - <!-- Emby Connect --> - <setting id="connectUsername" label="30543" type="text" default="" visible="!eq(0,) + eq(-2,1)" /> + + <category label="29999"><!-- Emby --> + <setting label="30003" id="idMethod" type="enum" values="Manual|Emby Connect" default="0" /> + <setting label="30024" id="username" type="text" default="" visible="eq(-1,0)" /> + <setting label="30543" id="connectUsername" type="text" default="" visible="!eq(0,) + eq(-2,1)" /> <setting label="30600" type="action" action="RunPlugin(plugin://plugin.video.emby?mode=connect)" visible="eq(-3,1) + eq(-1,)" option="close" /> <setting label="30618" type="action" action="RunPlugin(plugin://plugin.video.emby?mode=connect)" visible="eq(-4,1) + !eq(-2,)" option="close" /> - <!-- User settings --> - <setting id="serverName" label="30001" type="text" default="" /> - <setting id="server" label="30000" type="text" default="" visible="!eq(-1,)" /> - <setting id="sslverify" label="30500" type="bool" default="true" visible="!eq(-1,)" subsetting="true" /> - <!--<setting id="sslcert" label="30501" type="file" default="None" visible="eq(-1,true)" subsetting="true" />==> - <setting id="accessToken" type="text" default="" visible="false" /> - <setting id="userId" type="text" default="" visible="false" /> - <!-- Device settings --> + <setting label="30001" id="serverName" type="text" default="" /> + <setting label="30000" id="server" type="text" default="" visible="true" /> + <setting label="33150" type="action" action="RunPlugin(plugin://plugin.video.emby?mode=updateserver)" visible="!eq(-1,)" option="close" /> + <setting label="30500" id="sslverify" type="bool" default="true" visible="true" /> + <setting type="sep" /> - <setting id="deviceNameOpt" label="30504" type="bool" default="false" /> - <setting id="deviceName" label="30016" type="text" default="Kodi" visible="eq(-1,true)" subsetting="true" /> + <setting label="33110" type="lsep" /> + <setting label="30504" id="deviceNameOpt" type="bool" default="false" /> + <setting label="30016" id="deviceName" type="text" default="Kodi" visible="eq(-1,true)" subsetting="true" /> </category> <category label="30506"><!-- Sync Options --> - <setting id="dblock" type="bool" label="30544" default="false" /> - <setting id="serverSync" type="bool" label="30514" default="true" /> - <setting id="incSyncIndicator" label="30507" type="number" default="10" visible="eq(-1,true)" subsetting="true"/> - <setting id="limitIndex" type="number" label="30515" default="15" option="int" /> - <setting id="downloadThreads" type="slider" label="30548" default="3" range="1,1,7" option="int" subsetting="true" /> - <setting id="enableTextureCache" label="30512" type="bool" default="true" /> - <setting id="imageCacheLimit" type="enum" label="30513" values="Unlimited|5|10|15|20|25" default="5" visible="eq(-1,true)" subsetting="true" /> - <setting id="syncEmptyShows" type="bool" label="30508" default="false" /> - <setting id="dbSyncScreensaver" label="30536" type="bool" default="false" /> - <setting id="useDirectPaths" type="enum" label="30511" lvalues="33036|33037" default="0" /> - <setting id="enableMusic" type="bool" label="30509" default="true" /> - <setting id="streamMusic" type="bool" label="30510" default="false" visible="eq(-1,true)" subsetting="true" /> - <setting type="lsep" label="30523" /> - <setting id="enableImportSongRating" type="bool" label="30524" default="true" /> - <setting id="enableExportSongRating" type="bool" label="30525" default="false" /> - <setting id="enableUpdateSongRating" type="bool" label="30526" default="false" /> + <setting label="33137" id="kodiCompanion" type="bool" default="true" /> + <setting label="33111" type="lsep" /> + <setting label="30511" id="useDirectPaths" type="enum" lvalues="33036|33037" default="1" /> + + <setting type="sep" /> + <setting label="30515" id="limitIndex" type="number" default="15" option="int" /> + <setting label="30512" id="enableTextureCache" type="bool" default="true" /> + <setting label="30157" id="enableCoverArt" type="bool" default="true" /> + <setting label="33116" id="compressArt" type="bool" default="false" /> + <setting label="30508" id="syncEmptyShows" type="bool" default="false" /> + <setting label="30536" id="dbSyncScreensaver" type="bool" default="true" /> + <setting id="enableMusic" visible="false" default="false" /> </category> <category label="30516"><!-- Playback --> - <setting label="30517" type="action" action="RunPlugin(plugin://plugin.video.emby?mode=passwords)" option="close" /> + <setting label="33113" type="lsep" /> + <setting label="30518" id="enableCinema" type="bool" default="true" /> + <setting label="30519" id="askCinema" type="bool" default="false" visible="eq(-1,true)" subsetting="true" /> + <setting label="30002" id="playFromStream" type="bool" default="true" /> + <setting label="30522" id="transcode_h265" type="bool" default="false" /> + <setting label="30537" id="transcodeHi10P" type="bool" default="false"/> + <setting label="33114" id="enableExternalSubs" type="bool" default="true" /> + + <setting label="33115" type="lsep" /> + <setting label="30160" id="videoBitrate" type="enum" values="664 Kbps SD|996 Kbps HD|1.3 Mbps HD|2.0 Mbps HD|3.2 Mbps HD|4.7 Mbps HD|6.2 Mbps HD|7.7 Mbps HD|9.2 Mbps HD|10.7 Mbps HD|12.2 Mbps HD|13.7 Mbps HD|15.2 Mbps HD|16.7 Mbps HD|18.2 Mbps HD|20.0 Mbps HD|25.0 Mbps HD|30.0 Mbps HD|35.0 Mbps HD|40.0 Mbps HD|100.0 Mbps HD [default]|1000.0 Mbps HD" visible="true" default="20" /> + <setting type="sep" /> - <setting id="enableCinema" type="bool" label="30518" default="true" /> - <setting id="askCinema" type="bool" label="30519" default="false" visible="eq(-1,true)" subsetting="true" /> - <setting id="offerDelete" type="bool" label="30114" default="false" /> - <setting id="deleteTV" type="bool" label="30115" visible="eq(-1,true)" default="false" subsetting="true" /> - <setting id="deleteMovies" type="bool" label="30116" visible="eq(-2,true)" default="false" subsetting="true" /> - <setting id="resumeJumpBack" type="slider" label="30521" default="10" range="0,1,120" option="int" /> - <setting label="HTTP Playback (Direct play or transcode)" type="lsep"/> - <setting type="sep" /> - <setting id="playFromStream" type="bool" label="30002" default="true" /> - <setting id="videoBitrate" type="enum" label="30160" values="664 Kbps SD|996 Kbps HD|1.3 Mbps HD|2.0 Mbps HD|3.2 Mbps HD|4.7 Mbps HD|6.2 Mbps HD|7.7 Mbps HD|9.2 Mbps HD|10.7 Mbps HD|12.2 Mbps HD|13.7 Mbps HD|15.2 Mbps HD|16.7 Mbps HD|18.2 Mbps HD|20.0 Mbps HD|25.0 Mbps HD|30.0 Mbps HD|35.0 Mbps HD|40.0 Mbps HD|100.0 Mbps HD [default]|1000.0 Mbps HD" visible="true" default="20" /> - <setting label="Direct play (HTTP)" type="lsep"/> - <setting id="enableExternalSubs" type="bool" label="Enable external subtitles" default="true" /> - <setting label="Transcode" type="lsep"/> - <setting id="skipDialogTranscode" type="enum" label="Enable audio/subtitles selection" values="Enabled|Audio only|Subtitles only|Disabled" visible="true" default="0" /> - <setting id="ignoreTranscode" type="text" label="Never transcode codecs (i.e. h264,h265,...)" visible="true" /> - <setting id="transcode_h265" type="bool" label="30522" default="false" /> - <setting id="transcodeHi10P" type="bool" label="30537" default="false"/> + <setting label="33112" type="lsep" /> + <setting label="30521" id="resumeJumpBack" type="slider" default="10" range="0,1,120" option="int" /> + <setting label="30114" id="offerDelete" type="bool" default="false" /> + <setting label="30115" id="deleteTV" type="bool" visible="eq(-1,true)" default="false" subsetting="true" /> + <setting label="30116" id="deleteMovies" type="bool" visible="eq(-2,true)" default="false" subsetting="true" /> + <setting id="markPlayed" type="number" visible="false" default="90" /> - <setting id="failedCount" type="number" visible="false" default="0" /> - <setting id="networkCreds" type="text" visible="false" default="" /> </category> - <category label="30235"><!-- Extras --> - <setting id="enableCoverArt" type="bool" label="30157" default="true" /> - <setting id="ignoreSpecialsNextEpisodes" type="bool" label="30527" default="false" /> - <setting id="enableContext" type="bool" label="Enable the Emby context menu" default="true" /> - <setting id="enableContextTranscode" type="bool" label="Enable Force Transcode" visible="eq(-1,true)" default="true" subsetting="true" /> - <setting id="skipContextMenu" type="bool" label="30520" default="false" visible="eq(-2,true)" subsetting="true" /> - <setting id="additionalUsers" type="text" label="30528" default="" /> - <setting type="lsep" label="30534" /> - <setting id="connectMsg" type="bool" label="30249" default="true" /> - <setting id="offlinetMsg" type="bool" label="30545" default="true" /> - <setting id="restartMsg" type="bool" label="30530" default="false" /> - <setting id="displayMessage" type="slider" label="30547" default="4" range="4,1,20" option="int" /> - <setting id="newContent" type="bool" label="30531" default="false" /> - <setting id="newvideotime" type="number" label="30532" visible="eq(-1,true)" default="5" option="int" subsetting="true" /> - <setting id="newmusictime" type="number" label="30533" visible="eq(-2,true)" default="2" option="int" subsetting="true" /> + <category label="30235"><!-- Interface --> + <setting label="33105" id="enableContext" type="bool" default="true" /> + <setting label="33106" id="enableContextTranscode" type="bool" visible="eq(-1,true)" default="true" subsetting="true" /> + <setting label="33143" id="enableContextDelete" type="bool" visible="eq(-2,true)" default="true" subsetting="true" /> + <setting label="30520" id="skipContextMenu" type="bool" default="false" visible="eq(-1,true)" subsetting="true" /> + + <setting label="33107" type="lsep" /> + <setting label="30528" id="additionalUsers" type="text" default="" /> + + <setting type="sep"/> + <setting label="30534" type="lsep" /> + <setting label="30249" id="connectMsg" type="bool" default="true" /> + <setting label="30545" id="offlinetMsg" type="bool" default="true" /> + <setting label="30530" id="restartMsg" type="bool" default="true" /> + <setting label="30547" id="displayMessage" type="slider" default="4" range="4,1,20" option="int" /> + <setting label="33108" type="lsep" /> + <setting label="30531" id="newContent" type="bool" default="false" /> + <setting label="30532" id="newvideotime" type="number" visible="eq(-1,true)" default="5" option="int" subsetting="true" /> + <setting label="30533" id="newmusictime" type="number" visible="eq(-2,true)" default="2" option="int" subsetting="true" /> + </category> + + <category label="33109"><!-- Plugin --> + <setting id="ignoreSpecialsNextEpisodes" type="bool" label="30527" default="false" /> + <setting id="getCast" type="bool" label="33124" default="false" /> </category> <category label="30022"><!-- Advanced --> - <setting id="logLevel" type="enum" label="30004" values="Disabled|Info|Debug" default="1" /> - <!--<setting id="metricLogging" type="bool" label="30546" default="true" />--> - <setting id="startupDelay" type="number" label="30529" default="0" option="int" /> + <setting label="30004" id="logLevel" type="enum" values="Disabled|Info|Debug" default="1" /> + <setting label="30529" id="startupDelay" type="number" default="0" option="int" /> <setting label="30239" type="action" action="RunPlugin(plugin://plugin.video.emby?mode=reset)" option="close" /> <setting label="30535" type="action" action="RunPlugin(plugin://plugin.video.emby?mode=deviceid)" /> + + <setting type="sep"/> + <setting label="33104" type="lsep"/> <setting label="33093" type="folder" id="backupPath" option="writeable" /> <setting label="33092" type="action" action="RunPlugin(plugin://plugin.video.emby?mode=backup)" visible="!eq(-1,)" option="close" /> </category> + </settings> diff --git a/resources/skins/default/media/kodi-icon.png b/resources/skins/default/media/kodi-icon.png new file mode 100644 index 00000000..72eeb7c7 Binary files /dev/null and b/resources/skins/default/media/kodi-icon.png differ diff --git a/service.py b/service.py index b8a30ea1..e9c62522 100644 --- a/service.py +++ b/service.py @@ -11,47 +11,39 @@ import xbmcaddon ################################################################################################# -_ADDON = xbmcaddon.Addon(id='plugin.video.emby') -_CWD = _ADDON.getAddonInfo('path').decode('utf-8') -_BASE_LIB = xbmc.translatePath(os.path.join(_CWD, 'resources', 'lib')).decode('utf-8') -sys.path.append(_BASE_LIB) +__addon__ = xbmcaddon.Addon(id='plugin.video.emby').getAddonInfo('path').decode('utf-8') +__base__ = xbmc.translatePath(os.path.join(__addon__, 'resources', 'lib')).decode('utf-8') +sys.path.append(__base__) ################################################################################################# -import loghandler -from service_entry import Service -from utils import settings -#from ga_client import GoogleAnalytics +from entrypoint import Service +from helper import settings +from emby import Emby ################################################################################################# -loghandler.config() -log = logging.getLogger("EMBY.service") +LOG = logging.getLogger("EMBY.service") DELAY = int(settings('startupDelay') or 0) ################################################################################################# + if __name__ == "__main__": - log.warn("Delaying emby startup by: %s sec...", DELAY) - service = Service() + LOG.info("--->[ service ]") + LOG.warn("Delay startup by %s seconds.", DELAY) + + session = Service() try: - abort = False if DELAY and xbmc.Monitor().waitForAbort(DELAY): - log.info("Abort event while waiting to start Emby for kodi") - abort = True - # Start the service - if abort == False: - service.service_entry_point() + raise Exception("Aborted during startup delay") + session.service() except Exception as error: - """ - if not (hasattr(error, 'quiet') and error.quiet): - ga = GoogleAnalytics() - errStrings = ga.formatException() - ga.sendEventData("Exception", errStrings[0], errStrings[1]) - """ - log.exception(error) - log.info("Forcing shutdown") - service.shutdown() + + LOG.exception(error) + session.shutdown() + + LOG.info("---<[ service ]")