History

Added a simple client class.

David Boddie committed 8 years ago

Commit c88f4d31 parent dbde0f81 file · +174 −0Browse files

Unified · Split

client.py+174−0
@@ -0,0 +1,174 @@
1#!/usr/bin/env python
2
3# client.py - A client providing high level methods for accessing a Styx server.
4#
5# Copyright (C) 2018 David Boddie <david@boddie.org.uk>
6#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as published by
9# the Free Software Foundation, either version 3 of the License, or
10# (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19
20import socket
21import styx
22
23class ClientError(Exception):
24 pass
25
26class Client:
27
28 MSIZE = 16384
29
30 def __init__(self, host = None, port = None, uname = None, aname = None):
31
32 self.reset()
33
34 self.uname = uname
35 self.aname = aname
36
37 if host != None and port != None:
38 self.connect(host, port, uname, aname)
39
40 def reset(self):
41
42 self.host = None
43 self.port = None
44
45 self.socket = None
46 self.msize = Client.MSIZE
47 self.root_fid = 0
48 self.current_fid = None
49
50 self.replies = {}
51
52 def connect(self, host, port, uname, aname):
53
54 try:
55 s = socket.socket()
56 s.connect((host, port))
57 except socket.error:
58 raise ClientError("Failed to connect to %s:%i." % (host, port))
59
60 self.host = host
61 self.port = port
62 self.socket = s
63
64 # Negotiate a version and maximum message size.
65 reply = self.send(styx.Tversion(tag=0, msize=self.msize, version=u"9P2000"))
66
67 if reply.version != u"9P2000":
68 raise ClientError("Server's protocol version '%s' is not supported." % reply.version)
69
70 self.msize = min(self.msize, reply.msize)
71
72 reply = self.send(styx.Tattach(tag=1, fid=1, afid=0, uname=uname, aname=aname))
73
74 self.root_fid = self.current_fid = 1
75
76 def disconnect(self):
77
78 self.send(styx.Tclunk(tag=2, fid=self.root_fid))
79 self.socket.close()
80
81 self.reset()
82
83 def send(self, msg):
84
85 print msg
86 msg.encode(self.socket)
87
88 tag = msg.tag
89
90 while tag not in self.replies:
91
92 reply = styx.decode(sock=self.socket)
93 print reply
94 if reply.tag == tag:
95 break
96 else:
97 self.replies[tag] = reply
98 else:
99 reply = self.replies[tag]
100 del self.replies[tag]
101
102 if isinstance(reply, styx.Rerror):
103 raise ClientError(reply.ename)
104
105 return reply
106
107 def _clunk(self, fid):
108
109 self.send(styx.Tclunk(tag=2, fid=fid))
110
111 def _stat(self, fid):
112
113 reply = self.send(styx.Tstat(tag=2, fid=fid))
114 return reply.stat
115
116 def _next_fid(self):
117
118 if self.current_fid != self.root_fid:
119 return self.current_fid + 1
120 else:
121 return self.root_fid + 1
122
123 def ls(self, path):
124
125 pieces = path.split("/")
126 newfid = self._next_fid()
127
128 reply = self.send(styx.Twalk(tag=2, fid=self.current_fid,
129 newfid=newfid, wname=pieces))
130
131 if reply.nwqid < len(pieces):
132 raise ClientError("No such file or directory: %s" % \
133 "/".join(pieces[:reply.nwqid]))
134
135 s = self._stat(newfid)
136
137 if s.mode & styx.Stat.DMDIR:
138 self.send(styx.Topen(tag=2, fid=newfid, mode=0))
139
140 data = ""
141 amount = self.msize - 24
142
143 while True:
144
145 reply = self.send(styx.Tread(
146 tag=2, fid=newfid, offset=len(data), count=amount))
147 data += reply.data
148
149 if len(reply.data) == 0:
150 break
151
152 info = styx.Stat().decode(data=data)
153 else:
154 info = s
155
156 self._clunk(newfid)
157 return info
158
159 def cd(self, path):
160
161 pieces = path.split("/")
162 newfid = self._next_fid()
163
164 reply = self.send(styx.Twalk(tag=2, fid=self.current_fid,
165 newfid=newfid, wname=pieces))
166
167 if reply.nwqid < len(pieces):
168 raise ClientError("No such file or directory: %s" % \
169 "/".join(pieces[:reply.nwqid]))
170
171 if self.current_fid != self.root_fid:
172 self._clunk(self.current_fid)
173
174 self.current_fid = newfid