You've already forked OneDriveGUI
mirror of
https://github.com/bpozdena/OneDriveGUI.git
synced 2026-10-24 04:59:17 +03:00
Enhance file transfer UI with timestamp display #97
This commit is contained in:
@@ -39,7 +39,7 @@ from options import (
|
||||
version,
|
||||
)
|
||||
|
||||
from utils.utils import humanize_file_size, shorten_path
|
||||
from utils.utils import humanize_file_size, shorten_path, format_relative_time
|
||||
from workers import WorkerThread, MaintenanceWorker, TaskList, workers
|
||||
from gui_settings_window import gui_settings_window
|
||||
|
||||
@@ -131,6 +131,11 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
||||
self.profile_status_pages[profile].quit_gui_signal.connect(self.graceful_shutdown)
|
||||
self.stackedLayout.addWidget(self.profile_status_pages[profile])
|
||||
|
||||
# Connect scroll events for timestamp updates
|
||||
listWidget = self.profile_status_pages[profile].listWidget
|
||||
scrollbar = listWidget.verticalScrollBar()
|
||||
scrollbar.valueChanged.connect(self.on_list_scrolled)
|
||||
|
||||
self.verticalLayout_2.addLayout(self.stackedLayout)
|
||||
|
||||
# Ensure all profiles in comboBox are aligned to center
|
||||
@@ -203,6 +208,18 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
||||
self.auto_sync.timeout.connect(self.autostart_monitor)
|
||||
self.auto_sync.start(1000)
|
||||
|
||||
# Timer to refresh relative times for completed transfers
|
||||
self.refresh_relative_times = QTimer()
|
||||
self.refresh_relative_times.setSingleShot(False)
|
||||
self.refresh_relative_times.timeout.connect(self.update_relative_times)
|
||||
self.refresh_relative_times.start(60000) # Update every 60 seconds
|
||||
|
||||
# Debounce timer for scroll-triggered updates
|
||||
self.scroll_update_timer = QTimer()
|
||||
self.scroll_update_timer.setSingleShot(True)
|
||||
self.scroll_update_timer.timeout.connect(self.update_relative_times)
|
||||
self.scroll_update_timer.setInterval(200) # 200ms debounce delay
|
||||
|
||||
def mousePressEvent(self, event):
|
||||
if gui_settings.get("frameless_window") == "True":
|
||||
self.dragPos = event.globalPosition().toPoint()
|
||||
@@ -841,20 +858,48 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
||||
if file_operation == "Deleting":
|
||||
item_widget.set_label_1(f"Deleted from {parent_dir}")
|
||||
item_widget.set_label_2(f"")
|
||||
# Store timestamp and display for deleted files
|
||||
if "timestamp" in data and data["timestamp"]:
|
||||
item_widget.set_completion_timestamp(data["timestamp"])
|
||||
relative_time = format_relative_time(data["timestamp"])
|
||||
item_widget.set_timestamp(relative_time)
|
||||
else:
|
||||
item_widget.set_timestamp("")
|
||||
|
||||
elif file_operation == "Moving":
|
||||
item_widget.set_label_1(f"Trashed from {parent_dir}")
|
||||
item_widget.set_label_2(f"")
|
||||
# Store timestamp and display for moved files
|
||||
if "timestamp" in data and data["timestamp"]:
|
||||
item_widget.set_completion_timestamp(data["timestamp"])
|
||||
relative_time = format_relative_time(data["timestamp"])
|
||||
item_widget.set_timestamp(relative_time)
|
||||
else:
|
||||
item_widget.set_timestamp("")
|
||||
|
||||
elif transfer_complete and file_operation == "Uploading":
|
||||
shortened_path = shorten_path(relative_path_display, 32)
|
||||
item_widget.set_label_1(f"Uploaded from <a href=file:///{absolute_path}>{shortened_path}</a>")
|
||||
item_widget.set_label_2(f"{file_size_human}")
|
||||
# Store timestamp and display in separate timestamp label
|
||||
if "timestamp" in data and data["timestamp"]:
|
||||
item_widget.set_completion_timestamp(data["timestamp"])
|
||||
relative_time = format_relative_time(data["timestamp"])
|
||||
item_widget.set_timestamp(relative_time)
|
||||
else:
|
||||
item_widget.set_timestamp("")
|
||||
|
||||
elif transfer_complete and file_operation == "Downloading":
|
||||
shortened_path = shorten_path(relative_path_display, 32)
|
||||
item_widget.set_label_1(f"Downloaded from <a href=file:///{absolute_path}>{shortened_path}</a>")
|
||||
item_widget.set_label_2(f"{file_size_human}")
|
||||
# Store timestamp and display in separate timestamp label
|
||||
if "timestamp" in data and data["timestamp"]:
|
||||
item_widget.set_completion_timestamp(data["timestamp"])
|
||||
relative_time = format_relative_time(data["timestamp"])
|
||||
item_widget.set_timestamp(relative_time)
|
||||
else:
|
||||
item_widget.set_timestamp("")
|
||||
|
||||
elif file_operation == "Downloading":
|
||||
# Estimate final size of file before download completes
|
||||
@@ -881,20 +926,48 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
||||
if file_operation == "Deleting":
|
||||
myQCustomQWidget.set_label_1(f"Deleted from {parent_dir}")
|
||||
myQCustomQWidget.set_label_2(f"")
|
||||
# Store timestamp and display for deleted files
|
||||
if "timestamp" in data and data["timestamp"]:
|
||||
myQCustomQWidget.set_completion_timestamp(data["timestamp"])
|
||||
relative_time = format_relative_time(data["timestamp"])
|
||||
myQCustomQWidget.set_timestamp(relative_time)
|
||||
else:
|
||||
myQCustomQWidget.set_timestamp("")
|
||||
|
||||
elif file_operation == "Moving":
|
||||
myQCustomQWidget.set_label_1(f"Trashed from {parent_dir}")
|
||||
myQCustomQWidget.set_label_2(f"")
|
||||
# Store timestamp and display for moved files
|
||||
if "timestamp" in data and data["timestamp"]:
|
||||
myQCustomQWidget.set_completion_timestamp(data["timestamp"])
|
||||
relative_time = format_relative_time(data["timestamp"])
|
||||
myQCustomQWidget.set_timestamp(relative_time)
|
||||
else:
|
||||
myQCustomQWidget.set_timestamp("")
|
||||
|
||||
elif transfer_complete and file_operation == "Uploading":
|
||||
shortened_path = shorten_path(relative_path_display, 32)
|
||||
myQCustomQWidget.set_label_1(f"Uploaded from <a href=file:///{absolute_path}>{shortened_path}</a>")
|
||||
myQCustomQWidget.set_label_2(f"{file_size_human}")
|
||||
# Store timestamp and display in separate timestamp label
|
||||
if "timestamp" in data and data["timestamp"]:
|
||||
myQCustomQWidget.set_completion_timestamp(data["timestamp"])
|
||||
relative_time = format_relative_time(data["timestamp"])
|
||||
myQCustomQWidget.set_timestamp(relative_time)
|
||||
else:
|
||||
myQCustomQWidget.set_timestamp("")
|
||||
|
||||
elif transfer_complete and file_operation == "Downloading":
|
||||
shortened_path = shorten_path(relative_path_display, 32)
|
||||
myQCustomQWidget.set_label_1(f"Downloaded from <a href=file:///{absolute_path}>{shortened_path}</a>")
|
||||
myQCustomQWidget.set_label_2(f"{file_size_human}")
|
||||
# Store timestamp and display in separate timestamp label
|
||||
if "timestamp" in data and data["timestamp"]:
|
||||
myQCustomQWidget.set_completion_timestamp(data["timestamp"])
|
||||
relative_time = format_relative_time(data["timestamp"])
|
||||
myQCustomQWidget.set_timestamp(relative_time)
|
||||
else:
|
||||
myQCustomQWidget.set_timestamp("")
|
||||
|
||||
elif file_operation == "Downloading":
|
||||
# Estimate final size of file before download completes
|
||||
@@ -926,6 +999,54 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
||||
if listWidget.count() > 10_000:
|
||||
listWidget.takeItem(listWidget.count() - 1)
|
||||
|
||||
def on_list_scrolled(self, value):
|
||||
"""
|
||||
Debounced scroll handler - triggers timestamp update after scrolling stops.
|
||||
Called when scrollbar position changes (user scroll or automatic scroll from new items).
|
||||
"""
|
||||
# Restart the debounce timer - only triggers after 200ms of no scrolling
|
||||
self.scroll_update_timer.start()
|
||||
|
||||
def update_relative_times(self):
|
||||
"""
|
||||
Update relative time display only for visible items in the viewport.
|
||||
Optimized to update only 10-20 visible items instead of all 10,000 items.
|
||||
Called every 60 seconds by QTimer and when user scrolls the list.
|
||||
"""
|
||||
for profile in self.profile_status_pages:
|
||||
listWidget = self.profile_status_pages[profile].listWidget
|
||||
|
||||
# Get the visible viewport area
|
||||
rect = listWidget.viewport().contentsRect()
|
||||
top_index = listWidget.indexAt(rect.topLeft())
|
||||
|
||||
# Skip if no items are visible
|
||||
if not top_index.isValid():
|
||||
continue
|
||||
|
||||
# Get the bottom visible index
|
||||
bottom_index = listWidget.indexAt(rect.bottomLeft())
|
||||
|
||||
# Handle case where bottom index is invalid (last item is visible)
|
||||
if not bottom_index.isValid():
|
||||
bottom_row = listWidget.count() - 1
|
||||
else:
|
||||
bottom_row = bottom_index.row()
|
||||
|
||||
# Only iterate through visible rows (typically 10-20 items instead of 10,000)
|
||||
for row in range(top_index.row(), bottom_row + 1):
|
||||
item = listWidget.item(row)
|
||||
if item is not None:
|
||||
item_widget = listWidget.itemWidget(item)
|
||||
if item_widget is not None:
|
||||
# Get the completion timestamp
|
||||
timestamp = item_widget.get_completion_timestamp()
|
||||
|
||||
# If item has a timestamp, update the relative time in the timestamp label
|
||||
if timestamp is not None:
|
||||
relative_time = format_relative_time(timestamp)
|
||||
item_widget.set_timestamp(relative_time)
|
||||
|
||||
def show_login(self, profile):
|
||||
# Show login window with QT WebEngine
|
||||
self.window1 = QWidget()
|
||||
|
||||
@@ -67,11 +67,11 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1" colspan="2">
|
||||
<item row="0" column="1">
|
||||
<widget class="QLabel" name="ls_label_file_name">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>340</width>
|
||||
<width>270</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
@@ -85,6 +85,43 @@
|
||||
<property name="text">
|
||||
<string>FileName</string>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::PlainText</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<widget class="QLabel" name="ls_label_timestamp">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>70</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>70</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>8</pointsize>
|
||||
<weight>50</weight>
|
||||
</font>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">color: rgba(128, 128, 128, 200);</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string></string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::PlainText</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="2" rowspan="2">
|
||||
@@ -107,6 +144,12 @@
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::PlainText</enum>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1" rowspan="2">
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
################################################################################
|
||||
## Form generated from reading UI file 'list_item_widget.ui'
|
||||
##
|
||||
## Created by: Qt User Interface Compiler version 6.2.2
|
||||
## Created by: Qt User Interface Compiler version 6.10.0
|
||||
##
|
||||
## WARNING! All changes made in this file will be lost when recompiling UI file!
|
||||
################################################################################
|
||||
@@ -35,7 +35,7 @@ class Ui_list_item_widget(object):
|
||||
self.toolButton.setMinimumSize(QSize(0, 0))
|
||||
self.toolButton.setAutoFillBackground(False)
|
||||
icon = QIcon()
|
||||
icon.addFile(u"../resources/images/icons8-clouds-48.png", QSize(), QIcon.Normal, QIcon.Off)
|
||||
icon.addFile(u"../resources/images/icons8-clouds-48.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.toolButton.setIcon(icon)
|
||||
self.toolButton.setIconSize(QSize(30, 40))
|
||||
|
||||
@@ -43,29 +43,45 @@ class Ui_list_item_widget(object):
|
||||
|
||||
self.ls_label_file_name = QLabel(list_item_widget)
|
||||
self.ls_label_file_name.setObjectName(u"ls_label_file_name")
|
||||
self.ls_label_file_name.setMaximumSize(QSize(340, 16777215))
|
||||
self.ls_label_file_name.setMaximumSize(QSize(270, 16777215))
|
||||
font = QFont()
|
||||
font.setPointSize(10)
|
||||
font.setBold(True)
|
||||
self.ls_label_file_name.setFont(font)
|
||||
self.ls_label_file_name.setTextFormat(Qt.PlainText)
|
||||
|
||||
self.gridLayout.addWidget(self.ls_label_file_name, 0, 1, 1, 2)
|
||||
self.gridLayout.addWidget(self.ls_label_file_name, 0, 1, 1, 1)
|
||||
|
||||
self.ls_label_timestamp = QLabel(list_item_widget)
|
||||
self.ls_label_timestamp.setObjectName(u"ls_label_timestamp")
|
||||
self.ls_label_timestamp.setMinimumSize(QSize(70, 0))
|
||||
self.ls_label_timestamp.setMaximumSize(QSize(70, 16777215))
|
||||
font1 = QFont()
|
||||
font1.setPointSize(8)
|
||||
self.ls_label_timestamp.setFont(font1)
|
||||
self.ls_label_timestamp.setStyleSheet(u"color: rgba(128, 128, 128, 200);")
|
||||
self.ls_label_timestamp.setAlignment(Qt.AlignRight|Qt.AlignVCenter)
|
||||
self.ls_label_timestamp.setTextFormat(Qt.PlainText)
|
||||
|
||||
self.gridLayout.addWidget(self.ls_label_timestamp, 0, 2, 1, 1)
|
||||
|
||||
self.ls_label_2 = QLabel(list_item_widget)
|
||||
self.ls_label_2.setObjectName(u"ls_label_2")
|
||||
sizePolicy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)
|
||||
sizePolicy = QSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Preferred)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.ls_label_2.sizePolicy().hasHeightForWidth())
|
||||
self.ls_label_2.setSizePolicy(sizePolicy)
|
||||
self.ls_label_2.setMaximumSize(QSize(170, 16777215))
|
||||
self.ls_label_2.setAlignment(Qt.AlignRight|Qt.AlignTrailing|Qt.AlignVCenter)
|
||||
self.ls_label_2.setTextFormat(Qt.PlainText)
|
||||
self.ls_label_2.setWordWrap(False)
|
||||
|
||||
self.gridLayout.addWidget(self.ls_label_2, 2, 2, 2, 1)
|
||||
|
||||
self.ls_label_1 = QLabel(list_item_widget)
|
||||
self.ls_label_1.setObjectName(u"ls_label_1")
|
||||
sizePolicy1 = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
|
||||
sizePolicy1 = QSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Preferred)
|
||||
sizePolicy1.setHorizontalStretch(0)
|
||||
sizePolicy1.setVerticalStretch(0)
|
||||
sizePolicy1.setHeightForWidth(self.ls_label_1.sizePolicy().hasHeightForWidth())
|
||||
@@ -92,6 +108,7 @@ class Ui_list_item_widget(object):
|
||||
list_item_widget.setWindowTitle(QCoreApplication.translate("list_item_widget", u"Form", None))
|
||||
self.toolButton.setText(QCoreApplication.translate("list_item_widget", u"...", None))
|
||||
self.ls_label_file_name.setText(QCoreApplication.translate("list_item_widget", u"FileName", None))
|
||||
self.ls_label_timestamp.setText("")
|
||||
self.ls_label_2.setText(QCoreApplication.translate("list_item_widget", u"9960KB of 9100KB", None))
|
||||
self.ls_label_1.setText(QCoreApplication.translate("list_item_widget", u"Downloading/Uploading ", None))
|
||||
# retranslateUi
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import os
|
||||
import subprocess
|
||||
import re
|
||||
from datetime import datetime
|
||||
|
||||
import logging
|
||||
# from logger import logger
|
||||
@@ -105,3 +106,28 @@ def config_client_bin_path() -> str:
|
||||
return "onedrive"
|
||||
else:
|
||||
return gui_settings.get("client_bin_path")
|
||||
|
||||
|
||||
def format_relative_time(timestamp):
|
||||
"""
|
||||
Format a datetime timestamp into compact relative time string.
|
||||
Examples: "now", "2m ago", "3h ago", "2d ago"
|
||||
"""
|
||||
if timestamp is None:
|
||||
return ""
|
||||
|
||||
now = datetime.now()
|
||||
delta = now - timestamp
|
||||
total_seconds = int(delta.total_seconds())
|
||||
|
||||
if total_seconds < 60:
|
||||
return "now"
|
||||
elif total_seconds < 3600: # Less than 1 hour
|
||||
minutes = total_seconds // 60
|
||||
return f"{minutes}m ago"
|
||||
elif total_seconds < 86400: # Less than 1 day
|
||||
hours = total_seconds // 3600
|
||||
return f"{hours}h ago"
|
||||
else: # 1 day or more
|
||||
days = total_seconds // 86400
|
||||
return f"{days}d ago"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from PySide6.QtCore import QThread, Signal, QFileInfo
|
||||
from PySide6.QtCore import QThread, Signal, QFileInfo, Qt
|
||||
|
||||
from PySide6.QtWidgets import QWidget, QFileIconProvider
|
||||
from PySide6.QtGui import QFontMetrics
|
||||
|
||||
|
||||
# Imports for main window.
|
||||
@@ -10,6 +11,7 @@ from ui.ui_list_item_widget import Ui_list_item_widget
|
||||
import re
|
||||
import time
|
||||
import subprocess
|
||||
from datetime import datetime
|
||||
|
||||
from global_config import save_global_config
|
||||
from options import (
|
||||
@@ -260,7 +262,7 @@ class WorkerThread(QThread):
|
||||
self.profile_status["status_message"] = "Initializing the OneDrive API"
|
||||
self.update_profile_status.emit(self.profile_status, self.profile_name)
|
||||
|
||||
elif "Processing:" in stdout or "Number of items to download from Microsoft OneDrive" in stdout:
|
||||
elif "Processing:" in stdout or "Number of items to download from Microsoft OneDrive" in stdout or "OneDrive Client requested to create" in stdout:
|
||||
items_left = re.match(r"^Processing\s([0-9]+)\sOneDrive\sitems", stdout)
|
||||
if items_left != None:
|
||||
self.profile_status["status_message"] = f"OneDrive is processing {items_left.group(1)} items..."
|
||||
@@ -300,6 +302,7 @@ class WorkerThread(QThread):
|
||||
"file_path": "unknown file name" if self.file_path is None else self.file_path.group(1),
|
||||
"progress": progress,
|
||||
"transfer_complete": transfer_complete,
|
||||
"timestamp": datetime.now() if transfer_complete else None,
|
||||
}
|
||||
|
||||
# Update file transfer list
|
||||
@@ -333,6 +336,7 @@ class WorkerThread(QThread):
|
||||
"file_path": file_path,
|
||||
"progress": progress,
|
||||
"transfer_complete": transfer_complete,
|
||||
"timestamp": datetime.now() if transfer_complete else None,
|
||||
}
|
||||
|
||||
logging.info(transfer_progress_new)
|
||||
@@ -648,6 +652,14 @@ class TaskList(QWidget, Ui_list_item_widget):
|
||||
# Set up the user interface from Designer.
|
||||
self.setupUi(self)
|
||||
|
||||
# Store completion timestamp for relative time display
|
||||
self.completion_timestamp = None
|
||||
|
||||
# Enable text eliding for ls_label_2 to prevent horizontal overflow
|
||||
self.ls_label_2.setWordWrap(False)
|
||||
self.ls_label_2.setSizePolicy(self.ls_label_2.sizePolicy().horizontalPolicy(), self.ls_label_2.sizePolicy().verticalPolicy())
|
||||
self.ls_label_2_max_width = 170 # Store max width for eliding
|
||||
|
||||
def set_icon(self, file_path):
|
||||
self.fileInfo = QFileInfo(file_path)
|
||||
self.iconProvider = QFileIconProvider()
|
||||
@@ -656,7 +668,10 @@ class TaskList(QWidget, Ui_list_item_widget):
|
||||
self.toolButton.setIcon(self.icon)
|
||||
|
||||
def set_file_name(self, file_path):
|
||||
self.ls_label_file_name.setText(file_path)
|
||||
# Use font metrics to elide long filenames
|
||||
font_metrics = QFontMetrics(self.ls_label_file_name.font())
|
||||
elided_text = font_metrics.elidedText(file_path, Qt.ElideMiddle, 270)
|
||||
self.ls_label_file_name.setText(elided_text)
|
||||
|
||||
def get_file_name(self):
|
||||
return self.ls_label_file_name.text()
|
||||
@@ -669,7 +684,18 @@ class TaskList(QWidget, Ui_list_item_widget):
|
||||
self.ls_label_1.setText(text)
|
||||
|
||||
def set_label_2(self, text):
|
||||
self.ls_label_2.setText(text)
|
||||
# Use font metrics to elide text if it exceeds maximum width
|
||||
font_metrics = QFontMetrics(self.ls_label_2.font())
|
||||
elided_text = font_metrics.elidedText(text, Qt.ElideRight, self.ls_label_2_max_width)
|
||||
self.ls_label_2.setText(elided_text)
|
||||
|
||||
def set_timestamp(self, text):
|
||||
"""Set the timestamp label text"""
|
||||
self.ls_label_timestamp.setText(text)
|
||||
|
||||
def get_timestamp_text(self):
|
||||
"""Get current timestamp label text"""
|
||||
return self.ls_label_timestamp.text()
|
||||
|
||||
def hide_progress_bar(self, transfer_status: bool):
|
||||
if transfer_status:
|
||||
@@ -677,5 +703,13 @@ class TaskList(QWidget, Ui_list_item_widget):
|
||||
else:
|
||||
self.ls_progressBar.show()
|
||||
|
||||
def set_completion_timestamp(self, timestamp):
|
||||
"""Store the completion timestamp for this transfer"""
|
||||
self.completion_timestamp = timestamp
|
||||
|
||||
def get_completion_timestamp(self):
|
||||
"""Get the completion timestamp for this transfer"""
|
||||
return self.completion_timestamp
|
||||
|
||||
|
||||
workers = {}
|
||||
|
||||
Reference in New Issue
Block a user