Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

"on_incomplete_file_received" callback doesn't work #655

Open
Program2113 opened this issue Nov 22, 2024 · 1 comment
Open

"on_incomplete_file_received" callback doesn't work #655

Program2113 opened this issue Nov 22, 2024 · 1 comment

Comments

@Program2113
Copy link

I have a requirement where the server should be able to detect whether the file uploaded by the client is fully received or not. To simulate this process, I have uploaded multiple files (of size >150mb) to the server and intentionally closed the connection in the middle of the upload. The callback did work sometimes but it missed in as many times. I don't know the reason, but I think of it as an issue with the library itself. I'm sharing the server and client code for reference.
##Client Script##

import ftplib
import os
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtWidgets import QApplication, QMainWindow, QFileDialog, QProgressBar, QPushButton, QVBoxLayout, QWidget

class FTPClient:
    def __init__(self, host, port, username, password):
        self.host = host
        self.port = port
        self.username = username
        self.password = password
        self.ftp = None

    def connect(self):
        try:
            self.ftp = ftplib.FTP()
            self.ftp.connect(self.host, self.port)
            self.ftp.login(self.username, self.password)
            print(f"Connected to {self.host}:{self.port}")
        except ftplib.all_errors as e:
            print(f"Error connecting to FTP server: {e}")
            return False
        return True

    def upload_file(self, file_paths, progress_callback):
       try:
           for file_path in file_paths:
               file_size = os.path.getsize(file_path)
               with open(file_path, 'rb') as file:
                   def handle_progress(block):
                       progress_callback(file.tell(), file_size)

                   try:
                       self.ftp.storbinary(f'STOR {os.path.basename(file_path)}', file, 1024, callback=handle_progress)
                       print(f'File {file_path} uploaded successfully.')
                   except (ftplib.error_temp, ftplib.error_perm) as e:
                       print(f'Error during file upload: {e}')
                       continue  # Continue with the next file

       except ftplib.all_errors as e:
           print(f'Error uploading file: {e}') 

    def close(self):
        if self.ftp:
            self.ftp.quit()

class FTPClientApp(QMainWindow):
    def __init__(self):
        super().__init__()

        self.setWindowTitle("FTP Client")
        self.setGeometry(100, 100, 400, 200)

        self.ftp_client = None

        self.progress_bar = QProgressBar(self)
        self.progress_bar.setGeometry(50, 50, 300, 30)

        self.upload_button = QPushButton("Upload File", self)
        self.upload_button.setGeometry(150, 100, 100, 40)
        self.upload_button.clicked.connect(self.upload_file)

    def upload_file(self):
        file_path, _ = QFileDialog.getOpenFileNames(self, "Select Files to Upload")
        if file_path:
            self.progress_bar.setValue(0)
            self.ftp_client = FTPClient("localhost", 2121, "user", "12345")
            if self.ftp_client.connect():
                self.upload_button.setEnabled(False)
                self.ftp_client.upload_file(file_path, self.update_progress)
                self.upload_button.setEnabled(True)
                self.ftp_client.close()

    def update_progress(self, bytes_sent, total_size):
        progress = (bytes_sent / total_size) * 100
        self.progress_bar.setValue(int(progress))

if __name__ == "__main__":
    app = QApplication([])
    window = FTPClientApp()
    window.show()
    app.exec_()

##Server Script##

import os
from pyftpdlib.handlers import FTPHandler
from pyftpdlib.servers import FTPServer
from pyftpdlib.authorizers import DummyAuthorizer


class MyHandler(FTPHandler):
    def on_connect(self):
        print("%s:%s connected" % (self.remote_ip, self.remote_port))

    def on_disconnect(self):
        # do something when client disconnects
        pass

    def on_login(self, username):
        # do something when user login
        pass

    def on_logout(self, username):
        # do something when user logs out
        pass

    def on_file_sent(self, file):
        # do something when a file has been sent
        pass

    def on_file_received(self, file):
        # do something when a file has been received
        pass

    def on_incomplete_file_sent(self, file):
        # do something when a file is partially sent
        pass

    def on_incomplete_file_received(self, file):
        # remove partially uploaded files
        print(f"File incompletely shared: {file}")
        import os
        os.remove(file)

def main():
    homedir = os.path.dirname(os.path.abspath(__file__))
    print(homedir)
    authorizer = DummyAuthorizer()
    authorizer.add_user('user', '1234asff5', homedir=homedir, perm='elradfmwMT')
    authorizer.add_anonymous(homedir=homedir)

    handler = MyHandler
    handler.authorizer = authorizer
    server = FTPServer(('', 2121), handler)
    server.serve_forever()
@giampaolo
Copy link
Owner

giampaolo commented Nov 22, 2024

I have uploaded multiple files (of size >150mb) to the server and intentionally closed the connection in the middle of the upload.

Note that the server does not know the file size upfront. It considers the transfer completed when the client stops sending data and "cleanly" closes the data connection:

if self.receive:
self.transfer_finished = True

In terms of code, on the client side that corresponds to socket.close().

On the other hand, upload is considered "incomplete" if:

  • Client abruptly closes the connection somehow. Maybe via socket.shutdown(socket.SHUT_RDWR) but I'm not 100% sure.
  • Client sends ABOR command over the control connection.

In both cases the data connection is closed, and on_incomplete_file_received is triggered.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants