Files
1C_Bases/Main_v3.py
T

358 lines
19 KiB
Python

import os
import tkinter as tk
from tkinter import messagebox
import pyodbc
import uuid
import sys
_BANNER = ("""
....:::::::::
::::::::::-----::::::
::::------------=--------::..
:::--=--============---==+===-:::.
::---========++=++++======+++++==--:.:
:--==++++++++++++++++++========---==-::.
--==++++++++++++++++++++=====---:::-==--::
:-==+**++++++++++++++++++====------::-==-::
-==+**++++++++++++++++++++=====--------=-::
-=++*++++++++++++++++++++++=========-----::
-=+++++++++++++++++++++++++===========----:
:=++++++++++++++++++++++++++==========---::
-=++++++++++++++++++===+++++===-----====--
-=++++++++++++++++++++==+++++=====-----==-
-++++++++++++++++++++++++++++++++====-----
==+++++++*#####*******++++++++++++++++=--=
+===++++++****##%%%%##*****++++**###%##*+==-
+*++=++++****#%##%@%%%%###***++*#%%%%%###*===-
+*++=++++**####*#%@%#%%%##**++*#%%%@@%+*%%*+=-
*++=+++++**+****####%%##*++==+#%%#%%#+++*+==-
+*+++++++++++****###****++=====*####*++=====-
+*=+++++++++++++++++++++++=======+**++=-----
+*=+++++++++++++++++***++++==-=====+++==----
++=+++++++++++++++*****+++==---===========--
+=+++++++++++++*****+++++===---++======--=-
==++++++++++**********+++++==--+++++=====-
-==+++++++++*********##*******==++++++===-
-==++++++++*****#**##*####**+====++++++==
#%%%%%===+++++++++***##*****####**++++++==+++=-
*+*#%%%#*+====++++++++**#%%%##****##********+====-
++++++*##%@%#+++**====+++++++*##############***###*+====-
++******###%%@@@@@%####*+=+==+++++**************+++++=+*++===-
#***+**#*###%@@@@@@@@@@@@@%###*+=+==++++*****++*********++==++++=-=+=+=-
#*#%%%###%%@@@@@@@@@@@@@@@@%###*++++++++**+************+++========*****++==-
%%%######%%####%@@@@@@@@@@@@@@%%**+++++++++++++++***+++=========*###******+++++==
%%@@@@%%%%%%%%%#%%@@@@@@@@@@@@@@%#**+*+**++++++++++++++++=====+%###########***++++*
%@@@@@@@@@%%@@%%%%%%%@@@@@@@@@@@@@@###*****++++++*+++++++===+**#%####%###%####***+***
%%%@@@@@@@@@@@@@@@@@@@%@@@@@@@@@@@@@@@@%####**++++***++++*#####*%##%####%#%%%%##*******
%%%%@%@@@@@@@@@@@@@@@@@@@%@@@@@@@@@@@@@@@@@@@%%%%%%##%%%@%%####*%%#%####%#%%%%%#########
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%%%##%#*#%#%####%##%%%%%%###%#%##
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%%#%%###%######%##%#%%%%%%%%%%%##
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%%%%%%%####%%#####%##%%%%%%%%%%%%%###
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%%%%####%%#%%##%#%%%%%%%%%%@%@%###
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%%%%#####%%%#%%%%#%%%%%%%@@@@@@@%%##
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%%%%%%%#%%%%##%%#%%%%%%%%%%%%@@@@@@@%%#**
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%%%%@@@%####%%%%%%%%%%%%%%%%%%%%%%%%%%%%%@@@@@@%%%#**
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%%%%%%@@@@%%%%%%%#%%%%%%%##%%%%%%%%%%%%%%%%%@@@@@@%%%%##**
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%%%%@@@@%%@@@@@@@%%%%%%##%%%%%%%###%%%%%%%%%%%%%%%%%@@%%%%%%%%##*+
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%%%@@@@@@@@%%%%@@@@@%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%###%%*#
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%%@@@@@@%@@@%@@@@@@@%%%@%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%@%%%%
""")
# В оконном режиме (сборка без консоли) sys.stdout может быть None — не роняем старт
if getattr(sys, "stdout", None):
try:
print(_BANNER)
except Exception:
pass
class RemoteFolderSelector(tk.Tk):
def __init__(self):
super().__init__()
self.title("Remote Folder and SQL Database Selector")
self.geometry("620x560")
# Создадим фреймы для лучшей компоновки элементов
self.frame_left = tk.Frame(self)
self.frame_left.grid(row=0, column=0, padx=10, pady=10, sticky="nsew")
self.frame_right = tk.Frame(self)
self.frame_right.grid(row=0, column=1, padx=10, pady=10, sticky="nsew")
# Сделаем окна масштабируемыми
self.grid_columnconfigure(0, weight=1)
self.grid_columnconfigure(1, weight=1)
self.grid_rowconfigure(0, weight=1)
# Текущий режим подключения (локально либо по IP)
self.ip_address = None
# Поле ввода IP-адреса
self.ip_label = tk.Label(self.frame_left, text="Enter IP (optional):")
self.ip_label.grid(row=0, column=0, padx=5, pady=5, sticky="w")
self.ip_entry = tk.Entry(self.frame_left, width=30)
self.ip_entry.grid(row=1, column=0, padx=5, pady=5, sticky="w")
self.connect_button = tk.Button(self.frame_left, text="Load Users", command=self.load_users)
self.connect_button.grid(row=2, column=0, padx=5, pady=5)
# Панель "отметить все / снять все" для пользователей
self.user_toolbar = tk.Frame(self.frame_left)
self.user_toolbar.grid(row=3, column=0, padx=5, pady=(5, 0), sticky="w")
tk.Button(self.user_toolbar, text="Отметить все",
command=lambda: self._set_all(self.user_vars, True)).pack(side="left", padx=(0, 4))
tk.Button(self.user_toolbar, text="Снять все",
command=lambda: self._set_all(self.user_vars, False)).pack(side="left")
# Список пользователей с чекбоксами
self.user_vars = {}
self.user_container, self.user_inner = self._create_scrollable_checklist(self.frame_left)
self.user_container.grid(row=4, column=0, padx=5, pady=5, sticky="nsew")
self.frame_left.grid_rowconfigure(4, weight=1)
self.frame_left.grid_columnconfigure(0, weight=1)
# Поля для ввода данных SQL
self.server_label = tk.Label(self.frame_right, text="SQL Server:")
self.server_label.grid(row=0, column=0, padx=5, pady=5, sticky="w")
self.server_entry = tk.Entry(self.frame_right, width=30)
self.server_entry.grid(row=1, column=0, padx=5, pady=5, sticky="w")
self.server_entry.insert(0, "SERVERSQL")
self.login_label = tk.Label(self.frame_right, text="SQL Login:")
self.login_label.grid(row=2, column=0, padx=5, pady=5, sticky="w")
self.login_entry = tk.Entry(self.frame_right, width=30)
self.login_entry.grid(row=3, column=0, padx=5, pady=5, sticky="w")
self.login_entry.insert(0, "sa")
self.password_label = tk.Label(self.frame_right, text="SQL Password:")
self.password_label.grid(row=4, column=0, padx=5, pady=5, sticky="w")
self.password_entry = tk.Entry(self.frame_right, show="*", width=30)
self.password_entry.grid(row=5, column=0, padx=5, pady=5, sticky="w")
self.sql_button = tk.Button(self.frame_right, text="Load Databases", command=self.load_databases)
self.sql_button.grid(row=6, column=0, padx=5, pady=5)
# Панель "отметить все / снять все" для баз
self.db_toolbar = tk.Frame(self.frame_right)
self.db_toolbar.grid(row=7, column=0, padx=5, pady=(5, 0), sticky="w")
tk.Button(self.db_toolbar, text="Отметить все",
command=lambda: self._set_all(self.db_vars, True)).pack(side="left", padx=(0, 4))
tk.Button(self.db_toolbar, text="Снять все",
command=lambda: self._set_all(self.db_vars, False)).pack(side="left")
# Список баз данных с чекбоксами
self.db_vars = {}
self.db_container, self.db_inner = self._create_scrollable_checklist(self.frame_right)
self.db_container.grid(row=8, column=0, padx=5, pady=5, sticky="nsew")
self.frame_right.grid_rowconfigure(8, weight=1)
self.frame_right.grid_columnconfigure(0, weight=1)
# Кнопка добавления базы
self.add_button = tk.Button(self.frame_left, text="Добавить отмеченные базы", command=self.add_database_to_ibases)
self.add_button.grid(row=5, column=0, padx=5, pady=5)
# Загрузка пользователей с локального компьютера по умолчанию
self.load_users()
def _create_scrollable_checklist(self, parent, width=200, height=180):
"""Создаём прокручиваемую область для чекбоксов. Возвращаем (container, inner)."""
container = tk.Frame(parent, borderwidth=1, relief="sunken")
canvas = tk.Canvas(container, width=width, height=height, highlightthickness=0)
scrollbar = tk.Scrollbar(container, orient="vertical", command=canvas.yview)
inner = tk.Frame(canvas)
inner.bind("<Configure>", lambda e: canvas.configure(scrollregion=canvas.bbox("all")))
canvas.create_window((0, 0), window=inner, anchor="nw")
canvas.configure(yscrollcommand=scrollbar.set)
canvas.pack(side="left", fill="both", expand=True)
scrollbar.pack(side="right", fill="y")
# Прокрутка колесом мыши, когда курсор над областью
def _on_mousewheel(event):
canvas.yview_scroll(int(-event.delta / 120), "units")
canvas.bind("<Enter>", lambda e: canvas.bind_all("<MouseWheel>", _on_mousewheel))
canvas.bind("<Leave>", lambda e: canvas.unbind_all("<MouseWheel>"))
return container, inner
def _populate_checklist(self, inner, vars_dict, items):
"""Пересоздаём чекбоксы в области inner по списку items."""
for child in inner.winfo_children():
child.destroy()
vars_dict.clear()
for item in items:
var = tk.BooleanVar(value=False)
vars_dict[item] = var
tk.Checkbutton(inner, text=item, variable=var, anchor="w").pack(fill="x", anchor="w")
def _set_all(self, vars_dict, value):
"""Отмечаем (True) либо снимаем (False) все чекбоксы."""
for var in vars_dict.values():
var.set(value)
def _get_checked(self, vars_dict):
"""Возвращаем список отмеченных элементов."""
return [name for name, var in vars_dict.items() if var.get()]
def load_users(self):
"""Загружаем пользователей с указанного IP-адреса или локального ПК."""
self.ip_address = self.ip_entry.get()
if not self.ip_address: # Если IP не указан, используем локальный ПК
local_path = r"C:\Users"
self.load_local_users(local_path)
else:
try:
# Используем указанный IP для подключения к удалённому ПК
remote_path = f"\\\\{self.ip_address}\\C$\\Users"
self.load_remote_users(remote_path)
except Exception as e:
messagebox.showerror("Error", f"Could not load users: {str(e)}")
def load_local_users(self, path):
"""Загружаем список пользователей с локального ПК."""
try:
folders = [f for f in os.listdir(path) if os.path.isdir(os.path.join(path, f))]
self._populate_checklist(self.user_inner, self.user_vars, folders)
except Exception as e:
messagebox.showerror("Error", f"Could not load local users: {str(e)}")
def load_remote_users(self, remote_path):
"""Загружаем список пользователей с удалённого ПК."""
try:
folders = [f for f in os.listdir(remote_path) if os.path.isdir(os.path.join(remote_path, f))]
self._populate_checklist(self.user_inner, self.user_vars, folders)
except Exception as e:
messagebox.showerror("Error", f"Could not load remote users: {str(e)}")
def load_databases(self):
"""Подключаемся к SQL серверу и загружаем список баз данных."""
server = self.server_entry.get()
login = self.login_entry.get()
password = self.password_entry.get()
try:
connection_string = f"DRIVER={{SQL Server}};SERVER={server};UID={login};PWD={password};"
connection = pyodbc.connect(connection_string)
cursor = connection.cursor()
cursor.execute("SELECT name FROM sys.databases")
databases = cursor.fetchall()
# Скрываем служебные базы SQL Server, они не являются базами 1С
system_dbs = {"master", "tempdb", "model", "msdb"}
names = [db[0] for db in databases if db[0].lower() not in system_dbs]
self._populate_checklist(self.db_inner, self.db_vars, names)
cursor.close()
connection.close()
except Exception as e:
messagebox.showerror("Error", f"Could not connect to SQL server: {str(e)}")
def get_ibases_path(self, user):
"""Возвращаем путь к ibases.v8i выбранного пользователя (локально либо по IP)."""
if not self.ip_address:
base_path = f"C:\\Users\\{user}\\AppData\\Roaming\\1C\\1CEStart"
else:
base_path = f"\\\\{self.ip_address}\\C$\\Users\\{user}\\AppData\\Roaming\\1C\\1CEStart"
return os.path.join(base_path, "ibases.v8i")
def build_base_entry(self, base, server):
"""Формируем запись одной базы для ibases.v8i."""
return (
f"\n[{base}]\n"
f'Connect=Srvr="{server}";Ref="{base}";\n'
f"ID={uuid.uuid4()}\n"
"OrderInList=255\n"
"Folder=/\n"
"OrderInTree=16640\n"
"External=0\n"
"ClientConnectionSpeed=Normal\n"
"App=Auto\n"
"WA=1\n"
"Version=8.3\n"
"DefaultApp=ThickClient\n"
"DisableLocalSpeechToText=0\n"
)
def add_database_to_ibases(self):
"""Добавляем все выбранные базы всем выбранным пользователям."""
# Актуализируем режим (локально / по IP) на момент добавления
self.ip_address = self.ip_entry.get().strip()
users = self._get_checked(self.user_vars)
bases = self._get_checked(self.db_vars)
server = self.server_entry.get().strip()
if not users:
messagebox.showerror("Error", "Не отмечен ни один пользователь!")
return
if not bases:
messagebox.showerror("Error", "Не отмечена ни одна база!")
return
if not server:
messagebox.showerror("Error", "Не указан SQL-сервер!")
return
added = 0
skipped = 0
errors = []
for user in users:
try:
ibases_path = self.get_ibases_path(user)
base_dir = os.path.dirname(ibases_path)
# Создаём каталог, если его нет
if not os.path.exists(base_dir):
os.makedirs(base_dir)
# Читаем текущее содержимое файла для проверки дублей
existing = ""
if os.path.exists(ibases_path):
with open(ibases_path, "r", encoding="utf-8") as f:
existing = f.read()
entries_to_add = []
for base in bases:
# Дубль определяем по строке подключения (сервер + имя базы)
connect_line = f'Connect=Srvr="{server}";Ref="{base}";'
already_present = connect_line in existing or any(
connect_line in e for e in entries_to_add
)
if already_present:
skipped += 1
continue
entries_to_add.append(self.build_base_entry(base, server))
added += 1
# Дописываем новые записи одним заходом
if entries_to_add:
with open(ibases_path, "a", encoding="utf-8") as f:
for entry in entries_to_add:
f.write(entry)
except Exception as e:
errors.append(f"{user}: {e}")
# Итоговый отчёт
summary = (
f"Пользователей: {len(users)}, баз: {len(bases)}\n"
f"Добавлено записей: {added}\n"
f"Пропущено (уже есть): {skipped}"
)
if errors:
summary += "\n\nОшибки:\n" + "\n".join(errors)
messagebox.showwarning("Готово с ошибками", summary)
else:
messagebox.showinfo("Готово", summary)
if __name__ == "__main__":
app = RemoteFolderSelector()
app.mainloop()