# /*
#  * Copyright (C) 2020, KylinSoft Co., Ltd.
#  *
#  * This program is free software: you can redistribute it and/or modify
#  * it under the terms of the GNU General Public License as published by
#  * the Free Software Foundation, either version 3 of the License, or
#  * (at your option) any later version.
#  *
#  * This program is distributed in the hope that it will be useful,
#  * but WITHOUT ANY WARRANTY; without even the implied warranty of
#  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  * GNU General Public License for more details.
#  *
#  * You should have received a copy of the GNU General Public License
#  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
#  */

#!/usr/bin/env python

import gi
import shutil
import json
import copy
import traceback
import logging
import os,subprocess
from pathlib import Path
from SystemUpdater.UpdatesAvailable import Transaction
gi.require_version('OSTree', '1.0')
from gi.repository import GLib, Gio, OSTree,GObject
from SystemUpdater.Core.errors import *
from SystemUpdater.Core.enums import *
from SystemUpdater.configs import G_settings
from SystemUpdater.push_update import UpdateContentInfo
from SystemUpdater.constants import OFFLINE_REPO_POST,KYLIN_OS_NAME,CURRENT_PROGRESS
from SystemUpdater.modules.i18n import _
import SystemUpdater.self_upgrade as self_upgrade

RETRY_LIMIT = 5
DEFAULT_REPO_NAME = "kylin"

