3 from stat import S_IRUSR, S_IWUSR, S_IXUSR, S_IRGRP, S_IXGRP, S_IROTH, S_IXOTH, S_IFDIR, S_IFREG
6 from AESession import AESession
10 ###################################
12 # All paths are without leading /
13 # All paths are in Amiga charset.
15 ###################################
19 # Cancel a running operation.
22 session.sendMsg(1, b'')
29 def dir_list(session, path):
30 print("AECmds.dir_list: " + path)
32 session.sendMsg(0x64, path + b'\x00\x01')
34 dirlist = AEMultipart.recv(session)
35 # TODO: Check for None returned, meaning that the path doesn't exist.
36 # Actually, what can we return then?
37 # None is not iterable, and [] indicates an empty directory.
43 (numents,) = struct.unpack('!L', dirlist[0:4])
47 #kids['.'] = (dict(st_mode=(S_IFDIR | 0o755), st_nlink=2), {})
48 #kids['..'] = (dict(st_mode=(S_IFDIR | 0o755), st_nlink=2), {})
51 assert len(dirlist) > 29
52 (entlen, size, used, type, attrs, mdays, mmins, mticks, type2) = struct.unpack('!LLLHHLLLB', dirlist[0:29])
53 # entlen - Size of this entry in the list, in bytes
54 # size - Size of file.
55 # For drives, total space.
56 # used - 0 for files and folders.
57 # For drives, space used.
59 # attrs - Amiga attributes
60 # mdays - Last modification in days since 1978-01-01
61 # mmins - Last modification in minutes since mdays
62 # mticks - Last modification in ticks (1/50 sec) since mmins
70 assert entlen <= len(dirlist)
72 name = dirlist[29:entlen].split(b'\x00')[0].decode(encoding="iso8859-1")
74 dirlist = dirlist[entlen:]
80 #if attrs & 0x40: st['st_mode'] |= Script
81 #if attrs & 0x20: st['st_mode'] |= Pure
82 #if attrs & 0x10: st['st_mode'] |= Archive
83 if not attrs & 0x08: st['st_mode'] |= S_IRUSR | S_IRGRP | S_IROTH # Read
84 if not attrs & 0x04: st['st_mode'] |= S_IWUSR # Write
85 if not attrs & 0x02: st['st_mode'] |= S_IXUSR | S_IXGRP | S_IXOTH # Execute
86 #if not attrs & 0x01: st['st_mode'] |= Delete
90 st['st_mode'] |= S_IFDIR
92 st['st_mode'] |= S_IFREG
97 st['st_mtime'] = (2922*24*60*60) # Amiga epoch starts 1978-01-01
98 st['st_mtime'] += (mdays*24*60*60) # Days since 1978-01-01
99 st['st_mtime'] += (mmins*60) # Minutes
100 st['st_mtime'] += (mticks/50) # Ticks of 1/50 sec
102 st['st_blksize'] = session.packetsize - 4
104 # TODO: Convert time zone.
105 # Amiga time seems to be local time, we currently treat it as UTC.
107 kids[name] = (st, None)
116 def file_read_start(session, path):
117 print("AECmds.file_read_start: " + path)
120 session.sendMsg(0x65, path + b'\x00')
122 # Get file response header
123 (filelen, pktsize) = AEMultipart.recvHeader(session)
125 # TODO: Check for None returned, meaning that the path doesn't exist.
126 # This may not be necessary in case FUSE always issues a getattr()
132 def file_read_next(session):
133 (blockOffset, blockData) = AEMultipart.recvBlock(session)
135 if (blockData == None):
138 blen = len(blockData)
140 print("AECmds.file_read_next: " + str(blen) + ' @ ' + str(blockOffset))
142 return (blockOffset, blockData)
147 # Write a complete file and set its attributes and time.
149 def file_write(session, path, amigaattrs, unixtime, filebody):
150 print('AECmds.file_write: ' + path + ' -- ' + str(len(filebody)))
152 filelen = len(filebody)
154 _utime = unixtime - (2922*24*60*60) # Amiga epoch starts 1978-01-01
155 mdays = int(_utime / (24*60*60)) # Days since 1978-01-01
156 _utime -= mdays * (24*60*60)
157 mmins = int(_utime / 60) # Minutes
159 mticks = _utime * 50 # Ticks of 1/50 sec
161 filetype = 3 # Regular file
163 data = struct.pack('!LLLLLLLB',
164 29 + len(path) + 6, # Length of this message, really
173 data += b'\0\0\0\0\0\0' # No idea what this is for
174 session.sendMsg(0x66, data)
176 # TODO: Handle target FS full
177 # TODO: Handle permission denied on target FS
179 AEMultipart.send(session, filebody)
186 # Can be a file or folder, and will be deleted recursively.
188 def delete(session, path):
189 print("AECmds.delete: " + path)
191 session.sendMsg(0x67, path + b'\x00')
193 (type, _) = session.recvMsg()
197 # type == 0 means the file was deleted successfully
203 # Rename a file, leaving it in the same folder.
205 def rename(session, path, new_name):
206 print("AECmds.rename: " + path + ' - ' + new_name)
207 session.sendMsg(0x68, path + b'\x00' + new_name + b'\x00')
208 (type, _) = session.recvMsg()
211 print("AECmds.rename: Response type " + str(type))
215 # Assume that type == 0 means the file was renamed successfully
221 # Move a file from a folder to a different one, keeping its name.
223 def move(session, path, new_parent):
224 print("AECmds.move: " + path + ' - ' + new_parent)
225 session.sendMsg(0x69, path + b'\x00' + new_parent + b'\x00\xc9')
226 (type, _) = session.recvMsg()
229 print("AECmds.move: Response type " + str(type))
233 # Assume that type == 0 means the file was moved successfully
241 def copy(session, path, new_parent):
242 print("AECmds.copy: " + path + ' - ' + new_parent)
243 session.sendMsg(0x6a, path + b'\x00' + new_parent + b'\x00\xc9')
244 (type, _) = session.recvMsg()
247 print("AECmds.copy: Response type " + str(type))
251 # Assume that type == 0 means the file was copied successfully
257 # Set attributes and comment.
259 def setattr(session, amigaattrs, path, comment):
260 print("AECmds.setattr: " + str(amigaattrs) + ' - ' + path + ' - ' + comment)
262 data = struct.pack('!L', amigaattrs)
263 data += path + b'\x00'
264 data += comment + b'\x00'
265 data += struct.pack('!L', 0)
267 session.sendMsg(0x6b, data)
268 (type, _) = session.recvMsg()
271 print("AECmds.setattr: Response type " + str(type))
275 # Assume that type == 0 means the file was copied successfully
287 # Finish a running operation.
290 print('AECmds.close')
292 session.sendMsg(0x6d, b'')
294 (type, data) = session.recvMsg()
296 if data != b'\0\0\0\0\0':
297 print('AECmds.close: data == ' + binascii.hexlify(data))
298 # Format of data returned:
302 # Byte 3: 00 - No error
303 # 06 - File no longer exists
304 # 1c - Timed out waiting for host to request next read block
305 # Byte 4: Path in case of error 06.
306 # Empty (null-terminated) otherwise?
307 # Null-terminated string.
308 #assert data == b'\0\0\0\0\0'
319 # Create a new folder and matching .info file.
320 # This folder will be named "Unnamed1".