Implement FTP program supporting multi-user online (C/S)

1. demand

1. User encryption authentication
 2. Allow multiple users to log in
 3. Each user has his own home directory and can only access his own home directory
 4. Allocate disks to users, and each user can set their own free space
 5. Allow users to switch directories at will on ftp server
 6. Allow users to view files in their home directory
 7. Allow users to upload and download to ensure file consistency (md5)
8. Display progress bar during file upload and download
 9. Support multiple concurrent functions
 10. Use queue module to realize thread pool
 11. Allow users to configure the maximum number of concurrent users, for example, allow only 10 concurrent users
 Upgrade requirements: 10%
1. File supports breakpoint renewal

 

2. Development environment

  Python 3.7.3

3. Software development directory structure

directory structure

 

4. Start of server and client

11. Open cmd command line terminal 22.python3 + start file path + startftpserver33. Example: 4C:\Users \ hot pepper > python3z: \ pycharm \ path to develop FTP program \ content after the second FTP modification \ second implementation method \ server \ FTP \ server.pystartftpserver
Server start
1 1. Open cmd command line terminal
 2 2. python3 + startup file path
 33. example:
4 C: \ users \ hotpot > python3 Z: \ pycharm \ the way to develop FTP program \ the content after the second FTP modification \ the second implementation method \ client \ FTP \ client.py
Client start

5. User configuration information

1 user name: user password:
2 alex                    123
3 egon                    123
4 ly                    123
5 jzd                    123
6 shx                    123    
View Code

6. All function tests

(1) landing

1 username>>:egon
2 password>>:123
 3 the user name and password are correct, and the authentication is successful!
View Code

 

(2) view the help information corresponding to all commands

View method:

  • Command + – - help

 1 [egon@localhost ~]# ls --help
 2 
 3 to view the files in the current directory:
 4                 ls
 5. Files under the specified directory (only the range of your own directory can be viewed):
 6 LS / I'm the directory of egon
 7             
 8 [egon@localhost ~]# cd --help
 9 
10 relative path switching:
11 CD / I'm the directory of egon
 12 CD / I'm Jiang fool's catalog
 13 switch to the previous Directory:
14                 cd ..
15 absolute path switching:
16 CD / I'm the directory of egon / I'm the directory of Jackie Jiang
 17 cut the current directory under the current directory:
18                 cd .
View Code

 

(3) ls: View

① supporting functions:

  • View files in the current directory

    • ls

  • Files under the specified directory (only the range of your own directory can be viewed)

    • ls / directory 1 / directory 2

  • View help information

    • ls /?

② operation effect:

 1 [egon@localhost ~]# ls
 The volume in drive Z is a solid state drive
 The serial number of Volume 3 is AA26-64F0
 4 
 5 Z: \ pycharm \ the way to develop FTP program \ the content after the second FTP modification \ the directory of the second implementation method \ server\home\egon
 6 
 7 2019-10-26  22:34    <DIR>          .
 8 2019-10-26  22:34    <DIR>          ..
 9 2019-10-19  20:03         1,081,540 123.docx
 10 2019-10-27 13:45 < dir > I am the directory of egon
 11 1 file 108154 bytes
 12 3 directory 56465575936 available bytes
1) view files in the current directory

 

 

 1 [egon @ localhost ~] (LS / I am the directory of egon
 The volume in drive Z is a solid state drive
 The serial number of Volume 3 is AA26-64F0
 4 
 5 Z: \ pycharm \ the way to develop FTP program \ the content after the second FTP modification \ the second implementation method \ server\home\egon \ I am the directory of egon
 6 
 7 2019-10-27  13:45    <DIR>          .
 8 2019-10-27  13:45    <DIR>          ..
 9 2019-10-27 13:45 < dir > I'm Jiang fool's directory
 10 2019-10-27 12:34 0 Jiang fool
 11 1 files 0 bytes
 12 3 directory 56465575936 available bytes
2) files under the specified directory (only the range of your own directory can be viewed)

 

 1 [egon@localhost ~]# ls /?
 2 displays a list of files and subdirectories in the directory.
 3 
 4 DIR [drive:][path][filename] [/A[[:]attributes]] [/B] [/C] [/D] [/L] [/N]
 5   [/O[[:]sortorder]] [/P] [/Q] [/R] [/S] [/T[[:]timefield]] [/W] [/X] [/4]
 6 
 7   [drive:][path][filename]
 8 specifies the drives, directories, and / or files to list.
 9 
10 / a displays the file with the specified properties.
11 attribute D directory R read only file
 12 h hidden file A file to be archived
 13 s system file I no content index file
 14 l reanalyze point O offline files
 15 - prefix for "no"
16 / B uses an empty format (no title information or summary).
17 / C displays the thousands separator in the file size. This is the default. With /-C
 18 disable separator display.
19 / D is the same as wide format, but documents are listed by column classification.
20 / L in lowercase.
21 / n new long list format, where the filename is on the far right.
22 / O list files in sort order.
23 order N by name (alphabetical) S by size (small to large)
24 e by extension (alphabetical) D by date / time (first to last)
25 g group directory priority - reverse prefix
 26 / P pause after each message screen.
27 / Q shows the owner of the file.
28 / R shows the backup data stream for the file.
29 / s displays files in the specified directory and all subdirectories.
30 / t control the time character field displayed or used for classification
 31 time period C creation time
 32 a last visited
 33 w last written
 34 / W in wide list format.
35 / X shows the short name generated for non 8dot3 filenames. The format is / N,
36 short names are inserted before long names. If there is no short name, the
 37 is blank.
38 / 4 year in four digits
39 
40 the switch can be preset in the DIRCMD environment variable. By adding a prefix - (DASH)
41 instead of the preset switch. For example, / - W.
42 
43 [egon@localhost ~]# 
3) view help information

 

 

(4) cd: switch directory

① supporting functions:

  • Relative path switching

    • cd / directory 1

      • cd / directory 2

  • Switch to the previous directory

    • cd ..

  • Absolute path switching

    • cd / directory 1 / directory 2

  • Cut the current directory under the current directory

    • cd .

② operation effect:

 1 [egon @ localhost ~] (CD / I am the directory of egon
 2 directory switch succeeded
 3 [egon@localhost /home/egon / I am the directory of egon] ා ls
 The volume in drive Z is a solid state drive
 The serial number of Volume 5 is AA26-64F0
 6 
 7 Z: \ pycharm \ the way to develop FTP program \ the content after the second FTP modification \ the second implementation method \ server\home\egon \ I am the directory of egon
 8 
 9 2019-10-27  13:45    <DIR>          .
10 2019-10-27  13:45    <DIR>          ..
11 2019-10-27 13:45 < dir > I am the directory of Jiang silly
 12 2019-10-27 12:34 0 Jiang fool
 13 1 files 0 bytes
 14 3 directories 56465563648 available bytes
15 
16 [egon@localhost /home/egon / I'm the directory of egon] ා cd / I'm the directory of Jiang fool
 17 directory switch succeeded
 18 [egon@localhost /home/egon / I'm the directory of egon / I'm the directory of Jiang fool] ාls
 19 the volume in drive Z is a solid state drive
 The serial number of volume 20 is AA26-64F0
21 
22 Z: \ pycharm \ the way to develop FTP program \ the content after the second FTP modification \ the second implementation method \ server\home\egon \ I am the directory of egon \ I am the directory of Jiang fool
23 
24 2019-10-27  13:45    <DIR>          .
25 2019-10-27  13:45    <DIR>          ..
26 2019-10-27 13:45 0 I am Jiang dashu.txt
 27 1 files 0 bytes
 28 2 directories 56465563648 available bytes
29 
30 [egon@localhost /home/egon / I'm the directory of egon / I'm the directory of Jiang fool]# 
1) relative path switching

 

 1 [egon@localhost /home/egon / I am the directory of egon / I am the directory of Jiang fool] ා cd
 2 directory switch succeeded
 3 [egon@localhost /home/egon / I am the directory of egon] ා ls
 The volume in drive Z is a solid state drive
 The serial number of Volume 5 is AA26-64F0
 6 
 7 Z: \ pycharm \ the way to develop FTP program \ the content after the second FTP modification \ the second implementation method \ server\home\egon \ I am the directory of egon
 8 
 9 2019-10-27  13:45    <DIR>          .
10 2019-10-27  13:45    <DIR>          ..
11 2019-10-27 13:45 < dir > I am the directory of Jiang silly
 12 2019-10-27 12:34 0 Jiang fool
 13 1 files 0 bytes
 14 3 directories 56465559552 available bytes
15 
16 [egon@localhost /home/egon / I am the directory of egon]# 
2) switch to the previous directory

 

 

 1 [egon @ localhost ~] (CD / I am the directory of egon / I am the directory of Jiang fool
 2 directory switch succeeded
 3 [egon@localhost /home/egon / I'm the directory of egon / I'm the directory of Jiang fool] ාls
 The volume in drive Z is a solid state drive
 The serial number of Volume 5 is AA26-64F0
 6 
 7 Z: \ pycharm \ the way to develop FTP program \ the content after the second FTP modification \ the second implementation method \ server\home\egon \ I am the directory of egon \ I am the directory of Jiang fool
 8 
 9 2019-10-27  13:45    <DIR>          .
10 2019-10-27  13:45    <DIR>          ..
11 2019-10-27 13:45 0 I am Jiang dashu.txt
 12 1 files 0 bytes
 13 2 directories 56465559552 available bytes
14 
15 [egon@localhost /home/egon / I'm the directory of egon / I'm the directory of Jiang fool]# 
3) absolute path switching

 

 1 [egon@localhost /home/egon / I am the directory of egon / I am the directory of Jiang fool] ා cd
 2 directory switch succeeded
 3 [egon@localhost /home/egon / I'm the directory of egon / I'm the directory of Jiang fool] ාls
 The volume in drive Z is a solid state drive
 The serial number of Volume 5 is AA26-64F0
 6 
 7 Z: \ pycharm \ the way to develop FTP program \ the content after the second FTP modification \ the second implementation method \ server\home\egon \ I am the directory of egon \ I am the directory of Jiang fool
 8 
 9 2019-10-27  13:45    <DIR>          .
10 2019-10-27  13:45    <DIR>          ..
11 2019-10-27 13:45 0 I am Jiang dashu.txt
 12 1 files 0 bytes
 13 2 directories 56465559552 available bytes
4) cut the current directory under the current directory

 

(5) mkdir: create directory (recursive directory creation is supported)

① supporting functions:

  • Relative path creation:

    • mkdir / directory

  • To generate a multi-level recursive directory:

    • mkdir / directory 1 / directory 2

② operation effect:

1 [egon@localhost ~]# mkdir /a
 2 create directory successfully!
1) relative path creation:

 

1 [egon@localhost ~]# mkdir /a/b
 2 create directory successfully!
2) absolute path creation:

 

(6) rmdir: delete empty directory

① supporting functions:

  • Delete empty directory:

    • rmdir / directory 1 / empty directory 2

② operation effect:

1 [egon@localhost ~]# rmdir /a/b
 2 delete directory successfully!
1) delete empty directory

 

(7) remove: delete files

① supporting functions:

  • Delete files

    • remove / directory 1 / file

② operation effect:

 1 [egon @ localhost ~] (LS / I am the directory of egon / Jiang fool
 The volume in drive Z is a solid state drive
 The serial number of Volume 3 is AA26-64F0
 4 
 5 Z: \ pycharm \ the way to develop FTP program \ the content after the second FTP modification \ the second implementation method \ server\home\egon \ I am the directory of egon
 6 
 7 2019-10-27 12:34 0 Jiang fool
 8 1 files 0 bytes
 9 0 directory 56465010688 free bytes
10 
11 [egon @ localhost ~] ා remove / I am the directory of egon / Jiang fool
 12 delete file succeeded!
13 
14 [egon @ localhost ~] (LS / I'm egon's directory / Jiang fool
 15 file not found
 Volumes in 16 drive Z are solid state drives
 The serial number of Volume 17 is AA26-64F0
18 
19 Z: \ pycharm \ the way to develop FTP program \ the content after the second FTP modification \ the second implementation method \ server\home\egon \ I am the directory of egon
1) delete files

 

(8) upload: upload files to the server

① supporting functions:

  • Upload to the current path of the server:

    • upload file

  • Upload files to the directory by cd switching

    • cd / directory 1 / directory 2

      • upload file

② operation effect:

 1 [Egon @ localhost ~] (upload server management comprehensive report.docx)
 2 you can upload files, before you upload, your current space: 68.97MB!
 3 
 4 upload running...
 5 [##################################################] 100.00%
 6 upload succeed!
 7. Upload the file successfully. The remaining space after you upload is 66.07MB!
 8 
 9 [egon@localhost ~]# ls
 10 the volume in drive Z is a solid state drive
 The serial number of Volume 11 is AA26-64F0
12 
13 Z: \ pycharm \ the way to develop FTP program \ the content after the second FTP modification \ the directory of the second implementation method \ server\home\egon
14 
15 2019-10-31  16:37    <DIR>          .
16 2019-10-31  16:37    <DIR>          ..
17 2019-10-31  16:37    <DIR>          .upload
 18 2019-10-31 16:09 32535704 03 three forms of function call.mp4
 19 2019-10-28 09:53 < dir > I'm the directory of egon
 20 2019-10-31 16:37 3039102 server management comprehensive report.docx
 21 2 files 35574806 bytes
 22 4 directories 56393715712 available bytes
1) upload to the current path of the server:

 

 1 [egon @ localhost ~] (CD / I am the directory of egon
 2 directory switch succeeded
 3 
 4 [egon@localhost / my directory] ා upload server management comprehensive report.docx
 5 you can upload files. Before you upload, your current space is 66.07MB!
 6 
 7 upload running...
 8 [##################################################] 100.00%
 9 upload succeed!
10. Upload the file successfully. The remaining space after you upload is 63.17MB!
11 
12 
13 [egon@localhost / my directory] × ls
 14 the volume in drive Z is a solid state drive
 The serial number of Volume 15 is AA26-64F0
16 
17 Z: \ pycharm \ the way to develop FTP program \ the content after the second FTP modification \ the second implementation method \ server\home\egon \ I am the directory of egon
18 
19 2019-10-31  16:47    <DIR>          .
20 2019-10-31  16:47    <DIR>          ..
21 2019-10-27 13:45 < dir > I am the directory of Jiang silly
 22 2019-10-31 16:47 3039102 server management comprehensive report.docx
 23 1 file 3039102 bytes
 24 3 directories 56390676480 available bytes
Upload files to the directory by cd switching:

 

(9) resume uupload: renew the file that has not been uploaded to the server

① supporting functions:

  • Continue to upload the file to the current path of the server:

    • Resume \ upload filename

  • Switch directory through cd, and continue to upload to a directory of the specified server in the directory:

    • cd / directory 1 / directory 2

      • Resume \ upload filename

② operation effect:

 1 ------ files in your files folder------
 2 1: .download
 3 2: 03 three forms of function call.mp4
 4 3: server management comprehensive report.docx
 5 
 6 [Egon @ localhost ~] (three forms of function call of upload 03). mp4
 You can upload files, before you upload, your current space: 97.10MB!
 8 
 9 upload running...
10 [############                                      ] 25.43%
Disconnect transmission first:

 

 1 username>>:egon
 2 password>>:123
 3 the user name and password are correct, and the authentication is successful!
 4. Your files have been uploaded. Do you want to continue to upload!
 5 
 6 quantity: 1 file path / 03 three forms of function call.mp4 file name: 03 three forms of function call.mp4
 7 original file size: 32535704 bytes incomplete file size: 8273050 bytes upload percentage: 25.43%
 8             
 9 
10 ------ files in your files folder------
11 1: .download
 12 2: 03 three forms of function call.mp4
 13 3: server management comprehensive report.docx
14 
15 
16 [Egon @ localhost ~] ා resume ﹐ upload 03 ﹐ three forms of function call.mp4
 17 you are continuing to upload files. Before you continue, your current space is 89.21MB!
18 8273050
19 
20 upload running...
21 [##################################################] 100.00%
22 upload succeed!
23 the file is uploaded successfully. The remaining space after you upload is 66.07MB!
1) continue to upload the file to the current path of the server:

 

 1 username>>:egon
 2 password>>:123
 3 the user name and password are correct, and the authentication is successful!
 4. Your files have been uploaded. Do you want to continue to upload!
 5 
 6 quantity: 1 file path: directory of / 03 UU three forms of function call.mp4 file name: 03 UU three forms of function call.mp4
 7. Original file size: 32535704 bytes unfinished file size: 12534221 bytes upload percentage: 38.52%
 8             
 9 
10 ------ files in your files folder------
11 1: .download
 12 2: 03 three forms of function call.mp4
 13 3: server management comprehensive report.docx
14 
15 [egon @ localhost ~] (CD / I am the directory of egon
 16 directory switch succeeded
17 
18 ------ files in your files folder------
19 1: .download
 20 2: 03 three forms of function call.mp4
 21 3: server management comprehensive report.docx
22 
23 [egon@localhost / my directory] ා resume ﹣ upload 03 ﹣ three forms of function call.mp4
 You are continuing to upload files. Before you continue, your current space is 66.07MB!
25 
26 upload running...
27 [##################################################] 100.00%
28 upload succeed!
29 the file is uploaded successfully. The remaining space after you upload is 47.00MB!
2) switch directories through cd, and continue to upload to a directory of the specified server under the directory:

 

(10) download: download files

① supporting functions:

  • Download files from the current directory of the server

    • download File

  • Download files from the server absolute path

    • download / directory 1 / file

② operation effect:

1 [egon@localhost ~]# download Server management comprehensive report.docx
2 
3 download run...
4 [##################################################] 100.00%
5 download succeed!
1) download files from the current directory of the server

 

1 [egon@localhost ~]# download /I am egon Directory/03_Three forms of function call.mp4
2 
3 download run...
4 [##################################################] 100.00%
5 download succeed!
2) download files from the absolute path of the server

 

(11) on the basis of download: continue to download files from the server

① supporting functions:

② operation effect:

 1 username>>:egon
 2 password>>:123
 3 the user name and password are correct, and the authentication is successful!
 4. The server detects that you have not uploaded the completed file!
 5. It is detected that there are unfinished files in your local area
 6 ---------- ------- ------- ------- ------- ------- ------- ------- ------- ------- ------- ------- ------- ------- ------- ------- ------- ------- ------- ------- ------- ------- ------- ------- ------- ------- ------- ------- ------- ------- ------- ------- ---------------------------------------------------------------------------
 7 serial number: 1
 8 
 9 uncompleted file path: Z:\pycharm \ path to develop FTP program \ contents after the second FTP modification \ second implementation method \ client\files three forms of function call. mp4.download file name: 03 three forms of function call. mp4
 10 original file size: 32535704 bytes file size completed: 3511466 bytes upload percentage: 10.79%
11             
12 serial number: 2
13 
14 unfinished file path: Z:\pycharm \ path to develop FTP program \ content modified in the second FTP \ second implementation method \ client\files \ server management comprehensive report.docx.download file name: server management comprehensive report.docx
 15 original file size: 3039102 bytes file size completed: 712297 bytes upload percentage: 23.44%
16             
17 [exit: q/Q] please select whether you want to continue downloading incomplete files according to the serial number > > 1
 18 to continue
19 
20 download run...
21 [##################################################] 100.00%
22 download succeed!
23 
24 continue transmission completed!
25 ---------- ------- ------- ------- ------- ------- ------- ------- ------- ------- ------- ------- ------- ------- ------- ------- ------- ------- ------- ------- ------- ------- ------- ------- ------- ------- ------- ------- ------- ------- ------- ------- ---------------------------------------------------------------------------
26 serial number: 1
27 
28 unfinished file path: Z:\pycharm \ path to develop FTP program \ content modified in the second FTP \ second implementation method \ client\files \ server management comprehensive report.docx.download file name: server management comprehensive report.docx
 29 original file size: 3039102 bytes file size completed: 712297 bytes upload percentage: 23.44%
30             
31 [exit: q/Q] please select whether you want to continue downloading incomplete files according to the serial number > > 1
 32 start to renew
33 
34 download run...
35 [##################################################] 100.00%
36 download succeed!
37 
38. Continue!
39 
40 ------ files in your files folder------
41 1: .download
 42 2: 03 three forms of function call.mp4
 43 3: server management comprehensive report.docx
View Code

 

(12) disk quota for users

 1 [egon@localhost / directory I am / directory I'm Jackie Jiang / directory 1] ාාthree forms of function call. mp4
 2 you can upload files, before you upload, your current space: 35.04MB!
 3 
 4 upload running...
 5 [##################################################] 100.00%
 6 upload succeed!
 7. Upload the file successfully. The remaining space after you upload: 4.02MB!
 8 
 9 
10 [egon@localhost / my directory / my directory / my directory is Jiang fool / directory 2] ා three forms of upload 03 function call.mp4
 11 failed to upload the file, your space is insufficient, your remaining space: 4.02MB!
View Code

 

(13) use queue module to realize threads, and allow users to configure the maximum number of concurrent 5

(14) log function is recorded

① terminal printing

② in the saved documents

 

7. Deficiencies

8. Code display

① client

1) conf

 1 import os
 2 
 3 BASE_DIR = os.path.normpath(os.path.join(__file__, '..', '..'))
 4 
 5 FILES_PATH = os.path.join(BASE_DIR, 'files')
 6 UNFINISHED_DOWNLOAD_FILES_PATH = os.path.join(FILES_PATH, '.download', 'unfinished.shv')
 7 
 8 HOST = '127.0.0.1'
 9 PORT = 8080
10 
11 
12 help_dic = {
13     'ls --help': """
14 to view files in the current directory:
15                 ls
 16 files in the specified directory (you can only view the range of your own directory):
17 LS / directory 1 / directory 2
 18 for detailed help on ls:
19                 ls /?
20             """,
21     'cd --help': """
22 relative path switching:
23 CD / directory 1
 24 CD / catalogue 2
 25 absolute path switching:
26 CD / directory 1 / directory 2
 27 switch to the previous Directory:
28                 cd ..
29 to cut the current directory under the current directory:
30                 cd .
31             """,
32     'mkdir --help': """
33 relative path creation:
34 MKDIR / directory
 35 generate multi-level recursive directory:
36 MKDIR / directory 1 / directory 2
37             """,
38     'rmdir --help': """
39 delete empty directory:
40 rmdir / directory 1 / empty directory 2
41             """,
42     'remove --help': """
43 delete file:
44 remove / directory 1 / file
45             """,
46     'upload --help': """
47 upload to the current path of the server:
48 upload filename
 49 to upload files to the directory by cd switching:
50 CD / directory 1 / directory 2
 51 upload file
52             """,
53     'resume_upload --help': """
54 continue to upload the file to the current path of the server:
55 resume upload filename
 56 switch directory through cd, and continue to upload to a directory of the specified server under the directory:
57 CD / directory 1 / directory 2
 58 resume upload filename
59                 """,
60     None: """
61 to view the corresponding help information:
62                 1.  ls + --help         
63                 2.  cd --help
64                 3.  mkdir --help
65                 4.  rmdir --help
66                 5.  remove --help
67                 6.  upload --help
68                 7.  resume_upload --help
69             """,
70 }
settings.py

2) core

  1 import hashlib
  2 import json
  3 import os
  4 import shelve
  5 import socket
  6 import struct
  7 
  8 from conf import settings
  9 
 10 
 11 class FTPClient:
 12     """FTP Client."""
 13     address_family = socket.AF_INET
 14     socket_type = socket.SOCK_STREAM
 15     max_packet_size = 8192
 16     encoding = 'utf-8'
 17     windows_encoding = 'gbk'
 18 
 19     struct_fmt = 'i'
 20     fixed_packet_size = 4
 21 
 22     def __init__(self, server_address, connect=True):
 23         self.server_address = server_address
 24         self.socket = socket.socket(self.address_family, self.socket_type)
 25 
 26         self.breakpoint_resume = shelve.open(settings.UNFINISHED_DOWNLOAD_FILES_PATH)
 27 
 28         self.username = None
 29         self.current_dir = '~'
 30         if connect:
 31             try:
 32                 self.client_connect()
 33             except Exception:
 34                 self.client_close()
 35                 raise
 36 
 37     def client_connect(self):
 38         """Client connection server ip and port."""
 39         self.socket.connect(self.server_address)
 40 
 41     def client_close(self):
 42         """Close connection channel."""
 43         self.socket.close()
 44 
 45     def interactive(self):
 46         """All interactions with the server."""
 47         if self.auth():
 48             self.unfinished_file_check()
 49             while True:
 50                 self.show_str()
 51                 msg = input('[%s@localhost %s]# ' % (self.username, self.current_dir)).strip()
 52                 if not msg:
 53                     continue
 54                 if not self.help_msg(msg):
 55                     continue
 56                 # Verify command parameters
 57                 cmd, path = self.verify_args(msg)
 58                 if hasattr(self, '_%s' % cmd):
 59                     func = getattr(self, '_%s' % cmd)
 60                     func(path)
 61                 else:
 62                     self.help_msg()
 63 
 64     @staticmethod
 65     def verify_args(msg):
 66         """
 67         Validation parameters.
 68         :param msg: ls       or ls /Route       or ls /Path 1/Path 2/
 69         :return:    (ls, []) or (ls, ['Route']) or (ls, ['Path 1', 'Path 2'])
 70         """
 71         cmd_args = msg.split()
 72         cmd, path = cmd_args[0], cmd_args[1:]
 73         if path:
 74             path = ''.join(cmd_args[1:]).strip('//').split('/')
 75         # print('cmd, path:', cmd, path)
 76         return cmd, path
 77 
 78     def unfinished_file_check(self):
 79         if not list(self.breakpoint_resume.keys()):
 80             return
 81 
 82         print('It is detected that there are unfinished files in your local area')
 83         unfinished_path_list = []
 84         msg_list = []
 85         for unfinished_file_path in self.breakpoint_resume.keys():
 86             file_name = self.breakpoint_resume[unfinished_file_path]['file_name']
 87             file_size = self.breakpoint_resume[unfinished_file_path]['file_size']
 88             unfinished_file_size = os.path.getsize(unfinished_file_path)
 89             percent = unfinished_file_size / file_size * 100
 90             path = self.breakpoint_resume[unfinished_file_path]['path']
 91             dic = {'unfinished_file_size': unfinished_file_size, 'path': path}
 92             unfinished_path_list.append(dic)
 93             msg = """
 94             Incomplete file path: %s    file name: %s
 95             Original file size: %s byte      File size completed: %s byte     % Uploaded: %.2f%%
 96             """ % (unfinished_file_path, file_name, file_size, unfinished_file_size, percent)
 97             msg_list.append(msg)
 98 
 99         while msg_list:
100             print("Number of incomplete renewals: %s individual".center(150, '-') % len(msg_list))
101             for msg in msg_list:
102                 print('Serial number: %s' % (int(msg_list.index(msg) + 1)))
103                 print(msg)
104 
105             choice = input('[Sign out: q/Q]Please select whether you want to continue downloading incomplete files according to the serial number>>:').strip()
106             if choice.lower() == 'q':
107                 break
108             if not choice.isdigit():
109                 continue
110             choice = int(choice)
111             if 0 < choice <= len(unfinished_path_list):  # len(unfinished_path_list)=3
112                 dic = unfinished_path_list[choice - 1]
113                 path, unfinished_file_size = dic['path'], dic['unfinished_file_size']
114 
115                 print('Start renewal......')
116                 self.__resume_download(path, unfinished_file_size)
117                 print('\n Renewal is completed.!')
118 
119                 unfinished_path_list.pop(choice-1)
120                 msg_list.pop(choice-1)
121             else:
122                 print('Your choice is out of range!')
123 
124     def auth(self):
125         """
126         Land.
127         100: 'User name and password are correct, Certification success!',
128         199: 'Incorrect username and password, Authentication failed!',
129         850: 'You have also uploaded files for, Continue to upload!',
130         851: 'Check that you don't have any files that haven't been uploaded!',
131         """
132         count = 0
133         while count < 3:
134             username = input('username>>:').strip()
135             password = input('password>>:').strip()
136             if not all([username, password]):
137                 print('User name and password cannot be empty.')
138                 count += 1
139                 continue
140             # Header
141             self.send_header(action_type='auth', username=username, password=password)
142             # Receiver header
143             response_dic = self.receive_header()
144             status_code, status_msg = response_dic.get('status_code'), response_dic.get('status_msg')
145             # 100: 'User name and password are correct, Certification success!',
146             if status_code == 100:  # 100 Confirm success
147                 print(status_msg)
148                 self.username = username
149 
150                 # 850: 'You have also uploaded files for, Continue to upload!',
151                 # 851: 'Check that you don't have any files that haven't been uploaded!',
152                 response_dic = self.receive_header()
153                 status_code, status_msg, msg_list, msg_dic = response_dic.get('status_code'), response_dic.get(
154                     'status_msg'), response_dic.get('msg_list'), response_dic.get('msg_dic')
155                 if msg_list:
156                     print(status_msg)
157                     for unfinished_msg in msg_list:
158                         print(unfinished_msg)
159                 else:
160                     print(status_msg)
161 
162                 return True
163             else:
164                 # 199: 'Incorrect username and password, Authentication failed!',
165                 print(status_msg)
166                 count += 1
167         else:
168             print('Too many inputs,forced return!')
169             return False
170 
171     def _ls(self, path):
172         """
173         Display the list of files in the directory.
174         :param path: [] or ['Directory 1', 'Directory 2']
175         :return: None
176         """
177         # Sending header
178         self.send_header(action_type='ls', path=path)
179         # Receiving header
180         response_dic = self.receive_header()
181         status_code, status_msg, cmd_size = response_dic.get('status_code'), response_dic.get(
182             'status_msg'), response_dic.get('cmd_size')
183         if status_code == 301 and cmd_size:
184             # print('status_msg:', status_msg)
185             # print('cmd_size:', cmd_size)
186             # Receive news
187             windows_cmd = self.socket.recv(cmd_size).decode(self.windows_encoding)
188             print(windows_cmd)
189         else:
190             print(status_msg)
191 
192     def _cd(self, path):
193         """
194         Toggle directory.
195         :param path: ['..'] or ['Path 1', 'Directory 2']
196         :return: None
197         """
198         # Sending header
199         self.send_header(action_type='cd', path=path)
200         # Receiving header
201         response_dic = self.receive_header()
202         status_code, status_msg, current_dir = response_dic.get('status_code'), response_dic.get(
203             'status_msg'), response_dic.get('current_dir')
204         if status_code == 400:
205             self.current_dir = current_dir
206             print(status_msg)
207         else:
208             print(status_msg)
209 
210     def _mkdir(self, path):
211         """
212         new directory.
213         :param path: ['Directory 1']
214                 or   [Directory 2', 'Directory 3']
215         :return: None
216         """
217         # print(path)
218         # Sending header
219         self.send_header(action_type='mkdir', path=path)
220         # Receiving header
221         response_dic = self.receive_header()
222         status_code, status_msg = response_dic.get('status_code'), response_dic.get(
223             'status_msg')
224         if status_code == 500:
225             print(status_msg)
226         else:
227             print(status_msg)
228 
229     def _rmdir(self, path):
230         """
231         remove empty directories.
232         :param path: ['', '12312 All 1 hair.']
233         :return: None
234         """
235         # print(path)
236         # Sending header
237         self.send_header(action_type='rmdir', path=path)
238         # Receiving header
239         response_dic = self.receive_header()
240         status_code, status_msg = response_dic.get('status_code'), response_dic.get(
241             'status_msg')
242         if status_code == 600:
243             print(status_msg)
244         else:
245             print(status_msg)
246 
247     def _remove(self, path):
248         """
249         Delete files.
250         :param path: ['Directory 1', 'Document 1']
251         :return:
252         """
253         # print(path)
254         # Sending header
255         self.send_header(action_type='remove', path=path)
256         # Receiving header
257         response_dic = self.receive_header()
258         status_code, status_msg = response_dic.get('status_code'), response_dic.get(
259             'status_msg')
260         if status_code == 700:
261             print(status_msg)
262         else:
263             print(status_msg)
264 
265     def parser_path(self, action_type, path, **kwargs):
266         """
267         Resolve path parameters, Determine whether the path is a file name, Or the file name under the path.
268         :param action_type: Function types uploaded by users
269         :param path: User path example: ['Directory 1', 'Document 1']  or ['Document 1']
270         :param kwargs:
271         :return: path Return when the list length is reasonable True, Unreasonable return False
272         """
273         if len(path) > 1:
274             self.send_header(action_type=action_type, **kwargs, file_name=path[-1],
275                              path=path[:-1])
276         elif len(path) == 1:
277             self.send_header(action_type=action_type, **kwargs, file_name=path[-1],
278                              path=None)
279         else:
280             print('Path must be specified, Or file name!')
281             return False
282         return True
283 
284     def _resume_upload(self, path):
285         """
286         upload Breakpoint resume function of.
287         860: 'You are continuing to upload files, Before you continue, Your current space:%s!',
288         869: 'There are no files to be renewed in the file path you selected, Please check.!',
289         """
290         self._upload(path, resume_upload=True)
291 
292     def _upload(self, path, resume_upload=False):
293         """
294         Upload files to the server.
295         Normal upload:
296             800: 'You can upload files, Before you upload, Your current space:%s!',
297             801: 'Upload file succeeded, Space left after you upload:%s!',
298             852: 'You cannot renew, Because the file is complete!',
299             894: 'You do not need to upload files in this path, The file already exists in your current path!',
300             895: 'Failed to upload file, md5 Inconsistent validity, Some file contents are lost in the network, Please upload again!',
301             896: 'Failed to upload file, You don't have enough space, Your upload fake file size, Your remaining space:%s!',
302             897: 'Failed to upload file, You don't have enough space, Your remaining space:%s!',
303             898: 'Failed to upload file, Upload command is not standard!',
304             899: 'There must be a file for uploading md5 Value and filename!',
305         Renewal:
306             860: 'You are continuing to upload files, Before you continue, Your current space:%s!',
307             869: 'There are no files to be renewed in the file path you selected, Please check.!',
308         :param path: ['Directory 1', 'Document 1'] or ['Document 1']
309         :return: None
310         """
311         # Determine whether the user file path is FILES_PATH Files under path
312         file_path = os.path.normpath(os.path.join(settings.FILES_PATH, *path))
313         if not os.path.isfile(file_path):
314             print('The file you want to upload does not exist!')
315             return
316 
317         # Resolve user path, And submit upload Related functions of
318         file_size = os.path.getsize(file_path)
319         file_md5 = self.md5(file_path)
320 
321         if resume_upload:  # Execute when resuming a breakpoint
322             action_type = 'resume_upload'
323         else:  # Normal long transmission time execution
324             action_type = 'upload'
325 
326         if not self.parser_path(action_type=action_type, file_md5=file_md5, file_size=file_size, path=path):
327             return
328 
329         # Corresponding Dictionary of receiving server
330         # normal: 800, 894, 897, 898, 899
331         # 800: 'You can upload files, Before you upload, Your current space:%s!',
332         # 894: 'You do not need to upload files in this path, The file already exists in your current path!',
333         # 898: 'Failed to upload file, Upload command is not standard!',
334         # 897: 'Failed to upload file, You don't have enough space, Your remaining space:%s!',
335         # 899: 'There must be a file for uploading md5 Value and filename!',
336         # Renewal: 860, 869
337         # 860: 'You are continuing to upload files, Before you continue, Your current space:%s!',
338         # 869: 'There are no files to be renewed in the file path you selected, Please check.!',
339         response_dic = self.receive_header()
340         status_code, status_msg, residual_space_size, already_upload_size = response_dic.get(
341             'status_code'), response_dic.get(
342             'status_msg'), response_dic.get('residual_space_size'), response_dic.get('already_upload_size')
343 
344         # Judge status code
345         # 800: 'You can upload files, Before you upload, Your current space:%s!',
346         # 860: 'You are continuing to upload files, Before you continue, Your current space:%s!',
347         if status_code == 800 or status_code == 860:  # 800 Normal send file confirmation 860 renew file confirmation
348             print(status_msg % self.conversion_quota(residual_space_size))
349 
350             initial_size = 0
351             if resume_upload:  # Execute when resuming a breakpoint: At present, the total size of the file should be subtracted from the size of the location where the last upload was not completed
352                 total_size = file_size - already_upload_size
353             else:  # Execute during normal upload
354                 total_size = file_size
355             with open(file_path, 'rb') as f:
356                 if resume_upload:  # Execute when resuming a breakpoint: Move the cursor to the position where the last upload was not completed
357                     f.seek(already_upload_size)
358                 print('\nupload running...')
359                 for line in f:
360                     self.socket.sendall(line)
361                     initial_size += len(line)
362                     percent = initial_size / total_size
363                     self.progress_bar(percent)
364                 print('\nupload succeed!')
365 
366             # Receive message for the second time, Confirm that the file is uploaded
367             # 801, 895, 896
368             # 801: 'Upload file succeeded, Space left after you upload:%s!',
369             # 895: 'Failed to upload file, md5 Inconsistent validity, Some file contents are lost in the network, Please upload again!',
370             # 896: 'Failed to upload file, You don't have enough space, Your upload fake file size, Your remaining space:%s!',
371             response_dic = self.receive_header()
372             status_code, status_msg, residual_space_size = response_dic.get('status_code'), response_dic.get(
373                 'status_msg'), response_dic.get('residual_space_size')
374             if residual_space_size:  # 801, 896
375                 print(status_msg % self.conversion_quota(residual_space_size))
376             else:  # 895
377                 print(status_msg)
378         else:
379             # normal: 894, 897, 898, 899
380             # 894: 'You do not need to upload files in this path, The file already exists in your current path!',
381             # 897: 'Failed to upload file, You don't have enough space, Your remaining space:%s!',
382             # 898: 'Failed to upload file, Upload command is not standard!',
383             # 899: 'There must be a file for uploading md5 Value and filename!',
384             # Renewal:
385             # 869: 'There are no files to be renewed in the file path you selected, Please check.!',
386             if residual_space_size:  # 897
387                 print(status_msg % self.conversion_quota(residual_space_size))
388             else:  # 869, 894, 898, 899
389                 print(status_msg)
390 
391     def __resume_download(self, path, unfinished_file_size):
392         self._download(path, unfinished_file_size, resume_download=True)
393 
394     def _download(self, path, unfinished_file_size=None, resume_download=False):
395         """
396 
397         900: 'Ready to start downloading files!',
398         999: 'Download file failed, The file path you are trying to download is not standard!',
399         :param path:
400         :param resume_download:
401         :return:
402         """
403         if resume_download:
404             action_type = 'resume_download'
405         else:
406             action_type = 'download'
407         self.send_header(action_type=action_type, path=path, unfinished_file_size=unfinished_file_size)
408 
409         # Receive server message
410         # self.send_header(status_code=900, file_name=file_name, file_size=file_size, file_md5=file_md5)
411         response_dic = self.receive_header()
412         status_code, status_msg, file_name, file_size, file_md5 = response_dic.get('status_code'), response_dic.get(
413             'status_msg'), response_dic.get('file_name'), response_dic.get('file_size'), response_dic.get('file_md5')
414 
415         # Judge status code
416         # 900: 'Ready to start downloading files!',
417         # 950: 'Ready to resume files!',
418         # 998: 'Download file failed, The file path you want to download does not exist!',
419         # 999: 'Download file failed, The file path you are trying to download is not standard!',
420         if status_code == 900 or status_code == 950:
421 
422             file_path = os.path.join(settings.FILES_PATH, file_name)
423             if resume_download and file_path in self.breakpoint_resume.keys():
424                 unfinished_file_path = self.breakpoint_resume[file_path]['unfinished_file_path']
425             else:
426                 # Determine whether there are files in this path, Prompt if there are documents
427                 # file_path = os.path.join(settings.FILES_PATH, file_name)
428                 if os.path.isfile(file_path):
429                     print('The file already exists in this path, No need to continue downloading!')
430                     return
431                 # Add suffixes to filenames that have not been downloaded
432                 unfinished_file_path = '%s.%s' % (file_path, 'download')
433 
434             # Add breakpoint record for download terminal
435             self.breakpoint_resume[unfinished_file_path] = {'file_name': file_name, 'file_size': file_size,
436                                                             'path': path}
437 
438             # Start downloading
439             receive_size = 0
440             if resume_download:
441                 total_size = file_size - os.path.getsize(unfinished_file_path)
442                 mode = 'a'
443             else:
444                 total_size = file_size
445                 mode = 'w'
446             with open(unfinished_file_path, '%sb' % mode) as f:
447                 print('\ndownload run...')
448                 while receive_size < total_size:
449                     data_bytes = self.socket.recv(self.max_packet_size)
450                     f.write(data_bytes)
451                     receive_size += len(data_bytes)
452                     percent = receive_size / total_size
453                     self.progress_bar(percent)
454                 print('\ndownload succeed!')
455                 f.flush()
456 
457             # Remove the suffix successfully in normal download, files renaming, Delete breakpoint record
458             del self.breakpoint_resume[unfinished_file_path]
459             os.rename(unfinished_file_path, file_path)
460 
461             # Validation md5 Value asks the user whether to save
462             server_file_md5 = file_md5
463             current_file_md5 = self.md5(file_path)
464             if server_file_md5 != current_file_md5:
465                 print('Your file is not complete, May not open, Please download again!')
466         else:
467             # 998: 'Download file failed, The file path you want to download does not exist!',
468             # 999: 'Download file failed, The file path you are trying to download is not standard!',
469             print(status_msg)
470 
471     @staticmethod
472     def conversion_quota(residual_space_size):
473         """
474         Convert the bytes sent by the server to MB, Humanized display of space surplus of users.
475         :param residual_space_size: Bytes of space left
476         :return: MB Bytes in
477         """
478         residual_space_mb = residual_space_size / (1024 ** 2)
479         return '%.2fMB' % residual_space_mb
480 
481     def receive_header(self):
482         """
483         Receive the header dictionary sent by the server.
484         :return: {'status_code': 100, 'status_msg': 'Certification success', 'cmd_size': 199}
485         """
486         header_bytes = self.socket.recv(self.fixed_packet_size)
487         header_dic_json_length = struct.unpack(self.struct_fmt, header_bytes)[0]
488         # Receiving header
489         header_dic_json = self.socket.recv(header_dic_json_length).decode(self.encoding)
490         header_dic = json.loads(header_dic_json)
491         return header_dic
492 
493     def send_header(self, *, action_type, **kwargs):
494         """
495         Send header dictionary to client.
496         :param action_type: action_type='auth'
497         :param kwargs: {'username': 'egon', 'password': '123'}
498         :return: None
499         """
500         request_dic = kwargs
501         request_dic['action_type'] = action_type
502         request_dic.update(request_dic)
503 
504         request_dic_json_bytes = json.dumps(request_dic).encode(self.encoding)
505         request_dic_json_bytes_length = len(request_dic_json_bytes)
506         header_bytes = struct.pack(self.struct_fmt, request_dic_json_bytes_length)
507 
508         # Sending header
509         self.socket.sendall(header_bytes)
510         # Send out json after bytes Post dictionary request_dic
511         self.socket.sendall(request_dic_json_bytes)
512 
513     @staticmethod
514     def md5(file_path):
515         """
516         md5 Encrypt hash file.
517         :param file_path: files File path under
518         :return: file hash value
519         """
520         md5_obj = hashlib.md5()
521         with open(file_path, 'rb') as f:
522             for line in f:
523                 md5_obj.update(line)
524         return md5_obj.hexdigest()
525 
526     @staticmethod
527     def progress_bar(percent, width=50, symbol='#'):
528         """Progress bar function."""
529         if percent > 1:
530             percent = 1
531         show_str = ('[%%-%ds]' % width) % (int(width * percent) * symbol)
532         print('\r%s %.2f%%' % (show_str, percent * 100), end='')
533 
534     @staticmethod
535     def show_str():
536         """Show clients flies List of files in."""
537         print('\n------Your files Files in folder------')
538         for index, filename in enumerate(os.listdir(settings.FILES_PATH), 1):
539             print('%s: %s' % (index, filename))
540         print()
541 
542     @staticmethod
543     def help_msg(msgs=None):
544         """Help information."""
545         if msgs in settings.help_dic:
546             print(settings.help_dic[msgs])
547         else:
548             return True
main.py

3) files

 1     # encoding: utf-8
 2 
 3 import os
 4 import sys
 5 
 6 BASE_DIR = os.path.normpath(os.path.join(__file__, '..'))
 7 print(BASE_DIR)
 8 sys.path.append(BASE_DIR)
 9 
10 if __name__ == '__main__':
11     from core import main
12     client = main.FTPClient(('127.0.0.1', 8080))
13     client.interactive()
ftp_client.py

 

 

②server

1) conf

 1 [egon]
 2 password = 202cb962ac59075b964b07152d234b70
 3 quota = 100
 4 
 5 [alex]
 6 password = 202cb962ac59075b964b07152d234b70
 7 quota = 100
 8 
 9 [ly]
10 password = 202cb962ac59075b964b07152d234b70
11 quota = 200
12 
13 [jzd]
14 password = 202cb962ac59075b964b07152d234b70
15 quota = 300
16 
17 [shx]
18 password = 202cb962ac59075b964b07152d234b70
19 quota = 300
20 
21 
22 [xxx]
23 password = 202cb962ac59075b964b07152d234b70
24 quota = 300
accounts.ini
  1 import os
  2 
  3 
  4 def base_dir(*args):
  5     return os.path.normpath(os.path.join(__file__, '..', '..', *args))
  6 
  7 
  8 # User home directory storage path
  9 USER_HOME_DIR = base_dir('home')
 10 
 11 # User account information file path
 12 ACCOUNTS_FILE = base_dir('conf', 'accounts.ini')
 13 
 14 # Native tested ip and port
 15 HOST = '127.0.0.1'
 16 PORT = 8080
 17 
 18 # Status code: Responsible for providing prompt information feedback of interaction success and failure
 19 STATUS_CODE = {
 20     100: 'User name and password are correct, Certification success!',
 21     199: 'Incorrect username and password, Authentication failed!',
 22     200: 'Your function specification cannot be empty!',
 23     201: 'No such function, Please see help!',
 24     301: 'The returned result contains the command size.',
 25     400: 'Directory switch succeeded',
 26     498: 'Failed to switch directory, The switching command is not standard',
 27     499: 'Failed to switch directory, Destination address does not exist!',
 28     500: 'Directory created successfully!',
 29     598: 'The input of create directory command is not standard!',
 30     599: 'Created directory already exists!',
 31     600: 'Delete directory succeeded!',
 32     699: 'Failed to delete directory, The directory is not empty!',
 33     698: 'Failed to delete directory, The directory does not exist!',
 34     697: 'Failed to delete directory, Delete command is not standard!',
 35     700: 'Delete file succeeded!',
 36     799: 'Delete file failed, The file does not exist!',
 37     800: 'You can upload files, Before you upload, Your current space:%s!',
 38     801: 'Upload file succeeded, Space left after you upload:%s!',
 39     850: 'The server detects the files you have uploaded, Continue to upload!',
 40     851: 'The server detects that you have no unfinished files!',
 41     852: 'You cannot renew, Because the file is complete!',
 42     860: 'You are continuing to upload files, Before you continue, Your current space:%s!',
 43     869: 'There are no files to be renewed in the file path you selected, Please check.!',
 44     894: 'You do not need to upload files under this path, The file already exists in your current path!',
 45     895: 'Failed to upload file, md5 Inconsistent validity, Some file contents are lost in the network, Please upload again!',
 46     896: 'Failed to upload file, You don't have enough space, Your upload fake file size, Your remaining space:%s!',
 47     897: 'Failed to upload file, You don't have enough space, Your remaining space:%s!',
 48     898: 'Failed to upload file, Upload command is not standard!',
 49     899: 'There must be a file for uploading md5 Value and filename!',
 50     900: 'Ready to start downloading files!',
 51     950: 'Ready to resume files!',
 52     998: 'Download file failed, The file path you want to download does not exist!',
 53     999: 'Download file failed, The file path you are trying to download is not standard!',
 54 }
 55 
 56 # log Log path
 57 ACCESS_LOG_PATH = base_dir('log', 'access.log')
 58 
 59 # Definition log Log output format
 60 standard_format = '%(asctime)s - %(threadName)s:%(thread)d - task_id:%(name)s - %(filename)s:%(lineno)d - ' \
 61                   '%(levelname)s - %(message)s'  # among name by getlogger Specified name
 62 
 63 simple_format = '\n%(levelname)s - %(asctime)s - %(filename)s:%(lineno)d - %(message)s\n'
 64 
 65 
 66 # log Configuration dictionary
 67 LOGGING_DIC = {
 68     'version': 1,
 69     'disable_existing_loggers': False,
 70     'formatters': {
 71         'standard': {
 72             'format': standard_format
 73         },
 74         'simple': {
 75             'format': simple_format,
 76         },
 77     },
 78     'filters': {},
 79     'handlers': {
 80         # Log printed to terminal
 81         'console': {
 82             'level': 'DEBUG',
 83             'class': 'logging.StreamHandler',  # Print to screen
 84             'formatter': 'simple'
 85         },
 86         # Log printed to file,collect info And above
 87         'access': {
 88             'level': 'DEBUG',
 89             'class': 'logging.handlers.RotatingFileHandler',  # Save to file
 90             'formatter': 'standard',
 91             'filename': ACCESS_LOG_PATH,  # log file
 92             # 'maxBytes': 1024 * 1024 * 5,  # Log size 5M
 93             'maxBytes': 1024 * 1024 * 5,
 94             'backupCount': 10,
 95             'encoding': 'utf-8',  # No need to worry about the coding of log files log Messy code.
 96         },
 97     },
 98     'loggers': {
 99         # logging.getLogger(__name__)Get logger To configure
100         '': {
101             'handlers': ['access', 'console'],  # Here are the two defined above handler All added, i.e log Data is written to file and printed to screen
102             'level': 'DEBUG',
103             'propagate': False,  # Up (higher level Of logger)transmit
104         },
105     },
106 }
settings.py

2) core

  1 import json
  2 import os
  3 import shelve
  4 import struct
  5 import subprocess
  6 
  7 from conf import settings
  8 from lib import common
  9 
 10 
 11 class HandlerRequest:
 12     """Processing user requests."""
 13     max_packet_size = 8192
 14     encoding = 'utf-8'
 15 
 16     struct_fmt = 'i'
 17     fixed_packet_size = 4
 18 
 19     logger = common.load_my_logging_cfg()
 20 
 21     def __init__(self, request, address):
 22         self.request = request
 23         self.address = address
 24 
 25         self.residual_space_size = None
 26 
 27         self.breakpoint_resume = None
 28 
 29         self.username = None
 30         self.user_obj = None
 31         self.user_current_dir = None
 32 
 33     def client_close(self):
 34         """Close client connection."""
 35         self.request.close()
 36 
 37     def handle_request(self):
 38         """Processing client requests."""
 39         count = 0
 40         while count < 3:  # Connection cycle
 41             try:
 42                 if self.auth():
 43                     # Receive news
 44                     user_dic = self.receive_header()
 45                     action_type = user_dic.get('action_type')
 46                     if action_type:
 47                         if hasattr(self, '_%s' % action_type):
 48                             func = getattr(self, '_%s' % action_type)
 49                             func(user_dic)
 50                         else:
 51                             self.send_header(status_code=201)
 52                     # Send news
 53                     else:
 54                         self.send_header(status_code=200)
 55                 else:
 56                     count += 1
 57                     self.send_header(status_code=199)
 58             except ConnectionResetError:
 59                 break
 60         # Close client connection
 61         self.logger.info('----Connection disconnect---- ip:%s port:%s' % self.address)
 62         self.client_close()
 63 
 64     def unfinished_file_check(self):
 65         self.logger.info('#implement unfinished_file_check command# ip:%s port:%s' % self.address)
 66 
 67         if not list(self.breakpoint_resume.keys()):
 68             self.send_header(status_code=851)
 69             return
 70 
 71         #  self.breakpoint_resume[file_path] =
 72         #  {'file_size': _file_size, 'unfinished_file_path': unfinished_file_path, 'file_name': _file_name}
 73         msg_list = []
 74 
 75         for index, abs_path in enumerate(self.breakpoint_resume.keys(), 1):\
 76 
 77             user_path = '/'.join(abs_path.split(self.username)[-1].split(os.sep))
 78             print('abs_path:', user_path)
 79             file_name = self.breakpoint_resume[abs_path]['file_name']
 80             src_file_size = self.breakpoint_resume[abs_path]['file_size']
 81             unfinished_file_size = os.path.getsize(self.breakpoint_resume[abs_path]['unfinished_file_path'])
 82             percent = unfinished_file_size / src_file_size * 100
 83 
 84             msg = """
 85             Number: %s  File path: %s file name: %s
 86                 Original file size: %s Byte incomplete file size: %s Percentage of bytes Uploaded: %.2f%%
 87             """ % (index, user_path, file_name, src_file_size, unfinished_file_size, percent)
 88 
 89             msg_list.append(msg)
 90             # msg_dic['/03_Three forms of function call.mp4'] = 5772100
 91             # msg_dic[user_path] = unfinished_file_size
 92         # self.send_header(status_code=850, msg_list=msg_list, msg_dic=msg_dic)
 93         self.send_header(status_code=850, msg_list=msg_list)
 94 
 95     def auth(self):
 96         """User login authentication."""
 97         if self.user_current_dir:
 98             return True
 99 
100         # Cross import involved
101         from core import main
102         # Receive news
103         auth_dic = self.receive_header()
104 
105         user_name = auth_dic.get('username')
106         user_password = auth_dic.get('password')
107         md5_password = common.md5('password', password=user_password)
108 
109         # print(user_name, user_password,  md5_password)
110 
111         accounts = main.FTPServer.load_accounts()
112         if user_name in accounts.sections():
113             if md5_password == accounts[user_name]['password']:
114                 self.send_header(status_code=100)
115 
116                 self.username = user_name
117                 self.user_obj = accounts[user_name]
118                 self.user_obj['home'] = os.path.join(settings.USER_HOME_DIR, user_name)
119                 self.user_current_dir = self.user_obj['home']
120 
121                 # print('self.user_obj:', self.user_obj)
122                 # print("self.user_obj['home']:", self.user_obj['home'])
123 
124                 self.residual_space_size = common.conversion_quota(
125                     self.user_obj['quota']) - common.get_size(self.user_obj['home'])
126 
127                 breakpoint_resume_dir_path = os.path.join(self.user_obj['home'], '.upload')
128                 if not os.path.isdir(breakpoint_resume_dir_path):
129                     os.mkdir(breakpoint_resume_dir_path)
130                 self.breakpoint_resume = shelve.open(os.path.join(breakpoint_resume_dir_path, '.upload.shv'))
131                 self.unfinished_file_check()
132 
133                 self.logger.info('#Certification success# ip:%s port:%s' % self.address)
134                 return True
135         self.logger.info('#Authentication failed# ip:%s port:%s' % self.address)
136         return False
137 
138     def _ls(self, cmd_dic):
139         """
140         Function dir Command to send results to the client.
141         :param cmd_dic: {'path': [], 'action_type': 'ls'}
142                     or  {'path': ['Directory 1', 'Directory 2', 'Catalog xxx'], 'action_type': 'ls'}
143                     or  {'path': ['?'], 'action_type': 'ls'}
144         :return: None
145         """
146         # print('_ls:', cmd_dic)
147         self.logger.info('#implement ls command# ip:%s port:%s' % self.address)
148 
149         # Verification path
150         dir_path = self.verify_path(cmd_dic)
151         if not dir_path:
152             dir_path = self.user_current_dir
153 
154         if cmd_dic.get('path') == ['?']:  # For users ls /?command
155             dir_path = '/?'
156 
157         sub_obj = subprocess.Popen(
158             'dir %s' % dir_path,
159             shell=True,
160             stderr=subprocess.PIPE,
161             stdout=subprocess.PIPE
162         )
163         stderr_bytes, stdout_bytes = sub_obj.stderr.read(), sub_obj.stdout.read()
164         cmd_size = len(stderr_bytes) + len(stdout_bytes)
165 
166         # Header
167         self.send_header(status_code=301, cmd_size=cmd_size)
168         # Send news
169         self.request.sendall(stderr_bytes)
170         self.request.sendall(stdout_bytes)
171 
172     def _cd(self, cmd_dic):
173         """
174         According to the user's target directory, Change the value of the user's current directory.
175         :param cmd_dic: {'action_type': 'cd', 'path': ['..']}
176                      or {'action_type': 'cd', 'path': ['Directory 1', 'Directory 2', 'Catalog xxx'], }
177         :return: None
178                  Z:\\pycharm\\Development FTP The way of procedure\\The second time FTP_Operation of the fourth module\\FUCK_FTP\\server\\home\\egon\\Directory 1
179         """
180         # print('_cd:', cmd_dic)
181         self.logger.info('#implement cd command# ip:%s port:%s' % self.address)
182 
183         # Verification path
184         dir_path = self.verify_path(cmd_dic)
185         if dir_path:
186             if os.path.isdir(dir_path):  # Judge whether the path of user switching exists
187                 self.user_current_dir = dir_path
188                 if dir_path == self.user_obj['home']:
189                     current_dir = '~'
190                 else:
191                     join_dir = ''.join(dir_path.split('%s' % self.username)[1:])
192                     current_dir = '/'.join(join_dir.split('\\'))
193                 self.send_header(status_code=400, current_dir=current_dir)
194             else:
195                 self.send_header(status_code=499)
196         else:
197             self.send_header(status_code=498)
198 
199     def _mkdir(self, cmd_dic):
200         """
201         Target directory with more users, And the directory does not exist, Create a catalog, Generate multi-level recursive directory.
202         :param cmd_dic: {'action_type': 'mkdir', 'path': ['Directory 1']}
203                     or  {'action_type': 'mkdir', 'path': ['Directory 2', 'Directory 3', 'Catalog xxx']}
204         :return: None
205         """
206         # print('_mkdir:', cmd_dic)
207         self.logger.info('#implement mkdir command# ip:%s port:%s' % self.address)
208 
209         dir_path = self.verify_path(cmd_dic)
210         if dir_path:
211             if not os.path.isdir(dir_path):  # Determine whether the directory to be created by the user exists
212                 os.makedirs(dir_path)
213                 self.send_header(status_code=500)
214             else:
215                 self.send_header(status_code=599)
216         else:
217             self.send_header(status_code=598)
218 
219     def _rmdir(self, cmd_dic):
220         """
221         Target directory with more users, Delete a directory that is not empty.
222         :param cmd_dic: {'path': ['Directory 1', 'Catalog xxx', 'Empty directory'], 'action_type': 'rmdir'}
223         :return: None
224         """
225         # print('_rmdir:', cmd_dic)
226         self.logger.info('#implement rmdir command# ip:%s port:%s' % self.address)
227 
228         dir_path = self.verify_path(cmd_dic)
229         if dir_path:
230             if os.path.isdir(dir_path):
231                 if os.listdir(dir_path):
232                     self.send_header(status_code=699)
233                 else:
234                     os.rmdir(dir_path)
235                     self.send_header(status_code=600)
236             else:
237                 self.send_header(status_code=698)
238         else:
239             self.send_header(status_code=697)
240 
241     def _remove(self, cmd_dic):
242         """
243         More user oriented target files, Delete the file
244         :param cmd_dic: {'path': ['Directory 1', 'Catalog xxx', 'file'], 'action_type': 'remove'}
245         :return:
246         """
247         # print('_remove:', cmd_dic)
248         self.logger.info('#implement remove command# ip:%s port:%s' % self.address)
249         file_path = self.verify_path(cmd_dic)
250 
251         if file_path:
252             if os.path.isfile(file_path):
253                 # Determine whether the file deleted by the user is the file to be renewed, If so, delete the record of the renewal first
254                 if file_path in self.breakpoint_resume.keys:
255                     del self.breakpoint_resume[file_path]
256                 os.remove(file_path)
257                 self.send_header(status_code=700)
258             else:
259                 self.send_header(status_code=799)
260         else:
261             self.send_header(status_code=798)
262 
263     def _resume_upload(self, cmd_dic):
264         """
265         860: 'You are continuing to upload files, Before you continue, Your current space:%s!',
266         869: 'There are no files to be renewed in the file path you selected, Please check.!',
267         :param cmd_dic:
268         :return:
269         """
270         # print('def _resume_upload ===> cmd_args', cmd_dic)
271         self.logger.info('#implement resume_upload command# ip:%s port:%s' % self.address)
272         self._upload(cmd_dic, resume_upload=True)
273 
274     def _upload(self, cmd_dic, resume_upload=False):
275         """Client
276             800: 'You can upload files, Before you upload, Your current space:%s!',
277             801: 'Upload file succeeded, Space left after you upload:%s!',
278             850: 'You have also uploaded files for, Continue to upload!',
279             851: 'Check that you don't have any files that haven't been uploaded!',
280             852: 'You cannot renew, Because the file is complete!',
281             860: 'You are continuing to upload files, Before you continue, Your current space:%s!',
282             869: 'There are no files to be renewed in the file path you selected, Please check.!',
283             894: 'You do not need to upload files under this path, The file already exists in your current path!',
284             895: 'Failed to upload file, md5 Inconsistent validity, Some file contents are lost in the network, Please upload again!',
285             896: 'Failed to upload file, You don't have enough space, Your upload fake file size, Your remaining space:%s!',
286             897: 'Failed to upload file, You don't have enough space, Your remaining space:%s!',
287             898: 'Failed to upload file, Upload command is not standard!',
288             899: 'There must be a file for uploading md5 Value and filename!',
289         """
290         # print('_upload:', cmd_dic)
291         if not resume_upload:
292             self.logger.info('#implement upload command# ip:%s port:%s' % self.address)
293 
294         # Validation: 897, 898, 899
295         _path, _file_md5, _file_name, _file_size = cmd_dic.get('path'), cmd_dic.get('file_md5'), cmd_dic.get(
296             'file_name'), cmd_dic.get('file_size')
297         file_path = self.verify_upload_action(cmd_dic, _path=_path, _file_md5=_file_md5, _file_name=_file_name,
298 
299                                               _file_size=_file_size)
300 
301         if resume_upload:   # Execute when resuming a breakpoint
302             if not file_path or file_path not in self.breakpoint_resume.keys():
303                 # 869: 'There are no files to be renewed in the file path you selected, Please check.!',
304                 self.send_header(status_code=869)
305                 return
306 
307             # Find file names that have not been worn out before
308             unfinished_file_path = self.breakpoint_resume[file_path]['unfinished_file_path']
309             already_upload_size = os.path.getsize(unfinished_file_path)
310 
311             # Validation success notification renewal signal
312             # 860: 'You are continuing to upload files, Before you continue, Your current space:%s!',
313             self.send_header(status_code=860, residual_space_size=self.residual_space_size,
314                              already_upload_size=already_upload_size)
315 
316             total_size = _file_size - already_upload_size
317             mode = 'a'
318         else:           # Normal upload execution
319             if not file_path:
320                 return
321 
322             # Judge whether the file uploaded by the user is duplicate
323             if os.path.isfile(file_path):
324                 # 894: 'You do not need to upload files under this path, The file already exists in your current path!',
325                 self.send_header(status_code=894)
326                 return
327             else:
328                 unfinished_file_path = '%s.%s' % (file_path, 'upload')
329 
330             # Upload signal of successful validation notification: 800
331             # 800: 'You can upload files, Before you upload, Your current space:%s!',
332             self.send_header(status_code=800, residual_space_size=self.residual_space_size)
333 
334             total_size = _file_size
335             mode = 'w'
336 
337         # Function of recording breakpoints: The path of the user on the server, Record file size, Path with suffix, file name
338         # Or record the breakpoint for the unfinished file again
339         self.breakpoint_resume[file_path] = {'file_size': _file_size, 'unfinished_file_path': unfinished_file_path,
340                                              'file_name': _file_name}
341 
342         # Start receiving files
343         receive_size = 0
344         with open(unfinished_file_path, '%sb' % mode) as f:
345             while receive_size < total_size:
346                 data_bytes = self.request.recv(self.max_packet_size)
347                 receive_size += len(data_bytes)
348                 f.write(data_bytes)
349         # End of reception, Change the suffix to the file name uploaded by the user
350         os.rename(unfinished_file_path, file_path)
351         # Delete the function of recording breakpoints
352         del self.breakpoint_resume[file_path]
353 
354         # 801, 895, 896
355         # Validate the data sent by the client md5 After this upload md5 value
356         upload_file_md5 = common.md5(encryption_type='file', path=file_path)
357         if upload_file_md5 != _file_md5:
358             # print('def _upload ===> upload_file_md5:%s, _file_md5:%s' % (upload_file_md5, _file_md5))
359             # 895: 'Failed to upload file, md5 Inconsistent validity, Some file contents are lost in the network, Please upload again!',
360             self.send_header(status_code=895)
361             os.remove(file_path)
362             return
363 
364         # Security issues: Again, judge whether users use fake file size to jump out of the quota limited by the server
365         if receive_size > self.residual_space_size:
366             # 896: 'Failed to upload file, You don't have enough space, Your upload fake file size, Your remaining space:%s!',
367             self.send_header(status_code=896, residual_space_size=self.residual_space_size)
368             os.remove(file_path)
369             return
370         else:
371             self.residual_space_size = self.residual_space_size - receive_size
372             # print('def _upload ===> receive_size:', receive_size)
373             # print('def _upload ===> os.path.getsize(file_path)', os.path.getsize('%s' % file_path))
374             # 801: 'Upload file succeeded, Space left after you upload:%s!',
375             self.send_header(status_code=801, residual_space_size=self.residual_space_size)
376 
377     def _resume_download(self, cmd_dic):
378         self._download(cmd_dic, resume_download=True)
379 
380     def _download(self, cmd_dic, resume_download=False):
381         self.logger.info('#implement download command# ip:%s port:%s' % self.address)
382 
383         file_path = self.verify_path(cmd_dic)
384         if not file_path:
385             # 999: 'Download file failed, The file path you are trying to download is not standard!',
386             self.send_header(status_code=999)
387             return
388 
389         if not os.path.isfile(file_path):
390             # 998: 'Download file failed, The file path you want to download does not exist!',
391             self.send_header(status_code=998)
392             return
393 
394         # Notifications can start downloading
395         # 900: 'Ready to start downloading files!'.
396         file_name = file_path.split(os.sep)[-1]
397         file_size = os.path.getsize(file_path)
398         file_md5 = common.md5('file', file_path)
399         unfinished_file_size = cmd_dic.get('unfinished_file_size')
400         if resume_download:
401             # 950: 'Ready to resume files!',
402             self.send_header(status_code=950, file_name=file_name, file_size=file_size, file_md5=file_md5)
403         else:
404             # 900: 'Ready to start downloading files!'.
405             self.send_header(status_code=900, file_name=file_name, file_size=file_size, file_md5=file_md5)
406 
407         # Open file send to client
408         with open(file_path, 'rb') as f:
409             if resume_download:
410                 f.seek(unfinished_file_size)
411             for line in f:
412                 self.request.sendall(line)
413 
414     def verify_upload_action(self, cmd_dic, *, _path, _file_name, _file_md5, _file_size):
415         """
416         Verify upload function.
417         897: 'Failed to upload file, You don't have enough space, Your remaining space:%s!',
418         898: 'Failed to upload file, Upload command is not standard!',
419         899: 'There must be a file for uploading md5 Value and filename!',
420         """
421         # _path=['03_Three forms of function call.mp4']
422         if _path is None:
423             if _file_name and _file_md5 and _file_size:
424                 if _file_size > self.residual_space_size:
425                     # print('def _upload ===> self.residual_space_size:', self.residual_space_size)
426 
427                     # 897: 'Failed to upload file, You don't have enough space, Your remaining space:%s!',
428                     self.send_header(status_code=897, residual_space_size=self.residual_space_size)
429                     return False
430                 else:
431                     # Z:\pycharm\Development FTP The way of procedure\The second time FTP_Operation of the fourth module\FUCK_FTP\server\home\egon\03_Three forms of function call.mp4
432                     file_path = os.path.join(self.user_current_dir, _file_name)
433             else:
434                 # 899: 'There must be a file for uploading md5 Value and filename!',
435                 self.send_header(status_code=899)
436                 return False
437         else:
438             path = self.verify_path(cmd_dic)
439 
440             if not path:
441                 # 898: 'Failed to upload file, Upload command is not standard!',
442                 self.send_header(status_code=898)
443                 return False
444             else:
445                 # Z:\pycharm\Development FTP The way of procedure\The second time FTP_Operation of the fourth module\FUCK_FTP\server\home\egon\03_Three forms of function call.mp4
446                 file_path = os.path.join(path, _file_name)
447         return file_path
448 
449     def verify_path(self, cmd_dic):
450         """
451         Verify the path from the client.
452         :param cmd_dic: {'action_type': 'ls', 'path': []}
453                     or  {'action_type': 'ls', 'path': ['Directory 1', 'Catalog xxx']}
454                     or  {action_type': 'cd', 'path': ['Directory 2', 'Catalog xxx']}
455         :return: None
456                  Z:\\pycharm\\Development FTP The way of procedure\\The second time FTP_Operation of the fourth module\\FUCK_FTP\\server\\home\\egon\\Directory 1
457                  Z:\\pycharm\\Development FTP The way of procedure\\The second time FTP_Operation of the fourth module\\FUCK_FTP\\server\\home\\egon\\Directory 1
458         """
459         # print(cmd_dic)
460         path = cmd_dic.get('path')
461         if path:
462             if isinstance(path, list):
463                 for element in path:
464                     if not isinstance(element, str):
465                         path = None
466                         return path
467                 abspath = os.path.normpath(os.path.join(self.user_current_dir, *path))
468                 # print('def verify_path() ===> abspath:', abspath)
469                 if abspath.startswith(self.user_obj['home']):
470                     path = abspath
471                 else:
472                     path = None  # User directory out of limit
473             else:
474                 path = None  # Not a list type example: 'Character string'
475         else:
476             path = None  # []
477         # print('def verify_path() ====> path', path)
478         return path
479 
480     def receive_header(self):
481         """
482         Receive client data.
483         :return: {'action_type': 'cd', 'path': ['Directory 1', 'Catalog xxx']}
484         """
485         header_bytes = self.request.recv(self.fixed_packet_size)
486         request_dic_json_length = struct.unpack(self.struct_fmt, header_bytes)[0]
487         # print('request_dic_json_length:', request_dic_json_length)
488         # Receiving header
489         request_dic_json = self.request.recv(request_dic_json_length).decode(self.encoding)
490         request_dic = json.loads(request_dic_json)
491 
492         # print('request_dic:', request_dic)
493 
494         if not request_dic:
495             return {}
496         # print("def receive_header():", request_dic)
497         return request_dic
498 
499     def send_header(self, *, status_code, **kwargs):
500         """
501         Send data to client.
502         :param status_code: 400
503         :param kwargs: {'current_dir': '/home/egon/Directory 1/Catalog xxx'}
504         :return: None
505         """
506         # print(status_code)
507         # print(kwargs)
508         from core import main
509 
510         response_dic = kwargs
511         response_dic['status_code'] = status_code
512         response_dic['status_msg'] = main.FTPServer.STATUS_CODE[status_code]
513         response_dic.update(kwargs)
514 
515         response_dic_json_bytes = json.dumps(response_dic).encode(self.encoding)
516         response_dic_json_bytes_length = len(response_dic_json_bytes)
517         header_bytes = struct.pack(self.struct_fmt, response_dic_json_bytes_length)
518 
519         # print('header_bytes:', header_bytes)
520 
521         # Sending header
522         self.request.sendall(header_bytes)
523         # Send out json after bytes Post dictionary response_dic
524         self.request.sendall(response_dic_json_bytes)
handler_request.py
 1 import configparser
 2 import socket
 3 
 4 from conf import settings
 5 from core import handler_request, mythreadpool
 6 from lib import common
 7 
 8 
 9 class FTPServer:
10     """FTP The server."""
11     address_family = socket.AF_INET
12     socket_type = socket.SOCK_STREAM
13     allow_reuse_address = False
14     request_queue_size = 5
15 
16     max_pool_size = 5
17 
18     STATUS_CODE = settings.STATUS_CODE
19 
20     logger = common.load_my_logging_cfg()
21 
22     def __init__(self, management_instance, bind_address, bind_and_activate=True):
23         self.management_instance = management_instance
24 
25         self.pool = mythreadpool.MyThreadPool(self.max_pool_size)
26 
27         self.bind_address = bind_address
28         self.socket = socket.socket(self.address_family, self.socket_type)
29 
30         if bind_and_activate:
31             try:
32                 self.server_bind()
33                 self.server_activate()
34             except Exception:
35                 self.server_close()
36                 raise
37 
38     def server_bind(self):
39         """Server binding IP,port."""
40         if self.allow_reuse_address:
41             self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
42         self.socket.bind(self.bind_address)
43 
44     def server_activate(self):
45         """Server activation."""
46         self.socket.listen(self.request_queue_size)
47 
48     def server_close(self):
49         """Shut down service socket object."""
50         self.socket.close()
51 
52     def serve_forever(self):
53         """Server runs forever."""
54         while True:  # Communication cycle
55             request, address = self.socket.accept()
56 
57             self.logger.info('----Connect----# ip:%s port:%s' % address)
58 
59             # A connection, Instantiate an object to handle user requests
60             handler_response = handler_request.HandlerRequest(request, address)
61             # Here comes a connection to take a thread
62             thread = self.pool.get_thread()
63             # Add another thread at the same time
64             self.pool.put_thread()
65             t = thread(target=handler_response.handle_request)
66             t.start()
67 
68     @staticmethod
69     def load_accounts():
70         conf_obj = configparser.ConfigParser()
71         conf_obj.read(settings.ACCOUNTS_FILE)
72         return conf_obj
main.py
 1 import sys
 2 
 3 from conf import settings
 4 from core import main
 5 
 6 
 7 class ManagementTool(object):
 8     """Management server."""
 9     center_args1, center_args2 = 50, '-'
10 
11     def __init__(self):
12         self.script_argv = sys.argv
13         self.commands = None
14 
15         # print(self.script_argv)
16 
17         self.verify_argv()
18 
19     def verify_argv(self):
20         """
21         Check whether the parameters are reasonable.
22             example:
23             ['Boot file path', 'start', 'ftp', 'server']
24         """
25         if len(self.script_argv) != 4:
26             self.help_msg()
27 
28         action_type = self.script_argv[1]
29         self.commands = self.script_argv[2:]
30         if hasattr(self, action_type):
31             func = getattr(self, action_type)
32             func()
33         else:
34             self.help_msg()
35 
36     @staticmethod
37     def help_msg():
38         msg = """
39         ------The following commands are strictly required:------
40             ① start   ftp server
41             ② stop    ftp server
42             ③ restart ftp server
43         """
44         exit(msg)
45 
46     def start(self):
47         """start-up ftp service."""
48         if self.execute():
49             print('FTP started successfully!')
50             # FTPServer May be used in ManagementTool Medium function
51             server = main.FTPServer(self, (settings.HOST, settings.PORT))
52             server.serve_forever()
53         else:
54             self.help_msg()
55 
56     def execute(self):
57         """Analytical command."""
58         args1, args2 = self.commands
59         if args1 == 'ftp' and args2 == 'server':
60             return True
61         return False
management.py
 1 import os
 2 import queue
 3 from threading import Thread
 4 
 5 
 6 class MyThreadPool:
 7     def __init__(self, max_workers=None):
 8         if not max_workers:
 9             max_workers = os.cpu_count() * 5
10         if max_workers <= 0:
11             raise ValueError('max_workers Must be greater than 0')
12 
13         self.queue = queue.Queue(max_workers)
14         for count in range(max_workers):
15             self.put_thread()
16 
17     def put_thread(self):
18         self.queue.put(Thread)
19 
20     def get_thread(self):
21         return self.queue.get()
mythreadpool.py

3) home

4) lib

 1 import hashlib
 2 import logging.config
 3 import os
 4 
 5 from conf import settings
 6 
 7 
 8 def md5(encryption_type, path=None, password=None):
 9     """
10     md5 encryption.
11     :param encryption_type: Type of encryption, Support file and password two types
12     :param path: File or directory path
13     :param password: enable password
14     :return: Encrypted md5 value
15     """
16     md5_obj = hashlib.md5()
17     if encryption_type == 'file':
18         if os.path.isfile(path):
19             with open(path, 'rb') as f:
20                 for line in f:
21                     md5_obj.update(line)
22             return md5_obj.hexdigest()
23         for filename in os.listdir(path):
24             current_path = os.path.join(path, filename)
25             if os.path.isdir(current_path):
26                 md5(encryption_type, path=current_path)
27             else:
28                 with open(current_path, 'rb') as f:
29                     for line in f:
30                         md5_obj.update(line)
31     elif encryption_type == 'password':
32         md5_obj.update(password.encode('utf-8'))
33     return md5_obj.hexdigest()
34 
35 
36 def load_my_logging_cfg():
37     """
38     Load log dictionary.
39     :return: logger object
40     """
41     logging.config.dictConfig(settings.LOGGING_DIC)
42     logger = logging.getLogger(__name__)
43     return logger
44 
45 
46 def get_size(path):
47     """
48     Traversal user path, Get path Path size for, This size contains all files in the directory.
49     :param path: Route
50     :return: Size of all files under this path
51     """
52     initial_size = 0
53     if os.path.isfile(path):
54         return os.path.getsize(path)
55     for filename in os.listdir(path):
56         current_path = os.path.join(path, filename)
57         if os.path.isdir(current_path):
58             get_size(current_path)
59         else:
60             initial_size += os.path.getsize(current_path)
61     return initial_size
62 
63 
64 def conversion_quota(quota_mb: str):
65     """
66     Convert user disk quota, hold MB Convert into bytes.
67     :param quota_mb:
68     :return: satisfy isdigit Return quota_bytes, The default quota size is not met
69     """
70     if quota_mb.isdigit():
71         quota_mb = int(quota_mb)
72         quota_bytes = quota_mb * 1024 ** 2
73         # print('def conversion_quota ===> quota_bytes:', quota_bytes)
74         return quota_bytes
75     else:
76         default_quota_bytes = 50 * 1024 ** 2
77         return default_quota_bytes
common.py

5) log

 

 1 # encoding:utf-8
 2 
 3 import os
 4 import sys
 5 
 6 BASE_DIR = os.path.normpath(os.path.join(__file__, '..'))
 7 sys.path.append(BASE_DIR)
 8 
 9 if __name__ == '__main__':
10     from core import management
11     management = management.ManagementTool()
12     management.execute()
ftp_server.py

Tags: Python ftp socket Pycharm encoding

Posted on Wed, 06 Nov 2019 03:44:01 -0800 by scuba