class BackendBaseOstree(GObject.GObject):    
    __gsignals__ = {
                    "transaction-done": (GObject.SignalFlags.RUN_FIRST,
                                         None,
                                         (GObject.TYPE_PYOBJECT,)),
                    "download-info": (GObject.SIGNAL_RUN_FIRST,
                                GObject.TYPE_NONE,
                                (   GObject.TYPE_INT,
                                    GObject.TYPE_INT,
                                    GObject.TYPE_ULONG,
                                    GObject.TYPE_ULONG,
                                    GObject.TYPE_ULONG
                                )),
                    "deploy-progress": (GObject.SIGNAL_RUN_FIRST,
                                GObject.TYPE_NONE,
                                (   GObject.TYPE_INT,
                                )),
                    }
    
    def __init__(self,sysroot_path=None,osname=KYLIN_OS_NAME):
        GObject.GObject.__init__(self)
        self._async_progress = None
        if sysroot_path:
            sysroot_path = Gio.File.new_for_path(sysroot_path)
            
        self.sysroot = OSTree.Sysroot.new(sysroot_path)
        self.sysroot.set_mount_namespace_in_use()
        self.sysroot.initialize()
        self.sysroot.load()

        booted_deployment = self.sysroot.get_booted_deployment()
        if booted_deployment:
            self.osname = booted_deployment.get_osname()
        else:
            logging.warning("Booted deployment is None and use to defalut os name(%s)...",osname)
            self.osname = osname

        self.cur_deployment = self.sysroot.get_merge_deployment(self.osname)
        if not self.cur_deployment:
            logging.error("Now system deployment is None...")
            
        self.origin = self.cur_deployment.get_origin()
        self.download_size = 0

    def run(self,trans=None):
        logging.info("Processing transaction")
        GLib.idle_add(self._run_transaction_idle,trans)

    def _run_transaction_idle(self,trans):
        """Run the transaction"""
        try:
            self._async_progress = OSTree.AsyncProgress.new()
            self._async_progress.connect("changed",self._on_async_progress)
            self._try_lock()
            self._run_transaction(self.sysroot,trans)
        except GLib.Error as error:
            logging.error(str(error))
            trans.success = False
            self._parse_ostree_error(error,trans)
        except UpdateBaseError as excep:
            trans.success = False
            trans.error = excep.code
        except Exception as excep:
            logging.error(str(excep))
            trans.success = False

            tbk = traceback.format_exc()
            logging.error(str(tbk))
        else:
            trans.success = True
        
        finally:
            self._try_unlock()
            self._async_progress.finish()
            logging.info("Finished transaction")

            self._emit_transaction_done(trans)

        return False

    def _run_transaction(self,sysroot,trans):
        """This method needs to be implemented by the backends."""
        pass

    def _try_lock(self):
        _,out_acquired = self.sysroot.try_lock()
        if not out_acquired:
            raise UpdateBaseError(ERROR_OTHER_PROGRESS_OCCUPATION)
    
    def _try_unlock(self):
        self.sysroot.unlock()

    def _emit_transaction_done(self,trans):
        """Emit the transaction-done signal.

        Keyword argument:
        trans -- the finished transaction
        """
        self.emit("transaction-done",trans)

    def _on_async_progress(self,obj):
        try:
            start_time = obj.get_uint64("start-time")
            bytes_transferred = obj.get_uint64("bytes-transferred") 
            tmp_bytes_transferred = obj.get_uint64("tmp-bytes-transferred") 
            fetched = obj.get_uint("fetched")
            requested = obj.get_uint("requested")

            # 下载速度
            elapsed_secs=(GLib.get_monotonic_time()-start_time)/GLib.USEC_PER_SEC
            bytes_sec = int(bytes_transferred / elapsed_secs)

            if requested <= 2:
                return
            
            self.emit("download-info",fetched,requested,tmp_bytes_transferred,self.download_size,bytes_sec)
        except Exception as excep:
            tbk = traceback.format_exc()
            logging.error(str(tbk))

    def _get_current_deployed(self, ostree_repo):
        cur = UpdateContentInfo()
        cur.revision = self.cur_deployment.get_csum()
        cur.booted_serial = self.cur_deployment.get_deployserial()

        # 获取 refspec 中的 repo / branch
        _, cur.repo_name, cur.branch = OSTree.parse_refspec(
            self.origin.get_string("origin", "refspec")
        )

        # 获取当前 commit 的元数据
        _, meta = ostree_repo.load_variant(OSTree.ObjectType.COMMIT, cur.revision)
        cur.timestamp = OSTree.commit_get_timestamp(meta)
        cur.metadata = meta[0]

        if not cur.repo_name:
            logging.error("Now system deployment repo name is None...")
            raise UpdateBaseError(ERROR_REPO_IS_NONE)

        logging.info(f"Current Deployment:{cur.repo_name}:{cur.branch}@{cur.revision}...")

        return cur

    def _parse_ostree_error(self,error,trans: Transaction):
        if error.code in (37,39):
            trans.error = ERROR_NETWORK_FAILED
        #取消
        elif error.code == 19:
            trans._cancellable.reset()
            trans.error = EXIT_CANCELLED
        elif error.code == 1:
            # 在远程分支 未找到 对应的 分支 
            if "Server returned status 404" in error.message:
                trans.error = ERROR_PUSH_BRANCH_EXCEPTION
            # 未找到 对应的仓库信息 
            elif "Remote" in error.message and "not found" in error.message:
                trans.success = True
                trans.push_content = []
            else:
                trans.error = "#05%s"%error.code
                trans.error_message = error.message
        else:
            trans.error = "#05%s"%error.code
            trans.error_message = error.message
    
    def _find_offline_repo(self, mount_point="/media/OfflineSource"):
        """
        尝试在挂载点下找到有效的离线源目录。
        规则：存在 config 文件的子目录即为有效目录。
        """
        if not os.path.isdir(mount_point):
            return None

        try:
            for entry in os.listdir(mount_point):
                flag_file = os.path.join(mount_point, entry, "config")
                if os.path.isfile(flag_file):
                    return os.path.join(mount_point, entry)
        except Exception as e:
            logging.warning("Failed to scan offline source at %s: %s", mount_point, e)

        return None


    def _list_offline_deltas(self):
        """
        获取离线仓库的静态 delta 列表。
        如果未挂载或仓库无效，则返回空列表。
        """
        offline_path = self._find_offline_repo(OFFLINE_REPO_POST)
        if not offline_path:
            return []

        deltas_path = os.path.join(offline_path, "deltas")
        if not os.path.isdir(deltas_path):
            return []

        try:
            repo_file = Gio.File.new_for_path(offline_path)
            offline_repo = OSTree.Repo.new(repo_file)
            offline_repo.open(None)
            _, deltas_list = offline_repo.list_static_delta_names(None)
            return deltas_list or []
        except Exception as e:
            logging.warning("Failed to list static deltas from offline repo %s: %s", offline_path, e)
            return []

class UpdateBackend(BackendBaseOstree):
    def __init__(self,sysroot_path=None,osname=None):
        """Initialize a new AptWorker instance."""
        BackendBaseOstree.__init__(self,sysroot_path,osname)
        
    def _run_transaction(self, sysroot, trans):
        ostree_repo = sysroot.repo()

        # 获取当前部署的版本信息
        cur_deployed = self._get_current_deployed(ostree_repo)

        # 初始化 push 列表（如果没有 push_content，则默认当前分支）
        push_list = self._prepare_initial_push_list(trans, cur_deployed)

        # 检查拉取push_list推送列表元数据以及解析版本
        push_list = self._pull_and_collect_pushes(ostree_repo, trans, push_list)

        # 过滤不需要的 push（老版本、reflock冲突、与当前版本相同等）
        push_list = self._filter_push_content(push_list, cur_deployed)

        # 优化排序：不同分支优先，其次按时间戳升序
        push_list = self._sort_push_content(push_list, cur_deployed)

        # 处理优先升级
        self._handle_priority_upgrade(push_list, ostree_repo)

        for push in push_list:
            logging.info(
                f"Available Update: {push.repo_name}:{push.branch}@{push.revision} "
                f"ts={push.timestamp} reflock={push.reflock} active_update={push.active_update} "
                f"update_type={push.update_type}"
            )

        # 特殊情况下：源管理异常的情况下，不提示可更新 
        if self._should_ignore_passive_update(trans, push_list):
            logging.info(f"Ignore passive update due to missing origin push on kylin system.")
            push_list.clear()

        trans.cur_deployed = cur_deployed
        trans.push_content = push_list
        trans.ovl_size = self._get_booted_overlay_size(sysroot)

    def _should_ignore_passive_update(self, trans, push_list) -> bool:
        return (
            trans.is_kylin                     # 仅在麒麟系统生效
            and not trans.origin_push          # 源信息缺失，认为源管理异常
            and len(push_list) == 1            # 仅存在一个候选更新
            and not push_list[0].active_update # 被动更新（非主动式）
        )

    def _get_booted_overlay_size(self, sysroot):
        try:
            overlay_size = sysroot.get_booted_overlay_size()
            return overlay_size
        except Exception:
            return 0

    def _prepare_initial_push_list(self, trans, cur_deployed):
        if trans.origin_push:
            return copy.deepcopy(trans.origin_push)  # 深拷贝，安全
        else:
            return [
                UpdateContentInfo(
                    repo_name=cur_deployed.repo_name,
                    branch=cur_deployed.branch
                )
            ]

    def _build_pull_options(self, trans, push):
        opts = {
            "n-network-retries": GLib.Variant("u", RETRY_LIMIT),
            "arch-verify": GLib.Variant("b", True),
            "flags": GLib.Variant(
                "i",
                getattr(
                    OSTree.RepoPullFlags,
                    "COMMIT_AND_SIZE_ONLY",
                    OSTree.RepoPullFlags.COMMIT_ONLY,
                ),
            ),
            "max-speed-limit-kilobyte": GLib.Variant("u", trans.download_limit),
            "refs": GLib.Variant("as", [push.branch]),
        }

        # 若 push 指定 revision，则加入 override-commit-id
        if push.revision:
            opts["override-commit-ids"] = GLib.Variant("as", [push.revision])

        return opts

    def _pull_and_collect_pushes(self, ostree_repo, trans, push_list):
        """
        拉取所有 push 元数据，包括：
            - 通过 OSTree pull 拉取 commit metadata
            - 若 metadata 中包含 EOL rebase → 自动添加新的 push 到队列
            - 提取时间戳、reflock、update-type 等字段
            - 调用 _update_extra_metadata 载入额外元数据
        """
        from collections import deque

        q = deque(push_list)
        collected = []

        while q:
            push = q.popleft()

            # 构造 pull 参数
            g_opts = self._build_pull_options(trans, push)
            logging.info("Start pull metadata and options:%s...", g_opts)

            # 拉取
            ostree_repo.pull_with_options(
                push.repo_name,
                GLib.Variant("a{sv}", g_opts),
                self._async_progress,
                trans._cancellable,
            )

            # 若 revision 未指定，则解析出当前分支最新 revision
            if not push.revision:
                _, push.revision = ostree_repo.resolve_rev(
                    f"{push.repo_name}:{push.branch}", True
                )

            # 读取 commit metadata
            _, meta = ostree_repo.load_variant(OSTree.ObjectType.COMMIT, push.revision)
            push.timestamp = OSTree.commit_get_timestamp(meta)
            push.metadata = meta[0]
            push.reflock = bool(push.metadata.get("reflock", False))
            push.active_update = bool(push.metadata.get("active_update", False))
            push.update_type = push.metadata.get("update-type", "kylin-update-desktop-system")

            # 若 metadata 中包含 rebase → 追加新 push
            if OSTree.COMMIT_META_KEY_ENDOFLIFE_REBASE in push.metadata:
                rebase_data = push.metadata[OSTree.COMMIT_META_KEY_ENDOFLIFE_REBASE]
                logging.info("From origin ref:%s To new rebase:%s...", push.branch, rebase_data)
                repo, refs, revision = self._parse_push_content(rebase_data)
                q.append(UpdateContentInfo(
                    repo_name=repo or push.repo_name, branch=refs, revision=revision
                ))
                continue

            # 加载额外元数据
            self._update_extra_metadata(ostree_repo, push)

            collected.append(push)

        return collected

    def _filter_push_content(self, push_list, cur):
        """
        最终过滤规则：
            1) 时间戳比已部署版本旧 → 移除
            2) reflock=True 且分支不匹配 → 移除
            3) 与当前版本相同 → 移除
        """
        filtered = []

        for push in push_list:
            # 与当前版本完全相同 → 不升级
            if push.revision == cur.revision and push.branch == cur.branch:
                logging.info(
                    f"[Filtered] push({push.repo_name}:{push.branch}@{push.revision}) "
                    f"same as currently deployed ({cur.branch}@{cur.revision}) → skip"
                )
                continue

            # 带有分支锁：仅质量更新、驱动更新 漏洞修复这些使用
            if push.reflock:
                # 和当前部署分支不匹配的过滤掉
                if push.branch != cur.branch:
                    logging.info(
                        f"[Filtered] push({push.repo_name}:{push.branch}@{push.revision}) "
                        f"reflock=True but branch != cur_deployed.branch({cur.branch}) → removed"
                    )
                    continue

                # 将发布时间短的 去除掉
                if push.timestamp <= cur.timestamp:
                    logging.info(
                        f"[Filtered] push({push.repo_name}:{push.branch}@{push.revision}) "
                        f"ts={push.timestamp} < cur_deployed ts={cur.timestamp} → removed"
                    )
                    continue

            filtered.append(push)

        return filtered

    def _sort_push_content(self, push_list, cur):
        """
        push 优先级排序：
            1) 跟当前分支不同的 → 排在前面
            2) 同一优先级内 → 按时间戳降序
        """
        return sorted(
            push_list,
            key=lambda p: (
                0 if p.branch != cur.branch else 1,  # 不同分支优先
                -int(p.timestamp or 0),             # 时间戳越新越先升级
            ),
        )

    def _skip_deltas_selfupgrade(self) -> bool:
        # 无增量包：直接走优先升级，不跳过
        deltas = self._list_offline_deltas() 
        if not deltas:
            return False

        # 是增量包的情况下，默认跳过优先升级
        try:
            policy_path = Path(OFFLINE_REPO_POST) / "ostree-policy.json"
            if not policy_path.is_file():
                return True

            with policy_path.open("r", encoding="utf-8") as f:
                data =  json.load(f)
            return bool(data.get("skip_deltas_selfupgrade",True))
        except Exception as e:
            logging.warning(f"Failed to open ostree-policy.json and {e}")
        return True

    def _handle_priority_upgrade(self, push_content, ostree_repo):
        """
        处理优先升级流程

        :param trans: 升级事务对象
        :param ostree_repo: ostree 仓库对象
        :raises UpdateBaseError: 当优先升级成功时抛出
        """
        restart_list = set(["kylin-system-updater","kylin-update-frontend","kylin-software-properties"])

        if not push_content:
            return
        
        # 查找需要强制自更新的 push，如果没有则默认使用第一个 push
        update_push = next(
            (push for push in push_content if push.metadata.get("force_self_upgrade", False)),
            push_content[0]
        )

        if update_push.metadata.get("check_selfupgrade_deltas", True) and self._skip_deltas_selfupgrade():
            logging.info("Offline deltas detected, skipping priority upgrade.")
            return
        
        # 强制自我升级时清理进度
        if update_push.metadata.get("force_self_upgrade", False):
            logging.info("Force self-upgrade detected, removing current progress directory.")
            shutil.rmtree(CURRENT_PROGRESS, ignore_errors=True)

        # 跳过优先升级场景
        skip_funcs = set(G_settings.get("skip_function", [])) | set(
            update_push.metadata.get("skip_function", [])
        )
        if PRIORITY_UPGRADE_SUCCCESSED in skip_funcs:
            logging.info("Priority upgrade skipped by configuration.")
            return

        success,upgrade_list = self_upgrade.run_priority_upgrade(ostree_repo, update_push)
        logging.info(f"Priority upgrade result: success={success}, upgrade_list={upgrade_list}")
        restart_panel = any(x in restart_list for x in upgrade_list)
        if success and restart_panel:
            raise UpdateBaseError(PRIORITY_UPGRADE_SUCCCESSED)

    def _get_commit_recursive(self,ostree_repo,checksum):
        # 如果 start 不在 all_deltas 中，返回空列表
        _,metadata = ostree_repo.load_variant(OSTree.ObjectType.COMMIT,checksum)
        if not metadata:
            return []
        parent = OSTree.commit_get_parent(metadata)
        if parent:
            return [parent] + self._get_commit_recursive(ostree_repo, parent)
        else:
            return []

    def _parse_push_content(self, push_content):
        """
        解析 push_content 字符串，返回解析结果，不修改任何对象。
        支持格式：
            repo:ref@revision
            repo:ref
            ref@revision
            ref
        返回：
            (repo_or_none, refs, revision_or_none)
        """
        parts = push_content.split(":", 1)
        repo = parts[0] if len(parts) == 2 else None
        ref_and_rev = parts[1] if len(parts) == 2 else parts[0]

        refs_parts = ref_and_rev.split("@", 1)
        refs = refs_parts[0]
        revision = refs_parts[1] if len(refs_parts) == 2 and refs_parts[1] else None

        return repo, refs, revision

    def _get_all_commit(self,ostree_repo):
        commit_and_refs = {}
        _,out_all_refs = ostree_repo.list_refs(None,None)
        
        for refs in out_all_refs:
            commit_list = self._get_commit_recursive(ostree_repo,out_all_refs[refs])
            for commit in commit_list:
                commit_and_refs[commit] = refs

        logging.info(commit_and_refs)

    def _update_extra_metadata(self, ostree_repo, push):
        """
        读取 ostree commit 的 detached metadata 并更新 trans.update_metadata.

        :param ostree_repo: ostree 仓库对象
        :param trans: 包含 new_revision, cancellable, update_metadata 的事务对象
        """
        try:
            _, detached_meta = ostree_repo.read_commit_detached_metadata(
                push.revision, None
            )
            if not detached_meta:
                logging.info(f"No detached metadata for commit:{push.revision}")
                return

            extra_meta = dict(detached_meta.unpack())

            # 关键字段
            download_size = int(extra_meta.get("download-size", 0))
            install_size = int(extra_meta.get("install-size", 0))
            deploy_time = int(extra_meta.get("deploy-estimate-time", 0))

            # 更新事务 metadata
            push.metadata.update({
                "download-size": download_size,
                "install-size": install_size,
                "deploy_time": deploy_time,
            })
            
            logging.info(
                "Metadata loaded for commit %s: download_size=%d, install_size=%d,deploy_time=%d",
                push.revision, download_size, install_size,deploy_time
            )
        except Exception as e:
            logging.warning("Failed to read detached metadata for commit %s: %s",push.revision, e)

class DownloadBackend(BackendBaseOstree):
    def __init__(self,sysroot_path=None,osname=None):
        """Initialize a new AptWorker instance."""
        BackendBaseOstree.__init__(self,sysroot_path,osname)
        self.download_size = 0

    def _get_offline_deltas(self):
        """
        解析离线仓库中的 delta 信息。
        返回:
            all_deltas: dict[from_rev -> to_rev]
            to_refs: dict[to_rev -> ref_name]
        """
        all_deltas = {}
        to_refs = {}

        deltas_list = self._list_offline_deltas()
        if not deltas_list:
            return all_deltas, to_refs

        logging.info("Offline Repo(%s) has delta files: %r", OFFLINE_REPO_POST, deltas_list)

        for delta_info in deltas_list:
            delta_parts = delta_info.split("-")
            if len(delta_parts) < 2:
                logging.warning("Invalid delta format: %s", delta_info)
                continue

            from_rev, to_rev = delta_parts[0], delta_parts[1]
            all_deltas[from_rev] = to_rev

            # 解析 refs
            if len(delta_parts) == 3:
                ref_repo = delta_parts[2]
                if ":" in ref_repo:
                    _, ref_name = ref_repo.split(":", 1)
                else:
                    ref_name = ref_repo
                to_refs[to_rev] = ref_name

        return all_deltas, to_refs

    def _run_transaction(self, sysroot,trans: Transaction):
        g_options = {
                            "n-network-retries":GLib.Variant("u",RETRY_LIMIT),\
                            "arch-verify":GLib.Variant("b",True),\
                            "max-speed-limit-kilobyte": GLib.Variant("u",trans.download_limit),\
                            "flags":GLib.Variant("i",OSTree.RepoPullFlags.NONE)
                        }
        
        ostree_repo = sysroot.repo()
        
        deployed = trans.cur_deployed
        selected = trans.selected

        remote_repo = selected.repo_name
        if selected.metadata:
            self.download_size = int(selected.metadata.get("download-size",0))

        g_options["refs"] = GLib.Variant("as",[selected.branch])

        all_deltas,to_refs = self._get_offline_deltas()
        if all_deltas:
            pull_deltas = self._get_deltas_recursive(deployed.revision,all_deltas)
            g_options["require-static-deltas"] = GLib.Variant("b",True)

            # 循环拉取所有的 deltas
            for delta in pull_deltas:
                if delta in to_refs:
                    g_options["refs"] = GLib.Variant("as",[to_refs[delta]])
                g_options["override-commit-ids"] = GLib.Variant("as",[delta])
                logging.info("Start pull data and options:%s...",g_options)
                ostree_repo.pull_with_options(remote_repo,GLib.Variant("a{sv}",g_options),self._async_progress,trans._cancellable)

                # 检查到下载的 delta 已经是目标版本 则退出
                if selected.revision == delta:
                    logging.info("Delta %s is target version, stop pulling deltas.", delta)
                    break
        else:
            g_options["override-commit-ids"] = GLib.Variant("as",[selected.revision])
            logging.info("Start pull data and options:%s...",g_options)
            ostree_repo.pull_with_options(remote_repo,GLib.Variant("a{sv}",g_options),self._async_progress,trans._cancellable)

    
    def _get_deltas_recursive(self,start, all_deltas):
        # 如果 start 不在 all_deltas 中，返回空列表
        if start not in all_deltas:
            return []
        # 获取下一个值并递归处理
        next_value = all_deltas[start]
        return [next_value] + self._get_deltas_recursive(next_value, all_deltas)

class DeployBackend(BackendBaseOstree):
    def __init__(self,sysroot_path=None,osname=None):
        """Initialize a new AptWorker instance."""
        BackendBaseOstree.__init__(self,sysroot_path,osname)
        
    def _deployment_sanitycheck_true(self,rootfs_path):
        try:
            pid = os.fork()
            if pid == 0:
                os.chroot(rootfs_path)
                os.chdir("/")
                # 执行命令
                result = subprocess.run(["/usr/bin/true"],capture_output=True, text=True)
                os._exit(result.returncode)
            else:
                # 等待子进程结束
                _, exit_code = os.waitpid(pid, 0)
                logging.info("Command exited with status: %d",os.WEXITSTATUS(exit_code))
                if os.WIFEXITED(exit_code) != 0:
                    logging.error("Command did not exit normally.")
        except Exception as e:
            logging.error(str(e))

    def _on_async_progress(self,obj):
        try:
            current_progress = obj.get_uint("current-progress")
            self.emit("deploy-progress",int(current_progress))
        except Exception as excep:
            tbk = traceback.format_exc()
            logging.error(str(excep)+str(tbk))

    def _run_transaction(self, sysroot,trans):
        """
        执行 OSTree 部署事务
        """
        new_revision = trans.selected.revision
        refs = trans.selected.branch
        remote_repo = trans.selected.repo_name

        origin_refspec = f"{remote_repo}:{refs}"
        deploy_origin = sysroot.origin_new_from_refspec(origin_refspec)

        flags = OSTree.SysrootSimpleWriteDeploymentFlags.RETAIN
        logging.info("Start deploy: refs=%s, new_revision=%s", refs, new_revision)

        try:
            sysroot.prepare_cleanup(self._async_progress,None)
            _,upgrade_deployment = sysroot.deploy_tree(self._async_progress,self.osname,new_revision,deploy_origin,self.cur_deployment,None,None)
            sysroot.simple_write_deployment(self._async_progress,self.osname,upgrade_deployment,self.cur_deployment,flags,None)
        except TypeError as e:
            logging.info("Switch to using APIs from the previous of OStree and error:%s",str(e))
            self.emit("deploy-progress",20)
            sysroot.prepare_cleanup(None)
            self.emit("deploy-progress",30)
            _,upgrade_deployment = sysroot.deploy_tree(self.osname,new_revision,deploy_origin,self.cur_deployment,None,None)
            self.emit("deploy-progress",50)
            sysroot.simple_write_deployment(self.osname,upgrade_deployment,self.cur_deployment,flags,None)

        self.emit("deploy-progress",100)

        trans.new_revision = new_revision
        trans.prev_revision = trans.cur_deployed.revision

class RollbackBackend(BackendBaseOstree):
    def __init__(self,sysroot_path=None,osname=None):
        """Initialize a new AptWorker instance."""
        BackendBaseOstree.__init__(self,sysroot_path,osname)

    def _run_transaction(self, sysroot,trans):
        new_deployments = []

        out_pending,out_rollback = sysroot.query_deployments_for(self.osname)

        if out_rollback == None:
            raise UpdateBaseError(ERROR_NOT_ROLLBAK_DEPLOYMENT)

        rollback_csum = out_rollback.get_csum()

        old_deployments = sysroot.get_deployments()

        new_deployments.append(out_rollback)

        # 部署的列表
        for deploys in old_deployments:
            if not out_rollback.equal(deploys):
                new_deployments.append(deploys)

        logging.info("start reollback checksum:%s...",rollback_csum)
        sysroot.write_deployments(new_deployments,None)

        origin = out_rollback.get_origin()
        refspec = origin.get_string("origin","refspec")
        _,remote_repo,origin_ref = OSTree.parse_refspec(refspec)

        trans.available_refs = origin_ref

class CacheBackend(BackendBaseOstree):
    def __init__(self,sysroot_path=None,osname=None):
        """Initialize a new AptWorker instance."""
        BackendBaseOstree.__init__(self,sysroot_path,osname)

    def _run_transaction(self, sysroot,trans):
        pass

    def get_current_deployed(self):
        ostree_repo = self.sysroot.repo()
        cur_deployed = self._get_current_deployed(ostree_repo)
        return cur_deployed
