diff --git a/.gitignore b/.gitignore index f3ed42b..4018549 100644 --- a/.gitignore +++ b/.gitignore @@ -160,3 +160,4 @@ cython_debug/ .idea/ Pieces/ Pieces.sublime-package +_debug.py \ No newline at end of file diff --git a/_pieces_lib/notify/__init__.py b/_pieces_lib/notify/__init__.py new file mode 100644 index 0000000..cc3987a --- /dev/null +++ b/_pieces_lib/notify/__init__.py @@ -0,0 +1,131 @@ +""" +Notify. + +Copyright (c) 2013 - 2016 Isaac Muse +License: MIT +""" +from __future__ import absolute_import +import sys + +PY3 = (3, 0) <= sys.version_info < (4, 0) + +if PY3: + binary_type = bytes # noqa +else: + binary_type = str + +if sys.platform.startswith('win'): + _PLATFORM = "windows" +elif sys.platform == "darwin": + _PLATFORM = "macos" +else: + _PLATFORM = "linux" + +if _PLATFORM == "windows": + from .notify_windows import get_notify, alert, setup, windows_icons, destroy +elif _PLATFORM == "macos": + from .notify_osx import get_notify, alert, setup, destroy +elif _PLATFORM == "linux": + from .notify_linux import get_notify, alert, setup, destroy + +__all__ = ("info", "warning", "error", "setup_notifications", "destroy_notifications") + + +################################### +# Fallback Notifications +################################### +class NotifyFallback: + """Fallback class.""" + + def __init__(self, *args, **kwargs): + """Initialize class.""" + + self.sound = kwargs.get("sound", False) + + def Show(self): + """Fallback just plays an alert.""" + + if self.sound: + alert() + + +DEFAULT_NOTIFY = NotifyFallback + + +################################### +# Notification Calls +################################### +def info(title, message, sound=False): + """Info notification.""" + + send_notify(title, message, sound, "Info") + + +def error(title, message, sound=False): + """Error notification.""" + send_notify(title, message, sound, "Error") + + +def warning(title, message, sound=False): + """Warning notification.""" + + send_notify(title, message, sound, "Warning") + + +def send_notify(title, message, sound, level): + """Send notification.""" + + if title is not None and isinstance(title, binary_type): + title = title.decode('utf-8') + + if message is not None and isinstance(message, binary_type): + message = message.decode('utf-8') + + if level is not None and isinstance(level, binary_type): + level = level.decode('utf-8') + + def default_notify(title, message, sound): + """Default fallback notify.""" + + DEFAULT_NOTIFY(title, message, sound=sound).Show() + + notify = get_notify() + if _PLATFORM in ["macos", "linux"]: + notify(title, message, sound, default_notify) + elif _PLATFORM == "windows": + notify(title, message, sound, windows_icons[level], default_notify) + else: + default_notify(title, message, sound) + + +def play_alert(): + """Play alert sound.""" + + alert() + + +################################### +# Setup Notifications +################################### +def setup_notifications(app_name, img=None, **kwargs): + """Setup notifications for all platforms.""" + + destroy() + + if _PLATFORM == "windows" and img is not None and isinstance(img, binary_type): + img = img.decode('utf-8') + + if isinstance(app_name, binary_type): + app_name = app_name.decode('utf-8') + + setup( + app_name, + img, + **kwargs + ) + + +def destroy_notifications(): + """Destroy notifications if possible.""" + + destroy() diff --git a/_pieces_lib/notify/notify_linux.py b/_pieces_lib/notify/notify_linux.py new file mode 100644 index 0000000..7cc0907 --- /dev/null +++ b/_pieces_lib/notify/notify_linux.py @@ -0,0 +1,141 @@ +""" +Notify Linux. + +Copyright (c) 2013 - 2016 Isaac Muse +License: MIT +""" +import subprocess +import os +from . import util + +__all__ = ("get_notify", "alert", "setup", "destroy") + +PLAYERS = ('paplay', 'aplay', 'play') + + +class Options: + """Notification options.""" + + icon = None + notify = None + app_name = "" + sound = None + player = None + + @classmethod + def clear(cls): + """Clear.""" + + cls.icon = None + cls.notify = None + cls.app_name = "" + cls.sound = None + cls.player = None + + +def _alert(sound=None, player=None): + """Play an alert sound for the OS.""" + + if sound is None and Options.sound is not None: + sound = Options.sound + + if player is None and Options.player is not None: + player = Options.player + + if player is not None and sound is not None: + try: + if player == 'play': + subprocess.call([player, '-q', sound]) + else: + subprocess.call([player, sound]) + except Exception: + pass + + +def alert(): + """Alert.""" + + _alert() + + +@staticmethod +def notify_osd_fallback(title, message, sound, fallback): + """Ubuntu Notify OSD notifications fallback (just sound).""" + + # Fallback to wxPython notification + fallback(title, message, sound) + + +try: + if subprocess.call(["notify-send", "--version"]) != 0: + raise ValueError("Notification support does not appear to be available") + + @staticmethod + def notify_osd_call(title, message, sound, fallback): + """Ubuntu Notify OSD notifications.""" + + try: + params = ["notify-send", "-a", Options.app_name, "-t", "3000"] + if Options.icon is not None: + params += ["-i", Options.icon] + if message is not None: + params += [title, message] + subprocess.call(params) + + if sound: + # Play sound if desired + alert() + except Exception: + # Fallback to wxPython notification + fallback(title, message, sound) +except Exception: + notify_osd_call = None + print("no notify osd") + + +def setup_notify_osd(app_name): + """Setup Notify OSD.""" + + if notify_osd_call is not None: + Options.app_name = app_name + Options.notify = notify_osd_call + + +def setup(app_name, icon, **kwargs): + """Setup.""" + + Options.icon = None + sound = kwargs.get('sound') + if sound is not None and os.path.exists(sound): + Options.sound = sound + + player = kwargs.get('sound_player') + if player is not None and player in PLAYERS and util.which(player): + Options.player = player + + try: + if icon is None or not os.path.exists(icon): + raise ValueError("Icon does not appear to be valid") + Options.icon = icon + except Exception: + pass + + if notify_osd_call is not None: + Options.app_name = app_name + Options.notify = notify_osd_call + + +def destroy(): + """Destroy.""" + + Options.clear() + Options.notify = notify_osd_fallback + + +def get_notify(): + """Get notification.""" + + return Options.notify + + +Options.notify = notify_osd_fallback diff --git a/_pieces_lib/notify/notify_osx.py b/_pieces_lib/notify/notify_osx.py new file mode 100644 index 0000000..55cce50 --- /dev/null +++ b/_pieces_lib/notify/notify_osx.py @@ -0,0 +1,203 @@ +""" +Notify macOS. + +Copyright (c) 2013 - 2016 Isaac Muse +License: MIT +""" +import subprocess +import os +# ``` +# import ctypes +# import ctypes.util +# ``` +import sys +import platform + +__all__ = ("get_notify", "alert", "setup", "destroy") + +PY3 = (3, 0) <= sys.version_info < (4, 0) + +if PY3: + binary_type = bytes # noqa +else: + binary_type = str + +# ``` +# appkit = ctypes.cdll.LoadLibrary(ctypes.util.find_library('AppKit')) +# cf = ctypes.cdll.LoadLibrary(ctypes.util.find_library('CoreFoundation')) +# objc = ctypes.cdll.LoadLibrary(ctypes.util.find_library('objc')) +# +# kCFStringEncodingUTF8 = 0x08000100 +# +# cf.CFStringCreateWithCString.restype = ctypes.c_void_p +# cf.CFStringCreateWithCString.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_uint32] +# +# objc.objc_getClass.restype = ctypes.c_void_p +# objc.sel_registerName.restype = ctypes.c_void_p +# objc.objc_msgSend.restype = ctypes.c_void_p +# objc.objc_msgSend.argtypes = [ctypes.c_void_p, ctypes.c_void_p] +# +# NSSound = ctypes.c_void_p(objc.objc_getClass('NSSound')) +# NSAutoreleasePool = ctypes.c_void_p(objc.objc_getClass('NSAutoreleasePool')) +# +# +# def _nsstring(string): +# """Return an NSString object.""" +# +# return ctypes.c_void_p(cf.CFStringCreateWithCString(None, string.encode('utf8'), kCFStringEncodingUTF8)) +# +# +# def _callmethod(obj, method, *args, **kwargs): +# """Call the ObjC method.""" +# +# cast_return = kwargs.get("cast_return", ctypes.c_void_p) +# return cast_return(objc.objc_msgSend(obj, objc.sel_registerName(method), *args)) +# ``` + + +def _is_ver_okay(): + """See if version is > 10.8.""" + + try: + return float(platform.mac_ver()[0].split('.')[:2]) >= 10.9 + except Exception: + return False + + +class Options: + """Notification options.""" + + notify = None + sender = "com.apple.Terminal" + terminal_notifier = None + app_name = "" + icon = None + sound = None + + @classmethod + def clear(cls): + """Clear.""" + + cls.notify = None + cls.sender = "com.apple.Terminal" + cls.terminal_notifier = None + cls.app_name = "" + cls.icon = None + cls.sound = None + + +def _alert(sound=None): + """Play an alert sound for the OS.""" + + if sound is None and Options.sound is not None: + sound = Options.sound + + try: + if sound is not None: + subprocess.call(["afplay", sound]) + # ``` + # pool = _callmethod(_callmethod(NSAutoreleasePool, "alloc"), "init") + # snd = _nsstring(sound if sound is not None else "Glass") + # soundobj = _callmethod(NSSound, "soundNamed:", snd) + # _callmethod(soundobj, "play") + # _callmethod(pool, "drain") + # del pool + # ``` + except Exception: + pass + + +def alert(): + """Alert.""" + + _alert() + + +@staticmethod +def notify_osx_fallback(title, message, sound, fallback): + """The macOS notifications fallback (just sound).""" + + # Fallback to wxPython notification + fallback(title, message, sound) + + +@staticmethod +def notify_osx_call(title, message, sound, fallback): + """Notifications for macOS.""" + + try: + if Options.terminal_notifier is None or not os.path.exists(Options.terminal_notifier): + raise ValueError("Specified terminal notifier does not appear to be valid") + # Show Notification here + params = [Options.terminal_notifier, "-title", Options.app_name, "-timeout", "5"] + if message is not None: + params += ["-message", message] + if title is not None: + params += ["-subtitle", title] + if Options.sender is not None: + params += ["-sender", Options.sender] + if Options.icon is not None: + params += ["-appIcon", Options.icon] + subprocess.Popen(params) + + if sound: + # Play sound if desired + alert() + except Exception: + # Fallback notification + fallback(title, message, sound) + + +def setup(app_name, icon, **kwargs): + """Setup.""" + + term_notify = None + sender = None + + term_notify = kwargs.get('term_notify') + sender = kwargs.get('sender') + sound = kwargs.get('sound') + if sound is not None and os.path.exists(sound): + Options.sound = sound + notify_icon = icon + + if term_notify is not None and isinstance(term_notify, binary_type): + term_notify = term_notify.decode('utf-8') + + if sender is not None and isinstance(sender, binary_type): + sender = sender.decode('utf-8') + + if _is_ver_okay(): + notify_icon = None + elif notify_icon is not None and isinstance(notify_icon, binary_type): + notify_icon = notify_icon.decode('utf-8') + + Options.app_name = app_name + + try: + if term_notify is None or not os.path.exists(term_notify): + raise ValueError("Terminal notifier does not appear to be available") + Options.terminal_notifier = term_notify + if sender is not None: + Options.sender = sender + if notify_icon is not None and os.path.exists(notify_icon): + Options.icon = notify_icon + Options.notify = notify_osx_call + except Exception: + pass + + +def destroy(): + """Destroy.""" + + Options.clear() + Options.notify = notify_osx_fallback + + +def get_notify(): + """Get notification.""" + + return Options.notify + + +Options.notify = notify_osx_fallback diff --git a/_pieces_lib/notify/notify_windows.py b/_pieces_lib/notify/notify_windows.py new file mode 100644 index 0000000..0e355c1 --- /dev/null +++ b/_pieces_lib/notify/notify_windows.py @@ -0,0 +1,372 @@ +""" +Notify windows. + +Copyright (c) 2013 - 2016 Isaac Muse +License: MIT +""" +import traceback +import winsound +import ctypes +import ctypes.wintypes as wintypes +import os + +__all__ = ("get_notify", "alert", "setup", "windows_icons", "destroy") + +if ctypes.sizeof(ctypes.c_long) == ctypes.sizeof(ctypes.c_void_p): + WPARAM = ctypes.c_ulong + LPARAM = ctypes.c_long + LRESULT = ctypes.c_long +elif ctypes.sizeof(ctypes.c_longlong) == ctypes.sizeof(ctypes.c_void_p): + WPARAM = ctypes.c_ulonglong + LPARAM = ctypes.c_longlong + LRESULT = ctypes.c_longlong +HANDLE = ctypes.c_void_p +WNDPROCTYPE = WNDPROC = ctypes.CFUNCTYPE(LRESULT, HANDLE, ctypes.c_uint, WPARAM, LPARAM) + +WM_DESTROY = 2 +IMAGE_ICON = 1 +LR_LOADFROMFILE = 16 +LR_DEFAULTSIZE = 64 +IDI_APPLICATION = 1 +WS_OVERLAPPED = 0 +WS_SYSMENU = 524288 +CW_USEDEFAULT = -2147483648 +WM_USER = 1024 + +NIM_ADD = 0x00 +NIM_MODIFY = 0x01 +NIM_DELETE = 0x02 +NIM_SETVERSION = 0x04 +NIF_MESSAGE = 0x01 +NIF_ICON = 0x02 +NIF_TIP = 0x04 +NIF_STATE = 0x08 +NIF_INFO = 0x10 +NIF_REALTIME = 0x40 +NIF_SHOWTIP = 0x80 +NIIF_INFO = 0x1 +NIIF_WARNING = 0x2 +NIIF_ERROR = 0x3 +NIIF_NOSOUND = 0x10 +NIFF_USER = 0x00000004 + +NIS_HIDDEN = 0x01 + +HWND_MESSAGE = -3 + +NIN_BALLOONSHOW = WM_USER + 2 +NIN_BALLOONHIDE = WM_USER + 3 +NIN_BALLOONTIMEOUT = WM_USER + 4 +NIN_BALLOONUSERCLICK = WM_USER + 5 + + +class WndClassEx(ctypes.Structure): + """The `WNDCLASSEX` structure.""" + + _fields_ = [ + ("cbSize", ctypes.c_uint), + ("style", ctypes.c_uint), + ("lpfnWndProc", WNDPROCTYPE), + ("cbClsExtra", ctypes.c_int), + ("cbWndExtra", ctypes.c_int), + ("hInstance", HANDLE), + ("hIcon", HANDLE), + ("hCursor", HANDLE), + ("hbrBackground", HANDLE), + ("lpszMenuName", wintypes.LPCWSTR), + ("lpszClassName", wintypes.LPCWSTR), + ("hIconSm", HANDLE) + ] + + +class NotifyIconData(ctypes.Structure): + """The `NOTIFYICONDATA` structure.""" + + _fields_ = [ + ("cbSize", ctypes.c_uint), + ("hWnd", HANDLE), + ("uID", ctypes.c_uint), + ("uFlags", ctypes.c_uint), + ("uCallbackMessage", ctypes.c_uint), + ("hIcon", HANDLE), + ("szTip", ctypes.c_wchar * 128), + ("dwState", ctypes.c_uint), + ("dwStateMask", ctypes.c_uint), + ("szInfo", ctypes.c_wchar * 256), + ("uVersion", ctypes.c_uint), + ("szInfoTitle", ctypes.c_wchar * 64), + ("dwInfoFlags", ctypes.c_uint), + ("guidItem", ctypes.c_char * 16), + ("hBalloonIcon", HANDLE), + ] + + +class Options: + """Notification options.""" + + notify = None + instance = None + sound = None + + @classmethod + def clear(cls): + """Clear.""" + + cls.notify = None + cls.instance = None + cls.sound = None + + +def _alert(sound=None): + """Play an alert sound for the OS.""" + + if sound is None and Options.sound is not None: + sound = Options.sound + + try: + if sound: + winsound.PlaySound(sound, winsound.SND_FILENAME) + except Exception: + pass + + +def alert(): + """Alert.""" + + _alert() + + +class WinNotifyLevel: + """Windows notification level.""" + + ICON_INFORMATION = 0x01 + ICON_WARNING = 0x02 + ICON_ERROR = 0x04 + + +windows_icons = { + "Info": WinNotifyLevel.ICON_INFORMATION, + "Warning": WinNotifyLevel.ICON_WARNING, + "Error": WinNotifyLevel.ICON_ERROR +} + + +def notify_win_fallback(title, message, sound, icon, fallback): + """Notify win calls the fallback.""" + + fallback(title, message, sound) + + +class WindowsNotify: + """Windows notification class.""" + + window_handle = None + taskbar_icon = None + wc = None + + def __init__(self, app_name, icon, tooltip=None): + """ + Create the taskbar for the application and register it. + + Show nothing by default until called. + """ + + def winproc(hwnd, msg, wparam, lparam): + """Handle `winproc` events.""" + + if msg == WM_USER + 20 and lparam in (NIN_BALLOONTIMEOUT, NIN_BALLOONUSERCLICK): + pass + return hwnd + + self.tooltip = tooltip + self.visible = False + self.app_name = app_name + + # Register window class + wc = WndClassEx() + self.hinst = wc.hInstance = ctypes.windll.kernel32.GetModuleHandleW(None) + wc.cbSize = ctypes.sizeof(wc) + wc.lpszClassName = ctypes.c_wchar_p(app_name + "Taskbar") + wc.lpfnWndProc = WNDPROCTYPE(winproc) + wc.style = 0 + wc.cbClsExtra = 0 + wc.cbWndExtra = 0 + wc.hIcon = 0 + wc.hCursor = 0 + wc.hbrBackground = 0 + + if WindowsNotify.wc is not None: + self._destroy_window() + ctypes.windll.user32.UnregisterClassW(wc.lpszClassName, None) + WindowsNotify.wc = wc + ctypes.windll.user32.RegisterClassExW(ctypes.byref(wc)) + WindowsNotify.wc = wc + + self.hicon = self.get_icon(icon) + + self._show_notification('', '', False, self.hicon) + + def get_icon(self, icon): + """ + Get icon. + + Try to load the given icon from the path given, + else default to generic application icon from the OS. + """ + + if WindowsNotify.taskbar_icon is not None: + ctypes.windll.user32.DestroyIcon(wintypes.HICON(WindowsNotify.taskbar_icon)) + WindowsNotify.taskbar_icon = None + + icon_flags = LR_LOADFROMFILE + try: + if icon is None: + raise ValueError("Icon is not available") + hicon = ctypes.windll.user32.LoadImageW( + self.hinst, icon, + IMAGE_ICON, + 0, 0, icon_flags + ) + except Exception: + hicon = ctypes.windll.user32.LoadIconA(0, IDI_APPLICATION) + WindowsNotify.taskbar_icon = hicon + + return hicon + + def show_notification(self, title, msg, sound, icon, fallback): + """ + Attempt to show notifications. + + Provide fallback for consistency with other notification methods. + """ + + try: + self._show_notification(title, msg, sound, icon) + except Exception: + print(traceback.format_exc()) + fallback(title, msg, sound) + + def _get_window(self): + """Create the Window.""" + + if WindowsNotify.window_handle: + hwnd = WindowsNotify.window_handle + else: + hwnd = ctypes.windll.user32.FindWindowExW( + HWND_MESSAGE, None, WindowsNotify.wc.lpszClassName, None + ) + if not hwnd: + style = WS_OVERLAPPED | WS_SYSMENU + hwnd = ctypes.windll.user32.CreateWindowExW( + 0, WindowsNotify.wc.lpszClassName, WindowsNotify.wc.lpszClassName, style, + 0, 0, CW_USEDEFAULT, CW_USEDEFAULT, + HWND_MESSAGE, 0, self.hinst, None + ) + if hwnd: + WindowsNotify.window_handle = hwnd + ctypes.windll.user32.UpdateWindow(hwnd) + return hwnd + + def _destroy_window(self): + """Destroy the window.""" + + if WindowsNotify.window_handle: + if self.visible: + res = NotifyIconData() + res.cbSize = ctypes.sizeof(res) + res.hWnd = WindowsNotify.window_handle + res.uID = 0 + res.uFlags = 0 + res.uVersion = 4 + + ctypes.windll.shell32.Shell_NotifyIconW(NIM_DELETE, ctypes.byref(res)) + ctypes.windll.user32.UpdateWindow(WindowsNotify.window_handle) + self.visible = False + + ctypes.windll.user32.DestroyWindow(WindowsNotify.window_handle) + WindowsNotify.window_handle = None + + def _show_notification(self, title, msg, sound, icon): + """Call windows API to show notification.""" + + icon_level = 0 + if icon & WinNotifyLevel.ICON_INFORMATION: + icon_level |= NIIF_INFO + elif icon & WinNotifyLevel.ICON_WARNING: + icon_level |= NIIF_WARNING + elif icon & WinNotifyLevel.ICON_ERROR: + icon_level |= NIIF_ERROR + + hwnd = self._get_window() + + if hwnd: + res = NotifyIconData() + res.cbSize = ctypes.sizeof(res) + res.hWnd = hwnd + res.uID = 0 + # `NIF_SHOWTIP` and `NIF_TIP` is probably not needed for Windows 8+, but maybe for 7? + res.uFlags = NIF_INFO | NIF_ICON | NIF_STATE | NIF_SHOWTIP | NIF_TIP | NIF_MESSAGE + res.uCallbackMessage = WM_USER + 20 + res.hIcon = self.hicon + res.szTip = self.app_name[:128] + res.uVersion = 4 + res.szInfo = msg[:256] + res.szInfoTitle = title[:64] + res.dwInfoFlags = icon_level | NIIF_NOSOUND | NIFF_USER + + if not ctypes.windll.shell32.Shell_NotifyIconW(NIM_MODIFY, ctypes.byref(res)): + if not self.visible and WindowsNotify.window_handle: + ctypes.windll.shell32.Shell_NotifyIconW(NIM_ADD, ctypes.byref(res)) + ctypes.windll.shell32.Shell_NotifyIconW(NIM_SETVERSION, ctypes.byref(res)) + self.visible = WindowsNotify.window_handle is not None + + if sound: + alert() + + def destroy(self): + """Destroy.""" + + self._destroy_window() + + +@staticmethod +def NotifyWin(title, msg, sound, icon, fallback): + """Notify for windows.""" + + Options.instance.show_notification(title, msg, sound, icon, fallback) + + +def setup(app_name, icon, **kwargs): + """Setup.""" + + sound = kwargs.get('sound') + if sound is not None and os.path.exists(sound): + Options.sound = sound + + try: + if icon is None or not os.path.exists(icon): + raise ValueError("Icon does not appear to be valid") + except Exception: + icon = None + + Options.instance = WindowsNotify(app_name, icon, app_name) + Options.notify = NotifyWin + + +def destroy(): + """Destroy.""" + + if Options.instance is not None: + Options.instance.destroy() + + Options.clear() + Options.notify = notify_win_fallback + + +def get_notify(): + """Get the notification.""" + + return Options.notify + + +Options.notify = notify_win_fallback diff --git a/_pieces_lib/notify/util.py b/_pieces_lib/notify/util.py new file mode 100644 index 0000000..9fd2b53 --- /dev/null +++ b/_pieces_lib/notify/util.py @@ -0,0 +1,19 @@ +"""Utilities.""" +import os + + +def which(executable): + """See if executable exists.""" + + location = None + if os.path.basename(executable) != executable: + if os.path.isfile(executable): + location = executable + else: + paths = [x for x in os.environ["PATH"].split(os.pathsep) if not x.isspace()] + for path in paths: + exe = os.path.join(path, executable) + if os.path.isfile(exe): + location = exe + break + return location diff --git a/_pieces_lib/pieces_os_client/wrapper/basic_identifier/asset.py b/_pieces_lib/pieces_os_client/wrapper/basic_identifier/asset.py index 1d7b899..d41dbf9 100644 --- a/_pieces_lib/pieces_os_client/wrapper/basic_identifier/asset.py +++ b/_pieces_lib/pieces_os_client/wrapper/basic_identifier/asset.py @@ -21,7 +21,7 @@ Shares ) -from typing import Optional +from typing import Literal, Optional, List from .basic import Basic from .user import BasicUser @@ -81,19 +81,20 @@ def raw_content(self, content: str): Args: content: The new content to be set. - - Raises: - NotImplemented: If the asset is an image. """ format_api = AssetSnapshot.pieces_client.format_api - original = format_api.format_snapshot(self.asset.original.id, transferable=True) - if original.classification.generic == ClassificationGenericEnum.IMAGE: - raise NotImplemented("Can't edit an image yet") + if self.is_image: + original = self._get_ocr_format(self.asset) + else: + original = format_api.format_snapshot(self.asset.original.id, transferable=True) if original.fragment and original.fragment.string and original.fragment.string.raw: original.fragment.string.raw = content elif original.file and original.file.string and original.file.string.raw: original.file.string.raw = content + elif original.file and original.file.bytes and original.file.bytes.raw: + original.file.bytes.raw = list(content.encode('utf-8')) + format_api.format_update_value(transferable=False, format=original) @property @@ -160,7 +161,7 @@ def name(self) -> str: Get the name of the asset. Returns: - Optional[str]: The name of the asset if available, otherwise "Unnamed snippet". + str: The name of the asset if available, otherwise "Unnamed snippet". """ return self.asset.name if self.asset.name else "Unnamed snippet" @@ -175,7 +176,7 @@ def name(self, name: str): self._edit_asset(self.asset) @property - def description(self) -> str: + def description(self) -> Optional[str]: """ Retrieve the description of the asset. @@ -204,7 +205,7 @@ def annotations(self) -> Optional[Annotations]: return getattr(self.asset.annotations,"iterable",None) - def delete(self): + def delete(self) -> None: """ Delete the asset. """ @@ -251,6 +252,44 @@ def share_raw_content(cls,raw_content:str) -> Shares: """ return cls._share(seed = cls._get_seed(raw_content)) + + @staticmethod + def search(query:str,search_type:Literal["fts","ncs","fuzzy"] = "fts") -> Optional[List["BasicAsset"]]: + """ + Perform a search using either Full Text Search (FTS) or Neural Code Search (NCS) or Fuzzy search (fuzzy). + + Parameters: + query (str): The search query string. + search_type (Literal["fts", "ncs", "fuzzy"], optional): The type of search to perform. + 'fts' for Full Text Search (default) or 'ncs' for Neural Code Search. + + Returns: + Optional[List["BasicAsset"]]: A list of search results or None if no results are found. + """ + if search_type == 'ncs': + results = AssetSnapshot.pieces_client.search_api.neural_code_search(query=query) + elif search_type == 'fts': + results = AssetSnapshot.pieces_client.search_api.full_text_search(query=query) + elif search_type == "fuzzy": + results = AssetSnapshot.pieces_client.assets_api.search_assets(query=query,transferables=False) + + if results: + # Extract the iterable which contains the search results + iterable_list = results.iterable if hasattr(results, 'iterable') else [] + + # Check if iterable_list is a list and contains SearchedAsset objects + if isinstance(iterable_list, list) and all(hasattr(asset, 'exact') and hasattr(asset, 'identifier') for asset in iterable_list): + # Extracting suggested and exact IDs + suggested_ids = [asset.identifier for asset in iterable_list if not asset.exact] + exact_ids = [asset.identifier for asset in iterable_list if asset.exact] + + # Combine and store best and suggested matches in asset_ids + combined_ids = exact_ids + suggested_ids + + # Print the combined asset details + if combined_ids: + return [BasicAsset(id) for id in combined_ids] + @staticmethod def _get_seed(raw: str, metadata: Optional[FragmentMetadata] = None) -> Seed: return Seed( @@ -326,7 +365,7 @@ def _share(asset=None,seed=None): else: kwargs = {"seed" : seed} - user = BasicUser.user_profile + user = AssetSnapshot.pieces_client.user.user_profile if not user: raise PermissionError("You need to be logged in to generate a shareable link") @@ -340,3 +379,4 @@ def _share(asset=None,seed=None): **kwargs ) ) + diff --git a/_pieces_lib/pieces_os_client/wrapper/basic_identifier/chat.py b/_pieces_lib/pieces_os_client/wrapper/basic_identifier/chat.py index 5fb6d11..99e8e2d 100644 --- a/_pieces_lib/pieces_os_client/wrapper/basic_identifier/chat.py +++ b/_pieces_lib/pieces_os_client/wrapper/basic_identifier/chat.py @@ -1,4 +1,4 @@ -from Pieces._pieces_lib.pieces_os_client.models.conversation import Conversation +from Pieces._pieces_lib.pieces_os_client import Conversation,Annotations from ..streamed_identifiers import ConversationsSnapshot from typing import Optional, List from .basic import Basic @@ -16,7 +16,7 @@ def conversation(self) -> Conversation: return conversation @property - def id(self): + def id(self) -> str: """ Gets the ID of the conversation. @@ -62,7 +62,7 @@ def messages(self) -> List[BasicMessage]: @property - def annotations(self): + def annotations(self) -> Optional[Annotations]: """ Gets the annotations of the conversation. @@ -87,3 +87,4 @@ def _edit_conversation(conversation): """ ConversationsSnapshot.pieces_client.conversation_api.conversation_update(False, conversation) + diff --git a/_pieces_lib/pieces_os_client/wrapper/basic_identifier/user.py b/_pieces_lib/pieces_os_client/wrapper/basic_identifier/user.py index 080c6a3..47557f2 100644 --- a/_pieces_lib/pieces_os_client/wrapper/basic_identifier/user.py +++ b/_pieces_lib/pieces_os_client/wrapper/basic_identifier/user.py @@ -60,7 +60,7 @@ def logout(self): """ Logs the user out of the OS. """ - self.pieces_client.api_client.os_api.sign_out_of_os() + self.pieces_client.os_api.sign_out_of_os() def connect(self): """ @@ -84,7 +84,7 @@ def disconnect(self): if not self.user_profile: raise PermissionError("You must be logged in to use this feature") if self.user_profile.allocation: # Check if there is an allocation iterable - self.pieces_client.api_client.allocations_api.allocations_disconnect_cloud(self.user_profile.allocation) + self.pieces_client.allocations_api.allocations_disconnect_cloud(self.user_profile.allocation) @property def picture(self) -> Optional[str]: diff --git a/_pieces_lib/pieces_os_client/wrapper/client.py b/_pieces_lib/pieces_os_client/wrapper/client.py index 67a32bf..3716968 100644 --- a/_pieces_lib/pieces_os_client/wrapper/client.py +++ b/_pieces_lib/pieces_os_client/wrapper/client.py @@ -30,7 +30,6 @@ import urllib.request import urllib.error - from .copilot import Copilot from .basic_identifier import BasicAsset,BasicUser from .streamed_identifiers import AssetSnapshot @@ -42,8 +41,8 @@ def __init__(self, host:str="", seeded_connector: Optional[SeededConnectorConnec if not host: host = "http://localhost:5323" if 'Linux' in platform.platform() else "http://localhost:1000" - self.models = None self.host = host + self.models = None self._is_started_runned = False self.local_os = platform.system().upper() if platform.system().upper() in ["WINDOWS","LINUX","DARWIN"] else "WEB" self.local_os = "MACOS" if self.local_os == "DARWIN" else self.local_os @@ -57,12 +56,14 @@ def __init__(self, host:str="", seeded_connector: Optional[SeededConnectorConnec self.copilot = Copilot(self) self._startup() + def _startup(self) -> bool: if self._is_started_runned: return True if not self.is_pieces_running(): return False self._is_started_runned = True self.tracked_application = self.connector_api.connect(seeded_connector_connection=self._seeded_connector).application + self.api_client.set_default_header("application",self.tracked_application.id) if self._connect_websockets: self.conversation_ws = ConversationWS(self) @@ -213,4 +214,5 @@ def _check_startup(self): raise ValueError("PiecesClient is not started successfully\nPerhaps Pieces OS is not running") # Register the function to be called on exit -atexit.register(BaseWebsocket.close_all) \ No newline at end of file +atexit.register(BaseWebsocket.close_all) + diff --git a/_pieces_lib/pieces_os_client/wrapper/context.py b/_pieces_lib/pieces_os_client/wrapper/context.py index 7500664..9b55528 100644 --- a/_pieces_lib/pieces_os_client/wrapper/context.py +++ b/_pieces_lib/pieces_os_client/wrapper/context.py @@ -1,4 +1,5 @@ from typing import TYPE_CHECKING, List + from .basic_identifier import BasicAsset,BasicMessage import os from Pieces._pieces_lib.pieces_os_client import QGPTRelevanceInput,Seeds,FlattenedAssets,FlattenedConversationMessages diff --git a/_pieces_lib/pieces_os_client/wrapper/copilot.py b/_pieces_lib/pieces_os_client/wrapper/copilot.py index 2be1cc2..3546bd3 100644 --- a/_pieces_lib/pieces_os_client/wrapper/copilot.py +++ b/_pieces_lib/pieces_os_client/wrapper/copilot.py @@ -1,4 +1,4 @@ -from typing import TYPE_CHECKING, Optional, Generator,List +from typing import TYPE_CHECKING, List, Optional, Generator from Pieces._pieces_lib.pieces_os_client import (SeededConversation, QGPTStreamInput, RelevantQGPTSeeds, @@ -54,6 +54,7 @@ def stream_question(self, Yields: QGPTStreamOutput: The streamed output from the QGPT model. """ + self.pieces_client._check_startup() relevant = self.context._relevance_api(query) if self.context._check_relevant_existance else RelevantQGPTSeeds(iterable=[]) self.ask_stream_ws.send_message( QGPTStreamInput( @@ -67,7 +68,16 @@ def stream_question(self, conversation=self._chat_id, ) ) + return self._return_on_message() + def _return_on_message(self): + while True: + message: QGPTStreamOutput = self._on_message_queue.get() + if message.status != QGPTStreamEnum.IN_MINUS_PROGRESS: # Loop only while in progress + yield message + self.chat = BasicChat(message.conversation) # Save the conversation + break + yield message def question(self, @@ -87,6 +97,7 @@ def question(self, returns: QGPTQuestionOutput: The streamed output from the QGPT model. """ + self.pieces_client._check_startup() gpt_input = QGPTQuestionInput( query = query, model = self.pieces_client.model_id, diff --git a/_pieces_lib/pieces_os_client/wrapper/websockets/ask_ws.py b/_pieces_lib/pieces_os_client/wrapper/websockets/ask_ws.py index 0c9894a..6ad803b 100644 --- a/_pieces_lib/pieces_os_client/wrapper/websockets/ask_ws.py +++ b/_pieces_lib/pieces_os_client/wrapper/websockets/ask_ws.py @@ -16,13 +16,13 @@ class AskStreamWS(BaseWebsocket): on_message_callback (Callable[[QGPTStreamOutput], None]): Callback function to handle incoming messages. on_open_callback (Optional[Callable[[WebSocketApp], None]]): Optional callback function to handle WebSocket opening. on_error (Optional[Callable[[WebSocketApp, Exception], None]]): Optional callback function to handle errors. - on_close (Optional[Callable[[WebSocketApp], None]]): Optional callback function to handle WebSocket closing. + on_close (Optional[Callable[[WebSocketApp, str, str], None]]): Optional callback function to handle WebSocket closing. """ def __init__(self, pieces_client: "PiecesClient", on_message_callback: Callable[[QGPTStreamOutput], None], on_open_callback: Optional[Callable[[WebSocketApp], None]] = None, on_error: Optional[Callable[[WebSocketApp, Exception], None]] = None, - on_close: Optional[Callable[[WebSocketApp], None]] = None): + on_close: Optional[Callable[[WebSocketApp, str, str], None]] = None): """ Initializes the AskStreamWS instance. @@ -31,7 +31,7 @@ def __init__(self, pieces_client: "PiecesClient", on_message_callback (Callable[[QGPTStreamOutput], None]): Callback function to handle incoming messages. on_open_callback (Optional[Callable[[WebSocketApp], None]]): Optional callback function to handle WebSocket opening. on_error (Optional[Callable[[WebSocketApp, Exception], None]]): Optional callback function to handle errors. - on_close (Optional[Callable[[WebSocketApp], None]]): Optional callback function to handle WebSocket closing. + on_close (Optional[Callable[[WebSocketApp, str, str], None]]): Optional callback function to handle WebSocket closing. """ super().__init__(pieces_client, on_message_callback, on_open_callback, on_error, on_close) diff --git a/_pieces_lib/pieces_os_client/wrapper/websockets/assets_identifiers_ws.py b/_pieces_lib/pieces_os_client/wrapper/websockets/assets_identifiers_ws.py index 99ca531..52206a9 100644 --- a/_pieces_lib/pieces_os_client/wrapper/websockets/assets_identifiers_ws.py +++ b/_pieces_lib/pieces_os_client/wrapper/websockets/assets_identifiers_ws.py @@ -16,7 +16,7 @@ class AssetsIdentifiersWS(BaseWebsocket): on_asset_remove (Optional[Callable[[Asset], None]]): Callback function to handle asset removals. on_open_callback (Optional[Callable[[WebSocketApp], None]]): Callback function to handle WebSocket opening. on_error (Optional[Callable[[WebSocketApp, Exception], None]]): Callback function to handle WebSocket errors. - on_close (Optional[Callable[[WebSocketApp], None]]): Callback function to handle WebSocket closing. + on_close (Optional[Callable[[WebSocketApp, str, str], None]]): Callback function to handle WebSocket closing. """ def __init__(self, pieces_client: "PiecesClient", @@ -24,7 +24,7 @@ def __init__(self, pieces_client: "PiecesClient", on_asset_remove: Optional[Callable[[Asset], None]] = None, on_open_callback: Optional[Callable[[WebSocketApp], None]] = None, on_error: Optional[Callable[[WebSocketApp, Exception], None]] = None, - on_close: Optional[Callable[[WebSocketApp], None]] = None): + on_close: Optional[Callable[[WebSocketApp, str, str], None]] = None): """ Initializes the AssetsIdentifiersWS instance. @@ -34,7 +34,7 @@ def __init__(self, pieces_client: "PiecesClient", on_asset_remove (Optional[Callable[[Asset], None]]): Callback function to handle asset removals. on_open_callback (Optional[Callable[[WebSocketApp], None]]): Callback function to handle WebSocket opening. on_error (Optional[Callable[[WebSocketApp, Exception], None]]): Callback function to handle WebSocket errors. - on_close (Optional[Callable[[WebSocketApp], None]]): Callback function to handle WebSocket closing. + on_close (Optional[Callable[[WebSocketApp, str, str], None]]): Callback function to handle WebSocket closing. """ AssetSnapshot.pieces_client = pieces_client if on_asset_update: diff --git a/_pieces_lib/pieces_os_client/wrapper/websockets/auth_ws.py b/_pieces_lib/pieces_os_client/wrapper/websockets/auth_ws.py index f91316f..12a1ab5 100644 --- a/_pieces_lib/pieces_os_client/wrapper/websockets/auth_ws.py +++ b/_pieces_lib/pieces_os_client/wrapper/websockets/auth_ws.py @@ -16,7 +16,7 @@ class AuthWS(BaseWebsocket): on_message_callback (Callable[[Optional[UserProfile]], None]): Callback function to handle incoming messages. on_open_callback (Optional[Callable[[WebSocketApp], None]]): Optional callback function to handle WebSocket opening. on_error (Optional[Callable[[WebSocketApp, Exception], None]]): Optional callback function to handle errors. - on_close (Optional[Callable[[WebSocketApp], None]]): Optional callback function to handle WebSocket closing. + on_close (Optional[Callable[[WebSocketApp, str, str], None]]): Optional callback function to handle WebSocket closing. """ def __init__(self, @@ -24,7 +24,7 @@ def __init__(self, on_message_callback: Callable[[Optional[UserProfile]], None], on_open_callback: Optional[Callable[[WebSocketApp], None]] = None, on_error: Optional[Callable[[WebSocketApp, Exception], None]] = None, - on_close: Optional[Callable[[WebSocketApp], None]] = None): + on_close: Optional[Callable[[WebSocketApp, str, str], None]] = None): """ Initializes the AuthWS instance. @@ -33,7 +33,7 @@ def __init__(self, on_message_callback (Callable[[Optional[UserProfile]], None]): Callback function to handle incoming messages. on_open_callback (Optional[Callable[[WebSocketApp], None]]): Optional callback function to handle WebSocket opening. on_error (Optional[Callable[[WebSocketApp, Exception], None]]): Optional callback function to handle errors. - on_close (Optional[Callable[[WebSocketApp], None]]): Optional callback function to handle WebSocket closing. + on_close (Optional[Callable[[WebSocketApp, str, str], None]]): Optional callback function to handle WebSocket closing. """ super().__init__(pieces_client, on_message_callback, on_open_callback, on_error, on_close) diff --git a/_pieces_lib/pieces_os_client/wrapper/websockets/base_websocket.py b/_pieces_lib/pieces_os_client/wrapper/websockets/base_websocket.py index d4fb2b1..7bffa58 100644 --- a/_pieces_lib/pieces_os_client/wrapper/websockets/base_websocket.py +++ b/_pieces_lib/pieces_os_client/wrapper/websockets/base_websocket.py @@ -24,7 +24,7 @@ def __init__(self, on_message_callback: Callable[[str], None], on_open_callback: Optional[Callable[[websocket.WebSocketApp], None]] = None, on_error: Optional[Callable[[websocket.WebSocketApp, Exception], None]] = None, - on_close: Optional[Callable[[websocket.WebSocketApp,str,str], None]] = None): + on_close: Optional[Callable[[websocket.WebSocketApp, str, str], None]] = None): """ Initialize the BaseWebsocket instance. diff --git a/_pieces_lib/pieces_os_client/wrapper/websockets/conversations_ws.py b/_pieces_lib/pieces_os_client/wrapper/websockets/conversations_ws.py index 55e1419..a16c465 100644 --- a/_pieces_lib/pieces_os_client/wrapper/websockets/conversations_ws.py +++ b/_pieces_lib/pieces_os_client/wrapper/websockets/conversations_ws.py @@ -13,7 +13,7 @@ def __init__(self, pieces_client: "PiecesClient", on_conversation_remove: Optional[Callable[[Conversation], None]] = None, on_open_callback: Optional[Callable[[WebSocketApp], None]] = None, on_error: Optional[Callable[[WebSocketApp, Exception], None]] = None, - on_close: Optional[Callable[[WebSocketApp], None]] = None): + on_close: Optional[Callable[[WebSocketApp, str, str], None]] = None): """ Initialize the ConversationWS class. diff --git a/assets/share_asset.py b/assets/share_asset.py index b92b40e..8e65510 100644 --- a/assets/share_asset.py +++ b/assets/share_asset.py @@ -8,6 +8,11 @@ class PiecesShareAssetCommand(sublime_plugin.WindowCommand): + def __init__(self, window): + self.sheet = None + self.update_sheet = False # Should we update the current sheet + super().__init__(window) + def run(self,asset_id,update_sheet=False): self.update_sheet = update_sheet self.sheet = self.window.active_sheet() @@ -19,7 +24,6 @@ def run_async(self,asset_id=None,raw_content=None): """ You need to either give the seed or the asset_id """ - user = AuthUser.user_profile if not user: if sublime.ok_cancel_dialog("You need to be logged in to generate a shareable link",ok_title="Login",title="Pieces"): @@ -29,27 +33,20 @@ def run_async(self,asset_id=None,raw_content=None): if sublime.ok_cancel_dialog("You need to connect to the cloud to generate a shareable link",ok_title="Connect",title="Pieces"): self.window.run_command("pieces_allocation_connect") return - if asset_id: - self.thread = PiecesSettings.pool().apply_async(BasicAsset(asset_id).share) + share = BasicAsset(asset_id).share() if raw_content: - self.thread = PiecesSettings.pool().apply_async(BasicAsset.share_raw_content,raw_content) - - share = None - try: - share = self.thread.get(120) - if self.sheet and self.update_sheet: - if self.sheet.id() in PiecesListAssetsCommand.sheets_md: - PiecesListAssetsCommand.update_sheet(self.sheet,asset_id, - {"share": - {"title":"Copy Generated Link", - "url":f'subl:pieces_copy_link {{"content":"{share.iterable[0].link}", "asset_id":"{asset_id}"}}'} - }) - - except: - pass - - if share: return share + share = BasicAsset.share_raw_content(raw_content) + + if self.sheet and self.update_sheet: + if self.sheet.id() in PiecesListAssetsCommand.sheets_md: + PiecesListAssetsCommand.update_sheet(self.sheet,asset_id, + {"share": + {"title":"Copy Generated Link", + "url":f'subl:pieces_copy_link {{"content":"{share.iterable[0].link}", "asset_id":"{asset_id}"}}'} + }) + PiecesSettings.notify("Shareable Link Generated",share.iterable[0].link) + return share def is_enabled(self): return PiecesSettings.is_loaded @@ -58,6 +55,10 @@ def is_enabled(self): class PiecesGenerateShareableLinkCommand(sublime_plugin.TextCommand): def run(self,edit,data=None): self.data = data + if not data: + self.data = "\n".join([self.view.substr(selection) for selection in self.view.sel()]) + if not self.data: + return sublime.error_message("Please select a text") sublime.set_timeout_async(self.run_async) @@ -72,8 +73,7 @@ def run_async(self): # self.show_dialog(link) - # self.create_popup(self.view,link) - self.show_dialog(link) + self.create_popup(self.view,link) sublime.set_timeout(lambda: self.view.erase_status("pieces_share"),4000) @@ -114,7 +114,8 @@ def on_nav(href):
Pieces Link Generated: {link}
""", location=-1, - on_navigate=on_nav) + on_navigate=on_nav, + max_width=350) def is_enabled(self): return PiecesSettings.is_loaded diff --git a/copilot/ask_view.py b/copilot/ask_view.py index f259701..2b9d281 100644 --- a/copilot/ask_view.py +++ b/copilot/ask_view.py @@ -73,7 +73,7 @@ def gpt_view(self) -> sublime.View: # Update the Copilot message callback PiecesSettings.api_client.copilot.ask_stream_ws.on_message_callback = self.on_message_callback - + PiecesSettings.api_client.copilot._return_on_message = lambda:None # Modify the copilot becaue we will use the on_message_callback return CopilotViewManager._gpt_view @property @@ -183,7 +183,7 @@ def conversation_id(self): @conversation_id.setter def conversation_id(self,id): - PiecesSettings.api_client.copilot.chat = BasicChat(id) + PiecesSettings.api_client.copilot.chat = BasicChat(id) if id else None self.gpt_view.settings().set("conversation_id",id) @property @@ -279,30 +279,30 @@ def render_conversation(self,conversation_id): if conversation_id: try: - PiecesSettings.api_client.copilot.chat = BasicChat(conversation_id) + self.conversation_id = conversation_id except ValueError: return sublime.error_message("Conversation not found") # Error conversation not found else: - PiecesSettings.api_client.copilot.chat = None + self.conversation_id = None self.gpt_view # Nothing need to be rendered if hasattr(self,"_view_name"): delattr(self,"_view_name") return - - self.view_name = PiecesSettings.api_client.copilot.chat.name + chat = PiecesSettings.api_client.copilot.chat + self.view_name = chat.name if chat else "New Conversation" self.gpt_view.run_command("select_all") self.gpt_view.run_command("right_delete") # Clear the cursor created by default ">>>" - - for message in conversation.messages(): - if message.role == "USER": - self.show_cursor - else: - self.add_role("Copilot") - - if message.raw_content: - self.gpt_view.run_command("append",{"characters":message.raw_content}) - - self.new_line() + if chat: + for message in chat.messages(): + if message.role == "USER": + self.show_cursor + else: + self.add_role("Copilot") + + if message.raw_content: + self.gpt_view.run_command("append",{"characters":message.raw_content}) + + self.new_line() self.show_cursor self.end_response = self.gpt_view.size() diff --git a/copilot/explain.py b/copilot/explain.py index 7b73eb9..ba39fe5 100644 --- a/copilot/explain.py +++ b/copilot/explain.py @@ -1,4 +1,5 @@ import sublime_plugin +import sublime from .._pieces_lib.pieces_os_client import (QGPTTaskPipeline, QGPTTaskPipelineForCodeExplanation) from .ask_command import copilot diff --git a/icons/pieces_server.icns b/icons/pieces_server.icns new file mode 100644 index 0000000..b668bb1 Binary files /dev/null and b/icons/pieces_server.icns differ diff --git a/icons/pieces_server.ico b/icons/pieces_server.ico new file mode 100644 index 0000000..df9e936 Binary files /dev/null and b/icons/pieces_server.ico differ diff --git a/icons/pieces_server.png b/icons/pieces_server.png new file mode 100644 index 0000000..521c77d Binary files /dev/null and b/icons/pieces_server.png differ diff --git a/messages.json b/messages.json index 17520d6..c0000a4 100644 --- a/messages.json +++ b/messages.json @@ -2,4 +2,5 @@ "install": "messages/install.txt", "1.1.0":"messages/1.1.0.txt", "1.2.0":"messages/1.2.0.txt", + "1.3.0":"messages/1.3.0.txt", } diff --git a/settings.py b/settings.py index f7c6c1d..5086423 100644 --- a/settings.py +++ b/settings.py @@ -1,6 +1,7 @@ from ._pieces_lib.pieces_os_client import SeededConnectorConnection,SeededTrackedApplication from ._pieces_lib.pieces_os_client.wrapper.websockets.base_websocket import BaseWebsocket from ._pieces_lib.pieces_os_client.wrapper import PiecesClient +from ._pieces_lib import notify from multiprocessing.pool import ThreadPool import sublime import os @@ -8,6 +9,13 @@ from . import __version__ +try: + from . import _debug + debug = True + print("RUNNING DEBUG MODE") +except: + debug = False + class PiecesSettings: # Initialize class variables @@ -17,6 +25,7 @@ class PiecesSettings: platform = sublime.platform().upper() if sublime.platform() != 'osx' else "MACOS", version = __version__)),connect_wesockets=False) _pool = None + debug=debug is_loaded = False # is the plugin loaded ONBOARDING_SYNTAX = "Packages/Pieces/syntax/Onboarding.sublime-syntax" on_model_change_callbacks = [] # If the model change a function should be runned @@ -96,7 +105,32 @@ def pool(cls): cls._pool = ThreadPool(1) return cls._pool + # Load the settings from 'Pieces.sublime-settings' file using Sublime Text API pieces_settings = sublime.load_settings('Pieces.sublime-settings') pieces_settings.add_on_change("PIECES_SETTINGS",on_settings_change) + @staticmethod + def notify(title,message,level="info"): + try: + if level == "error": + notify.error(title, message, False) + elif level == "warning": + notify.warning(title, message, False) + else: + notify.info(title, message, False) + notify.destroy() + except: + pass +if PiecesSettings.api_client.local_os == "MACOS": + os_icon = "pieces_server.icns" +elif PiecesSettings.api_client.local_os == "WINDOWS": + os_icon = "pieces_server.ico" +else: + os_icon = "pieces_server.png" + +package_path = os.path.join(sublime.packages_path(),"Pieces") if debug else os.path.join(sublime.installed_packages_path(),"Pieces.sublime-package") +path = os.path.join(package_path,"icons", os_icon) if os_icon else None +os_icon = path +notify.setup_notifications("Pieces for Sublime Text",os_icon,sender=None) +