ipaapk - skynocover/Wiki-for-GoLang GitHub Wiki

ipa and apk

Version

Interface

import (
	"errors"
)

// GenerateEXT ...
func GenerateEXT(ext string) (Ires, error) {
	switch ext {
	case "ipa":
		return &ios{}, nil
	case "apk":
		return &android{}, nil
	default:
		return nil, errors.New("副檔名必須是ipa或apk")
	}
}

// GenerateENV ...
func GenerateENV(env string) (Ires, error) {
	switch env {
	case "ios":
		return &ios{}, nil
	case "android":
		return &android{}, nil
	default:
		return nil, errors.New("下載時取得環境錯誤")
	}
}

// Ires ....
type Ires interface {
	Version(string) (string, error)
	Env() string
	Rename() error
	ReadFile() ([]byte, error)
	CompareVersion(string) int
}

func parseVersion(s string, width int) int64 { //為版本加上填充字元以便比較兩版本大小
	strList := strings.Split(s, ".")
	format := fmt.Sprintf("%%s%%0%ds", width)
	v := ""
	for _, value := range strList {
		v = fmt.Sprintf(format, v, value)
	}
	var result int64
	var err error
	if result, err = strconv.ParseInt(v, 10, 64); err != nil {
		fmt.Printf("ugh: parseVersion(%s): error=%s", s, err)
		return 0
	}
	// fmt.Printf("parseVersion: [%s] => [%s] => [%d]\n", s, v, result)
	return result
}

Android

import (
	"auth/src/globals"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"

	"github.com/shogo82148/androidbinary/apk"
)

type android struct {
}

func (a *android) Rename() (err error) {
	err = os.Remove(fmt.Sprintf("%s/android/app.apk", globals.Config.Setting.Uploads))
	err = os.Rename(fmt.Sprintf("%s/android/temp.apk", globals.Config.Setting.Uploads), fmt.Sprintf("%s/android/app.apk", globals.Config.Setting.Uploads))
	return
}

func (a *android) Env() string {
	return "android"
}

func (a *android) Version(filename string) (version string, err error) {

	path := filepath.Join(globals.Config.Setting.Uploads, "android", filename+".apk")
	pkg, errpkg := apk.OpenFile(path)
	if errpkg != nil {
		err = errpkg
		return
	}
	defer pkg.Close()

	return pkg.Manifest().VersionName.String()
}

func (a *android) ReadFile() (file []byte, err error) {
	return ioutil.ReadFile(fmt.Sprintf("%s/android/app.apk", globals.Config.Setting.Uploads))
}

func (a *android) CompareVersion(version string) (check int) {

	newVersion := parseVersion(version, 4)

	var oldVersion = parseVersion(globals.ApkVersion, 4)
	switch {
	case newVersion > oldVersion:
		globals.ApkVersion = version
		check = 1
	case newVersion < oldVersion:
		check = -1
	default:
		check = 0
	}
	return
}

IOS

import (
	"archive/zip"
	"auth/src/globals"
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
	"howett.net/plist"
)

type ios struct {
	bid    string // CFBundleIdentifier
	bver   string // CFBundleVersion
	btitle string // CFBundleName
}

type buildPlist struct {
	Items []item `plist:"items"`
}

type item struct {
	Assets   []asset `plist:"assets"`
	Metadata meta    `plist:"metadata"`
}

type meta struct {
	BundleIdentifier string `plist:"bundle-identifier"`
	BundleVersion    string `plist:"bundle-version"`
	Kind             string `plist:"kind"`
	Title            string `plist:"title"`
}

type asset struct {
	Kind string `plist:"kind"`
	URL  string `plist:"url"`
}

func (i *ios) Rename() (err error) {
	err = os.Remove(fmt.Sprintf("%s/ios/app.ipa", globals.Config.Setting.Uploads))
	err = os.Rename(fmt.Sprintf("%s/ios/temp.ipa", globals.Config.Setting.Uploads), fmt.Sprintf("%s/ios/app.ipa", globals.Config.Setting.Uploads))

	data := &buildPlist{
		Items: []item{
			{
				Assets: []asset{
					{
						Kind: "software-package",
						URL:  fmt.Sprintf("%s/ios/app.ipa", globals.Config.IOSDL[globals.Env].URL),
					},
				},
				Metadata: meta{
					BundleIdentifier: i.bid,
					BundleVersion:    i.bver,
					Kind:             "software",
					Title:            i.btitle,
				},
			},
		},
	}

	buf := &bytes.Buffer{}
	encoder := plist.NewEncoder(buf)

	if err = encoder.Encode(data); err != nil {
		fmt.Println(err)
	}

	f, err := os.Create(fmt.Sprintf("%s/ios/app.plist", globals.Config.Setting.Uploads))
	if err != nil {
		return err
	}
	defer f.Close()

	buffer := make([]byte, 1024)
	for {
		// read a chunk
		n, err := buf.Read(buffer)
		if err != nil && err != io.EOF {
			return err

		}
		if n == 0 {
			break
		}

		// write a chunk
		if _, err := f.Write(buffer[:n]); err != nil {
			return err
		}
	}
	return
}

func (i *ios) Env() string {
	return "ios"
}

func (i *ios) Version(filename string) (version string, err error) {

	path := filepath.Join(globals.Config.Setting.Uploads, "ios", filename+".ipa")

	zr, errzip := zip.OpenReader(path)
	if errzip != nil {
		err = errzip
		return
	}
	defer zr.Close()

	for _, file := range zr.File { // 讀取plist檔
		if strings.Contains(file.Name, "Info.plist") {
			nameSplit := strings.Split(file.Name, "/")
			if len(nameSplit) == 3 && nameSplit[2] == "Info.plist" {

				fp, errfile := file.Open()
				if errfile != nil {
					err = errfile
					return
				}
				bs, _ := ioutil.ReadAll(fp)
				fp.Close()
				var result map[string]interface{}
				_, errplist := plist.Unmarshal(bs, &result)
				if errplist != nil {
					err = errplist
					return
				}

				// for k, v := range result {
				// 	log.Println(fmt.Sprintf("%s: %v", k, v))
				// }
				shortVersion, okSV := result["CFBundleShortVersionString"]
				if okSV {
					// fmt.Printf("CFBundleShortVersionString: %s", shortVersion.(string))
					version = shortVersion.(string)
				}

				bid, okID := result["CFBundleIdentifier"]
				if okID {
					i.bid = bid.(string)
					// fmt.Printf("CFBundleIdentifier: %s", i.bid)
				}
				bver, okVer := result["CFBundleVersion"]
				if okVer {
					i.bver = bver.(string)
					// fmt.Printf("CFBundleVersion: %s", i.bver)
				}
				btitle, okTitle := result["CFBundleName"]
				if okTitle {
					i.btitle = btitle.(string)
					// fmt.Printf("CFBundleName: %s", i.btitle)
				}
				return
			}
		}
	}

	return
}

func (i *ios) ReadFile() (file []byte, err error) {
	return ioutil.ReadFile(fmt.Sprintf("%s/ios/app.plist", globals.Config.Setting.Uploads))
}

func (i *ios) CompareVersion(version string) (check int) {
	newVersion := parseVersion(version, 4)

	var oldVersion = parseVersion(globals.IpaVersion, 4)
	switch {
	case newVersion > oldVersion:
		globals.IpaVersion = version
		check = 1
	case newVersion < oldVersion:
		check = -1
	default:
		check = 0
	}
	return
}

Download

// Download ...
func Download(ctx iris.Context) {
	env := ctx.FormValue("env")

	res, err := resource.GenerateENV(env)
	if err != nil {
		ctx.StatusCode(400)
		return
	}

	if env == "ios" { //IOS需要轉址
		redir := fmt.Sprintf("itms-services://?action=download-manifest&url=%s/ios/app.plist", globals.Config.IOSDL[globals.Env].URL)
		ctx.Redirect(redir)
		return
	}

	ctx.Header("Content-Type", "application/vnd.android.package-archive")
	ctx.Header("Access-Control-Allow-Origin", "*")

	file, err := res.ReadFile()
	if err != nil {
		ctx.StatusCode(500)
		return
	}
	ctx.Write(file)
}