Forum Archive

Robocopy entire photo directory to NAS?

margusch

Hi guys!

Before I buy pythonista I'd like to ask if there is any possibility to build a application which gets triggered via shortcuts daily and 'robocopys'/mirrors my photos to a certain folder on my NAS.

I know that you can connect via smbclient() and retrieve the last "x" photos. But is it possible to copy the delta to the NAS? I do not want to check every photo everytime if it already exists on the NAS by hand.

Thank you very much!

cvp

@margusch your script needs only to store in a local file the highest date-time of the photos that you send to your NAS and only send newer photos.

Édit: personally, I keep a thumb copy of the photo, with the same file name, after having sent to the NAS, so I know which ones is already backuped. Obviously, this works if you don't have millions of photos.

cvp

@margusch or at start of your script, you can ask a list of the photos names already on your NAS, and, by program, not by hand, compare if new local photos already exist in the list.

cvp

@margusch said:

triggered via shortcuts

And yes, you can define an automation which is an automatic shortcut which launch's a Pythonista script daily at a certain hour.

margusch

@cvp said:

@margusch your script needs only to store in a local file the highest date-time of the photos that you send to your NAS and only send newer photos.

Édit: personally, I keep a thumb copy of the photo, with the same file name, after having sent to the NAS, so I know which ones is already backuped. Obviously, this works if you don't have millions of photos.

Thats a good idea!
Would you mind sharing your code with me?

I am thinking if buying the app "PhotoSync" wouldn't just do the job..

cvp

@margusch said:

I am thinking if buying the app "PhotoSync" wouldn't just do the job..

It is sure that to buy Pythonista only for this job is not very profitable.

Édit: be careful that PhotoSync may have an annual fee for some functionalities.
Check also FileBrowser Pro with backup/sync features.

cvp

@margusch said:

Would you mind sharing your code with me?

Not easy to extract quickly needed parts of code but be sure that if/when you buy Pythonista, I could help you

It would depend of:
- are your photos in camera roll or local directory of Pythonista or in Files app
- do you want to keep original
- do you want to keep a thumb in local Pythonista file
- how much photos
- which solution: smb or ftp or sftp
- sure that no duplicate file names (if yes, more complex process)
- work with a control file or each time compare NAS directory (quickly downloaded)
- quid if you want to delete a local photo, does the deletion on NAS must occur
- which NAS, for instance Synology has a synchronization function included
- etc...

margusch

@cvp Thank you very much for your help!

I will try FileBrowser Pro and PhotoSync and if they are working fine I will go with one of that.

cvp

@margusch here, little quick and dirty, but tested, script

from   ftplib import FTP
from   objc_util import *
import photos

# get photos from NAS
user = 'admin'
pwd = '...'
ip = '192.168.0.47'
ftp = FTP(ip) # Connect NAS
ftp.encoding = 'utf-8'
ftp.login(user,pwd)
remote_folder = 'Photos/'
filesnas= ftp.nlst(remote_folder+'*')

# get photos from camera roll
for asset in photos.get_assets():
    fname = str(ObjCInstance(asset).valueForKey_('filename'))
    if (remote_folder+fname) not in filesnas:
        b = asset.get_image_data(original=False)    # file as io.BytesIO
        ftp.storbinary('STOR '+remote_folder+fname, b,blocksize=32768)
        print('sent:',fname)

ftp.quit() # Disconnect
cvp

@margusch said:

I will try FileBrowser Pro and PhotoSync and if they are working fine I will go with one of that.

Compare the prices but if you know Python, you'll have a lot of fun with Pythonista for solving new ideas.

ccc
user = 'admin'
pwd = '...'
ip = '192.168.0.47'
ftp = FTP(ip) # Connect NAS
ftp.encoding = 'utf-8'
# …
ftp.quit() # Disconnect
# —>
with FTP(host='192.168.0.47', user='admin', passwd='…', encoding='utf-8') as ftp:

https://docs.python.org/3/library/ftplib.html#ftplib.FTP

cvp

@ccc I didn't know that the quit was not mandatory. Anyway, nicer as usual.

cvp

@ccc thus, even ftp.login(..) is not necessary

cvp

@ccc said:

with FTP(host='192.168.0.47', user='admin', passwd='…', encoding='utf-8') as ftp:

encoding not a parameter of FTP.init, thus

from   ftplib import FTP
from   objc_util import *
import photos

with FTP(host='192.168.0.47', user='admin', passwd='...') as ftp:
    ftp.encoding='utf-8'
    # get photos from NAS
    remote_folder = 'Photos/'   
    filesnas= ftp.nlst(remote_folder+'*')
    # get photos from camera roll
    for asset in photos.get_assets():
        fname = str(ObjCInstance(asset).valueForKey_('filename'))
        if (remote_folder+fname) not in filesnas:
            b = asset.get_image_data(original=False)    # file as io.BytesIO
            ftp.storbinary('STOR '+remote_folder+fname, b,blocksize=32768)
            print('sent:',fname)                                    
ccc

Python < 3.9 did not have an encoding parameter: https://docs.python.org/3.8/library/ftplib.html#ftplib.FTP but was added in Py3.9

mikael

@margusch: "build a application which gets triggered via shortcuts daily"

Good to note that it will (probably) not happen in the background on a locked device, so this would in effect be more like a reminder and an easier way for you to start the process.

cvp

@mikael said:

Good to note that it will (probably) not happen in the background on a locked device

I think that the shortcut runs in the background of a locked device (don't remember if I had tested) but if the shortcut launchs an app, this one would not, excepted perhaps if Pyto in background mode, not tested.

cvp

@mikael but perhaps could you SFTP a file to the NAS via the SSH action of shortcut....

cvp

@mikael I had tested background in locked device for this topic

mikael

@cvp, thanks, really nice if so. I remember some of my earlier attempts being somewhat hit and miss.

aldoblack

So far this is the code that I'm using uploading with Synology REST API. It works well until Pythonista crashes when uploading photos between 900-1000.

import appex
import photos
import requests
import os
import editor
import sys
from objc_util import *

from datetime import datetime


class Synology(object):
    def __init__(self, url, port, account, passwd):
        """

        :param url: IP/URL or Synology. So far it only supports HTTP.
        :param port:
        :param account:
        :param passwd:
        :returnv:
        """
        self.url = url
        self.port = port

        self.account = account
        self.passwd = passwd

        self.sid = None

        # self.session = requests.Session()

    def login(self):
        print("Logging in...")

        param = {
            "version": "2",
            "method": "login",
            "account": self.account,
            "passwd": self.passwd,
            "session": "FileStation",
            "format": "cookie"
        }

        url = f"http://{self.url}:{self.port}/webapi/auth.cgi?api=SYNO.API.Auth"

        r = requests.get(url, param, verify=False)
        r = r.json()

        if not r["success"]:
            raise Exception("Authenticatoin failed. Note that account with 2FA enabled does not work.")

        print("Logging successful.")

        self.sid = r["data"]["sid"]
        return self.sid

    def upload_file(self, files):
        upload_url = f"http://{self.url}:{self.port}/webapi/entry.cgi?api=SYNO.FileStation.Upload&version=2&method=upload&_sid={self.sid}"

        args = {
            "path": "/home/b",
            "create_parents": "true",
            "overwrite": "true"
        }

        # Ignore this ugly code. I am trying to figure out if I can do bulk upload in one request.
        file = {'file': (files[0]["fname"], files[0]["payload"], 'application/octet-stream')}
        # file = [('files', (x["fname"], x["payload"], "application/octet-stream")) for x in files]

        r = requests.post(upload_url, data=args, files=file, verify=False)



def main():
    # if not appex.is_running_extension():
    #   print("Sript is intended to be un from sharing extension.")
    #   return

    print("Starting...")
    s = Synology(url="1.1.1.1", port="2000", account="user", passwd="pass")
    s.login()

    startTime = datetime.now()

    all_photos = photos.get_assets()
    images = []

    for idx, asset in enumerate(all_photos):

        if idx % 10 == 0:
            print(f"Uploading... {idx}/{len(all_photos)}")

        fname = str(ObjCInstance(asset).valueForKey_('filename'))
        payload = asset.get_image_data(original=True)

        images = [{"fname": fname, "payload": payload}]

        s.upload_file(images)

        # print(fname)

    print(datetime.now() - startTime)

if __name__ == "__main__":
    main()