Welcome!
This is the community forum for my apps Pythonista and Editorial.
For individual support questions, you can also send an email. If you have a very short question or just want to say hello — I'm @olemoritz on Twitter.
NAS Access
-
I have a collection of mp3 files on a NAS on my local network. I'd like to read the mp3 tags of some of these files using Pythonista. Can I access a file using its UNC path (e.g. "\NAS\Media\Music\Artist\Album\Song.mp3")? When I try to open that path, I get a "No such file or directory" exception.
It's a huge library and I don't want to put it in Dropbox.
-
Non-Windows systems generally don't support UNC paths (or any sort of path with backslashes as separators). On macOS you can use a
smb:
URL instead, in your case that would besmb://NAS/Media/Music/Artist/Album/Song.mp3
. However iOS doesn't support network shares natively like macOS does, so opening a file from a SMB share isn't as straightforward as on macOS.You could search on PyPI and see if you can find a library for accessing SMB shares. If it's written in pure Python, you can probably install it in Pythonista using Stash's
pip
command. -
Personally, I use FTP to access my NAS
-
-
@brumm Hello, I want to use your smbclient but I see it needs to run in Python 2.
Would it be possible to run it in Python 3?
Or more generally, can I run a Python 3 script and call functions of a Python 2 imported module?Edit: I see that two days ago, the needed impacket does not yet support Python 3
-
@cvp: My first library was pysmb, but then I switched to impacket, because of this issue. However it is fixed and pysmb is Python 3 ready. Of course you have to change the smb calls... Do you like to try it?
-
@brumm Yes, I would try in a few days. Thanks
-
-
@cvp oh yes, this is my first try. I think it should be a good start. Let me know when I can support you. And I saw there's another dependency - pyasn1, but it is also python3 ready.
-
@brumm Thanks for your future help, I'm sure I'll need it but not before some days.
-
@brumm I want to thank you one more time. I'm more than happy with your marvelous little test script. I have
- installed your smb-test.py
- installed python 3 version of smb and nmb
- from https://github.com/miketeo/pysmb/tree/master/python3
- installed pyasn1 (supporting python 3)
- from https://github.com/etingof/pyasn1
- tested getRemoteDir => ok
- tested download => errors
-
modified to use BytesIO instead of StringIo
-
to open local file as 'wb'
- tested upload => ok
-
after same modif (open local file as 'rb')
- tested delete_remote_file => ok
- tested getServiceName => ok
- tested getBIOSName => ok (ip -> name)
- tested createRemoteDir => ok
#- tested removeRemoteDir => ok (after correction, see here-under) - original code of smb-test.py did contain two removeRemoteDir
-
second one should be renameRemoteFileOrDir
- tested renameRemoteFileOrDir => ok (for a dir)
- tested renameRemoteFileOrDir => ok (for a file)
- as my ip is dynamic, I created a new def getIP from fixed remote name
- tested getIP => ok (name -> ip)
This has been a lot easier than I thought...
Now, I'll integrate that in my script, without connecting and closing the SMBconnection at each access.def getIP(remote_name, timeout=5): try: bios = NetBIOS() ip = bios.queryName(remote_name) except Exception as e: print('Error on line {}'.format(sys.exc_info()[-1].tb_lineno), type(e).__name__, e) finally: bios.close() return ip[0]
-
And I put this in site-packages
# -*- coding: utf-8 -*- # based on https://github.com/humberry/smb-example/blob/master/smb-test.py # smb+nmb from https://github.com/miketeo/pysmb/tree/master/python3 # pyasn1 from https://github.com/etingof/pyasn1 from io import BytesIO from smb.SMBConnection import SMBConnection from smb import smb_structs from nmb.NetBIOS import NetBIOS import os import sys from socket import gethostname class SMB_client(): def __init__(self,username=None,password=None,smb_name=None): self.username = username self.password = password self.smb_name = smb_name self.smb_ip = None self.conn = None self.service_name = None self.my_name = None self.tree = [] def getBIOSName(self, remote_smb_ip, timeout=5): # unused if dynamic IP # ip -> smb name try: bios = NetBIOS() srv_name = bios.queryIPForName(remote_smb_ip, timeout=timeout) return srv_name[0] except Exception as e: print('Error on line {}'.format(sys.exc_info()[-1].tb_lineno), type(e).__name__, e) return None def getIP(self): # smb name -> ip try: bios = NetBIOS() ip = bios.queryName(self.smb_name) return ip[0] except Exception as e: print('Error on line {}'.format(sys.exc_info()[-1].tb_lineno), type(e).__name__, e) return None def connect(self): try: self.my_name = gethostname() # iDevice name self.smb_ip = self.getIP() smb_structs.SUPPORT_SMB2 = True self.conn = SMBConnection(self.username, self.password, self.my_name, self.smb_name, use_ntlm_v2 = True) self.conn.connect(self.smb_ip, 139) #139=NetBIOS / 445=TCP if self.conn: shares = self.conn.listShares() for share in shares: if share.type == 0: # 0 = DISK_TREE self.service_name = share.name except Exception as e: print('Error on line {}'.format(sys.exc_info()[-1].tb_lineno), type(e).__name__, e) def close(self): try: self.conn.close() except Exception as e: print('Error on line {}'.format(sys.exc_info()[-1].tb_lineno), type(e).__name__, e) def getRemoteDir(self, path, pattern): try: files = self.conn.listPath(self.service_name, path, pattern=pattern) return files except Exception as e: print('Error on line {}'.format(sys.exc_info()[-1].tb_lineno), type(e).__name__, e) return None def getRemoteTree(self,path=''): try: if path == '': w = '' else: w = path+'/' files = self.getRemoteDir(path, '*') if files: for file in files: if file.filename[0] == '.': continue self.tree.append({'name':w+file.filename, 'isdir':file.isDirectory, 'size':file.file_size}) if file.isDirectory: self.getRemoteTree(path=w+file.filename) return self.tree except Exception as e: print('Error on line {}'.format(sys.exc_info()[-1].tb_lineno), type(e).__name__, e) return None def download(self, path, filename): try: print('Download = ' + path + filename) attr = self.conn.getAttributes(self.service_name, path+filename) print('Size = %.1f kB' % (attr.file_size / 1024.0)) print('start download') file_obj = BytesIO() file_attributes, filesize = self.conn.retrieveFile(self.service_name, path+filename, file_obj) fw = open(filename, 'wb') file_obj.seek(0) for line in file_obj: fw.write(line) fw.close() print('download finished') except Exception as e: print('Error on line {}'.format(sys.exc_info()[-1].tb_lineno), type(e).__name__, e) def upload(self, path, filename): try: print('Upload = ' + path + filename) print('Size = %.1f kB' % (os.path.getsize(filename) / 1024.0)) print('start upload') with open(filename, 'rb') as file_obj: filesize = self.conn.storeFile(self.service_name, path+filename, file_obj) print('upload finished') except Exception as e: print('Error on line {}'.format(sys.exc_info()[-1].tb_lineno), type(e).__name__, e) def delete_remote_file(self,path, filename): try: self.conn.deleteFiles(self.service_name, path+filename) print('Remotefile ' + path + filename + ' deleted') except Exception as e: print('Error on line {}'.format(sys.exc_info()[-1].tb_lineno), type(e).__name__, e) def createRemoteDir(self, path): try: self.conn.createDirectory(self.service_name, path) except Exception as e: print('Error on line {}'.format(sys.exc_info()[-1].tb_lineno), type(e).__name__, e) def removeRemoteDir(self,path): try: self.conn.deleteDirectory(self.service_name, path) except Exception as e: print('Error on line {}'.format(sys.exc_info()[-1].tb_lineno), type(e).__name__, e) def renameRemoteFileOrDir(self,old_path, new_path): try: self.conn.rename(self.service_name, old_path, new_path) except Exception as e: print('Error on line {}'.format(sys.exc_info()[-1].tb_lineno), type(e).__name__, e)
Use like
from SMB_client import SMB_client my_smb = SMB_client(username='xxxxx',password='yuyy',smb_name='zzzzzz') my_smb.connect() tree = my_smb.getRemoteTree() for elem in tree: print(elem) #my_smb.download(path, filename) #my_smb.upload(path, filename) #my_smb.delete_remote_file(path, filename) #my_smb.createRemoteDir(path) #my_smb.removeRemoteDir(path) #my_smb.renameRemoteFileOrDir(path, new_path) my_smb.close()
-
I'm very happy that the code was useable.
-
@brumm Without your code, I never could get this smb working. Sincerely, thanks a lot
-
@brumm If you want to add some callback to upload and download...
def download(self, path, filename,buffersize=None,callback=None): try: #print('Download = ' + path + filename) attr = self.conn.getAttributes(self.service_name, path+filename) #print('Size = %.1f kB' % (attr.file_size / 1024.0)) #print('start download') file_obj = BytesIO() fw = open(filename, 'wb') offset = 0 transmit =0 while True: if not buffersize: file_attributes, filesize = self.conn.retrieveFile(self.service_name, path+filename, file_obj) else: file_attributes, filesize = self.conn.retrieveFileFromOffset(self.service_name, path+filename, file_obj,offset=offset,max_length=buffersize) if callback: transmit = transmit + filesize callback(transmit) file_obj.seek(offset) for line in file_obj: fw.write(line) offset = offset + filesize if (not buffersize) or (filesize == 0): break fw.close() #print('download finished') except Exception as e: print('Error on line {}'.format(sys.exc_info()[-1].tb_lineno), type(e).__name__, e) def upload(self, path, filename,buffersize=None,callback=None): try: #print('Upload = ' + path + filename) #print('Size = %.1f kB' % (os.path.getsize(filename) / 1024.0)) #print('start upload') file_obj = open(filename, 'rb') offset = 0 while True: if not buffersize: filesize = self.conn.storeFile(self.service_name, path+filename, file_obj) break else: buffer_obj = file_obj.read(buffersize) if buffer_obj: buffer_fileobj = BytesIO() buffer_fileobj.write(buffer_obj) buffer_fileobj.seek(0) offset_new = self.conn.storeFileFromOffset(self.service_name, path+filename, buffer_fileobj, offset=offset, truncate=False) #return the file position where the next byte will be written. offset = offset_new if callback: callback(offset) else: break file_obj.close() #print('upload finished') except Exception as e: print('Error on line {}'.format(sys.exc_info()[-1].tb_lineno), type(e).__name__, e)```
-
@cvp Please feel free to upload it to your own github site, if you like. It's okay for me.
-
@brumm ok, Thanks, I'll do it
Edit: done at https://github.com/cvpe/Pythonista-scripts
-
@brumm As you have deeply studied SMB, I have a little question .
I use it to access (read and write) an USB key connected to an USB slot of my internet router.
Do you think I can remove safely the USB key as soon I have closed the smb connection? -
@cvp I guess that your internet router has no (or a small) read/write buffer. Before you lose data I would recommend to use a USB key with a LED, to get sure that your router has enough time to write all data.
-
@brumm Thanks for the advice. But do you think that all buffers are flushed when the smb connection is closed?