diff --git a/.idea/Log_Viewer.iml b/.idea/Log_Viewer.iml new file mode 100644 index 0000000..799a15f --- /dev/null +++ b/.idea/Log_Viewer.iml @@ -0,0 +1,11 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..88b7f69 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..be782af --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/UI_modules/custom_notebook.py b/UI_modules/custom_notebook.py new file mode 100644 index 0000000..2e927ff --- /dev/null +++ b/UI_modules/custom_notebook.py @@ -0,0 +1,98 @@ +import tkinter +from tkinter import ttk +from modules.list_of_tab import list_of_tab + + +class CustomNotebook(ttk.Notebook): + """A ttk Notebook with close buttons on each tab""" + + __initialized = False + + def __init__(self, *args, **kwargs): + if not self.__initialized: + self.__initialize_custom_style() + self.__initialized = True + + kwargs["style"] = "CustomNotebook" + ttk.Notebook.__init__(self, *args, **kwargs) + + self._active = None + + self.bind("", self.on_close_press, True) + self.bind("", self.on_close_release) + + def on_close_press(self, event): + """Called when the button is pressed over the close button""" + + element = self.identify(event.x, event.y) + + if "close" in element: + index = self.index("@%d,%d" % (event.x, event.y)) + self.state(['pressed']) + self._active = index + + def on_close_release(self, event): + """Called when the button is released over the close button""" + if not self.instate(['pressed']): + return + + element = self.identify(event.x, event.y) + index = self.index("@%d,%d" % (event.x, event.y)) + tab_name_for_delete = self.tab(index, "text") # get name of closed tab + + if "close" in element and self._active == index: + self.forget(index) + self.event_generate("<>") + + self.state(["!pressed"]) + self._active = None + + for tab in list_of_tab.get_all_tab(): + if tab.tab_name == tab_name_for_delete: + list_of_tab.remove_tab(tab) + break + + def __initialize_custom_style(self): + style = ttk.Style() + self.images = ( + tkinter.PhotoImage("img_close", data=''' + R0lGODlhCAAIAMIBAAAAADs7O4+Pj9nZ2Ts7Ozs7Ozs7Ozs7OyH+EUNyZWF0ZWQg + d2l0aCBHSU1QACH5BAEKAAQALAAAAAAIAAgAAAMVGDBEA0qNJyGw7AmxmuaZhWEU + 5kEJADs= + '''), + tkinter.PhotoImage("img_closeactive", data=''' + R0lGODlhCAAIAMIEAAAAAP/SAP/bNNnZ2cbGxsbGxsbGxsbGxiH5BAEKAAQALAAA + AAAIAAgAAAMVGDBEA0qNJyGw7AmxmuaZhWEU5kEJADs= + '''), + tkinter.PhotoImage("img_closepressed", data=''' + R0lGODlhCAAIAMIEAAAAAOUqKv9mZtnZ2Ts7Ozs7Ozs7Ozs7OyH+EUNyZWF0ZWQg + d2l0aCBHSU1QACH5BAEKAAQALAAAAAAIAAgAAAMVGDBEA0qNJyGw7AmxmuaZhWEU + 5kEJADs= + ''') + ) + + style.element_create("close", "image", "img_close", + ("active", "pressed", "!disabled", "img_closepressed"), + ("active", "!disabled", "img_closeactive"), border=8, sticky='') + style.layout("CustomNotebook", [("CustomNotebook.client", {"sticky": "nswe"})]) + style.layout("CustomNotebook.Tab", [ + ("CustomNotebook.tab", { + "sticky": "nswe", + "children": [ + ("CustomNotebook.padding", { + "side": "top", + "sticky": "nswe", + "children": [ + ("CustomNotebook.focus", { + "side": "top", + "sticky": "nswe", + "children": [ + ("CustomNotebook.label", {"side": "left", "sticky": ''}), + ("CustomNotebook.close", {"side": "left", "sticky": ''}), + ] + }) + ] + }) + ] + }) + ]) \ No newline at end of file diff --git a/UI_modules/custom_tab.py b/UI_modules/custom_tab.py new file mode 100644 index 0000000..6e91eb5 --- /dev/null +++ b/UI_modules/custom_tab.py @@ -0,0 +1,107 @@ +from modules.loader import Tail +import tkinter +from tkinter import ttk +import threading +import time +from modules.list_of_tab import list_of_tab + + +class Tab: + def __init__(self, main_space, file_path=''): + self.path_to_file = file_path + self.__end = 0 + self.search_index = '1.0' + self.all_visible_text = '' + self.document = Tail(file_path) # создаем на вкладке объект документа, который читаем + self.page = ttk.Frame(main_space) # объект вкладка + self.__tab_name_expect = file_path.split('/')[-1] # имя вкладки, берем последнее значение после разделения по символу / + + self.tab_name = self.__set_tab_name(self.__tab_name_expect) + self.txt = tkinter.Text(self.page, font="TextFont", spacing3=2) # объект текстовое поле + self.scroll = tkinter.Scrollbar(self.txt) # объект скролбарр на вкладку + + self.txt.config(yscrollcommand=self.scroll.set) + self.scroll.config(command=self.txt.yview) # прикрепляем скроллбар к текстовому полю + + self.txt.pack(side='top', fill='both', expand=True) # задаем размещение текстового поле + self.scroll.pack(side='right', fill=tkinter.Y) # задаем размещение скроллбара + + self.txt.tag_config("red", background="red", foreground="white") + + main_space.add(self.page, text='{}'.format(self.tab_name)) # добавляем вкладку + + self.txt.bind('', self.__watch_tail) # при нажатии на кнопку END начинается просмотр последних данных + self.txt.bind('', self.__stop_watch_tail) # при 2-м клике останавливаем просмотр + + self.txt.insert(tkinter.END, self.document.get_lines()) # вставляем текст из нашего документа + self.txt.config(state='disabled') # закрываем возможность редактировать + self.thread_highlight = threading.Thread(target=self.__highlight_word, + args=['error', self.search_index], + daemon=True, + name='__highlight_red') # поток для выделения текста + self.thread_show_last_string = threading.Thread(target=self.__shows_the_last_string, + daemon=True, + name='__watch_tail') # поток для просмотра последней строки + self.thread_highlight.start() + + def __set_tab_name(self, tab_name_expect): + self.__name, self.__file_fmt = tab_name_expect.split('.') + self.__all_tabs = list_of_tab.get_all_tab() + self.__count = 1 + for tab in self.__all_tabs: + if tab.tab_name == tab_name_expect: + tab_name_expect = '{0}({1}).{2}'.format(self.__name, self.__count, self.__file_fmt) + self.__count += 1 + return tab_name_expect + + def update_text(self): + """Эта функция должна была обновлять текст на вкладке""" + self.txt.config(state='normal') + self.txt.insert(tkinter.END, self.document.get_lines()) + self.txt.config(state='disabled') + + def __shows_the_last_string(self): + """На постоянке крутиться проверка для перехода к концу""" + while True: + while self.__end: + self.txt.see(tkinter.END) + time.sleep(1) + time.sleep(1) + + def __watch_tail(self, event): + """запуск потока для постоянного просмотра последнего файла""" + self.__end = 1 + if not self.thread_show_last_string.isAlive(): + self.thread_show_last_string.start() + + def __stop_watch_tail(self, event): + """останавливаем цикл, который постоянно мониторит последнюю строку""" + self.__end = 0 + + def __search_word(self, word, start_index): + """Find first position of the word, from start position""" + pos = self.txt.search(word, start_index, tkinter.END) + if pos: + string, sym = pos.split('.') + new_sym = str(int(sym) + len(word)) + next_start_index = '{0}.{1}'.format(string, new_sym) + self.search_index = next_start_index + return pos, next_start_index + else: + next_start_index = '' + return pos, next_start_index + + def __highlight_word(self, word, start_index): + next_index = start_index + while True: + first_sym, last_sym = self.__search_word(word, start_index=next_index) + if last_sym: + next_index = last_sym + self.txt.tag_add('red', first_sym, last_sym) + time.sleep(0.2) + else: + time.sleep(0.7) + + def get_all_text(self): + self.all_visible_text = self.txt.get(1.0, tkinter.END) + return self.all_visible_text diff --git a/UI_modules/modal_window.py b/UI_modules/modal_window.py new file mode 100644 index 0000000..5084fc6 --- /dev/null +++ b/UI_modules/modal_window.py @@ -0,0 +1,30 @@ +import tkinter +import os + + +class ModalWindow: + def __init__(self, file_name): + self.__file_name = file_name + self.width = 200 + self.height = 100 + self.top = tkinter.Toplevel(padx=1, pady=1) + self.start_pos_x = int((self.top.winfo_screenwidth() / 2) - (self.width / 2)) + self.start_pos_y = int((self.top.winfo_screenheight() / 2.5) - (self.height / 2)) + self.top.title("Warning!") + self.top.geometry('{0}x{1}+{2}+{3}'.format(self.width, self.height, + self.start_pos_x, self.start_pos_y)) + self.top.maxsize(self.width, self.height) + self.top.resizable(0, 0) + if os.name == 'nt': + self.top.attributes('-toolwindow', 1) + + self.msg = tkinter.Message(self.top, text="{0}\n is not a text file!".format(self.__file_name), + justify='left', width=190, font='Arial 12') + self.msg.pack(side='top', fill='both', expand=True) + + self.button = tkinter.Button(self.top, text="Close", command=self.top.destroy) + self.button.pack(side='top', expand=True) + + def show(self): + self.top.focus_set() + self.top.mainloop() diff --git a/fill_file_utilite/dict.txt b/fill_file_utilite/dict.txt new file mode 100644 index 0000000..fe12ef6 --- /dev/null +++ b/fill_file_utilite/dict.txt @@ -0,0 +1,14 @@ +error +Error +warn +Warning +INFO +info +Info +DEBUG +Debug +Left +Right +Up +AND +Down \ No newline at end of file diff --git a/fill_file_utilite/fillFile.py b/fill_file_utilite/fillFile.py new file mode 100644 index 0000000..d9d163c --- /dev/null +++ b/fill_file_utilite/fillFile.py @@ -0,0 +1,53 @@ +import time +import sys +import msvcrt +import threading +import os +import random + +pressed_key = '' + + +def press_key(): + while True: + pressed_key = msvcrt.getwch() + if pressed_key == 'q': + sys.exit() + time.sleep(0.5) + + +th = threading.Thread(target=press_key,name=2) + + +def fill_file(): + k = 1 + words: list + with open('dict.txt', 'r') as dictionary: + words = dictionary.read().split('\n') + for count in range(11): + file_path = '{0}{1}{2}'.format(os.getcwd(), os.sep, 'test_{0}.log'.format(count)) + if not os.path.exists(r'{0}'.format(file_path)): + while True: + if pressed_key == 'q': + print('Stop the script') + sys.exit(0) + else: + with open('test_{0}.log'.format(count), 'a') as file: + file.writelines('New string {0} {1} Новая строка {0} {2}\n'.format(k, + random.choice(words), + random.choice(words))) + print('New string {0} {1} Новая строка {0} {2}\n'.format(k, + random.choice(words), + random.choice(words))) + k += 1 + time.sleep(1) + else: + pass + + +def starter(): + th.start() + fill_file() + + +starter() \ No newline at end of file diff --git a/icons/icon.ico b/icons/icon.ico new file mode 100644 index 0000000..aeb39e4 Binary files /dev/null and b/icons/icon.ico differ diff --git a/main_form.py b/main_form.py new file mode 100644 index 0000000..a4661da --- /dev/null +++ b/main_form.py @@ -0,0 +1,114 @@ +import tkinter +from tkinter.filedialog import askopenfilename +import time +import threading +import os +import mimetypes +from UI_modules.custom_notebook import CustomNotebook +from UI_modules.custom_tab import Tab +from UI_modules.modal_window import ModalWindow +from modules.saver import Saver +from modules.list_of_tab import list_of_tab + + +APP_WIDTH_WIN = 750 +APP_WIDTH_LINUX = 825 +APP_HEIGHT = 400 + + +# список всех вкладок +list_of_tab = list_of_tab + + +# диалоговое окно открытия файла, возвращает путь к файлу +def path_to_file(): + op = askopenfilename(defaultextension='.log', + filetypes=(("Log files", ".log"), + ("Text files", ".txt"), + ("All files", ".*"))) + return op + + +def save_file(tabs=list_of_tab.get_all_tab()): + tab_name = nb.tab(nb.select(), 'text') + saver = Saver(tabs, tab_name) + saver.save_one() + + +def save_all_file(tabs=list_of_tab.get_all_tab()): + saver = Saver(tabs) + saver.save_all() + + +# обновление информации на всех вкладках +def update_tabs(): + while True: + for line in list_of_tab.get_all_tab(): + line.update_text() + time.sleep(1) + + +# добавление вкладки +def add_tab(): + file_path = path_to_file() + if file_path: + type_of_file = mimetypes.guess_type(file_path) + + if type_of_file[0]: + type_of_file = type_of_file[0].split('/')[0] + + if type_of_file == 'text' or not type_of_file[0]: + list_of_tab.add_tab(Tab(nb, file_path)) + else: + file_name = file_path.split('/')[-1] + modal_window = ModalWindow(file_name) + modal_window.show() + else: + return + + +if os.name == 'posix': + app_width = APP_WIDTH_LINUX + path_to_icon = '' +else: + app_width = APP_WIDTH_WIN + path_to_icon = 'icons\icon.ico' + path_to_icon = os.path.join(os.getcwd(), path_to_icon) + +# здесь начинается описание UI +root = tkinter.Tk() + +app_height = APP_HEIGHT +start_pos_x = int(root.winfo_screenwidth() / 2) - int((app_width / 2)) +start_pos_y = int(root.winfo_screenheight() / 2.5) - int((app_height / 2)) + +menu_bar = tkinter.Menu(root, tearoff=False) +save_button = tkinter.Menu(root) + +root.config(menu=menu_bar) + +submenu = tkinter.Menu(menu_bar, tearoff=False) +save_submenu = tkinter.Menu(submenu) + +menu_bar.add_cascade(label='File', menu=submenu) +submenu.add_command(label="Open file", command=add_tab) +submenu.add_cascade(label='Save..', menu=save_submenu) +save_submenu.add_command(label='Save active', command=save_file) +save_submenu.add_command(label='Save all...', command=save_all_file) + +# Defines and places the notebook widget +nb = CustomNotebook(root) +nb.pack(fill='both', expand='yes') +# здесь заканчивается описание UI + + +# поток для обновления вкладок +thread_update_tabs = threading.Thread(target=update_tabs, daemon=True, name='update_tabs') +thread_update_tabs.start() + +root.title('LogViewer') +root.iconbitmap(path_to_icon) +root.geometry('{0}x{1}'.format(app_width, app_height)) +root.minsize(app_width, app_height) +root.geometry('+{0}+{1}'.format(start_pos_x, start_pos_y)) +root.mainloop() # запуск отрисовки UI diff --git a/modules/list_of_tab.py b/modules/list_of_tab.py new file mode 100644 index 0000000..ff6d96b --- /dev/null +++ b/modules/list_of_tab.py @@ -0,0 +1,15 @@ +class ListOfTab: + def __init__(self): + self.__list_of_tab = [] + + def add_tab(self, tab): + self.__list_of_tab.append(tab) + + def remove_tab(self, tab): + self.__list_of_tab.remove(tab) + + def get_all_tab(self): + return self.__list_of_tab + + +list_of_tab = ListOfTab() diff --git a/modules/loader.py b/modules/loader.py new file mode 100644 index 0000000..3a843de --- /dev/null +++ b/modules/loader.py @@ -0,0 +1,32 @@ +import os +import modules.recognize_codec as codec + + +class Tail: + def __init__(self, path): + self.__path = path + self.__tail = 0 + self.__last_change = 0 + self.__log_content = '' + self.__fmt = '' + + def __check_update(self): + self.__last_change = os.stat(self.__path).st_mtime + return self.__last_change + + def __recognize_format(self, path_to_file): + self.__fmt = codec.get_string_to_recognize(path_to_file) + + def get_lines(self): + if not self.__fmt: + self.__recognize_format(self.__path) + if self.__last_change < self.__check_update(): + with open(r'{0}'.format(self.__path), 'r', encoding='{}'.format(self.__fmt), errors='replace') as file: + file.seek(self.__tail) + self.__log_content = file.read() + self.__tail = file.tell() + return self.__log_content + else: + self.__log_content = '' + return self.__log_content + diff --git a/modules/recognize_codec.py b/modules/recognize_codec.py new file mode 100644 index 0000000..0f80372 --- /dev/null +++ b/modules/recognize_codec.py @@ -0,0 +1,87 @@ +__encodings = { + 'UTF-8': 'utf-8', + 'WIN-1251': 'windows-1251', + 'KOI8-R': 'koi8-r', + 'IBM866': 'ibm866', + 'ISO-8859-5': 'iso-8859-5', + 'MAC': 'mac', +} + +""" +Определение кодировки текста +""" + + +def __get_codepage(string=None): + uppercase = 1 + lowercase = 3 + utf_upper = 5 + utf_lower = 7 + code_pages = {} + for enc in __encodings.keys(): + code_pages[enc] = 0 + if string is not None and len(string) > 0: + last_simb = 0 + for simb in string: + simb_ord = simb + + """non-russian characters""" + if simb_ord < 128 or simb_ord > 256: + continue + + """UTF-8""" + if last_simb == 208 and (143 < simb_ord < 176 or simb_ord == 129): + code_pages['UTF-8'] += (utf_upper * 2) + if (last_simb == 208 and (simb_ord == 145 or 175 < simb_ord < 192)) \ + or (last_simb == 209 and (127 < simb_ord < 144)): + code_pages['UTF-8'] += (utf_lower * 2) + + """WIN-1251""" + if 223 < simb_ord < 256 or simb_ord == 184: + code_pages['WIN-1251'] += lowercase + if 191 < simb_ord < 224 or simb_ord == 168: + code_pages['WIN-1251'] += uppercase + + """KOI8-R""" + if 191 < simb_ord < 224 or simb_ord == 163: + code_pages['KOI8-R'] += lowercase + if 222 < simb_ord < 256 or simb_ord == 179: + code_pages['KOI8-R'] += uppercase + + """IBM866""" + if 159 < simb_ord < 176 or 223 < simb_ord < 241: + code_pages['IBM866'] += lowercase + if 127 < simb_ord < 160 or simb_ord == 241: + code_pages['IBM866'] += uppercase + + """ISO-8859-5""" + if 207 < simb_ord < 240 or simb_ord == 161: + code_pages['ISO-8859-5'] += lowercase + if 175 < simb_ord < 208 or simb_ord == 241: + code_pages['ISO-8859-5'] += uppercase + + """MAC""" + if 221 < simb_ord < 255: + code_pages['MAC'] += lowercase + if 127 < simb_ord < 160: + code_pages['MAC'] += uppercase + + last_simb = simb_ord + + idx = '' + maximum = 0 + for item in code_pages: + if code_pages[item] > maximum: + maximum = code_pages[item] + idx = item + if idx == 'WIN-1251': + idx = 'windows-1251' + if not maximum: + idx = 'ASCII' + return idx.lower() + + +def get_string_to_recognize(path): + with open(path, 'rb') as file: + string_massive = file.read(25000) + return __get_codepage(string_massive) diff --git a/modules/saver.py b/modules/saver.py new file mode 100644 index 0000000..31b5368 --- /dev/null +++ b/modules/saver.py @@ -0,0 +1,52 @@ +import zipfile +import os +from tkinter.filedialog import asksaveasfilename +from datetime import datetime + + +class Saver: + def __init__(self, all_tabs, tab_name=''): + self.tabs_paths = [] + self.tab_name = tab_name + self.all_tabs = all_tabs + self.original_path = '' + self.current_tab_text = '' + for tab in self.all_tabs: + self.tabs_paths.append(tab.path_to_file) + self.path_to_save = '' + + def save_all(self): + # get current time + current_moment = datetime.now().strftime('%Y%m%d_%H%M%S') + # set the save path + self.path_to_save = asksaveasfilename(title='Save archive', initialfile='{}.zip'.format(current_moment), + filetypes=(("ZIP File", "*.zip"), ("All files", "*.*"))) + if self.path_to_save: + with zipfile.ZipFile('{}'.format(self.path_to_save),'a', + compression=zipfile.ZIP_STORED, + allowZip64=True) as my_zip: + for tab in self.all_tabs: + self.current_tab_text = tab.get_all_text() + tmp_file = tab.tab_name + with open(tmp_file, 'w') as file: + file.write(self.current_tab_text) + my_zip.write(tmp_file) + os.remove(tmp_file) + else: + return + + def save_one(self): + for tab in self.all_tabs: + if tab.tab_name == self.tab_name: + self.current_tab_text = tab.get_all_text() + tmp_file = tab.tab_name + self.path_to_save = asksaveasfilename(title='Save file', + initialfile='{}'.format(tmp_file), + filetypes=(("Log File", "*.log"), + ("Text File", "*.txt"), + ("All files", "*.*"))) + if self.path_to_save: + with open(tmp_file, 'w') as file: + file.write(self.current_tab_text) + else: + return