HEX
Server: Apache/2.4.63 (Unix)
System: Linux Synopilou92 4.4.302+ #72806 SMP Mon Jul 21 23:16:00 CST 2025 x86_64
User: pilou92 (1026)
PHP: 8.0.30
Disabled: NONE
Upload Files
File: //volume1/@appstore/SynologyPhotos/migration/python/dump_moments_db.py
#!/usr/bin/env python3

import json
import logging
import sys

import pg8000


class MomentsPGSql:
    def __init__(self):
        self.cursor = self.init_cursor()

    def init_cursor(self):
        user = "postgres"
        unix_sock = "/var/run/postgresql/.s.PGSQL.5432"
        database = "synophoto"
        conn = pg8000.connect(user, unix_sock=unix_sock, database=database)

        cursor = conn.cursor()
        cursor.execute("SET CLIENT_ENCODING TO 'UTF8'")

        return cursor

    def GetUserInfos(self):
        self.cursor.execute(
            """
            SELECT id, uid, name, config, enable FROM user_info
        """
        )
        result = []
        for id, uid, name, config, enable in self.cursor:
            result.append(
                {
                    "id": id,
                    "uid": 0 if uid is None else uid,
                    "name": name,
                    "config": config,
                    "enable": enable,
                }
            )
        return result

    def GetAlbum(self, id_user_info):
        self.cursor.execute(
            """
            SELECT id, name, shared, create_time, cover, passphrase_share, item_count, start_time, end_time FROM user_{}.normal_album
        """.format(
                id_user_info
            )
        )
        result = []
        for (
            id,
            name,
            shared,
            create_time,
            cover,
            passphrase_share,
            item_count,
            start_time,
            end_time,
        ) in self.cursor.fetchall():

            if name[0:2] == "\x08\x07":
                name = name[2:]
                is_temp_share = True
            else:
                is_temp_share = False

            cover_path = self.GetCoverPath(cover, id_user_info)

            result.append(
                {
                    "id": id,
                    "name": name,
                    "is_temp_share": is_temp_share,
                    "shared": shared,
                    "create_time": create_time,
                    "cover": cover_path,
                    "passphrase_share": passphrase_share or "",
                    "item_count": item_count,
                    "start_time": start_time,
                    "end_time": end_time,
                }
            )
        return result

    def GetCoverPath(self, cover, id_user_info):
        if 0 == cover:
            return ""

        self.cursor.execute(
            """
            SELECT filename, id_folder FROM user_{}.unit WHERE id_item = {};
        """.format(
                id_user_info, cover
            )
        )

        filename, id_folder = self.cursor.fetchone()

        self.cursor.execute(
            """
            SELECT name
            FROM user_{}.folder WHERE id = {};
        """.format(
                id_user_info, id_folder
            )
        )

        [folder_name] = self.cursor.fetchone()

        return folder_name + "/" + filename

    def GetAlbumUnit(self, id_user_info, id_album):
        self.cursor.execute(
            """
            SELECT filename, id_folder
            FROM user_{}.unit WHERE id_item IN(SELECT id_item FROM user_{}.many_item_has_many_normal_album WHERE id_normal_album = {});
        """.format(
                id_user_info, id_user_info, id_album
            )
        )

        folder_ids = []
        units = self.cursor.fetchall()

        if not units:
            return []

        for filename, id_folder in units:
            folder_ids.append(id_folder)

        self.cursor.execute(
            """
            SELECT id, name
            FROM user_{}.folder WHERE id IN ({});
        """.format(
                id_user_info, ",".join(str(v) for v in folder_ids)
            )
        )
        folders = {}
        for folder_id, name in self.cursor:
            folders[folder_id] = name

        result = []
        for filename, id_folder in units:
            result.append(folders[id_folder] + "/" + filename)

        return result

    def GetAlbumSharingInfo(self, passphrase_share):
        self.cursor.execute(
            """
            SELECT allow_operation, access_permission
            FROM share WHERE passphrase = '{}';
        """.format(
                passphrase_share
            )
        )

        allow_operation, access_permission = self.cursor.fetchone()

        self.cursor.execute(
            """
            SELECT uid
            FROM user_info WHERE id IN (SELECT id_user_info FROM many_share_has_many_user_info WHERE passphrase_share = '{}');
        """.format(
                passphrase_share
            )
        )

        uids = []
        for [uid] in self.cursor:
            uids.append(uid)

        self.cursor.execute(
            """
            SELECT gid
            FROM group_info WHERE id IN (SELECT id_group_info FROM many_share_has_many_group_info WHERE passphrase_share = '{}');
        """.format(
                passphrase_share
            )
        )

        gids = []
        for [gid] in self.cursor:
            gids.append(gid)

        result = {
            "allow_download": allow_operation & 1 > 0,
            "allow_upload": allow_operation & 2 > 0,
            "allow_comment": allow_operation & 4 > 0,
            "share_type": "DSM" if (access_permission >> 1) == 0 else "Public",
            "uid": uids,
            "gid": gids,
        }

        return result

    def GetConfig(self):
        try:
            self.cursor.execute(
                """
                SELECT key, value FROM config
            """
            )
        except Exception as err:
            logging.error("Failed to get config, error: {}".format(err))
            return {}

        result = {}
        for key, value in self.cursor:
            result.update(
                {
                    key: value,
                }
            )
        return result

    def GetTeamLibrary(self):
        try:
            # get default team library
            self.cursor.execute(
                """
                SELECT name, path FROM team_library ORDER BY name ASC LIMIT 1
            """
            )
        except Exception as err:
            logging.error("Failed to get config, error: {}".format(err))
            return []

        result = []
        for name, path in self.cursor:
            result.append(
                {
                    "name": name,
                    "path": path,
                }
            )
        return result


def GetUserInfoWithAlbum(pgsql):
    try:
        result = []
        for user_info in pgsql.GetUserInfos():
            result_albums = []
            for album in pgsql.GetAlbum(user_info["id"]):
                units = pgsql.GetAlbumUnit(user_info["id"], album["id"])
                result_album = {
                    "album": album,
                    "unit": units,
                }

                if album["passphrase_share"] != "":
                    result_album["sharing_info"] = pgsql.GetAlbumSharingInfo(
                        album["passphrase_share"]
                    )

                result_albums.append(result_album)

            result.append(
                {
                    "user_info": user_info,
                    "album": result_albums,
                }
            )
    except Exception as err:
        logging.error("Failed to get user info, error: {}".format(err))
        return []

    return result


def main():
    pgsql = MomentsPGSql()

    result = {
        "user_info": GetUserInfoWithAlbum(pgsql),
        "config": pgsql.GetConfig(),
        "team_library": pgsql.GetTeamLibrary(),
    }

    return result


if __name__ == "__main__":
    result = main()

    if len(sys.argv) >= 2:
        path = sys.argv[1]
        json.dump(result, open(path, "w"))
    else:
        print(json.dumps(result))