init
commit
2240204c5e
@ -0,0 +1,16 @@
|
||||
import sys
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
from main_window import MainWindow
|
||||
from database import create_tables, delete_old_logs
|
||||
|
||||
if __name__ == '__main__':
|
||||
create_tables()
|
||||
delete_old_logs()
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
window = MainWindow()
|
||||
window.show()
|
||||
sys.exit(app.exec_())
|
||||
|
||||
|
||||
# pyinstaller --onefile bunnysync.py --add-data "images/realrabbit.png;images" --add-data "images/realrabbit2.png;images" --icon=images/realrabbit.png --noconsole
|
@ -0,0 +1,87 @@
|
||||
import sqlite3
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
DB_NAME = 'my_data.db'
|
||||
|
||||
def get_db_conn():
|
||||
conn = sqlite3.connect(DB_NAME)
|
||||
return conn
|
||||
|
||||
def create_tables():
|
||||
conn = sqlite3.connect(DB_NAME)
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('''CREATE TABLE IF NOT EXISTS jobs (
|
||||
id INTEGER PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
folder1 TEXT NOT NULL,
|
||||
folder2 TEXT NOT NULL,
|
||||
interval INTEGER NOT NULL
|
||||
)''')
|
||||
cursor.execute('''CREATE TABLE IF NOT EXISTS logs (
|
||||
id INTEGER PRIMARY KEY,
|
||||
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
message TEXT NOT NULL
|
||||
)''')
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
def add_job_to_db(name, folder1, folder2, interval):
|
||||
conn = sqlite3.connect(DB_NAME)
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('INSERT INTO jobs (name, folder1, folder2, interval) VALUES (?, ?, ?, ?)', (name, folder1, folder2, interval))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
def get_jobs_from_db():
|
||||
conn = sqlite3.connect(DB_NAME)
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('SELECT * FROM jobs')
|
||||
jobs = cursor.fetchall()
|
||||
conn.close()
|
||||
return [{'name': job[1], 'folder1': job[2], 'folder2': job[3], 'interval': job[4]} for job in jobs]
|
||||
|
||||
def update_job_in_db(old_name, new_name, folder1, folder2, interval):
|
||||
conn = sqlite3.connect(DB_NAME)
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('''
|
||||
UPDATE jobs SET name = ?, folder1 = ?, folder2 = ?, interval = ? WHERE name = ?
|
||||
''', (new_name, folder1, folder2, interval, old_name))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
def delete_job_from_db(name):
|
||||
conn = sqlite3.connect(DB_NAME)
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('DELETE FROM jobs WHERE name = ?', (name,))
|
||||
cursor.execute(f"DROP TABLE IF EXISTS {name}")
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
def add_log_to_db(message):
|
||||
conn = sqlite3.connect(DB_NAME)
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('INSERT INTO logs (message) VALUES (?)', (message,))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
def get_logs_from_db():
|
||||
conn = sqlite3.connect(DB_NAME)
|
||||
cursor = conn.cursor()
|
||||
# cursor.execute('SELECT * FROM logs')
|
||||
cursor.execute('''
|
||||
SELECT *
|
||||
FROM logs
|
||||
ORDER BY timestamp DESC
|
||||
LIMIT 30
|
||||
''')
|
||||
logs = cursor.fetchall()
|
||||
conn.close()
|
||||
return [(log[1], log[2]) for log in logs]
|
||||
|
||||
def delete_old_logs():
|
||||
conn = sqlite3.connect(DB_NAME)
|
||||
cursor = conn.cursor()
|
||||
one_month_ago = datetime.now() - timedelta(days=30)
|
||||
cursor.execute('DELETE FROM logs WHERE timestamp < ?', (one_month_ago,))
|
||||
conn.commit()
|
||||
conn.close()
|
Binary file not shown.
After Width: | Height: | Size: 165 KiB |
Binary file not shown.
After Width: | Height: | Size: 132 KiB |
@ -0,0 +1,35 @@
|
||||
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel, QSizePolicy
|
||||
from PyQt5.QtGui import QPixmap
|
||||
from PyQt5.QtCore import Qt
|
||||
import sys, os
|
||||
|
||||
class InfoWidget(QWidget):
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.setup_ui()
|
||||
|
||||
def setup_ui(self):
|
||||
layout = QVBoxLayout()
|
||||
self.setLayout(layout)
|
||||
|
||||
image_label = QLabel()
|
||||
image_path = self.resource_path("images/realrabbit2.png")
|
||||
pixmap = QPixmap(image_path)
|
||||
scaled_pixmap = pixmap.scaled(400,500, Qt.KeepAspectRatio, Qt.SmoothTransformation)
|
||||
image_label.setPixmap(scaled_pixmap)
|
||||
image_label.setAlignment(Qt.AlignCenter)
|
||||
layout.addWidget(image_label)
|
||||
|
||||
self.info_label = QLabel('''
|
||||
This is \'Bunny Sync\' Application !\n
|
||||
Thank you for using the app 🤗\n
|
||||
App Version 1.0\n
|
||||
Made by PINB\n
|
||||
''')
|
||||
self.info_label.setAlignment(Qt.AlignCenter)
|
||||
layout.addWidget(self.info_label)
|
||||
|
||||
def resource_path(self, relative_path):
|
||||
if hasattr(sys, '_MEIPASS'):
|
||||
return os.path.join(sys._MEIPASS, relative_path)
|
||||
return os.path.join(os.path.abspath("."), relative_path)
|
@ -0,0 +1,75 @@
|
||||
from PyQt5.QtWidgets import QWidget, QHBoxLayout, QLabel, QPushButton
|
||||
from job_thread import JobThread
|
||||
|
||||
class JobItem(QWidget):
|
||||
def __init__(self, name, folder1, folder2, interval):
|
||||
super().__init__()
|
||||
self.name = name
|
||||
self.folder1 = folder1
|
||||
self.folder2 = folder2
|
||||
self.interval = interval
|
||||
self.thread = JobThread(self)
|
||||
self.setup_ui()
|
||||
|
||||
def setup_ui(self):
|
||||
layout = QHBoxLayout()
|
||||
self.setLayout(layout)
|
||||
|
||||
self.name_label = QLabel(self.name)
|
||||
self.name_label.setObjectName('jobitem')
|
||||
self.play_button = QPushButton("Play")
|
||||
self.sync_button = QPushButton("Sync")
|
||||
self.delete_button = QPushButton("Delete")
|
||||
|
||||
layout.addWidget(self.name_label)
|
||||
layout.addWidget(self.play_button)
|
||||
layout.addWidget(self.sync_button)
|
||||
layout.addWidget(self.delete_button)
|
||||
|
||||
self.setup_connections()
|
||||
|
||||
def setup_connections(self):
|
||||
self.play_button.clicked.connect(self.toggle_play)
|
||||
self.sync_button.clicked.connect(self.run_sync)
|
||||
self.thread.current_log.connect(self.update_log)
|
||||
|
||||
def toggle_play(self):
|
||||
if self.thread.is_running:
|
||||
self.thread.stop()
|
||||
self.play_button.setText("Play")
|
||||
self.play_button.setStyleSheet('''
|
||||
QPushButton {
|
||||
background-color: #558DED;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 5px 10px;
|
||||
border-radius: 5px;
|
||||
font-size: 14px;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background-color: #55ED86;
|
||||
}
|
||||
''')
|
||||
else:
|
||||
self.thread.start()
|
||||
self.play_button.setText("Pause")
|
||||
self.play_button.setStyleSheet('''
|
||||
QPushButton {
|
||||
background-color: #55ED86;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 5px 10px;
|
||||
border-radius: 5px;
|
||||
font-size: 14px;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background-color: #558DED;
|
||||
}
|
||||
''')
|
||||
|
||||
def run_sync(self):
|
||||
if not self.thread.is_running:
|
||||
self.thread.sync()
|
||||
|
||||
def update_log(self, msg, fg, bg):
|
||||
self.window().add_log(msg, fg, bg)
|
@ -0,0 +1,171 @@
|
||||
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QPushButton, QListWidget, QListWidgetItem
|
||||
from PyQt5.QtGui import QColor
|
||||
from PyQt5.QtCore import Qt
|
||||
from job_item import JobItem
|
||||
from database import add_job_to_db, get_jobs_from_db, update_job_in_db, delete_job_from_db
|
||||
|
||||
class JobListWidget(QWidget):
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.setup_ui()
|
||||
self.load_jobs_from_db()
|
||||
|
||||
def setup_ui(self):
|
||||
layout = QVBoxLayout()
|
||||
self.setLayout(layout)
|
||||
|
||||
job_info_widget = QWidget()
|
||||
job_info_layout = QVBoxLayout()
|
||||
job_info_widget.setLayout(job_info_layout)
|
||||
|
||||
name_layout = QHBoxLayout()
|
||||
name_label = QLabel("Job Name:")
|
||||
name_label.setFixedSize(150, 35)
|
||||
name_label.setObjectName('jobset')
|
||||
name_label.setAlignment(Qt.AlignCenter)
|
||||
name_layout.addWidget(name_label)
|
||||
self.job_name_edit = QLineEdit()
|
||||
name_layout.addWidget(self.job_name_edit)
|
||||
|
||||
folder1_layout = QHBoxLayout()
|
||||
folder1_label = QLabel("Folder 1:")
|
||||
folder1_label.setFixedSize(150, 35)
|
||||
folder1_label.setObjectName('jobset')
|
||||
folder1_label.setAlignment(Qt.AlignCenter)
|
||||
folder1_layout.addWidget(folder1_label)
|
||||
self.folder1_edit = QLineEdit()
|
||||
folder1_layout.addWidget(self.folder1_edit)
|
||||
|
||||
folder2_layout = QHBoxLayout()
|
||||
folder2_label = QLabel("Folder 2:")
|
||||
folder2_label.setFixedSize(150, 35)
|
||||
folder2_label.setObjectName('jobset')
|
||||
folder2_label.setAlignment(Qt.AlignCenter)
|
||||
folder2_layout.addWidget(folder2_label)
|
||||
self.folder2_edit = QLineEdit()
|
||||
folder2_layout.addWidget(self.folder2_edit)
|
||||
|
||||
interval_layout = QHBoxLayout()
|
||||
interval_label = QLabel("Interval:")
|
||||
interval_label.setFixedSize(150, 35)
|
||||
interval_label.setObjectName('jobset')
|
||||
interval_label.setAlignment(Qt.AlignCenter)
|
||||
interval_layout.addWidget(interval_label)
|
||||
self.interval_edit = QLineEdit()
|
||||
interval_layout.addWidget(self.interval_edit)
|
||||
|
||||
buttons_layout = QHBoxLayout()
|
||||
self.new_button = QPushButton("New")
|
||||
self.new_button.clicked.connect(self.new_job)
|
||||
buttons_layout.addWidget(self.new_button)
|
||||
|
||||
self.add_apply_button = QPushButton("Add")
|
||||
self.add_apply_button.clicked.connect(self.add_job)
|
||||
buttons_layout.addWidget(self.add_apply_button)
|
||||
|
||||
self.apply_button = QPushButton("Apply")
|
||||
self.apply_button.clicked.connect(self.apply_job)
|
||||
buttons_layout.addWidget(self.apply_button)
|
||||
|
||||
job_info_layout.addLayout(name_layout)
|
||||
job_info_layout.addLayout(folder1_layout)
|
||||
job_info_layout.addLayout(folder2_layout)
|
||||
job_info_layout.addLayout(interval_layout)
|
||||
job_info_layout.addLayout(buttons_layout)
|
||||
|
||||
layout.addWidget(job_info_widget)
|
||||
|
||||
self.job_list = QListWidget()
|
||||
self.job_list.itemClicked.connect(self.show_job_info)
|
||||
layout.addWidget(self.job_list)
|
||||
|
||||
def new_job(self):
|
||||
self.job_name_edit.clear()
|
||||
self.folder1_edit.clear()
|
||||
self.folder2_edit.clear()
|
||||
self.interval_edit.clear()
|
||||
self.window().add_log('Clicked \'New Job\' !', QColor('yellow'), QColor('green'))
|
||||
|
||||
def add_job(self):
|
||||
name = self.job_name_edit.text()
|
||||
folder1 = self.folder1_edit.text()
|
||||
folder2 = self.folder2_edit.text()
|
||||
interval = self.interval_edit.text()
|
||||
|
||||
if name and folder1 and folder2 and interval:
|
||||
# Check if job name already exists
|
||||
if name == 'jobs' or name == 'logs' or name == 'sqlite_sequence':
|
||||
self.window().add_log(f"Job with name '{name}' can not create.", QColor('white'), QColor('red'))
|
||||
return
|
||||
if any(self.job_list.itemWidget(self.job_list.item(i)).name == name for i in range(self.job_list.count())):
|
||||
self.window().add_log(f"Job with name '{name}' already exists.", QColor('white'), QColor('red'))
|
||||
return
|
||||
|
||||
job_item = JobItem(name, folder1, folder2, int(interval))
|
||||
list_item = QListWidgetItem(self.job_list)
|
||||
list_item.setSizeHint(job_item.sizeHint())
|
||||
self.job_list.addItem(list_item)
|
||||
self.job_list.setItemWidget(list_item, job_item)
|
||||
|
||||
job_item.delete_button.clicked.connect(lambda checked, item=list_item: self.delete_job(item))
|
||||
|
||||
add_job_to_db(name, folder1, folder2, int(interval))
|
||||
self.window().add_log(f'Clicked \'Add Job\' - ({job_item.name}) !', QColor('yellow'), QColor('green'))
|
||||
|
||||
def delete_job(self, list_item):
|
||||
job_item = self.job_list.itemWidget(list_item)
|
||||
if job_item.thread.is_running:
|
||||
job_item.thread.stop()
|
||||
|
||||
self.job_list.takeItem(self.job_list.row(list_item))
|
||||
delete_job_from_db(job_item.name)
|
||||
self.window().add_log(f'Clicked \'delete Job\' - ({job_item.name}) !', QColor('red'), QColor('green'))
|
||||
|
||||
def show_job_info(self, item):
|
||||
job_item = self.job_list.itemWidget(item)
|
||||
self.job_name_edit.setText(job_item.name)
|
||||
self.folder1_edit.setText(job_item.folder1)
|
||||
self.folder2_edit.setText(job_item.folder2)
|
||||
self.interval_edit.setText(str(job_item.interval))
|
||||
|
||||
def apply_job(self):
|
||||
self.selected_items = self.job_list.selectedItems()
|
||||
if self.selected_items:
|
||||
job_item = self.job_list.itemWidget(self.selected_items[0])
|
||||
old_name = job_item.name
|
||||
|
||||
new_name = self.job_name_edit.text()
|
||||
folder1 = self.folder1_edit.text()
|
||||
folder2 = self.folder2_edit.text()
|
||||
interval = self.interval_edit.text()
|
||||
|
||||
if new_name and folder1 and folder2 and interval:
|
||||
# Check if new job name already exists in other jobs
|
||||
if old_name != new_name and any(self.job_list.itemWidget(self.job_list.item(i)).name == new_name for i in range(self.job_list.count())):
|
||||
self.window().add_log(f"Job with name '{old_name}' already exists.", QColor('white'), QColor('red'))
|
||||
return
|
||||
|
||||
# Update the job item
|
||||
job_item.name = new_name
|
||||
job_item.folder1 = folder1
|
||||
job_item.folder2 = folder2
|
||||
job_item.interval = int(interval)
|
||||
job_item.name_label.setText(new_name)
|
||||
|
||||
# Update the database
|
||||
update_job_in_db(old_name, new_name, folder1, folder2, int(interval))
|
||||
|
||||
self.window().add_log(f'Clicked \'Apply Job\' - ({job_item.name}) !', QColor('yellow'), QColor('green'))
|
||||
|
||||
def load_jobs_from_db(self):
|
||||
jobs = get_jobs_from_db()
|
||||
for job in jobs:
|
||||
job_item = JobItem(job['name'], job['folder1'], job['folder2'], job['interval'])
|
||||
list_item = QListWidgetItem(self.job_list)
|
||||
list_item.setSizeHint(job_item.sizeHint())
|
||||
self.job_list.addItem(list_item)
|
||||
self.job_list.setItemWidget(list_item, job_item)
|
||||
|
||||
job_item.delete_button.clicked.connect(lambda checked, item=list_item: self.delete_job(item))
|
||||
|
||||
|
@ -0,0 +1,30 @@
|
||||
from PyQt5.QtCore import QThread, pyqtSignal
|
||||
from PyQt5.QtGui import QColor
|
||||
import time
|
||||
from sync import FolderSynchronizer
|
||||
|
||||
class JobThread(QThread):
|
||||
current_log = pyqtSignal(str, QColor, QColor)
|
||||
|
||||
def __init__(self, job_item):
|
||||
super().__init__()
|
||||
self.name = job_item.name
|
||||
self.folder1 = job_item.folder1
|
||||
self.folder2 = job_item.folder2
|
||||
self.interval = job_item.interval
|
||||
self.is_running = False
|
||||
self.synchronizer = FolderSynchronizer(self.name, self.folder1, self.folder2, self.interval)
|
||||
|
||||
def run(self):
|
||||
self.is_running = True
|
||||
while self.is_running:
|
||||
log = self.synchronizer.sync_folders()
|
||||
self.current_log.emit(log, QColor("white"), QColor("green"))
|
||||
time.sleep(self.interval)
|
||||
|
||||
def stop(self):
|
||||
self.is_running = False
|
||||
|
||||
def sync(self):
|
||||
log = self.synchronizer.sync_folders()
|
||||
self.current_log.emit(log, QColor("yellow"), QColor("green"))
|
@ -0,0 +1,93 @@
|
||||
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QLineEdit, QTextEdit, QPushButton
|
||||
from PyQt5.QtGui import QTextCursor, QTextBlockFormat, QTextCharFormat, QColor
|
||||
from database import add_log_to_db, get_logs_from_db
|
||||
from datetime import datetime
|
||||
|
||||
class LogWidget(QWidget):
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.setup_ui()
|
||||
self.load_logs_from_db()
|
||||
|
||||
def setup_ui(self):
|
||||
layout = QVBoxLayout()
|
||||
self.setLayout(layout)
|
||||
|
||||
self.log_text = QTextEdit()
|
||||
self.log_text.setReadOnly(True)
|
||||
layout.addWidget(self.log_text)
|
||||
|
||||
self.log_input = QLineEdit()
|
||||
self.log_input.setPlaceholderText("Enter log message")
|
||||
layout.addWidget(self.log_input)
|
||||
|
||||
buttons_layout = QHBoxLayout()
|
||||
log_button = QPushButton("Add Log")
|
||||
log_button.clicked.connect(self.add_uilog)
|
||||
buttons_layout.addWidget(log_button)
|
||||
clear_button = QPushButton("Clear Log")
|
||||
clear_button.clicked.connect(self.clear_log)
|
||||
buttons_layout.addWidget(clear_button)
|
||||
|
||||
buttons_widget = QWidget()
|
||||
buttons_widget.setLayout(buttons_layout)
|
||||
layout.addWidget(buttons_widget)
|
||||
|
||||
def get_line_count(self):
|
||||
document = self.log_text.document()
|
||||
return document.blockCount()
|
||||
|
||||
def delete_first_line(self):
|
||||
cursor = self.log_text.textCursor()
|
||||
cursor.movePosition(QTextCursor.Start)
|
||||
cursor.movePosition(QTextCursor.NextBlock, QTextCursor.KeepAnchor)
|
||||
cursor.removeSelectedText()
|
||||
self.log_text.setTextCursor(cursor)
|
||||
|
||||
def delete_lines(self, n):
|
||||
cursor = self.log_text.textCursor()
|
||||
cursor.movePosition(QTextCursor.Start)
|
||||
|
||||
for _ in range(n):
|
||||
if cursor.atEnd():
|
||||
break
|
||||
cursor.movePosition(QTextCursor.NextBlock, QTextCursor.KeepAnchor)
|
||||
cursor.removeSelectedText()
|
||||
|
||||
self.log_text.setTextCursor(cursor)
|
||||
|
||||
def clear_log(self):
|
||||
self.log_text.clear()
|
||||
|
||||
def update_log(self, message, foreground=QColor("black"), background=QColor("white")):
|
||||
char_format = QTextCharFormat()
|
||||
char_format.setForeground(foreground)
|
||||
char_format.setBackground(background)
|
||||
|
||||
block_format = QTextBlockFormat()
|
||||
block_format.setBackground(background)
|
||||
|
||||
self.log_text.moveCursor(QTextCursor.End)
|
||||
cursor = self.log_text.textCursor()
|
||||
cursor.insertBlock(block_format)
|
||||
cursor.insertText(message, char_format)
|
||||
self.log_text.setTextCursor(cursor)
|
||||
|
||||
def add_log(self, message, foreground=QColor("black"), background=QColor("white")):
|
||||
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||
log_message = f"[{timestamp}] {message}"
|
||||
self.update_log(log_message, foreground, background)
|
||||
add_log_to_db(message)
|
||||
self.log_input.clear()
|
||||
if 3000 < self.get_line_count():
|
||||
self.delete_first_line()
|
||||
|
||||
def add_uilog(self):
|
||||
message = self.log_input.text()
|
||||
self.add_log(message)
|
||||
|
||||
def load_logs_from_db(self):
|
||||
logs = get_logs_from_db()
|
||||
for timestamp, message in logs:
|
||||
log_message = f"[{timestamp}] {message}"
|
||||
self.update_log(log_message, QColor("dark"), QColor("gray"))
|
@ -0,0 +1,129 @@
|
||||
from PyQt5.QtWidgets import QMainWindow, QVBoxLayout, QHBoxLayout, QWidget, QPushButton, QLabel
|
||||
from PyQt5.QtGui import QColor, QIcon
|
||||
from job_list_widget import JobListWidget
|
||||
from log_widget import LogWidget
|
||||
from info_widget import InfoWidget
|
||||
import sys, os
|
||||
|
||||
class MainWindow(QMainWindow):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.setWindowTitle("Bunny Sync Application")
|
||||
self.setGeometry(100, 100, 800, 600)
|
||||
image_path = self.resource_path("images/realrabbit.png")
|
||||
self.setWindowIcon(QIcon(image_path))
|
||||
self.setup_ui()
|
||||
|
||||
def resource_path(self, relative_path):
|
||||
if hasattr(sys, '_MEIPASS'):
|
||||
return os.path.join(sys._MEIPASS, relative_path)
|
||||
return os.path.join(os.path.abspath("."), relative_path)
|
||||
|
||||
def setup_ui(self):
|
||||
main_widget = QWidget()
|
||||
main_layout = QHBoxLayout()
|
||||
main_widget.setLayout(main_layout)
|
||||
self.setCentralWidget(main_widget)
|
||||
|
||||
left_panel = QWidget()
|
||||
left_layout = QVBoxLayout()
|
||||
left_panel.setLayout(left_layout)
|
||||
left_panel.setObjectName('menu')
|
||||
left_panel.setFixedWidth(80)
|
||||
|
||||
self.list_button = QPushButton("List")
|
||||
self.log_button = QPushButton("Log")
|
||||
self.info_button = QPushButton("Info")
|
||||
left_layout.addWidget(self.list_button)
|
||||
left_layout.addWidget(self.log_button)
|
||||
left_layout.addWidget(self.info_button)
|
||||
left_layout.addStretch()
|
||||
|
||||
self.right_panel = QWidget()
|
||||
self.right_layout = QVBoxLayout()
|
||||
self.right_panel.setLayout(self.right_layout)
|
||||
|
||||
main_layout.addWidget(left_panel)
|
||||
main_layout.addWidget(self.right_panel)
|
||||
|
||||
self.job_list_widget = JobListWidget(self)
|
||||
self.log_widget = LogWidget(self)
|
||||
self.info_widget = InfoWidget(self)
|
||||
|
||||
self.right_layout.addWidget(self.job_list_widget)
|
||||
self.right_layout.addWidget(self.log_widget)
|
||||
self.right_layout.addWidget(self.info_widget)
|
||||
|
||||
self.list_button.clicked.connect(self.show_job_list)
|
||||
self.log_button.clicked.connect(self.show_log)
|
||||
self.info_button.clicked.connect(self.show_info)
|
||||
|
||||
self.setStyleSheet("""
|
||||
QWidget {
|
||||
background-color: #C8F0F0;
|
||||
border-radius: 10px;
|
||||
padding: 10px;
|
||||
}
|
||||
QTextEdit {
|
||||
background-color: #ffffff;
|
||||
color: #333333;
|
||||
border-radius: 10px;
|
||||
padding: 10px;
|
||||
}
|
||||
QLineEdit {
|
||||
background-color: #56BFF0;
|
||||
color: #333333;
|
||||
border-radius: 10px;
|
||||
padding: 10px;
|
||||
}
|
||||
QLabel {
|
||||
color: #333333;
|
||||
font-size: 14px;
|
||||
}
|
||||
QLabel#jobset {
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
}
|
||||
QLabel#jobitem {
|
||||
background-color: #ffffff;
|
||||
font-size: 10px;
|
||||
font-weight: bold;
|
||||
}
|
||||
QPushButton {
|
||||
background-color: #558DED;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 5px 10px;
|
||||
border-radius: 5px;
|
||||
font-size: 14px;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background-color: #55ED86;
|
||||
}
|
||||
QListWidget {
|
||||
background-color: #56BFF0;
|
||||
}
|
||||
QWidget#menu {
|
||||
background-color: #55EDED;
|
||||
}
|
||||
""")
|
||||
|
||||
self.show_job_list()
|
||||
|
||||
def show_job_list(self):
|
||||
self.job_list_widget.show()
|
||||
self.log_widget.hide()
|
||||
self.info_widget.hide()
|
||||
|
||||
def show_log(self):
|
||||
self.job_list_widget.hide()
|
||||
self.log_widget.show()
|
||||
self.info_widget.hide()
|
||||
|
||||
def show_info(self):
|
||||
self.job_list_widget.hide()
|
||||
self.log_widget.hide()
|
||||
self.info_widget.show()
|
||||
|
||||
def add_log(self, message, foreground=QColor("black"), background=QColor("white")):
|
||||
self.log_widget.add_log(message, foreground, background)
|
Binary file not shown.
@ -0,0 +1,151 @@
|
||||
import os
|
||||
import shutil
|
||||
import time
|
||||
import datetime
|
||||
import sqlite3
|
||||
import threading
|
||||
|
||||
DB_NAME = 'my_data.db'
|
||||
|
||||
class FolderSynchronizer:
|
||||
def __init__(self, name, folder1, folder2, interval):
|
||||
self.name = name
|
||||
self.folder1 = folder1
|
||||
self.folder2 = folder2
|
||||
self.interval = interval
|
||||
self.local = threading.local()
|
||||
self.create_table()
|
||||
|
||||
def get_connection(self):
|
||||
if not hasattr(self.local, 'conn'):
|
||||
self.local.conn = sqlite3.connect(DB_NAME)
|
||||
return self.local.conn
|
||||
|
||||
def get_cursor(self):
|
||||
return self.get_connection().cursor()
|
||||
|
||||
def create_table(self):
|
||||
conn = self.get_connection()
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(f'''
|
||||
CREATE TABLE IF NOT EXISTS {self.name} (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
rel_dir TEXT,
|
||||
file_name TEXT,
|
||||
modified1 REAL,
|
||||
exist1 INTEGER,
|
||||
modified2 REAL,
|
||||
exist2 INTEGER,
|
||||
UNIQUE(rel_dir, file_name)
|
||||
)
|
||||
''')
|
||||
conn.commit()
|
||||
|
||||
# def delete_job(self):
|
||||
# conn = self.get_connection()
|
||||
# cursor = conn.cursor()
|
||||
# cursor.execute(f"DROP TABLE IF EXISTS {self.name}")
|
||||
# conn.commit()
|
||||
# print(f"Job '{self.name}' has been deleted.")
|
||||
|
||||
def update_db(self, rel_dir, file_name, modified1, exist1, modified2, exist2):
|
||||
conn = self.get_connection()
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(f'''
|
||||
INSERT OR REPLACE INTO {self.name}
|
||||
(rel_dir, file_name, modified1, exist1, modified2, exist2)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
''', (rel_dir, file_name, modified1, exist1, modified2, exist2))
|
||||
conn.commit()
|
||||
|
||||
def get_file_info(self, rel_dir, file_name):
|
||||
conn = self.get_connection()
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(f'''
|
||||
SELECT * FROM {self.name}
|
||||
WHERE rel_dir = ? AND file_name = ?
|
||||
''', (rel_dir, file_name))
|
||||
return cursor.fetchone()
|
||||
|
||||
def sync_folders(self):
|
||||
def sync_files(src_folder, dest_folder):
|
||||
current_time = time.time()
|
||||
total_sync = 0
|
||||
count_sync = 0
|
||||
for root, _, files in os.walk(src_folder):
|
||||
for filename in files:
|
||||
src_path = os.path.join(root, filename)
|
||||
rel_dir = os.path.relpath(root, src_folder)
|
||||
dest_path = os.path.join(dest_folder, rel_dir, filename)
|
||||
total_sync += 1
|
||||
|
||||
src_modified = os.path.getmtime(src_path)
|
||||
file_info = self.get_file_info(rel_dir, filename)
|
||||
|
||||
if not file_info:
|
||||
# New file
|
||||
os.makedirs(os.path.dirname(dest_path), exist_ok=True)
|
||||
shutil.copy2(src_path, dest_path)
|
||||
count_sync += 1
|
||||
dest_modified = current_time
|
||||
modified1, exist1, modified2, exist2 = src_modified, 1, dest_modified, 1
|
||||
else:
|
||||
# Existing file
|
||||
if src_modified > file_info[3] or not file_info[4]:
|
||||
shutil.copy2(src_path, dest_path)
|
||||
count_sync += 1
|
||||
dest_modified = current_time
|
||||
modified1, exist1, modified2, exist2 = src_modified, 1, dest_modified, 1
|
||||
else:
|
||||
modified1, exist1, modified2, exist2 = src_modified, 1, file_info[5], file_info[6]
|
||||
|
||||
self.update_db(rel_dir, filename, modified1, exist1, modified2, exist2)
|
||||
|
||||
return total_sync, count_sync
|
||||
|
||||
def check_deleted_files():
|
||||
conn = self.get_connection()
|
||||
cursor = conn.cursor()
|
||||
current_time = time.time()
|
||||
count_sync = 0
|
||||
cursor.execute(f"SELECT rel_dir, file_name, modified1, exist1, modified2, exist2 FROM {self.name}")
|
||||
for row in cursor.fetchall():
|
||||
rel_dir, file_name, modified1, exist1, modified2, exist2 = row
|
||||
path1 = os.path.join(self.folder1, rel_dir, file_name)
|
||||
path2 = os.path.join(self.folder2, rel_dir, file_name)
|
||||
|
||||
if not os.path.exists(path1) and not os.path.exists(path2):
|
||||
count_sync += 1
|
||||
cursor.execute(f"DELETE FROM {self.name} WHERE rel_dir = ? AND file_name = ?", (rel_dir, file_name))
|
||||
elif not os.path.exists(path1) and exist1:
|
||||
modified1 = current_time
|
||||
if modified1 > modified2:
|
||||
count_sync += 1
|
||||
os.remove(path2)
|
||||
cursor.execute(f"DELETE FROM {self.name} WHERE rel_dir = ? AND file_name = ?", (rel_dir, file_name))
|
||||
else:
|
||||
self.update_db(rel_dir, file_name, modified1, 0, modified2, exist2)
|
||||
elif not os.path.exists(path2) and exist2:
|
||||
modified2 = current_time
|
||||
if modified2 > modified1:
|
||||
count_sync += 1
|
||||
os.remove(path1)
|
||||
cursor.execute(f"DELETE FROM {self.name} WHERE rel_dir = ? AND file_name = ?", (rel_dir, file_name))
|
||||
else:
|
||||
self.update_db(rel_dir, file_name, modified1, exist1, modified2, 0)
|
||||
|
||||
conn.commit()
|
||||
return count_sync
|
||||
|
||||
total_sync1, count_sync1 = sync_files(self.folder1, self.folder2)
|
||||
total_sync2, count_sync2 = sync_files(self.folder2, self.folder1)
|
||||
count_sync3 = check_deleted_files()
|
||||
|
||||
total_sync = max(total_sync1, total_sync2)
|
||||
count_sync = count_sync1 + count_sync2 + count_sync3
|
||||
|
||||
# now = datetime.datetime.now()
|
||||
# formatted_datetime = now.strftime("[%Y-%m-%d %H:%M:%S]")
|
||||
|
||||
# return f"[{self.name}] \'{self.folder1} <-> {self.folder2}\' Sync completed! (sync count: {count_sync}, total files: {total_sync})"
|
||||
return f"[{self.name}] Sync completed! (sync count: {count_sync}, total files: {total_sync})"
|
Loading…
Reference in New Issue