Advertisement
FocusedWolf

Python: Software Version Checker

Feb 1st, 2024 (edited)
464
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 6.84 KB | None | 0 0
  1. #!/usr/bin/env python3
  2.  
  3. # Version 15
  4.  
  5. # POSTED ONLINE: https://pastebin.com/Dp4dK0Mz
  6.  
  7. # SOURCE: https://stackoverflow.com/questions/3029816/how-do-i-get-a-thread-safe-print-in-python-2-6
  8. # Python uses separate opcodes for object printing and newline printing.
  9. # With multiple threads printing you would encounter newlines printed out of turn separate from the objects.
  10. # Joining the strings before calling print(...) fixes the issue.
  11. def safe_print(*args, sep=' ', end='\n', file=None, flush=False):
  12.     # NOTE: Using end=None, or removing end='', will result in an extra newline in the output.
  13.     print(sep.join(map(str, args)) + end, sep=sep, end='', file=file, flush=flush)
  14.  
  15. # -----
  16.  
  17. import asyncio
  18. class App:
  19.     def __init__(self, name, installedVersion, patternData, urlData):
  20.         self.name = name
  21.         self.installedVersion = installedVersion
  22.         self.__pattern = patternData[0]
  23.         self.__patternMatchIndex = patternData[1]
  24.         self.__url = urlData[0]
  25.         self.__urlEncoding = urlData[1]
  26.  
  27.     async def GetLatestVersion(self):
  28.         loop = asyncio.get_running_loop()
  29.         if 'api.github.com' in self.__url:
  30.             latestVersion = await loop.run_in_executor(None, search_json, self.__pattern, self.__url, [0, 'name'], self.__urlEncoding, self.__patternMatchIndex)
  31.         else:
  32.             latestVersion = await loop.run_in_executor(None, search_url, self.__pattern, self.__url, self.__urlEncoding, self.__patternMatchIndex)
  33.         return latestVersion
  34.  
  35.     async def DisplayStatus(self, skipUpToDate=True):
  36.         latestVersion = await self.GetLatestVersion()
  37.         upToDate = self.installedVersion == latestVersion
  38.         if latestVersion == None:
  39.             safe_print(color(' ! {}: Failed to detect the latest version (Installed Version: {} --> ?)\n   URL: {}\n'.format(self.name, self.installedVersion, self.__url), RED))
  40.         elif not upToDate:
  41.             safe_print(color(' * {}: Update detected (Installed Version: {} --> {})\n   URL: {}\n'.format(self.name, self.installedVersion, latestVersion, self.__url), YELLOW))
  42.         elif not skipUpToDate:
  43.             safe_print(color(' - {}: No update detected (Installed Version: {})\n'.format(self.name, latestVersion), GREEN))
  44.         return not upToDate
  45.  
  46. # -----
  47.  
  48. import json
  49. def search_json(pattern, url, indicies, encoding='UTF-8', matchIndex=0, timeoutSeconds=5):
  50.     response = download_url(url, encoding, timeoutSeconds)
  51.     if response == None:
  52.         return None
  53.     data = json.loads(response)
  54.     for index in indicies:
  55.         data = data[index]
  56.     return regex_search(data, pattern, matchIndex)
  57.  
  58. def search_url(pattern, url, encoding='UTF-8', matchIndex=0, timeoutSeconds=5):
  59.     response = download_url(url, encoding, timeoutSeconds)
  60.     return regex_search(response, pattern, matchIndex)
  61.  
  62. # -----
  63.  
  64. import urllib.request
  65. from urllib.error import *
  66. import socket
  67. def download_url(url, encoding='UTF-8', timeoutSeconds=5):
  68.     try:
  69.         response = urllib.request.urlopen(url, timeout=timeoutSeconds, context=unverifiedContext).read().decode(encoding)
  70.     except HTTPError as error:
  71.         safe_print('   HTTP-Error: Data not retrieved because {}\n   URL: {}\n'.format(error, url))
  72.     except URLError as error:
  73.         if isinstance(error.reason, socket.timeout):
  74.             safe_print('   Timeout-Error: Data not retrieved because {}\n   URL: {}\n'.format(error, url))
  75.         else:
  76.             safe_print('   URL-Error: Data not retrieved because {}\n   URL: {}\n'.format(error, url))
  77.     else:
  78.         return response
  79.  
  80. # Disable certificate verification with unverified SSL context.
  81. # SOURCE: https://support.chainstack.com/hc/en-us/articles/9117198436249-Common-SSL-Issues-on-Python-and-How-to-Fix-it
  82. import ssl
  83. unverifiedContext = ssl._create_unverified_context()
  84.  
  85. import re
  86. def regex_search(string, pattern, matchIndex=0):
  87.     if string == None:
  88.         return None
  89.     matches = re.findall(pattern, string)
  90.     if matches == None or len(matches) == 0:
  91.         return None
  92.     return matches[matchIndex]
  93.  
  94. # -----
  95.  
  96. # BLACK = '\033[30m'
  97. RED = '\033[31m'
  98. GREEN = '\033[32m'
  99. YELLOW = '\033[33m'
  100. BLUE = '\033[34m'
  101. MAGENTA = '\033[35m'
  102. CYAN = '\033[36m'
  103. WHITE = '\033[37m'
  104. UNDERLINE = '\033[4m'
  105. RESET = '\033[0m'
  106.  
  107. def color(var, color):
  108.     return color + str(var) + RESET
  109.  
  110. import platform
  111. system = platform.system()
  112. if system == 'Windows':
  113.     import os
  114.     # Needed to display colored text on Windows for some reason.
  115.     os.system('cls')
  116.  
  117. # -----
  118.  
  119. # NOTE: You need to update these installed-version numbers manually, until you find a way to query them from the installed software directly.
  120. apps = [
  121.     App('Voicemeeter Banana', '2.1.1.1 (FEB 2024)', (r'Version ([0-9.]+ \([\w\s]+\))', 1),              ('https://voicemeeter.com', 'UTF-8')),
  122.   # App('Voicemeeter Banana', '2.0.6.8',            (r'Voicemeeter ([0-9.]+) \(ZIP Package\)', 0),      ('https://vb-audio.com/Voicemeeter/banana.htm', 'UTF-8')), # This website keeps dying...
  123.     App('Equalizer APO',      '1.3.2',              (r'EqualizerAPO64-([0-9.]+)\.exe', 0),              ('https://sourceforge.net/projects/equalizerapo/files/', 'UTF-8')),
  124.     App('HeSuVi',             '2.0.0.1',            (r'HeSuVi_([0-9.]+)\.exe', 0),                      ('https://sourceforge.net/projects/hesuvi/files/', 'UTF-8')),
  125.     App('IEM Plug-in Suite',  '1.14.1',             (r'Latest version: <strong>v([0-9.]+)', 0),         ('https://plugins.iem.at/download/', 'UTF-8')),
  126.   # App('Vim',                '9.1',                (r'Vim ([0-9.]+) is the latest stable version', 0), ('https://www.vim.org/download.php/', 'ISO-8859-1')), # This website keeps dying...
  127.     App('Vim',                '9.1',                (r'\d+\.\d+', 0),                                   ('https://api.github.com/repos/vim/vim/tags', 'UTF-8')),
  128. ]
  129.  
  130. async def main():
  131.     import sys
  132.     pauseNeeded = len(sys.argv) == 1 or sys.argv[1] != 'nopause'
  133.     updateNeeded = False
  134.     tasks = [app.DisplayStatus(skipUpToDate=not pauseNeeded) for app in apps]
  135.     for future in asyncio.as_completed(tasks):
  136.         updateNeeded |= await future
  137.  
  138.     if pauseNeeded or updateNeeded:
  139.         wait_for_any_keypress()
  140.  
  141. # ----- Press any key to continue -----
  142.  
  143. def wait_for_any_keypress():
  144.     import sys
  145.     if sys.platform == 'win32':
  146.         import os
  147.         os.system('pause')
  148.     elif sys.platform.startswith('linux') or sys.platform == 'darwin':
  149.         print('Press any key to continue . . .')
  150.         import termios
  151.         import tty
  152.         stdin_file_desc = sys.stdin.fileno()
  153.         old_stdin_tty_attr = termios.tcgetattr(stdin_file_desc)
  154.         try:
  155.             tty.setraw(stdin_file_desc)
  156.             sys.stdin.read(1)
  157.         finally:
  158.             termios.tcsetattr(stdin_file_desc, termios.TCSADRAIN, old_stdin_tty_attr)
  159.  
  160. # -----
  161.  
  162. if __name__ == '__main__':
  163.     asyncio.run(main())
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement