© Magnus Lie Hetland 2017

Magnus Lie Hetland, Beginning Python, 10.1007/978-1-4842-0028-5_28

28. Project 9: File Sharing II—Now with GUI!

Magnus Lie Hetland

(1)Trondheim, Norway

This is a relatively short project because much of the functionality you need has already been written—in Chapter 27. In this chapter, you see how easy it can be to add a GUI to an existing Python program.

What’s the Problem?

In this project, we’ll expand the file-sharing system developed in Chapter 27, with a GUI client. This will make the program easier to use, which means that more people might choose to use it (and, of course, multiple users sharing files is the whole point of the program). A secondary goal of this project is to show that a program that has a sufficiently modular design can be quite easy to extend (one of the arguments for using object-oriented programming).

The GUI client should satisfy the following requirements :

  • It should allow you to enter a file name and submit it to the server’s fetch method.

  • It should list the files currently available in the server’s file directory.

That’s it. Because you already have much of the system working, the GUI part is a relatively simple extension.

Useful Tools

In addition to the tools used in Chapter 27, you will need the Tkinter toolkit, which comes bundled with most Python installations. For more information about Tkinter, see Chapter 12. If you want to use another GUI toolkit, feel free to do so. The example in this chapter will give you the general idea of how you can build your own implementation, with your favorite tools.

Preparations

Before you begin this project, you should have Project 8 (from Chapter 27) in place and a usable GUI toolkit installed, as mentioned in the previous section. Beyond that, no significant preparations are necessary for this project.

First Implementation

If you want to take a peek at the full source code for the first implementation, you can find it in Listing 28-1 later in this section. Much of the functionality is quite similar to that of the project in the preceding chapter. The client presents an interface (the fetch method ) through which the user may access the functionality of the server. Let’s review the GUI-specific parts of the code.

The client in Chapter 27 was a subclass of cmd.Cmd; the Client described in this chapter subclasses tkinter.Frame. While you’re not required to subclass tkinter.Frame (you could create a completely separate Client class), it can be a natural way of organizing your code. The GUI-related setup is placed in a separate method, called create_widgets, which is called in the constructor. It creates an entry for file names, and a button for fetching a given file, with the action of the button set to the method fetch_handler. This event handler is quite similar to the handler do_fetch from Chapter 27. It retrieves the query from self.input (the text field). It then calls self.server.fetch inside a try/except statement.

The source code for the first implementation is shown in Listing 28-1.

Listing 28-1. A Simple GUI Client (simple_guiclient.py)
from xmlrpc.client import ServerProxy, Fault
from server import Node, UNHANDLED
from client import random_string
from threading import Thread
from time import sleep
from os import listdir
import sys
import tkinter as tk


HEAD_START = 0.1 # Seconds
SECRET_LENGTH = 100


class Client(tk.Frame):

    def __init__(self, master, url, dirname, urlfile):
        super().__init__(master)
        self.node_setup(url, dirname, urlfile)
        self.pack()
        self.create_widgets()


    def node_setup(self, url, dirname, urlfile):
        self.secret = random_string(SECRET_LENGTH)
        n = Node(url, dirname, self.secret)
        t = Thread(target=n._start)
        t.setDaemon(1)
        t.start()
        # Give the server a head start:
        sleep(HEAD_START)
        self.server = ServerProxy(url)
        for line in open(urlfile):
            line = line.strip()
            self.server.hello(line)


    def create_widgets(self):
        self.input = input = tk.Entry(self)
        input.pack(side='left')


        self.submit = submit = tk.Button(self)
        submit['text'] = "Fetch"
        submit['command'] = self.fetch_handler
        submit.pack()


    def fetch_handler(self):
        query = self.input.get()
        try:
            self.server.fetch(query, self.secret)
        except Fault as f:
            if f.faultCode != UNHANDLED: raise
            print("Couldn't find the file", query)


def main():
    urlfile, directory, url = sys.argv[1:]


    root = tk.Tk()
    root.title("File Sharing Client")


    client = Client(root, url, directory, urlfile)
    client.mainloop()


if __name__ == "__main__": main()

Except for the relatively simple code explained previously, the GUI client works just like the text-based client in Chapter 27. You can run it in the same manner, too. To run this program, you need a URL file, a directory of files to share, and a URL for your Node. Here is a sample run:

$ python simple_guiclient.py urlfile.txt files/ http://localhost:8000

Note that the file urlfile.txt must contain the URLs of some other Nodes for the program to be of any use. You can either start several programs on the same machine (with different port numbers) for testing purposes or run them on different machines. Figure 28-1 shows the GUI of the client.

A326949_3_En_28_Fig1_HTML.jpg
Figure 28-1. The simple GUI client

This implementation works, but it performs only part of its job. It should also list the files available in the server’s file directory. To do that, the server (Node) itself must be extended.

Second Implementation

The first prototype was very simple. It did its job as a file-sharing system but wasn’t very user friendly. It would help a lot if users could see which files they had available (either located in the file directory when the program starts or subsequently downloaded from another Node). The second implementation will address this file listing issue. The full source code can be found in Listing 28-2.

To get a listing from a Node, you must add a method. You could protect it with a password as you have done with fetch, but making it publicly available may be useful, and it doesn’t represent any real security risk. Extending an object is really easy: you can do it through subclassing. You simply construct a subclass of Node called ListableNode, with a single additional method, list, which uses the method os.listdir, which returns a list of all the files in a directory.

class ListableNode(Node):

    def list(self):
        return listdir(self.dirname)

To access this server method, the method update_list is added to the client.

def update_list(self):
    self.files.Set(self.server.list())

The attribute self.files refers to a list box, which has been added in the create_widgets method. The update_list method is called in create_widgets at the point where the list box is created and again each time fetch_handler is called (because calling fetch_handler may potentially alter the list of files).

Listing 28-2. The Finished GUI Client (guiclient.py)
from xmlrpc.client import ServerProxy, Fault
from server import Node, UNHANDLED
from client import random_string
from threading import Thread
from time import sleep
from os import listdir
import sys
import tkinter as tk


HEAD_START = 0.1 # Seconds
SECRET_LENGTH = 100


class ListableNode(Node):

    def list(self):
        return listdir(self.dirname)


class Client(tk.Frame):

    def __init__(self, master, url, dirname, urlfile):
        super().__init__(master)
        self.node_setup(url, dirname, urlfile)
        self.pack()
        self.create_widgets()


    def node_setup(self, url, dirname, urlfile):
        self.secret = random_string(SECRET_LENGTH)
        n = ListableNode(url, dirname, self.secret)
        t = Thread(target=n._start)
        t.setDaemon(1)
        t.start()
        # Give the server a head start:
        sleep(HEAD_START)
        self.server = ServerProxy(url)
        for line in open(urlfile):
            line = line.strip()
            self.server.hello(line)


    def create_widgets(self):
        self.input = input = tk.Entry(self)
        input.pack(side='left')


        self.submit = submit = tk.Button(self)
        submit['text'] = "Fetch"
        submit['command'] = self.fetch_handler
        submit.pack()


        self.files = files = tk.Listbox()
        files.pack(side='bottom', expand=True, fill=tk.BOTH)
        self.update_list()


    def fetch_handler(self):
        query = self.input.get()
        try:
            self.server.fetch(query, self.secret)
            self.update_list()
        except Fault as f:
            if f.faultCode != UNHANDLED: raise
            print("Couldn't find the file", query)


    def update_list(self):
        self.files.delete(0, tk.END)
        self.files.insert(tk.END, self.server.list())


def main():
    urlfile, directory, url = sys.argv[1:]


    root = tk.Tk()
    root.title("File Sharing Client")


    client = Client(root, url, directory, urlfile)
    client.mainloop()


if __name__ == '__main__': main()

And that’s it. You now have a GUI-enabled peer-to-peer file-sharing program, which can be run with this command:

$ python guiclient.py urlfile.txt files/ http://localhost:8000

Figure 28-2 shows the finished GUI client.

A326949_3_En_28_Fig2_HTML.jpg
Figure 28-2. The finished GUI client

Of course, there are plenty of ways to expand the program. For some ideas, see the next section. Beyond that, just let your imagination go wild.

Further Exploration

Some ideas for extending the file-sharing system are given in Chapter 27. Here are some more:

  • Let the user select the desired file, rather than typing in its name.

  • Add a status bar that displays such messages as “Downloading” or “Couldn’t find file foo.txt.”

  • Figure out ways for Nodes to share their “friends.” For example, when one Node is introduced to another, each of them could introduce the other to the Nodes it already knows. Also, before a Node shuts down, it might tell all its current neighbors about all the Nodes it knows.

  • Add a list of known Nodes (URLs) to the GUI. Make it possible to add new URLs and save them in a URL file.

What Now?

You’ve written a full-fledged GUI-enabled peer-to-peer file sharing system. Although that sounds pretty challenging, it wasn’t all that hard, was it? Now it’s time to face the last and greatest challenge: writing your own arcade game.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset