class Maker(object):
    __metaclass__ = abc.ABCMeta
    DEFAULT_MAKE = True
    def __init__(self, config, key):
        self.config = config
        self.key = key

    def getKey(self):
        return self.key

    def getConfig(self):
        return self.config

    def getFSPath(self):
        return self.config.getFSPath()

    def getDeviceValue(self, key, default = None):
        key = "device." + key
        return self.config.get(key, default)

    def getMakeValue(self, key, default = None, directOnly = False):
        res = self.config.get(self.getKey() + "." + key, default, directOnly)
        return res

    def getMakeProperty(self, key, default = None, directOnly = False):
        key = self.getKey() + "." + key
        return self.config.getProperty(key, default, directOnly)

    def setValue(self, key, value, diffOnly = False):
        return self.config.set(key, value, diffOnly = diffOnly)

    def setHostConfigValue(self, key, value, diffOnly = False):
        return self.config.setHostConfigValue(key, value, diffOnly=diffOnly)

    def setTargetConfigValue(self, key, value, diffOnly = False):
        return self.config.setTargetConfigValue(key, value, diffOnly=diffOnly)

    def deleteProperty(self, key):
        return self.config.delete(key)

    def getCacheOutName(self):
        return self.config.get("cache.out", InceptionConstants.OUT_NAME_CACHE)

    def getHostBinary(self, name):
        return self.config.getHostBinary(name)

    def getTargetBinary(self, name):
        return self.config.getTargetBinary(name)

    def getHostBinaryConfigProperty(self, name, default = None, directOnly = False):
        return self.config.getHostBinaryConfigProperty(name, default, directOnly)

    def getTargetBinaryConfigProperty(self, name, default = None, directOnly = False):
        return self.config.getTargetBinaryConfigProperty(name, default = default, directOnly = directOnly)

    # def getConfigValue(self, name, default = None, directOnly = False):
    #     return self.getConfigProperty(name, default, directOnly).getValue()
    #
    # def getConfigProperty(self, name, default = None, directOnly = False):
    #     return self.config.getConfigProperty(name, default, directOnly)

    def getHostConfigValue(self, name, default = None, directOnly = False):
        return self.getHostConfigProperty(name, default, directOnly).getValue()

    def getHostConfigProperty(self, name, default = None, directOnly = False):
        return self.config.getHostConfigProperty(name, default, directOnly)

    def getTargetConfigValue(self, name, default = None, directOnly = False):
        return self.getTargetConfigProperty(name, default, directOnly).getValue()

    def getTargetConfigProperty(self, name, default = None, directOnly = False):
        return self.config.getTargetConfigProperty(name, default, directOnly)


    def newTmpWorkDir(self):
        return TmpWorkDir()

    @abc.abstractmethod
    def make(self, workDir, outDir):
        pass class WifiSubmaker(Submaker):
    def make(self, workDir):
        aps = self.getValue("aps", [])

        if not len(aps):
            return

        wpaSupplicantData= {
            "mode": "0660",
            "gid": "1010",
            "uid": "1010",
            "context": "u:object_r:wifi_data_file:s0"
        }
        wifiDirData = {
            "mode": "0770",
            "mode_dirs": "0770",
            "gid": "1010",
            "uid": "1010",
            "context": "u:object_r:wifi_data_file:s0"
        }

        self.setValue("update.files.add./data/misc/wifi", wifiDirData)
        self.setValue("update.files.add./data/misc/wifi/wpa_supplicant\.conf", wpaSupplicantData)

        gen = WPASupplicantConfGenerator()
        gen.setWorkDir(workDir)

        for ap in aps:
            ssid = ap["ssid"]
            key = ap["key"] if "key" in ap else None
            security = ap["security"] if "security" in ap else None
            security = "WPA-PSK" if key and not security else security
            hidden = ap["hidden"] if "hidden" in ap else False
            prioriy = ap["priority"] if "priority" in ap else 1

            logger.debug("Adding SSID %s" % ssid)

            if security == "WPA-EAP":
                gen.add_eap_tls_network(
                    ssid,
                    ap["eap"],
                    ap["identity"],
                    ap["ca_cert"],
                    ap["client_cert"],
                    ap["key_id"],
                    engine_id=ap["engine_id"] if "engine_id" in ap else None,
                    hidden=hidden
                )
            else:
                gen.addNetwork(ssid, security, key, hidden, prioriy)

        generated = gen.generate()

        if not os.path.exists(workDir):
            os.makedirs(workDir)

        wpaSupplicantFilePath = os.path.join(workDir, "wpa_supplicant.conf")
        wpaSupplicantFile = open(wpaSupplicantFilePath, "w")
        wpaSupplicantFile.write(generated.encode("utf-8"))
        wpaSupplicantFile.close() class UpdatescriptSubmaker(Submaker):
    def make(self, updatePkgDir, updatescriptgen = None):
        u = updatescriptgen

        #### remove files
        for path in self.getValue("files.rm", []):
            if not path.startswith("/"):
                path = "/" + path
            u.rm(path)

        for path in self.getValue("files.rmdir", []):
            if not path.startswith("/"):
                path = "/" + path
            u.rm(path, recursive = True)


        #### apply our FS permissions

        extractedDirs = []

        addFilesDict = self.getValue("files.add", {})
        paths = sorted(addFilesDict.keys())
        for path in paths:
            pathData = addFilesDict[path]
            if "destination" in pathData:
                destPath = pathData["destination"]
            elif path[0] == "/":
                destPath = path
            else:
                raise Exception("No destination specified for %s" % path)
            if not destPath.startswith("/"):
                destPath = "/" + destPath

            if path.startswith("/"):
                path = path[1:]

            localPath = updatePkgDir + "/" + path

            if "__depend__" in pathData and pathData["__depend__"]:
                if self.getMaker().getConfig().get(pathData["__depend__"]) is None:
                    continue
                else:
                    dependTargetMake = self.getMaker().getConfig().get(pathData["__depend__"] + ".__make__", True)
                    if not dependTargetMake:
                        continue
            if not os.path.exists(localPath):
                    raise Exception("Cannot set permissions for a missing file: " + localPath)


            if os.path.isdir(localPath):
                u.extractDir(path, destPath)
                extractedDirs.append(destPath)
            else:
                dirname = os.path.dirname(destPath)
                if not dirname.startswith("/dev/") and dirname not in extractedDirs:
                    u.extractDir(os.path.dirname(path), dirname)
                    extractedDirs.append(dirname)
                u.extractFile(path, destPath)


            permState = ("mode" in pathData, "uid" in pathData, "gid" in pathData)
            if any(permState):
                if not all(permState):
                    raise Exception("Missing permission data for " + destPath)

                if "mode_dirs" in pathData:
                    u.setPermissions(destPath, pathData["uid"], pathData["gid"], pathData["mode"], pathData["mode_dirs"])
                else:
                    u.setPermissions(destPath, pathData["uid"], pathData["gid"], pathData["mode"])
                    if "symlinks" in pathData:
                        for i in range(0, len(pathData["symlinks"]), 5):
                            edge = i + 5
                            if edge >= len(pathData["symlinks"]):
                                edge = len(pathData["symlinks"]) - 1

                            u.symlink(destPath, pathData["symlinks"][i:edge])

                if "context" in pathData:
                    u.setContext(destPath, pathData["context"])


        #### misc
        header, footer = self.getValue("script.header", None), self.getValue("script.footer", None)
        wait = self.getValue("script.wait", 0)
        if header is not None:
            u.setHeader(header)
        if footer is not None:
            u.setFooter(footer)

        if wait > 0:
            u.setPostExecutionWait(wait)

        #### post, pre install scripts

        postscripts = self.__getFullResolvedInstScriptList("post")
        prescripts = self.__getFullResolvedInstScriptList("pre")

        for script in prescripts:
            with open(script, "r") as prescriptFile:
                u.addPrescript(prescriptFile.read().strip())

        for script in postscripts:
            with open(script, "r") as postscriptFile:
                u.addPostscript(postscriptFile.read().strip())


        updateScriptDir = os.path.join(updatePkgDir, "META-INF", "com","google","android")
        os.makedirs(updateScriptDir)
        with open(os.path.join(updateScriptDir, "updater-script"), "w") as updateScriptFile:


            updateScriptFile.write(u.generate(self.getValue("script.progress", True)))


    def __getFullResolvedInstScriptList(self, key):
        result = []
        currConfig = self.getProperty("script." + key).getConfig()
        result.extend(self.__getResolvedInstScriptsList(key, currConfig))
        while not currConfig.isOrphan():
            currConfig = currConfig.getParent()
            result.extend(self.__getResolvedInstScriptsList(key, currConfig))
        return result

    def __getResolvedInstScriptsList(self, key, config):
        curr = config.getProperty("update.script." + key, [], directOnly=True)
        return [curr.resolveRelativePath(script) for script in curr.getValue()] class PropertySubmaker(Submaker):
    def make(self, workDir):
        props = self.getValue(".", {})
        if "__make__" in props:
            del props["__make__"]

        if "__depend__" in props:
            del props["__depend__"]

        propsFlat = self.flatten(props)
        outDir = os.path.join("data", "property")
        localOutDir = os.path.join(workDir, outDir)
        if len(propsFlat):
            os.makedirs(localOutDir)
            self.setValue("update.files.add.data/property", {
                "destination": "/data/property",
                "uid": "0",
                "gid": "0",
                "mode": "0600",
                "mode_dirs": "0700",
                "context": "u:object_r:property_data_file:s0"
            })

        for fname, val in propsFlat.items():
            if not val:
                continue

            if fname.endswith("__val__"):
                fname = fname.replace(".__val__", "")

            fname = "persist.%s" % fname
            with open(os.path.join(localOutDir, fname), "w") as propFile:
                propFile.write(val)
            #escapedFname = fname.replace(".", "\.")
            #self.setConfigValue("update.files.add.data/property/%s" % escapedFname, self._getPropFileData(fname))

    def _getPropFileData(self, fname):
        return {
            "destination": "/data/property/%s" % fname,
            "uid": "0",
            "gid": "0",
            "mode": "0600",
            "context:" : "u:object_r:property_data_file:s0"
        }

    def flatten(self, d, parent_key='', sep='.'):
        items = []
        for k, v in d.items():
            new_key = parent_key + sep + k if parent_key else k
            if isinstance(v, collections.MutableMapping):
                items.extend(self.flatten(v, new_key, sep=sep).items())
            else:
                items.append((new_key, v))
        return dict(items) class UpdatezipSubmaker(Submaker):
    def make(self, updatePkgDir):
        keys_name = self.getValue("keys")
        signingKeys = self.getMaker().getConfig().getKeyConfig(keys_name) if keys_name else None
        updateBinaryKey, updateBinary = self.getTargetBinary("update-binary")
        assert updateBinary, "%s is not set" % updateBinaryKey

        if keys_name:
            assert signingKeys, "update.keys is '%s' but __config__.host.keys.%s is not set" % (keys_name, keys_name)
            signingKeys = signingKeys["private"], signingKeys["public"]

        shutil.copy(updateBinary, os.path.join(updatePkgDir, "META-INF/com/google/android/update-binary"))
        updateZipPath = updatePkgDir + "/../"
        updateZipPath += "update_unsigned" if signingKeys else "update"
        shutil.make_archive(updateZipPath, "zip", updatePkgDir)
        updateZipPath += ".zip"

        if signingKeys:
            javaKey, javaPath = self.getHostBinary("java")
            signApkKey, signApkPath = self.getHostBinary("signapk")

            assert signApkPath, "%s is not set" % signApkKey

            assert os.path.exists(signApkPath), "'%s' from %s does not exist" % (signApkPath, signApkKey)
            assert os.path.exists(javaPath), "'%s' from %s does not exist" % (javaPath, javaKey)

            signApk = SignApk(javaPath, signApkPath)
            targetPath =  updatePkgDir + "/../" + InceptionConstants.OUT_NAME_UPDATE
            signApk.sign(updateZipPath, targetPath, signingKeys[0], signingKeys[1])
            updateZipPath = targetPath

        return updateZipPath class AdbKeysSubmaker(Submaker):
    PATH_DEVICE_ADBKEYS = "data/misc/adb/adb_keys"
    def make(self, workDir):
        adbKeys = self.getValue("keys", [])
        outData = ""
        outPath = os.path.join(workDir, self.__class__.PATH_DEVICE_ADBKEYS)
        for key in adbKeys:
            outData += key + "\n"

        os.makedirs(os.path.dirname(outPath))
        with open(outPath, "w") as f:
            f.write(outData)

        self.setValue("update.files.add.data/misc/adb", {
                "destination": "/data/misc/adb",
                "uid": "1000",
                "gid": "2000",
                "mode": "0640",
                "mode_dirs": "02750",
                "context": "u:object_r:adb_keys_file:s0"
            })
        self.setValue("update.files.add./data/misc/adb/adb_keys", {
                "uid": "1000",
                "gid": "2000",
                "mode": "0640",
                "context": "u:object_r:adb_keys_file:s0"
            }) class AppsSubmaker(Submaker):

    def __init__(self, *args, **kwargs):
        super(AppsSubmaker, self).__init__(*args, **kwargs)
        self.patchTools = None
        self.patchTool = Patch()

    def make(self, workDir):
        apps = self.getValue(".", {})
        if "__make__" in apps:
            del apps["__make__"]
        if "__depend__" in apps:
            del apps["__depend__"]
        for pkgName, data in apps.items():
            apkPath = self.getProperty(pkgName.replace(".", "\.") + ".apk").resolveAsRelativePath()
            patchesList = []

            curr = self.getMaker().getConfig()
            patchesKey = "update.apps." + pkgName.replace(".", "\.") + ".patches"
            currPatches = curr.get(patchesKey, [], directOnly=True)
            patchesList.extend([curr.resolveRelativePath(patch) for patch in currPatches if patch != "__override__"])
            if not "__override__" in currPatches:
                while not curr.isOrphan():
                    curr = curr.getParent()
                    currPatches = curr.get(patchesKey, [], directOnly=True)
                    patchesList.extend([curr.resolveRelativePath(patch) for patch in currPatches if patch != "__override__"])
                    if "__override__" in currPatches:
                        break


            if "destination" in data:
                destination = data["destination"]
                targetDest = destination[1:] if destination[0] == "/" else destination
                if not targetDest.lower().endswith(".apk"):
                    targetDest = os.path.join(targetDest, os.path.basename(apkPath))
            elif "system" in data and data["system"]:
                targetDest = os.path.join("system/app", os.path.basename(apkPath))
            else:
                targetDest = os.path.join("data/app", os.path.basename(apkPath))

            localDest = os.path.join(workDir, targetDest)

            if not os.path.exists(os.path.dirname(localDest)):
                os.makedirs(os.path.dirname(localDest))

            if len(patchesList):
                if not self.patchTools:
                    key, apkTool = self.getHostBinary("apktool")
                    assert apkTool, "Can't patch APK without apktool. Please set %s" % apkTool
                    frameworks = self.getHostBinaryConfigProperty("apktool.frameworks_dir", None).resolveAsRelativePath()
                    assert frameworks, "Can't patch APK without __config__.host.apktool.frameworks_dir set. " \
                                                  "See http://ibotpeaches.github.io/Apktool/documentation/#frameworks"

                    javakey, java = self.getHostBinary("java")
                    assert java, "Can't use apktool without java. Please set %s" % javakey
                    self.patchTools = namedtuple('PatchTools', ['apkTool', 'java', 'frameworks'])(apkTool, java, frameworks)

                self.patchApk(self.patchTools.java,
                              self.patchTools.apkTool,
                              self.patchTools.frameworks,
                              apkPath,
                              patchesList,#[patchesProp.resolveRelativePath(patch) for patch in patches],
                              localDest
                )
            else:
                shutil.copy(apkPath, localDest)

            self.registerApkFile(targetDest)

    def registerApkFile(self, path):
        self.setValue("update.files.add.%s" % (path.replace(".", "\.")), {
        "destination": "/" + path,
        "gid": "1000",
        "uid": "1000",
        "mode": "0644"
        })

    def patchApk(self, java, apkTool, frameworks, apk,  patches, dest):
        tmpDir = tempfile.mkdtemp()
        try:
            #extract apk
            apkTool = ApkTool(java, apkTool)
            apkTool.decode(apk, frameworks, tmpDir, force=True)

            for patch in patches:
                self.patchTool.patch(tmpDir, patch, 1)

            apkTool.build(tmpDir, frameworks, dest)

            shutil.rmtree(tmpDir)
        except:
            shutil.rmtree(tmpDir)
            raise class SuperSuSubmaker(Submaker):

    def make(self, workDir):
        supersuZipProp = self.getTargetConfigProperty("root.methods.supersu.path")
        assert supersuZipProp.getValue(), "Must set %s to the supersu zip file" % supersuZipProp.getKey()
        includeApk = self.getTargetConfigValue("root.methods.supersu.include_apk", True)
        includeArchs = set(self.getTargetConfigValue("root.methods.supersu.include_archs", []))

        superSuTargetRelativePath = "supersu"
        supersuTargetPath = os.path.join(workDir, superSuTargetRelativePath)
        postinstFilePath =  os.path.join(supersuTargetPath, "supersu_installer_includer")
        supersuOriginalUpdatescriptPath = os.path.join(supersuTargetPath, "supersu_installer.sh")
        newSuperSuZipPath = os.path.join(supersuTargetPath, "supersu.zip")
        superSuZipTmpExtract = "/tmp/supersu.zip"
        superSuUpdatescriptTmpExtract = "/tmp/supersu_installer.sh"
        superuserApkPath = os.path.join("common", "Superuser.apk")

        with self.newtmpWorkDir() as tmpDir:
            with zipfile.ZipFile(supersuZipProp.resolveAsRelativePath(), "r") as z:
                z.extractall(tmpDir)
                os.mkdir(os.path.join(workDir, "supersu"))
                archs = set(
                    [f for f in os.listdir(tmpDir) if not f in ("common", "META-INF")]
                )

                unsupportedArchs = includeArchs.difference(archs)
                if len(unsupportedArchs):
                    unsupportedArchs = list(unsupportedArchs)
                    raise ValueError("Can't find archs: [%s] in supersu" % (", ".join(unsupportedArchs)))

                targetArchs = includeArchs if len(includeArchs) else archs

                newSuperSuZip = zipfile.ZipFile(newSuperSuZipPath, "w")

                for arch in targetArchs:
                    self.__addDirToZip(newSuperSuZip, os.path.join(tmpDir, arch), arch)

                if not includeApk:
                    os.remove(os.path.join(tmpDir, superuserApkPath))
                self.__addDirToZip(newSuperSuZip, os.path.join(tmpDir, "common"), "common")


                if self.getMaker().getConfig().isMakeable("update.busybox"):
                    #process file, with busybox onboard in assumption
                    with open(os.path.join(tmpDir, "META-INF/com/google/android/update-binary"), "r") as f:
                        with open(supersuOriginalUpdatescriptPath, "w") as targetF:
                            for l in f.readlines():
                                if l.startswith("#!"):
                                    targetF.write("#!" + self.getTargetConfigValue("root.methods.supersu.sh", "/system/bin/sh") + "\n")
                                else:
                                    targetF.write(l)
                else:
                    shutil.copy(os.path.join(tmpDir, "META-INF/com/google/android/update-binary"), supersuOriginalUpdatescriptPath)

                postInstscript = "ui_print(\"Installing SuperSU..\");\n"
                postInstscript += "run_program(\"%s\", \"1\", \"stdout\", \"%s\");" % (superSuUpdatescriptTmpExtract, superSuZipTmpExtract)

                with open(postinstFilePath, "w") as postinstFile:
                    postinstFile.write(postInstscript)

                superSuConfig = supersuZipProp.getConfig()
                currPostInst = superSuConfig.get("script.post", [], directOnly=True)
                currPostInst.append(postinstFilePath)
                superSuConfig.set("update.script.post", currPostInst)

        self.setValue("update.files.add." + newSuperSuZipPath.replace(workDir, "").replace(".", "\.") , {
            "destination": superSuZipTmpExtract
        })

        self.setValue("update.files.add." + supersuOriginalUpdatescriptPath.replace(workDir, "").replace(".", "\."), {
            "destination": superSuUpdatescriptTmpExtract,
            "mode": "0755",
            "uid": "0",
            "gid": "0"
        })

    def __addDirToZip(self, zipFile, dirPath, zipRoot):
        zipFile.write(dirPath, zipRoot)
        for f in os.listdir(dirPath):
            src = os.path.join(dirPath, f)
            dest = os.path.join(zipRoot, f)

            if os.path.isdir(src):
                self.__addDirToZip(zipFile, src, dest)
            else:
                zipFile.write(src, dest) class UpdatescriptInitSubmaker(Submaker):
    def make(self, updatePkgDir, updatescriptGen = None):
        updatescriptGen.setVerbose(self.getValue("script.verbose", True))
        mountNames= ["system", "data"]
        dataFormatted = False
        formatData = self.getValue("script.format_data", False)
        formatSupported = self.getTargetConfigValue("bin.update-binary.config.format_supported", False)
        if formatData and formatSupported:
            fsInfo = self.getTargetConfigValue("mount.data", None)
            if fsInfo is not None:
                try:
                    partType = fsInfo["partition_type"] if "partition_type" in fsInfo else None
                    if partType is None:
                        if fsInfo["fs"].lower() in ("ext4", "f2fs"):
                            partType = "EMMC"
                        elif fsInfo["fs"].lower() == "yaffs2":
                            partType = "MTD"
                        else:
                            logger.error(
                                "Could not detect partition type. Please set __config__.target.mount.data.partition_type")
                            sys.exit(1)

                    updatescriptGen.format(fsInfo["fs"], partType, fsInfo["dev"], fsInfo["mount"])
                    dataFormatted = True
                except KeyError as e:
                    logger.error(
                        "data FS info is missing property '%s' required for formatting, doing a normal erase only" %
                        e.args[0])

        for mountName in mountNames:
            updatescriptGen.run("/sbin/mount", "/" + mountName)


        if formatData and not dataFormatted:
            updatescriptGen.rm("/data", recursive=True)


            # dev = self.getMaker().getConfig().getMountConfig(mountName + ".dev")
            # type_ = self.getMaker().getConfig().getMountConfig(mountName + ".type", "EMMC")
            # fs = self.getMaker().getConfig().getMountConfig(mountName + ".fs", "ext4")
            # mountPoint = self.getMaker().getConfig().getMountConfig(mountName + ".mount", "/" + mountName)
            #
            # assert dev, "__config__.target.mount.%s.dev is not set" % mountName
            #
            # updatescriptGen.mount(dev, mountPoint,  fs, type_) class BusyboxSubmaker(Submaker):
    SYMLINKS = [
       "djtimex","arp","ash","awk","base64","basename","bbconfig","blkid","blockdev","brctl","bunzip2","bzcat"
        ,"bzip2","cal","cat","catv","chattr","chgrp","chmod","chown","chroot","clear","cmp","comm","cp","cpio",
       "crond","crontab","cut","date","dc","dd","depmod","devmem","df","diff","dirname","dmesg","dnsd","dos2unix",
       "du","echo","ed","egrep","env","expand","expr","false","fbsplash","fdisk","fgrep","find","flash_lock",
       "flash_unlock","flashcp","flock","fold","free","freeramdisk","fsync","ftpget","ftpput","fuser","getopt",
       "grep","groups","gunzip","gzip","halt","head","hexdump","id","ifconfig","inetd","insmod","install","ionice",
       "iostat","ip","kill","killall","killall5","less","ln","losetup","ls","lsattr","lsmod","lsusb","lzcat","lzma",
       "lzop","lzopcat","man","md5sum","mesg","mkdir","mke2fs","mkfifo","mkfs.ext2","mkfs.vfat","mknod","mkswap",
       "mktemp","modinfo","modprobe","more","mount","mountpoint","mpstat","mv","nanddump","nandwrite","nbd-client",
       "netstat","nice","nohup","nslookup","od","patch","pgrep","pidof","ping","pipe_progress","pkill","pmap",
       "poweroff","printenv","printf","ps","pstree","pwd","pwdx","rdev","readlink","realpath","renice","reset",
       "resize","rev","rm","rmdir","rmmod","route","run-parts","rx","sed","seq","setconsole","setserial","setsid",
       "sh","sha1sum","sha256sum","sha3sum","sha512sum","sleep","sort","split","stat","strings","stty","sum","swapoff",
       "swapon","sync","sysctl","tac","tail","tar","tee","telnet","telnetd","test","tftp","tftpd","time","timeout",
       "top","touch","tr","traceroute","true","ttysize","tune2fs","umount","uname","uncompress","unexpand","uniq",
       "unix2dos","unlzma","unlzop","unxz","unzip","uptime","usleep","uudecode","uuencode","vi","watch","wc","wget",
       "which","whoami","xargs","xz","xzcat","yes","zcat"
    ]

    PATH_SYMLINKS = "/system/xbin"
    def make(self, workDir, updateScriptGenerator = None):
        """
        :param workDir: str
        :param updatescriptgen: UpdateScriptGenerator
        """

        xbinPath = os.path.join(workDir,"system","xbin")
        busyboxTargetPath =  xbinPath + "/busybox"

        key, busyboxBinPath = self.getTargetBinary("busybox")

        assert busyboxBinPath,"Must set %s to busybox path" % key

        self.setValue("update.files.add./system/xbin/busybox", {
            "uid":"0",
            "gid":"2000",
            "mode":"0755",
            "symlinks": [os.path.join(self.__class__.PATH_SYMLINKS, symlink) for symlink in self.__class__.SYMLINKS]
        })

        if not os.path.exists(xbinPath):
            os.makedirs(xbinPath)


        shutil.copy(busyboxBinPath, busyboxTargetPath) class UpdateScriptGenerator(Generator):

    ASCII_INCEPTION = """
.__                            __             .___
|__| ____   ____  ____ _______/  |_  ____   __| _/
|  |/    \_/ ___\/ __ \\____ \   __\/ __ \ / __ | 
|  |   |  \  \__\  ___/|  |_> >  | \  ___// /_/ | 
|__|___|  /\___  >___  >   __/|__|  \___  >____ | 
        \/     \/    \/|__|             \/     \/ 
"""

    def __init__(self, metadataSupported = False):
        super(UpdateScriptGenerator, self).__init__()
        self.commands = []
        self.metadataSupported = metadataSupported
        self.dirty = False
        self.header = ""
        self.footer = self.__class__.ASCII_INCEPTION
        self.wait = 0
        self.verbose = True
        self.preScripts = []
        self.postScripts = []

    def addPrescript(self, script):
        self.preScripts.append(script)

    def addPostscript(self, script):
        self.postScripts.append(script)

    def setVerbose(self, verbose):
        self.verbose = verbose

    def isDirty(self):
        return self.dirty

    def setHeader(self, header):
        self.header = header

    def setFooter(self, footer):
        self.footer = footer

    def setPostExecutionWait(self, wait):
        self.wait = wait

    def getPrintCommands(self, header = None):
        header = header or self.header
        commands = []
        if header is None: return commands
        headerLines = header.split("\n")
        for l in headerLines:
            commands.append(self._genCmd("ui_print", self._quote(l)))

        return commands

    def mount(self, name, mountPoint, fsType, type_ = "EMMC"):
        # self.run("/sbin/mount", mountPoint)
        self._add("mount", self._quote(fsType), self._quote(type_), self._quote(name), self._quote(mountPoint))

    def echo(self, text):
        self._add("ui_print", self._quote(text))

    def format(self, fsType, partitionType, device, mountpoint, fsSize = 0):
        self._add("format", self._quote(fsType), self._quote(partitionType), self._quote( device), str(fsSize), self._quote(mountpoint))
        #format(fs_type, partition_type, device, fs_size, mountpoint) - usually use "0" for fs_size (entire partition)

    def rm(self, path, recursive = False):
        self.dirty = True
        if recursive:
            self._add("delete_recursive", self._quote(path))
        else:
            self._add("delete", self._quote(path))

    def symlink(self, target, links):
        self._add("symlink", self._quote(target), *tuple([self._quote(link) for link in links]))

    def run(self, *args):
        if not args[0].endswith("/mount"):
            self.dirty = True
        self._add("run_program", *(self._quote(a) for a in args))

    def writeImage(self, path, blockDevice):
        self.dirty = True
        #fname = os.path.basename(path)
        #tmpExt = "/tmp/" + fname
        #self.extractFile(path, tmpExt)
        #self._add("write_raw_image", self._quote(tmpExt), self._quote(blockDevice))
        self.run("/sbin/busybox", "dd", "if=%s" % path, "of=%s" % blockDevice)

    def extractFile(self, f, out):
        self.dirty = True
        self._add("package_extract_file", self._quote(f), self._quote(out))

    def extractDir(self, dir, out):
        self.dirty = True
        self._add("package_extract_dir", self._quote(dir), self._quote(out))

    def setPermissions(self, path, uid, gid, fmode, dmode = None):
        if self.metadataSupported:
            self.setMetaData(path, uid, gid, fmode, dmode)
        else:
            self.dirty = True
            if dmode is None:
                self._add("set_perm", uid, gid, fmode, self._quote(path))
            else:
                self._add("set_perm_recursive", uid, gid, dmode, fmode, self._quote(path))

    def setContext(self, path, context):
        self.dirty = True
        self.run("/sbin/chcon", context, path)

    def setMetaData(self,  path, uid, gid, fmode, dmode = None):
        self.dirty = True
        uidKey = self._quote("uid")
        gidKey = self._quote("gid")
        fmodeKey = self._quote("fmode")
        dmodeKey = self._quote("dmode")
        if dmode is None:
            self._add("set_metadata", self._quote(path), uidKey, uid, gidKey, gid, fmodeKey, fmode)
        else:
            self._add("set_metadata_recursive", self._quote(path), uidKey, uid, gidKey, gid, fmodeKey, fmode, dmodeKey, dmode)

    def _quote(self, string):
        return "\"%s\"" % string

    def _genCmd(self, *args):
        cmd = args[0]
        cmdArgs = args[1:]
        cmdTmpl="{cmd}({args});"
        return cmdTmpl.format(cmd = cmd, args = ", ".join(cmdArgs))
    
    def _add(self, *args):
        cmd = self._genCmd(*args)
        if args[0] != "ui_print" and self.verbose:
            self.echo(cmd.replace("\"","'"))
        self.commands.append(cmd)

    def _genProgress(self, total, done):
        val = "%.6f" % (float(done)/float(total))

        return self._genCmd("set_progress", val)

    def generateSleepCountdown(self):
        commands = []
        for remaining in range(self.wait, 0, -1):
            commands.append(self._genCmd("ui_print", self._quote(str(remaining))))
            commands.append(self._genCmd("sleep", "1"))

        return commands


    def getFinishingCommands(self):
        sleepCountDown = self.generateSleepCountdown()

        return sleepCountDown + [self._genCmd("ui_print", self._quote("bye!"))]

    def generate(self, showProgress = True):
        headerCommands = self.getPrintCommands()
        commandsProgressified = []
        total = len(self.commands)
        lastProgress = ""
        for i in range(0, total):
            c = self.commands[i]
            progress = self._genProgress(total, i)

            if progress != lastProgress:
                lastProgress = progress
                commandsProgressified.append(progress)

            commandsProgressified.append(c)

        if showProgress:
            commands = headerCommands + commandsProgressified + self.getPrintCommands(self.footer)
        else:
            commands = headerCommands + self.commands + self.getPrintCommands(self.footer)
        
        for i in range(0, 5):
            commands.append(self._genCmd("ui_print", self._quote("#")))

        generated = "\n".join(commands)

        return "{pre}\n{generated}\n{post}\n{finish}".format(
            pre="\n".join(self.preScripts),
            generated=generated,
            post="\n".join(self.postScripts),
            finish="\n".join(self.getFinishingCommands())
        )
from .maker import Maker
from .submakers.submaker_wifi import WifiSubmaker
from .submakers.submaker_fs import FsSubmaker
from .submakers.submaker_updatescript import UpdatescriptSubmaker
from .submakers.submaker_property import PropertySubmaker
from .submakers.submaker_updatezip import UpdatezipSubmaker
from .submakers.submaker_adbkeys import AdbKeysSubmaker
from .submakers.submaker_apps import AppsSubmaker
from .submakers.submaker_supersu import SuperSuSubmaker
from .submakers.submaker_databases import DatabasesSubmaker
from .submakers.submaker_settings import SettingsSubmaker
from .submakers.submaker_updatescriptinit import UpdatescriptInitSubmaker
from .submakers.submaker_busybox import BusyboxSubmaker
from inception.generators.updatescript import UpdateScriptGenerator
import shutil
import os
import logging
logger = logging.getLogger(__name__)
class UpdateMaker(Maker):
    def __init__(self, config):
        super(UpdateMaker, self).__init__(config, "update")
        self.rootFs = "fs"
        self.updatescriptGen = UpdateScriptGenerator(
            self.getTargetConfigValue("bin.update-binary.config.metadata_supported", False)
        )

    def make(self, workDir, outDir):
        logger.info("Making update package")
        rootFS = os.path.join(workDir, self.rootFs)
        self.makeUpdateScriptInit(rootFS)
        self.makeFS(rootFS)
        self.makeProps(rootFS)
        self.makeWPASupplicant(rootFS)
        self.makeBusyBox(rootFS)
        self.makeSettings(rootFS)
        self.makeDatabases(rootFS)
        self.makeAdbKeys(rootFS)
        self.makeStockRecovery(rootFS)
        self.makeRoot(rootFS)
        self.makeApps(rootFS)
        self.makeUpdateScript(rootFS)
        return self.makeUpdateZip(rootFS, outDir)

    def isMakeTrue(self, key):
        return self.getMakeValue(key + ".__make__", True)

    def makeFS(self, fsPath):
        logger.info("Making FS")
        if not os.path.exists(fsPath):
            os.makedirs(fsPath)

        smaker = FsSubmaker(self, "files")
        smaker.make(fsPath)


    def makeBusyBox(self, fsPath):
        if self.isMakeTrue("busybox"):
            logger.info("Making Busybox")
            smaker = BusyboxSubmaker(self, "busybox")
            smaker.make(fsPath, self.updatescriptGen)

    def makeRoot(self, fsPath):
        rootMethod = self.getMakeValue("root_method", None)

        if rootMethod:
            logger.info("Selected root method: %s" % rootMethod)
            if rootMethod == "supersu":
                return self.makeSuperSu(fsPath)

            raise ValueError("Unknown root method %s" % rootMethod)


    def makeSuperSu(self, fsPath):
        if self.isMakeTrue("supersu"):
            logger.info("Making SuperSU")
            smaker = SuperSuSubmaker(self, ".")
            smaker.make(fsPath)

    def makeSettings(self, workDir):
        if self.isMakeTrue("settings"):
            logger.info("Making Settings")
            smaker = SettingsSubmaker(self, "settings")
            smaker.make(workDir)
        else:
            self.setValue("update.settings", {"__override__": True})

    def makeDatabases(self, workDir):
        if self.isMakeTrue("databases"):
            logger.info("Making databases")
            smaker = DatabasesSubmaker(self, "databases")
            smaker.make(workDir)
        else:
            self.setValue("update.databases", {"__override__": True})

    def makeWPASupplicant(self, workDir):
        if self.isMakeTrue("network"):
            logger.info("Making WPASupplicant")
            wpaSupplicantDir = os.path.join(workDir, "data", "misc", "wifi")
            smaker = WifiSubmaker(self, "network")
            smaker.make(wpaSupplicantDir)


    def makeUpdateScriptInit(self, updatePkgDir):
        logger.info("Init Update script")

        smaker = UpdatescriptInitSubmaker(self, ".")