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+
20+import socket
21+import styx
22+
23+class ClientError(Exception):
24+ pass
25+
26+class 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