<commit_before>// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/

package main

import (
	"crypto/dsa"
	"crypto/rand"
	"encoding/json"
	"flag"
	"fmt"
	"log"
	"net/http"
)

const (
	MOCKMYID_API_ROOT           = "/"
	MOCKMYID_API_LISTEN_ADDRESS = "127.0.0.1"
	MOCKMYID_API_LISTEN_PORT    = 8080
)

type LoginResponse struct {
	Email     string `json:"email"`
	Audience  string `json:"audience"`
	Assertion string `json:"assertion"`
}

func generateRandomKey() (*dsa.PrivateKey, error) {
	params := new(dsa.Parameters)
	if err := dsa.GenerateParameters(params, rand.Reader, dsa.L1024N160); err != nil {
		return nil, err
	}
	priv := new(dsa.PrivateKey)
	priv.PublicKey.Parameters = *params
	if err := dsa.GenerateKey(priv, rand.Reader); err != nil {
		return nil, err
	}
	return priv, nil
}

func handleLogin(w http.ResponseWriter, r *http.Request) {
	query := r.URL.Query()
	email := query["email"][0]
	audience := query["audience"][0]
	uniqueKey := query["uniqueKey"]

	var clientKey *dsa.PrivateKey
	if len(uniqueKey) != 0 {
		key, err := generateRandomKey()
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		clientKey = key
	} else {
		clientKey = globalRandomKey
	}

	assertion, err := CreateShortLivedMockMyIDAssertion(*clientKey, email, audience)

	loginResponse := &LoginResponse{
		Email:     email,
		Audience:  audience,
		Assertion: assertion,
	}

	encodedLoginResponse, err := json.Marshal(loginResponse)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/json; charset=utf-8")
	w.Write(encodedLoginResponse)
}

var globalRandomKey *dsa.PrivateKey

func main() {
	address := flag.String("address", MOCKMYID_API_LISTEN_ADDRESS, "address to listen on")
	port := flag.Int("port", MOCKMYID_API_LISTEN_PORT, "port to listen on")
	root := flag.String("root", MOCKMYID_API_ROOT, "application root (path prefix)")
	flag.Parse()

	prefix := *root
	if prefix == "/" {
		prefix = ""
	}

	log.Print("Generating client DSA key")
	key, err := generateRandomKey()
	if err != nil {
		panic("Cannot generate random key")
	}
	globalRandomKey = key

	http.HandleFunc(prefix+"/login", handleLogin)
	addr := fmt.Sprintf("%s:%d", *address, *port)
	log.Printf("Starting mockmyid-api server on http://%s%s", addr, prefix)
	err = http.ListenAndServe(addr, nil)
	if err != nil {
		log.Fatal(err)
	}
}
<commit_msg>Added a /key API and renamed /login to /assertion<commit_after>// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/

package main

import (
	"crypto/dsa"
	"crypto/rand"
	"encoding/json"
	"flag"
	"fmt"
	"log"
	"net/http"
)

const (
	MOCKMYID_API_ROOT           = "/"
	MOCKMYID_API_LISTEN_ADDRESS = "127.0.0.1"
	MOCKMYID_API_LISTEN_PORT    = 8080
)

type LoginResponse struct {
	Email     string `json:"email"`
	Audience  string `json:"audience"`
	Assertion string `json:"assertion"`
}

func generateRandomKey() (*dsa.PrivateKey, error) {
	params := new(dsa.Parameters)
	if err := dsa.GenerateParameters(params, rand.Reader, dsa.L1024N160); err != nil {
		return nil, err
	}
	priv := new(dsa.PrivateKey)
	priv.PublicKey.Parameters = *params
	if err := dsa.GenerateKey(priv, rand.Reader); err != nil {
		return nil, err
	}
	return priv, nil
}

type KeyResponse struct {
	Algorithm string `json:"algorithm"`
	X         string `json:"x"`
	Y         string `json:"y"`
	P         string `json:"p"`
	Q         string `json:"q"`
	G         string `json:"g"`
}

func handleKey(w http.ResponseWriter, r *http.Request) {
	keyResponse := KeyResponse{
		Algorithm: "DS",
		X:         fmt.Sprintf("%x", MOCKMYID_KEY.X),
		Y:         fmt.Sprintf("%x", MOCKMYID_KEY.PublicKey.Y),
		P:         fmt.Sprintf("%x", MOCKMYID_KEY.PublicKey.Parameters.P),
		Q:         fmt.Sprintf("%x", MOCKMYID_KEY.PublicKey.Parameters.Q),
		G:         fmt.Sprintf("%x", MOCKMYID_KEY.PublicKey.Parameters.G),
	}

	encodedKeyResponse, err := json.Marshal(keyResponse)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/json; charset=utf-8")
	w.Write(encodedKeyResponse)
}

func handleAssertion(w http.ResponseWriter, r *http.Request) {
	query := r.URL.Query()
	email := query["email"][0]
	audience := query["audience"][0]
	uniqueKey := query["uniqueKey"]

	var clientKey *dsa.PrivateKey
	if len(uniqueKey) != 0 {
		key, err := generateRandomKey()
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		clientKey = key
	} else {
		clientKey = globalRandomKey
	}

	assertion, err := CreateShortLivedMockMyIDAssertion(*clientKey, email, audience)

	loginResponse := &LoginResponse{
		Email:     email,
		Audience:  audience,
		Assertion: assertion,
	}

	encodedLoginResponse, err := json.Marshal(loginResponse)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/json; charset=utf-8")
	w.Write(encodedLoginResponse)
}

var globalRandomKey *dsa.PrivateKey

func main() {
	address := flag.String("address", MOCKMYID_API_LISTEN_ADDRESS, "address to listen on")
	port := flag.Int("port", MOCKMYID_API_LISTEN_PORT, "port to listen on")
	root := flag.String("root", MOCKMYID_API_ROOT, "application root (path prefix)")
	flag.Parse()

	prefix := *root
	if prefix == "/" {
		prefix = ""
	}

	log.Print("Generating client DSA key")
	key, err := generateRandomKey()
	if err != nil {
		panic("Cannot generate random key")
	}
	globalRandomKey = key

	http.HandleFunc(prefix+"/assertion", handleAssertion)
	http.HandleFunc(prefix+"/key", handleKey)

	addr := fmt.Sprintf("%s:%d", *address, *port)
	log.Printf("Starting mockmyid-api server on http://%s%s", addr, prefix)
	err = http.ListenAndServe(addr, nil)
	if err != nil {
		log.Fatal(err)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/csv"
	"fmt"
	"strconv"
	"log"
	"os"
	"strings"
	"time"
)

const (
	SELLWIRE_TIMESTAMP_FORMAT = "2006-01-02 15:04:05"
	SELLWIRE_TRANSACTION_COLUMN_STATUS=16
	SELLWIRE_TRANSACTION_COLUMN_TRANSACTION_ID=2
	SELLWIRE_TRANSACTION_COLUMN_TIMESTAMP=12
	SELLWIRE_TRANSACTION_COLUMN_CUSTOMER_NAME=3
	SELLWIRE_TRANSACTION_COLUMN_CUSTOMER_TAX=6
	SELLWIRE_TRANSACTION_COLUMN_CUSTOMER_AMOUNT=7
	SELLWIRE_TRANSACTION_COLUMN_CUSTOMER_IS_EU=9
	SELLWIRE_TRANSACTION_COLUMN_CUSTOMER_COUNTRY_CODE=10
	SELLWIRE_TRANSACTION_COLUMN_CUSTOMER_TAX_NUMBER=11
	PAYPAL_DATE_OUTPUT_FORMAT = "02.01.2006"
	STRIPE_TRANSFER_DATE_FORMAT = "2006-01-02 15:04"
	STRIPE_TRANSFER_COLUMN_STATUS=3
	STRIPE_TRANSFER_COLUMN_DATE=0
	STRIPE_TRANSFER_COLUMN_TRANSFER_ID=1
	STRIPE_TRANSFER_COLUMND_AMOUNT=5
	STRIPE_PAYMENT_COLUMN_PAYMENT_ID=0
	STRIPE_PAYMENT_COLUMN_TRANSFER_ID=45
	STRIPE_PAYMENT_COLUMN_STATUS=12
)

type TransactionType string

const (
	TransactionTypePaypal TransactionType = "paypal"
	TransactionTypeStripe TransactionType = "stripe"
)

type Amount struct {
	Dollars int64
	Cents int64
}

func (a Amount) IsZero() bool {
	return a.Dollars == 0 && a.Cents == 0
}

func (a Amount) ToStringGermany() string {
	if a.Dollars == 0 && a.Cents == 0 {
		return ""
	}
	return fmt.Sprintf("%d,%02d",a.Dollars, a.Cents)
}

// VAT % of total amount (including VAT)
func (a Amount) VATPercentOfAsStringGermany(other Amount) string {
	totalCentsA := a.Dollars * 100 + a.Cents
	totalCentsB := other.Dollars * 100 + other.Cents - totalCentsA
	percentage := float64(totalCentsA) * 100.0 / float64(totalCentsB)
	percentageRounded := int64(percentage + 0.5)
	if percentageRounded == 0 {
		return ""
	}
	return fmt.Sprintf("%d%%", percentageRounded)
}

type SellwireTransaction struct {
	TransactionType TransactionType
	TransactionId string
	Timestamp time.Time
	CustomerName string
	Amount Amount
	TaxAmount Amount
	IsEU bool
	IsPrivate bool
	CountryCode string
	TaxNumber string
}

type StripeTransfer struct {
	TransferId string
	Date time.Time
	Amount Amount
	Status string
}

var transactions []SellwireTransaction
var stripeTransfersByTransactionId map[string]StripeTransfer
var vatCorrections map[string]string // from broken number to corrected number (including country prefix)

func main() {
	importVatCorrections()
	importSellwireTransactions()
	importStripeTransferMap()
	outputPaypalTransactions()
	outputStripeTransactions()
}

func importSellwireTransactions() {
	sellwireOrdersFile, err := os.Open("input/SellwireOrders.csv")
	if err != nil {
		log.Fatal(err)
	}
	r := csv.NewReader(sellwireOrdersFile)

	records, err := r.ReadAll()
	if err != nil {
		log.Fatal(err)
	}

	for _, record := range records[1:] {
		status := record[SELLWIRE_TRANSACTION_COLUMN_STATUS]
		if status != "complete" {
			continue
		}
		timestampStr := record[SELLWIRE_TRANSACTION_COLUMN_TIMESTAMP]
		timestamp, err := time.Parse(SELLWIRE_TIMESTAMP_FORMAT, timestampStr)
		if err != nil {
			log.Fatal(err)
		}

		countryCode := record[SELLWIRE_TRANSACTION_COLUMN_CUSTOMER_COUNTRY_CODE]
		if countryCode == "" {
			countryCode = "US"
		}
		taxNumber := strings.ToUpper(record[SELLWIRE_TRANSACTION_COLUMN_CUSTOMER_TAX_NUMBER])

		if taxNumber != "" && !strings.HasPrefix(taxNumber, countryCode) {
			taxNumber = fmt.Sprintf("%s%s", countryCode, taxNumber)
		}

		if correction, ok := vatCorrections[taxNumber]; ok {
			taxNumber = correction
		}

		isEU, _ := strconv.ParseBool(record[SELLWIRE_TRANSACTION_COLUMN_CUSTOMER_IS_EU])

		amount := parseAmount(record[SELLWIRE_TRANSACTION_COLUMN_CUSTOMER_AMOUNT])
		taxAmount := parseAmount(record[SELLWIRE_TRANSACTION_COLUMN_CUSTOMER_TAX])

		transactionId := record[SELLWIRE_TRANSACTION_COLUMN_TRANSACTION_ID]

		transactionType := TransactionTypePaypal
		if strings.HasPrefix(transactionId, "ch_") {
			transactionType = TransactionTypeStripe
		}

		if amount.IsZero() {
			continue
		}
		sellwireRecord := SellwireTransaction{
			TransactionType: transactionType,
			TransactionId: transactionId,
			Timestamp: timestamp,
			CustomerName: strings.Title(strings.ToLower(record[SELLWIRE_TRANSACTION_COLUMN_CUSTOMER_NAME])),
			Amount: amount,
			TaxAmount: taxAmount,
			IsEU: isEU,
			IsPrivate: isEU && taxNumber == "",
			CountryCode: countryCode,
			TaxNumber: taxNumber,
		}

		transactions = append(transactions, sellwireRecord)
	}
}

func parseAmount(amountStr string) Amount {
			var amount Amount
			amountStrStripped := strings.Replace(amountStr, ",", "", -1)
			amountParts := strings.Split(amountStrStripped, ".")
			if len(amountParts) > 2 {
				log.Fatalf("Invalid amount found: %s", amountStr)
			}
			if len(amountParts) > 0 {
				dollars, err := strconv.ParseInt(amountParts[0], 10, 64)
				if err != nil {
					log.Fatal(err)
				}
				amount.Dollars = dollars
				if len(amountParts) > 1 {
					cents, err  := strconv.ParseInt(amountParts[1], 10, 64)
					if cents < 10 { // 9.8 is 9 dollar 80 cents and not 9 dollar 8 cents
						cents *= 10
					}
					if err != nil {
						log.Fatal(err)
					}
					amount.Cents = cents
				}
			}
			return amount
		}

func importStripeTransferMap() {
	stripeTransfersByTransferId := make(map[string]StripeTransfer)

	stripeTransfersFile, err := os.Open("input/transfers.csv")
	if err != nil {
		log.Fatal(err)
	}
	r := csv.NewReader(stripeTransfersFile)

	transferRecords, err := r.ReadAll()
	if err != nil {
		log.Fatal(err)
	}

	for _, record := range transferRecords[1:] {
		status := record[STRIPE_TRANSFER_COLUMN_STATUS]

		dateStr := record[STRIPE_TRANSFER_COLUMN_DATE]
		date, err := time.Parse(STRIPE_TRANSFER_DATE_FORMAT, dateStr)
		if err != nil {
			log.Fatal(err)
		}

		var amount Amount
		amountStr := record[STRIPE_TRANSFER_COLUMND_AMOUNT]
		amountStrStripped := strings.Replace(amountStr, ".", "", -1)
		amountParts := strings.Split(amountStrStripped, ",")
		if len(amountParts) > 2 {
			log.Fatalf("Invalid amount found: %s", amountStr)
		}
		if len(amountParts) > 0 {
			dollars, err := strconv.ParseInt(amountParts[0], 10, 64)
			if err != nil {
				log.Fatal(err)
			}
			amount.Dollars = dollars
			if len(amountParts) > 1 {
				cents, err  := strconv.ParseInt(amountParts[1], 10, 64)
				if err != nil {
					log.Fatal(err)
				}
				amount.Cents = cents
			}
		}

		transferId := record[STRIPE_TRANSFER_COLUMN_TRANSFER_ID]

		transferRecord := StripeTransfer{
			TransferId: transferId,
			Date: date,
			Amount: amount,
			Status: status,
		}

		stripeTransfersByTransferId[transferId] = transferRecord
	}

	stripePaymentsFile, err := os.Open("input/payments.csv")
	if err != nil {
		log.Fatal(err)
	}
	r2 := csv.NewReader(stripePaymentsFile)

	paymentRecords, err := r2.ReadAll()
	if err != nil {
		log.Fatal(err)
	}

	stripeTransfersByTransactionId = make(map[string]StripeTransfer)

	for _, record := range paymentRecords[1:] {
		paymentId := record[STRIPE_PAYMENT_COLUMN_PAYMENT_ID]
		transferId := record[STRIPE_PAYMENT_COLUMN_TRANSFER_ID]
		status := record[STRIPE_PAYMENT_COLUMN_STATUS]

		if status != "Paid" {
			continue
		}

		transfer, ok := stripeTransfersByTransferId[transferId]
		if !ok {
			log.Fatalf("transfer id %s no transfer found for payment id %s", transferId, paymentId)
		}
		stripeTransfersByTransactionId[paymentId] = transfer
	}
}

func importVatCorrections() {
	vatCorrections = make(map[string]string)

	correctionsFile, err := os.Open("input/vat_corrections.csv")
	if err != nil {
		log.Fatal(err)
	}
	r := csv.NewReader(correctionsFile)

	corrections, err := r.ReadAll()
	if err != nil {
		log.Fatal(err)
	}

	for _, record := range corrections {
		vatCorrections[record[0]] = record[1]
	}
}

func outputPaypalTransactions() {
	paypalOutput := [][]string{
		{"Datum", "Kundenname", "Betrag USD", "VAT USD", "VAT", "Land", "EU", "Privat", "USt-ID"},
	}

	for _, tx := range transactions {
		if tx.TransactionType != TransactionTypePaypal {
			continue
		}

		isEU := ""
		if tx.IsEU {
			isEU = "x"
		}

		isPrivate := ""
		if tx.IsPrivate {
			isPrivate = "x"
		}
		if !tx.IsEU {
			isPrivate = "-"
		}

		record := []string{
			tx.Timestamp.Format(PAYPAL_DATE_OUTPUT_FORMAT),
			tx.CustomerName,
			tx.Amount.ToStringGermany(),
			tx.TaxAmount.ToStringGermany(),
			tx.TaxAmount.VATPercentOfAsStringGermany(tx.Amount),
			tx.CountryCode,
			isEU,
			isPrivate,
			tx.TaxNumber,
		}
		paypalOutput = append(paypalOutput, record)
	}

	outputFile, err := os.Create("output/Paypal.csv")
	if err != nil {
		log.Fatal(err)
	}

	w := csv.NewWriter(outputFile)
	w.WriteAll(paypalOutput)

	if err := w.Error(); err != nil {
		log.Fatalln("error writing paypal output csv: %v", err)
	}
}

func outputStripeTransactions() {
	stripeOutput := [][]string{
		{"Datum", "Kundenname", "Betrag USD", "VAT USD", "VAT", "Land", "EU", "Privat", "USt-ID", "Datum Transfer", "Gesamtbetrag Transfer EUR"},
	}

	for _, tx := range transactions {
		if tx.TransactionType != TransactionTypeStripe {
			continue
		}

		isEU := ""
		if tx.IsEU {
			isEU = "x"
		}

		isPrivate := ""
		if tx.IsPrivate {
			isPrivate = "x"
		}
		if !tx.IsEU {
			isPrivate = "-"
		}

		transfer := stripeTransfersByTransactionId[tx.TransactionId]

		record := []string{
			tx.Timestamp.Format(PAYPAL_DATE_OUTPUT_FORMAT),
			tx.CustomerName,
			tx.Amount.ToStringGermany(),
			tx.TaxAmount.ToStringGermany(),
			tx.TaxAmount.VATPercentOfAsStringGermany(tx.Amount),
			tx.CountryCode,
			isEU,
			isPrivate,
			tx.TaxNumber,
			transfer.Date.Format(PAYPAL_DATE_OUTPUT_FORMAT),
			transfer.Amount.ToStringGermany(),
		}
		stripeOutput = append(stripeOutput, record)
	}

	outputFile, err := os.Create("output/Stripe.csv")
	if err != nil {
		log.Fatal(err)
	}

	w := csv.NewWriter(outputFile)
	w.WriteAll(stripeOutput)

	if err := w.Error(); err != nil {
		log.Fatalln("error writing stripe output csv: %v", err)
	}
}
<commit_msg>Print payment with date instead of fatal<commit_after>package main

import (
	"encoding/csv"
	"fmt"
	"strconv"
	"log"
	"os"
	"strings"
	"time"
)

const (
	SELLWIRE_TIMESTAMP_FORMAT = "2006-01-02 15:04:05"
	SELLWIRE_TRANSACTION_COLUMN_STATUS=16
	SELLWIRE_TRANSACTION_COLUMN_TRANSACTION_ID=2
	SELLWIRE_TRANSACTION_COLUMN_TIMESTAMP=12
	SELLWIRE_TRANSACTION_COLUMN_CUSTOMER_NAME=3
	SELLWIRE_TRANSACTION_COLUMN_CUSTOMER_TAX=6
	SELLWIRE_TRANSACTION_COLUMN_CUSTOMER_AMOUNT=7
	SELLWIRE_TRANSACTION_COLUMN_CUSTOMER_IS_EU=9
	SELLWIRE_TRANSACTION_COLUMN_CUSTOMER_COUNTRY_CODE=10
	SELLWIRE_TRANSACTION_COLUMN_CUSTOMER_TAX_NUMBER=11
	PAYPAL_DATE_OUTPUT_FORMAT = "02.01.2006"
	STRIPE_TRANSFER_DATE_FORMAT = "2006-01-02 15:04"
	STRIPE_TRANSFER_COLUMN_STATUS=3
	STRIPE_TRANSFER_COLUMN_DATE=0
	STRIPE_TRANSFER_COLUMN_TRANSFER_ID=1
	STRIPE_TRANSFER_COLUMND_AMOUNT=5
	STRIPE_PAYMENT_COLUMN_PAYMENT_ID=0
	STRIPE_PAYMENT_COLUMN_PAYMENT_DATE=2
	STRIPE_PAYMENT_COLUMN_TRANSFER_ID=45
	STRIPE_PAYMENT_COLUMN_STATUS=12
)

type TransactionType string

const (
	TransactionTypePaypal TransactionType = "paypal"
	TransactionTypeStripe TransactionType = "stripe"
)

type Amount struct {
	Dollars int64
	Cents int64
}

func (a Amount) IsZero() bool {
	return a.Dollars == 0 && a.Cents == 0
}

func (a Amount) ToStringGermany() string {
	if a.Dollars == 0 && a.Cents == 0 {
		return ""
	}
	return fmt.Sprintf("%d,%02d",a.Dollars, a.Cents)
}

// VAT % of total amount (including VAT)
func (a Amount) VATPercentOfAsStringGermany(other Amount) string {
	totalCentsA := a.Dollars * 100 + a.Cents
	totalCentsB := other.Dollars * 100 + other.Cents - totalCentsA
	percentage := float64(totalCentsA) * 100.0 / float64(totalCentsB)
	percentageRounded := int64(percentage + 0.5)
	if percentageRounded == 0 {
		return ""
	}
	return fmt.Sprintf("%d%%", percentageRounded)
}

type SellwireTransaction struct {
	TransactionType TransactionType
	TransactionId string
	Timestamp time.Time
	CustomerName string
	Amount Amount
	TaxAmount Amount
	IsEU bool
	IsPrivate bool
	CountryCode string
	TaxNumber string
}

type StripeTransfer struct {
	TransferId string
	Date time.Time
	Amount Amount
	Status string
}

var transactions []SellwireTransaction
var stripeTransfersByTransactionId map[string]StripeTransfer
var vatCorrections map[string]string // from broken number to corrected number (including country prefix)

func main() {
	importVatCorrections()
	importSellwireTransactions()
	importStripeTransferMap()
	outputPaypalTransactions()
	outputStripeTransactions()
}

func importSellwireTransactions() {
	sellwireOrdersFile, err := os.Open("input/SellwireOrders.csv")
	if err != nil {
		log.Fatal(err)
	}
	r := csv.NewReader(sellwireOrdersFile)

	records, err := r.ReadAll()
	if err != nil {
		log.Fatal(err)
	}

	for _, record := range records[1:] {
		status := record[SELLWIRE_TRANSACTION_COLUMN_STATUS]
		if status != "complete" {
			continue
		}
		timestampStr := record[SELLWIRE_TRANSACTION_COLUMN_TIMESTAMP]
		timestamp, err := time.Parse(SELLWIRE_TIMESTAMP_FORMAT, timestampStr)
		if err != nil {
			log.Fatal(err)
		}

		countryCode := record[SELLWIRE_TRANSACTION_COLUMN_CUSTOMER_COUNTRY_CODE]
		if countryCode == "" {
			countryCode = "US"
		}
		taxNumber := strings.ToUpper(record[SELLWIRE_TRANSACTION_COLUMN_CUSTOMER_TAX_NUMBER])

		if taxNumber != "" && !strings.HasPrefix(taxNumber, countryCode) {
			taxNumber = fmt.Sprintf("%s%s", countryCode, taxNumber)
		}

		if correction, ok := vatCorrections[taxNumber]; ok {
			taxNumber = correction
		}

		isEU, _ := strconv.ParseBool(record[SELLWIRE_TRANSACTION_COLUMN_CUSTOMER_IS_EU])

		amount := parseAmount(record[SELLWIRE_TRANSACTION_COLUMN_CUSTOMER_AMOUNT])
		taxAmount := parseAmount(record[SELLWIRE_TRANSACTION_COLUMN_CUSTOMER_TAX])

		transactionId := record[SELLWIRE_TRANSACTION_COLUMN_TRANSACTION_ID]

		transactionType := TransactionTypePaypal
		if strings.HasPrefix(transactionId, "ch_") {
			transactionType = TransactionTypeStripe
		}

		if amount.IsZero() {
			continue
		}
		sellwireRecord := SellwireTransaction{
			TransactionType: transactionType,
			TransactionId: transactionId,
			Timestamp: timestamp,
			CustomerName: strings.Title(strings.ToLower(record[SELLWIRE_TRANSACTION_COLUMN_CUSTOMER_NAME])),
			Amount: amount,
			TaxAmount: taxAmount,
			IsEU: isEU,
			IsPrivate: isEU && taxNumber == "",
			CountryCode: countryCode,
			TaxNumber: taxNumber,
		}

		transactions = append(transactions, sellwireRecord)
	}
}

func parseAmount(amountStr string) Amount {
			var amount Amount
			amountStrStripped := strings.Replace(amountStr, ",", "", -1)
			amountParts := strings.Split(amountStrStripped, ".")
			if len(amountParts) > 2 {
				log.Fatalf("Invalid amount found: %s", amountStr)
			}
			if len(amountParts) > 0 {
				dollars, err := strconv.ParseInt(amountParts[0], 10, 64)
				if err != nil {
					log.Fatal(err)
				}
				amount.Dollars = dollars
				if len(amountParts) > 1 {
					cents, err  := strconv.ParseInt(amountParts[1], 10, 64)
					if cents < 10 { // 9.8 is 9 dollar 80 cents and not 9 dollar 8 cents
						cents *= 10
					}
					if err != nil {
						log.Fatal(err)
					}
					amount.Cents = cents
				}
			}
			return amount
		}

func importStripeTransferMap() {
	stripeTransfersByTransferId := make(map[string]StripeTransfer)

	stripeTransfersFile, err := os.Open("input/transfers.csv")
	if err != nil {
		log.Fatal(err)
	}
	r := csv.NewReader(stripeTransfersFile)

	transferRecords, err := r.ReadAll()
	if err != nil {
		log.Fatal(err)
	}

	for _, record := range transferRecords[1:] {
		status := record[STRIPE_TRANSFER_COLUMN_STATUS]

		dateStr := record[STRIPE_TRANSFER_COLUMN_DATE]
		date, err := time.Parse(STRIPE_TRANSFER_DATE_FORMAT, dateStr)
		if err != nil {
			log.Fatal(err)
		}

		var amount Amount
		amountStr := record[STRIPE_TRANSFER_COLUMND_AMOUNT]
		amountStrStripped := strings.Replace(amountStr, ".", "", -1)
		amountParts := strings.Split(amountStrStripped, ",")
		if len(amountParts) > 2 {
			log.Fatalf("Invalid amount found: %s", amountStr)
		}
		if len(amountParts) > 0 {
			dollars, err := strconv.ParseInt(amountParts[0], 10, 64)
			if err != nil {
				log.Fatal(err)
			}
			amount.Dollars = dollars
			if len(amountParts) > 1 {
				cents, err  := strconv.ParseInt(amountParts[1], 10, 64)
				if err != nil {
					log.Fatal(err)
				}
				amount.Cents = cents
			}
		}

		transferId := record[STRIPE_TRANSFER_COLUMN_TRANSFER_ID]

		transferRecord := StripeTransfer{
			TransferId: transferId,
			Date: date,
			Amount: amount,
			Status: status,
		}

		stripeTransfersByTransferId[transferId] = transferRecord
	}

	stripePaymentsFile, err := os.Open("input/payments.csv")
	if err != nil {
		log.Fatal(err)
	}
	r2 := csv.NewReader(stripePaymentsFile)

	paymentRecords, err := r2.ReadAll()
	if err != nil {
		log.Fatal(err)
	}

	stripeTransfersByTransactionId = make(map[string]StripeTransfer)

	for _, record := range paymentRecords[1:] {
		paymentId := record[STRIPE_PAYMENT_COLUMN_PAYMENT_ID]
		transferId := record[STRIPE_PAYMENT_COLUMN_TRANSFER_ID]
		status := record[STRIPE_PAYMENT_COLUMN_STATUS]
		paymentDate := record[STRIPE_PAYMENT_COLUMN_PAYMENT_DATE]

		if status != "Paid" {
			continue
		}

		transfer, ok := stripeTransfersByTransferId[transferId]
		if !ok {
			log.Printf("transfer id %s no transfer found for payment id %s from date %v", transferId, paymentId, paymentDate)
		}
		stripeTransfersByTransactionId[paymentId] = transfer
	}
}

func importVatCorrections() {
	vatCorrections = make(map[string]string)

	correctionsFile, err := os.Open("input/vat_corrections.csv")
	if err != nil {
		log.Fatal(err)
	}
	r := csv.NewReader(correctionsFile)

	corrections, err := r.ReadAll()
	if err != nil {
		log.Fatal(err)
	}

	for _, record := range corrections {
		vatCorrections[record[0]] = record[1]
	}
}

func outputPaypalTransactions() {
	paypalOutput := [][]string{
		{"Datum", "Kundenname", "Betrag USD", "VAT USD", "VAT", "Land", "EU", "Privat", "USt-ID"},
	}

	for _, tx := range transactions {
		if tx.TransactionType != TransactionTypePaypal {
			continue
		}

		isEU := ""
		if tx.IsEU {
			isEU = "x"
		}

		isPrivate := ""
		if tx.IsPrivate {
			isPrivate = "x"
		}
		if !tx.IsEU {
			isPrivate = "-"
		}

		record := []string{
			tx.Timestamp.Format(PAYPAL_DATE_OUTPUT_FORMAT),
			tx.CustomerName,
			tx.Amount.ToStringGermany(),
			tx.TaxAmount.ToStringGermany(),
			tx.TaxAmount.VATPercentOfAsStringGermany(tx.Amount),
			tx.CountryCode,
			isEU,
			isPrivate,
			tx.TaxNumber,
		}
		paypalOutput = append(paypalOutput, record)
	}

	outputFile, err := os.Create("output/Paypal.csv")
	if err != nil {
		log.Fatal(err)
	}

	w := csv.NewWriter(outputFile)
	w.WriteAll(paypalOutput)

	if err := w.Error(); err != nil {
		log.Fatalln("error writing paypal output csv: %v", err)
	}
}

func outputStripeTransactions() {
	stripeOutput := [][]string{
		{"Datum", "Kundenname", "Betrag USD", "VAT USD", "VAT", "Land", "EU", "Privat", "USt-ID", "Datum Transfer", "Gesamtbetrag Transfer EUR"},
	}

	for _, tx := range transactions {
		if tx.TransactionType != TransactionTypeStripe {
			continue
		}

		isEU := ""
		if tx.IsEU {
			isEU = "x"
		}

		isPrivate := ""
		if tx.IsPrivate {
			isPrivate = "x"
		}
		if !tx.IsEU {
			isPrivate = "-"
		}

		transfer := stripeTransfersByTransactionId[tx.TransactionId]

		record := []string{
			tx.Timestamp.Format(PAYPAL_DATE_OUTPUT_FORMAT),
			tx.CustomerName,
			tx.Amount.ToStringGermany(),
			tx.TaxAmount.ToStringGermany(),
			tx.TaxAmount.VATPercentOfAsStringGermany(tx.Amount),
			tx.CountryCode,
			isEU,
			isPrivate,
			tx.TaxNumber,
			transfer.Date.Format(PAYPAL_DATE_OUTPUT_FORMAT),
			transfer.Amount.ToStringGermany(),
		}
		stripeOutput = append(stripeOutput, record)
	}

	outputFile, err := os.Create("output/Stripe.csv")
	if err != nil {
		log.Fatal(err)
	}

	w := csv.NewWriter(outputFile)
	w.WriteAll(stripeOutput)

	if err := w.Error(); err != nil {
		log.Fatalln("error writing stripe output csv: %v", err)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"net/http"

	"github.com/auth0/go-jwt-middleware"
	"github.com/caarlos0/env"
	"github.com/codegangsta/negroni"
	"github.com/gorilla/mux"

	"github.com/dgrijalva/jwt-go"
	"github.com/nuveo/prest/config"
	"github.com/nuveo/prest/controllers"
)

func main() {
	cfg := config.Prest{}
	env.Parse(&cfg)

	n := negroni.Classic()
	n.Use(negroni.HandlerFunc(handlerSet))
	if cfg.JWTKey != "" {
		n.Use(jwtMiddleware(cfg.JWTKey))
	}
	r := mux.NewRouter()
	r.HandleFunc("/databases", controllers.GetDatabases).Methods("GET")
	r.HandleFunc("/schemas", controllers.GetSchemas).Methods("GET")
	r.HandleFunc("/tables", controllers.GetTables).Methods("GET")
	r.HandleFunc("/{database}/{schema}", controllers.GetTablesByDatabaseAndSchema).Methods("GET")
	r.HandleFunc("/{database}/{schema}/{table}", controllers.SelectFromTables).Methods("GET")
	r.HandleFunc("/{database}/{schema}/{table}", controllers.InsertInTables).Methods("POST")

	n.UseHandler(r)
	n.Run(fmt.Sprintf(":%v", cfg.HTTPPort))
}

func handlerSet(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
	w.Header().Set("Content-Type", "application/json")
	next(w, r)
}

func jwtMiddleware(key string) negroni.Handler {
	jwtMiddleware := jwtmiddleware.New(jwtmiddleware.Options{
		ValidationKeyGetter: func(token *jwt.Token) (interface{}, error) {
			return []byte(key), nil
		},
		SigningMethod: jwt.SigningMethodHS256,
	})
	return negroni.HandlerFunc(jwtMiddleware.HandlerWithNext)
}
<commit_msg>delete route<commit_after>package main

import (
	"fmt"
	"net/http"

	"github.com/auth0/go-jwt-middleware"
	"github.com/caarlos0/env"
	"github.com/codegangsta/negroni"
	"github.com/gorilla/mux"

	"github.com/dgrijalva/jwt-go"
	"github.com/nuveo/prest/config"
	"github.com/nuveo/prest/controllers"
)

func main() {
	cfg := config.Prest{}
	env.Parse(&cfg)

	n := negroni.Classic()
	n.Use(negroni.HandlerFunc(handlerSet))
	if cfg.JWTKey != "" {
		n.Use(jwtMiddleware(cfg.JWTKey))
	}
	r := mux.NewRouter()
	r.HandleFunc("/databases", controllers.GetDatabases).Methods("GET")
	r.HandleFunc("/schemas", controllers.GetSchemas).Methods("GET")
	r.HandleFunc("/tables", controllers.GetTables).Methods("GET")
	r.HandleFunc("/{database}/{schema}", controllers.GetTablesByDatabaseAndSchema).Methods("GET")
	r.HandleFunc("/{database}/{schema}/{table}", controllers.SelectFromTables).Methods("GET")
	r.HandleFunc("/{database}/{schema}/{table}", controllers.InsertInTables).Methods("POST")
	r.HandleFunc("/{database}/{schema}/{table}", controllers.DeleteFromTable).Methods("DELETE")

	n.UseHandler(r)
	n.Run(fmt.Sprintf(":%v", cfg.HTTPPort))
}

func handlerSet(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
	w.Header().Set("Content-Type", "application/json")
	next(w, r)
}

func jwtMiddleware(key string) negroni.Handler {
	jwtMiddleware := jwtmiddleware.New(jwtmiddleware.Options{
		ValidationKeyGetter: func(token *jwt.Token) (interface{}, error) {
			return []byte(key), nil
		},
		SigningMethod: jwt.SigningMethodHS256,
	})
	return negroni.HandlerFunc(jwtMiddleware.HandlerWithNext)
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/xml"
	"flag"
	"fmt"
	"log"
	"net/http"
	"os"
	"strings"

	"github.com/awslabs/aws-sdk-go/aws"
	"github.com/awslabs/aws-sdk-go/gen/route53"
)

var region = "us-east-1"

// awsString turns the pointers back to strings without needless nil checking each time
func awsString(val aws.StringValue) string {
	if val == nil {
		return ""
	}
	return *val
}

// zoneIDByName takes a record name and returns the Route53 zone ID
// TODO: handle paging
func zoneIDByName(r53 *route53.Route53, name string) (string, error) {
	resp, err := r53.ListHostedZones(&route53.ListHostedZonesRequest{})
	if err != nil {
		return "", err
	}
	c := strings.Split(name, ".")
	if len(c) < 3 {
		return "", fmt.Errorf("name must have at least one period")
	}
	name = strings.Join(c[len(c)-3:], ".")
	for _, zone := range resp.HostedZones {
		if *zone.Name == name {
			// zone.ID looks like /hostedzone/Z22CR2RGPPKRQB but we just want the last part
			c := strings.Split(*zone.ID, "/")
			if len(c) != 3 {
				return "", fmt.Errorf("problem splitting id from %s\n", *zone.ID)
			}
			return c[len(c)-1], nil
		}
	}
	return "", fmt.Errorf("zone %s not found", name)
}

// printResourceRecordSet is a pretty printer
func printResourceRecordSet(rrs route53.ResourceRecordSet) {
	enc := xml.NewEncoder(os.Stdout)
	enc.Indent("", "  ")
	enc.Encode(rrs)
	fmt.Println()
}

// addToARecordResourceRecordSet adds one or more IP addresses to the Resource Record Set
func addToARecordResourceRecordSet(r53 *route53.Route53, zoneID string, rrs route53.ResourceRecordSet, ips ...string) error {
	if len(ips) == 0 {
		return fmt.Errorf("at least one IP needs to be passed")
	}
	req := &route53.ChangeResourceRecordSetsRequest{HostedZoneID: aws.String(zoneID)}
	// change := route53.Change{}
	// change.ResourceRecordSet = &rrs
	printResourceRecordSet(rrs)
	for _, ip := range ips {
		rrs.ResourceRecords = append(rrs.ResourceRecords, route53.ResourceRecord{Value: aws.String(ip)})
	}
	change := route53.Change{Action: aws.String("UPSERT"), ResourceRecordSet: &rrs}
	changeBatch := route53.ChangeBatch{Changes: []route53.Change{change}}
	req.ChangeBatch = &changeBatch
	resp, err := r53.ChangeResourceRecordSets(req)
	if err != nil {
		return err
	}
	log.Printf("ChangeResourceRecordSets response=%+v\n", resp)
	return nil
}

// getResourceRecordSet finds an existing resource record set matching the criteria
func getResourceRecordSet(r53 *route53.Route53, zoneID string, recordName string, recordType string, setID string) (route53.ResourceRecordSet, error) {
	zoneID, err := zoneIDByName(r53, recordName)
	if err != nil {
		return route53.ResourceRecordSet{}, err
	}
	req := route53.ListResourceRecordSetsRequest{HostedZoneID: &zoneID}
	req.StartRecordName = aws.String(recordName)
	req.StartRecordType = aws.String(recordType)
	resp, err := r53.ListResourceRecordSets(&req)
	if err != nil {
		return route53.ResourceRecordSet{}, err
	}

	for _, rrs := range resp.ResourceRecordSets {
		if *rrs.Name == recordName && *rrs.SetIdentifier == setID {
			return rrs, nil
		}
	}
	return route53.ResourceRecordSet{}, fmt.Errorf("no ResourceRecordSets found for zoneID=%s recordName=%s recordType=%s setIdentifier=%s\n", zoneID, recordName, recordType, setID)
}

func usage() {
	example := `
	Usage: r53cli [options] ipaddr <ipaddr2 ipaddr3 ...>

	options
	--
	-name="sports.xre.bookmanager.org": record name
	-region="us-east-1": AWS region
	-setid="": record set identifier
	-type="A": record type
	-v=false: verbose

	This tool will update Route53 record sets with additional resources.

	Standard AWS environment variables are used to supply authentication credentials

`
	fmt.Println(example)
	os.Exit(1)
}

func main() {
	recordName := flag.String("name", "record.example.com", "record name")
	recordType := flag.String("type", "A", "record type")
	setID := flag.String("setid", "", "record set identifier")
	region := flag.String("region", "us-east-1", "AWS region")
	verbose := flag.Bool("v", false, "verbose")
	flag.Parse()

	ips := flag.Args()
	if len(ips) == 0 {
		usage()
	}

	auth, err := aws.EnvCreds()
	if err != nil {
		log.Fatal(err)
	}

	r53 := route53.New(auth, *region, http.DefaultClient)

	if !strings.HasSuffix(*recordName, ".") {
		*recordName += "."
	}

	zoneID, err := zoneIDByName(r53, *recordName)
	if err != nil {
		log.Fatal(err)
	}

	if *verbose {
		log.Printf("zoneID for %s is %s", *recordName, zoneID)
	}

	rrs, err := getResourceRecordSet(r53, zoneID, *recordName, *recordType, *setID)
	if err != nil {
		log.Fatal(err)
	}

	if *verbose {
		printResourceRecordSet(rrs)
	}

	err = addToARecordResourceRecordSet(r53, zoneID, rrs, ips...)
	if err != nil {
		log.Fatal(err)
	}
}
<commit_msg>update docs, remove extraneous printing<commit_after>package main

import (
	"encoding/xml"
	"flag"
	"fmt"
	"log"
	"net/http"
	"os"
	"strings"

	"github.com/awslabs/aws-sdk-go/aws"
	"github.com/awslabs/aws-sdk-go/gen/route53"
)

var region = "us-east-1"

// awsString turns the pointers back to strings without needless nil checking each time
func awsString(val aws.StringValue) string {
	if val == nil {
		return ""
	}
	return *val
}

// zoneIDByName takes a record name and returns the Route53 zone ID
// TODO: handle paging
func zoneIDByName(r53 *route53.Route53, name string) (string, error) {
	resp, err := r53.ListHostedZones(&route53.ListHostedZonesRequest{})
	if err != nil {
		return "", err
	}
	c := strings.Split(name, ".")
	if len(c) < 3 {
		return "", fmt.Errorf("name must have at least one period")
	}
	name = strings.Join(c[len(c)-3:], ".")
	for _, zone := range resp.HostedZones {
		if *zone.Name == name {
			// zone.ID looks like /hostedzone/Z22CR2RGPPKRQB but we just want the last part
			c := strings.Split(*zone.ID, "/")
			if len(c) != 3 {
				return "", fmt.Errorf("problem splitting id from %s\n", *zone.ID)
			}
			return c[len(c)-1], nil
		}
	}
	return "", fmt.Errorf("zone %s not found", name)
}

// printResourceRecordSet is a pretty printer
func printResourceRecordSet(rrs route53.ResourceRecordSet) {
	enc := xml.NewEncoder(os.Stdout)
	enc.Indent("", "  ")
	enc.Encode(rrs)
	fmt.Println()
}

// addToARecordResourceRecordSet adds one or more IP addresses to the Resource Record Set
func addToARecordResourceRecordSet(r53 *route53.Route53, zoneID string, rrs route53.ResourceRecordSet, ips ...string) error {
	if len(ips) == 0 {
		return fmt.Errorf("at least one IP needs to be passed")
	}
	req := &route53.ChangeResourceRecordSetsRequest{HostedZoneID: aws.String(zoneID)}
	for _, ip := range ips {
		rrs.ResourceRecords = append(rrs.ResourceRecords, route53.ResourceRecord{Value: aws.String(ip)})
	}
	change := route53.Change{Action: aws.String("UPSERT"), ResourceRecordSet: &rrs}
	changeBatch := route53.ChangeBatch{Changes: []route53.Change{change}}
	req.ChangeBatch = &changeBatch
	resp, err := r53.ChangeResourceRecordSets(req)
	if err != nil {
		return err
	}
	log.Printf("ChangeResourceRecordSets response=%+v\n", resp)
	return nil
}

// getResourceRecordSet finds an existing resource record set matching the criteria
func getResourceRecordSet(r53 *route53.Route53, zoneID string, recordName string, recordType string, setID string) (route53.ResourceRecordSet, error) {
	zoneID, err := zoneIDByName(r53, recordName)
	if err != nil {
		return route53.ResourceRecordSet{}, err
	}
	req := route53.ListResourceRecordSetsRequest{HostedZoneID: &zoneID}
	req.StartRecordName = aws.String(recordName)
	req.StartRecordType = aws.String(recordType)
	resp, err := r53.ListResourceRecordSets(&req)
	if err != nil {
		return route53.ResourceRecordSet{}, err
	}

	for _, rrs := range resp.ResourceRecordSets {
		if *rrs.Name == recordName && *rrs.SetIdentifier == setID {
			return rrs, nil
		}
	}
	return route53.ResourceRecordSet{}, fmt.Errorf("no ResourceRecordSets found for zoneID=%s recordName=%s recordType=%s setIdentifier=%s\n", zoneID, recordName, recordType, setID)
}

func usage() {
	example := `
	Usage: r53cli [options] ipaddr <ipaddr2 ipaddr3 ...>

	options
	--
	-name="record.example.com.": record name
	-region="us-east-1": AWS region
	-setid="": record set identifier
	-type="A": record type
	-v=false: verbose

	This tool will update Route53 record sets with additional resources.

	Standard AWS environment variables are used to supply authentication credentials

`
	fmt.Println(example)
	os.Exit(1)
}

func main() {
	recordName := flag.String("name", "record.example.com", "record name")
	recordType := flag.String("type", "A", "record type")
	setID := flag.String("setid", "", "record set identifier")
	region := flag.String("region", "us-east-1", "AWS region")
	verbose := flag.Bool("v", false, "verbose")
	flag.Parse()

	ips := flag.Args()
	if len(ips) == 0 {
		usage()
	}

	auth, err := aws.EnvCreds()
	if err != nil {
		log.Fatal(err)
	}

	r53 := route53.New(auth, *region, http.DefaultClient)

	if !strings.HasSuffix(*recordName, ".") {
		*recordName += "."
	}

	zoneID, err := zoneIDByName(r53, *recordName)
	if err != nil {
		log.Fatal(err)
	}

	if *verbose {
		log.Printf("zoneID for %s is %s", *recordName, zoneID)
	}

	rrs, err := getResourceRecordSet(r53, zoneID, *recordName, *recordType, *setID)
	if err != nil {
		log.Fatal(err)
	}

	if *verbose {
		printResourceRecordSet(rrs)
	}

	err = addToARecordResourceRecordSet(r53, zoneID, rrs, ips...)
	if err != nil {
		log.Fatal(err)
	}
}
<|endoftext|>


<commit_before>//go:generate goversioninfo -icon=icon.ico

package main

import (
	"fmt"
	"net/url"
	"os"
	"strconv"
	"strings"

	"github.com/fatih/color"
	"github.com/sdwolfe32/ANIRip/anirip"
	"github.com/sdwolfe32/ANIRip/crunchyroll"
	"github.com/sdwolfe32/ANIRip/daisuki"
)

const (
	daisukiIntroLength = 5040
	aniplexIntroLength = 6747
	sunriseIntroLength = 8227
	engineDir          = "engine"   // Where our clis are required to be
	cookieDir          = "cookies"  // Where we will store the cookies
	tempDir            = "temp"     // Where our temporary video/audio/subtitle streams will be held
	outputDir          = "finished" // Where our finalized show directories will be held
)

func main() {
	// Asks the user for their desired show URLs delimited by spaces
	showURLs := ""
	getStandardUserInput("Enter a list of Show URLs (delimited by spaces) : ", &showURLs)
	showURLArray := strings.Split(showURLs, " ")

	// Iterates over every showURL found in the array
	for _, showURL := range showURLArray {
		// Parses the URL so we can accurately judge the provider based on the host
		url, err := url.Parse(showURL)
		if err != nil {
			color.Red("There was an error parsing the URL you entered.\n")
			main()
		}

		// Creates the authentication & show objects for the provider we're ripping from
		var session anirip.Session
		var show anirip.Show
		if strings.Contains(strings.ToLower(url.Host), "crunchyroll") {
			show = new(crunchyroll.CrunchyrollShow)
			session = new(crunchyroll.CrunchyrollSession)
		} else if strings.Contains(strings.ToLower(url.Host), "daisuki") {
			show = new(daisuki.DaisukiShow)
			session = new(daisuki.DaisukiSession)
		}

		// Performs the generic login procedure
		username := ""
		password := ""
		login(session, username, password, cookieDir)

		// Attempts to scrape the shows metadata/information
		if err := show.ScrapeEpisodes(showURL, session.GetCookies()); err != nil {
			color.Red(err.Error() + "\n\n")
			pause()
			return
		}

		// Asks if the user wants to trim any intro clips off of the final file IF provider is daisuki
		daisukiIntroTrim := "n"
		aniplexIntroTrim := "n"
		sunriseIntroTrim := "n"
		if strings.Contains(strings.ToLower(showURL), "daisuki") {
			getStandardUserInput("Do you want to trim a Daisuki intro? [Y/N] : ", &daisukiIntroTrim)
			getStandardUserInput("Do you want to trim an Aniplex intro? [Y/N] : ", &aniplexIntroTrim)
			getStandardUserInput("Do you want to trim a Sunrise intro? [Y/N] : ", &sunriseIntroTrim)
			fmt.Printf("\n")
		}

		// Sets up an array that we will use for season directory naming
		seasonNameArray := []string{
			"Specials",   // Season num 0
			"Season One", // Season num 1...
			"Season Two",
			"Season Three",
			"Season Four",
			"Season Five",
			"Season Six",
			"Season Seven",
			"Season Eight",
			"Season Nine",
			"Season Ten",
		}

		// Creates a folder in the temporary directory that will store the seasons
		os.Mkdir(tempDir+"\\"+show.GetTitle(), 0777)
		for _, season := range show.GetSeasons() {
			// Creates a folder in the show directory that will store the episodes
			os.Mkdir(tempDir+"\\"+show.GetTitle()+"\\"+seasonNameArray[season.GetNumber()], 0777)
			for _, episode := range season.GetEpisodes() {
				color.Cyan("> Getting Episode Info...\n")
				if err := episode.GetEpisodeInfo("1080p", session.GetCookies()); err != nil {
					color.Red(err.Error() + "\n\n")
					pause()
					continue
				}

				// Checks to see if the episode already exists, in which case we continue to the next
				_, err = os.Stat(tempDir + "\\" + show.GetTitle() + "\\" + seasonNameArray[season.GetNumber()] + "\\" + episode.GetFileName() + ".mkv")
				if err == nil {
					color.Cyan(episode.GetFileName() + ".mkv has already been downloaded successfully..." + "\n\n")
					continue
				}

				subOffset := 0
				color.Cyan("> Downloading " + episode.GetFileName() + "\n\n")
				// Downloads full MKV video from stream provider
				color.Green("> Downloading video...\n")
				if err := episode.DownloadEpisode("1080p", engineDir, tempDir, session.GetCookies()); err != nil {
					color.Red(err.Error() + "\n\n")
					pause()
					continue
				}

				// Trims down the downloaded MKV if the user wants to trim a Daisuki intro
				if strings.ToLower(daisukiIntroTrim) == "y" {
					subOffset = subOffset + daisukiIntroLength
					color.Green("> Trimming off Daisuki Intro - " + strconv.Itoa(daisukiIntroLength) + "ms\n")
					if err := trimMKV(daisukiIntroLength, engineDir, tempDir); err != nil {
						color.Red(err.Error() + "\n\n")
						pause()
						continue
					}
				}

				// Trims down the downloaded MKV if the user wants to trim an Aniplex intro
				if strings.ToLower(aniplexIntroTrim) == "y" {
					subOffset = subOffset + aniplexIntroLength
					color.Green("> Trimming off Aniplex Intro - " + strconv.Itoa(aniplexIntroLength) + "ms\n")
					if err := trimMKV(aniplexIntroLength, engineDir, tempDir); err != nil {
						color.Red(err.Error() + "\n\n")
						pause()
						continue
					}
				}

				// Trims down the downloaded MKV if the user wants to trim a Sunrise intro
				if strings.ToLower(sunriseIntroTrim) == "y" {
					subOffset = subOffset + sunriseIntroLength
					color.Green("> Trimming off Sunrise Intro - " + strconv.Itoa(sunriseIntroLength) + "ms\n")
					if err := trimMKV(sunriseIntroLength, engineDir, tempDir); err != nil {
						color.Red(err.Error() + "\n\n")
						pause()
						continue
					}
				}

				// Downloads the subtitles to .ass format and
				// offsets their times by the passed provided interval
				color.Green("> Downloading subtitles with a total offset of " + strconv.Itoa(subOffset) + "ms...\n")
				subtitleLang, err := episode.DownloadSubtitles("English", subOffset, tempDir, session.GetCookies())
				if err != nil {
					color.Red(err.Error() + "\n\n")
					pause()
					continue
				}

				// Attempts to merge the downloaded subtitles into the video strea
				color.Green("> Merging subtitles into mkv container...\n")
				if err := mergeSubtitles("jpn", subtitleLang, engineDir, tempDir); err != nil {
					color.Red(err.Error() + "\n\n")
					pause()
					continue
				}

				// Cleans and optimizes the final MKV
				color.Green("> Cleaning and optimizing mkv...\n")
				if err := cleanMKV(engineDir, tempDir); err != nil {
					color.Red(err.Error() + "\n\n")
					pause()
					continue
				}

				// Moves the episode to the appropriate directory
				if err := anirip.Rename(tempDir+"\\episode.mkv",
					tempDir+"\\"+show.GetTitle()+"\\"+seasonNameArray[season.GetNumber()]+"\\"+episode.GetFileName()+".mkv", 10); err != nil {
					color.Red(err.Error() + "\n\n")
					pause()
				}
				color.Green("> Downloading and merging completed successfully.\n\n")
			}
		}
		if err := anirip.Rename(tempDir+"\\"+show.GetTitle(), outputDir+"\\"+show.GetTitle(), 10); err != nil {
			color.Red(err.Error() + "\n\n")
			pause()
		}
		color.Cyan("> Completed processing episodes for " + show.GetTitle() + "\n\n")
	}
	pause()
}

func init() {
	// Intro header
	color.Green("-------------------------------------------------------------\n")
	color.Green("------------------- ANIRip v1.2.0 by Viz_ -------------------\n")
	color.Green("-------------------------------------------------------------\n\n")
	color.Green("- Currently supports Crunchyroll & Daisuki show URLs\n\n")

	// Checks for the existance of our required output folders and creates them if they don't
	_, err := os.Stat(outputDir)
	if err != nil {
		os.Mkdir(outputDir, 0777)
	}

	_, err = os.Stat(tempDir)
	if err != nil {
		os.Mkdir(tempDir, 0777)
	}

	_, err = os.Stat(cookieDir)
	if err != nil {
		os.Mkdir(cookieDir, 0777)
	}
}

func login(session anirip.Session, username, password, cookieDir string) {
	fmt.Printf("\n")
	if err := session.Login(username, password, cookieDir); err != nil {
		color.Green("Please login to the above provider.")
		getStandardUserInput("Username : ", &username)
		getStandardUserInput("Password : ", &password)
		login(session, username, password, cookieDir)
	}
	return
}
<commit_msg>Cleaning directory names for show titles that have invalid characters<commit_after>//go:generate goversioninfo -icon=icon.ico

package main

import (
	"fmt"
	"net/url"
	"os"
	"strconv"
	"strings"

	"github.com/fatih/color"
	"github.com/sdwolfe32/ANIRip/anirip"
	"github.com/sdwolfe32/ANIRip/crunchyroll"
	"github.com/sdwolfe32/ANIRip/daisuki"
)

const (
	daisukiIntroLength = 5040
	aniplexIntroLength = 6747
	sunriseIntroLength = 8227
	engineDir          = "engine"   // Where our clis are required to be
	cookieDir          = "cookies"  // Where we will store the cookies
	tempDir            = "temp"     // Where our temporary video/audio/subtitle streams will be held
	outputDir          = "finished" // Where our finalized show directories will be held
)

func main() {
	// Asks the user for their desired show URLs delimited by spaces
	showURLs := ""
	getStandardUserInput("Enter a list of Show URLs (delimited by spaces) : ", &showURLs)
	showURLArray := strings.Split(showURLs, " ")

	// Iterates over every showURL found in the array
	for _, showURL := range showURLArray {
		// Parses the URL so we can accurately judge the provider based on the host
		url, err := url.Parse(showURL)
		if err != nil {
			color.Red("There was an error parsing the URL you entered.\n")
			main()
		}

		// Creates the authentication & show objects for the provider we're ripping from
		var session anirip.Session
		var show anirip.Show
		if strings.Contains(strings.ToLower(url.Host), "crunchyroll") {
			show = new(crunchyroll.CrunchyrollShow)
			session = new(crunchyroll.CrunchyrollSession)
		} else if strings.Contains(strings.ToLower(url.Host), "daisuki") {
			show = new(daisuki.DaisukiShow)
			session = new(daisuki.DaisukiSession)
		}

		// Performs the generic login procedure
		username := ""
		password := ""
		login(session, username, password, cookieDir)

		// Attempts to scrape the shows metadata/information
		if err := show.ScrapeEpisodes(showURL, session.GetCookies()); err != nil {
			color.Red(err.Error() + "\n\n")
			pause()
			return
		}

		// Asks if the user wants to trim any intro clips off of the final file IF provider is daisuki
		daisukiIntroTrim := "n"
		aniplexIntroTrim := "n"
		sunriseIntroTrim := "n"
		if strings.Contains(strings.ToLower(showURL), "daisuki") {
			getStandardUserInput("Do you want to trim a Daisuki intro? [Y/N] : ", &daisukiIntroTrim)
			getStandardUserInput("Do you want to trim an Aniplex intro? [Y/N] : ", &aniplexIntroTrim)
			getStandardUserInput("Do you want to trim a Sunrise intro? [Y/N] : ", &sunriseIntroTrim)
			fmt.Printf("\n")
		}

		// Sets up an array that we will use for season directory naming
		seasonNameArray := []string{
			"Specials",   // Season num 0
			"Season One", // Season num 1...
			"Season Two",
			"Season Three",
			"Season Four",
			"Season Five",
			"Season Six",
			"Season Seven",
			"Season Eight",
			"Season Nine",
			"Season Ten",
		}

		// Creates a folder in the temporary directory that will store the seasons
		os.Mkdir(tempDir+"\\"+anirip.CleanFileName(show.GetTitle()), 0777)
		for _, season := range show.GetSeasons() {
			// Creates a folder in the show directory that will store the episodes
			os.Mkdir(tempDir+"\\"+anirip.CleanFileName(show.GetTitle())+"\\"+seasonNameArray[season.GetNumber()], 0777)
			for _, episode := range season.GetEpisodes() {
				color.Cyan("> Getting Episode Info...\n")
				if err := episode.GetEpisodeInfo("1080p", session.GetCookies()); err != nil {
					color.Red(err.Error() + "\n\n")
					pause()
					continue
				}

				// Checks to see if the episode already exists, in which case we continue to the next
				_, err = os.Stat(tempDir + "\\" + anirip.CleanFileName(show.GetTitle()) + "\\" + seasonNameArray[season.GetNumber()] + "\\" + episode.GetFileName() + ".mkv")
				if err == nil {
					color.Cyan(episode.GetFileName() + ".mkv has already been downloaded successfully..." + "\n\n")
					continue
				}

				subOffset := 0
				color.Cyan("> Downloading " + episode.GetFileName() + "\n\n")
				// Downloads full MKV video from stream provider
				color.Green("> Downloading video...\n")
				if err := episode.DownloadEpisode("1080p", engineDir, tempDir, session.GetCookies()); err != nil {
					color.Red(err.Error() + "\n\n")
					pause()
					continue
				}

				// Trims down the downloaded MKV if the user wants to trim a Daisuki intro
				if strings.ToLower(daisukiIntroTrim) == "y" {
					subOffset = subOffset + daisukiIntroLength
					color.Green("> Trimming off Daisuki Intro - " + strconv.Itoa(daisukiIntroLength) + "ms\n")
					if err := trimMKV(daisukiIntroLength, engineDir, tempDir); err != nil {
						color.Red(err.Error() + "\n\n")
						pause()
						continue
					}
				}

				// Trims down the downloaded MKV if the user wants to trim an Aniplex intro
				if strings.ToLower(aniplexIntroTrim) == "y" {
					subOffset = subOffset + aniplexIntroLength
					color.Green("> Trimming off Aniplex Intro - " + strconv.Itoa(aniplexIntroLength) + "ms\n")
					if err := trimMKV(aniplexIntroLength, engineDir, tempDir); err != nil {
						color.Red(err.Error() + "\n\n")
						pause()
						continue
					}
				}

				// Trims down the downloaded MKV if the user wants to trim a Sunrise intro
				if strings.ToLower(sunriseIntroTrim) == "y" {
					subOffset = subOffset + sunriseIntroLength
					color.Green("> Trimming off Sunrise Intro - " + strconv.Itoa(sunriseIntroLength) + "ms\n")
					if err := trimMKV(sunriseIntroLength, engineDir, tempDir); err != nil {
						color.Red(err.Error() + "\n\n")
						pause()
						continue
					}
				}

				// Downloads the subtitles to .ass format and
				// offsets their times by the passed provided interval
				color.Green("> Downloading subtitles with a total offset of " + strconv.Itoa(subOffset) + "ms...\n")
				subtitleLang, err := episode.DownloadSubtitles("English", subOffset, tempDir, session.GetCookies())
				if err != nil {
					color.Red(err.Error() + "\n\n")
					pause()
					continue
				}

				// Attempts to merge the downloaded subtitles into the video strea
				color.Green("> Merging subtitles into mkv container...\n")
				if err := mergeSubtitles("jpn", subtitleLang, engineDir, tempDir); err != nil {
					color.Red(err.Error() + "\n\n")
					pause()
					continue
				}

				// Cleans and optimizes the final MKV
				color.Green("> Cleaning and optimizing mkv...\n")
				if err := cleanMKV(engineDir, tempDir); err != nil {
					color.Red(err.Error() + "\n\n")
					pause()
					continue
				}

				// Moves the episode to the appropriate directory
				if err := anirip.Rename(tempDir+"\\episode.mkv",
					tempDir+"\\"+anirip.CleanFileName(show.GetTitle())+"\\"+seasonNameArray[season.GetNumber()]+"\\"+episode.GetFileName()+".mkv", 10); err != nil {
					color.Red(err.Error() + "\n\n")
					pause()
				}
				color.Green("> Downloading and merging completed successfully.\n\n")
			}
		}
		if err := anirip.Rename(tempDir+"\\"+anirip.CleanFileName(show.GetTitle()), outputDir+"\\"+anirip.CleanFileName(show.GetTitle()), 10); err != nil {
			color.Red(err.Error() + "\n\n")
			pause()
		}
		color.Cyan("> Completed processing episodes for " + show.GetTitle() + "\n\n")
	}
	pause()
}

func init() {
	// Intro header
	color.Green("-------------------------------------------------------------\n")
	color.Green("------------------- ANIRip v1.2.0 by Viz_ -------------------\n")
	color.Green("-------------------------------------------------------------\n\n")
	color.Green("- Currently supports Crunchyroll & Daisuki show URLs\n\n")

	// Checks for the existance of our required output folders and creates them if they don't
	_, err := os.Stat(outputDir)
	if err != nil {
		os.Mkdir(outputDir, 0777)
	}

	_, err = os.Stat(tempDir)
	if err != nil {
		os.Mkdir(tempDir, 0777)
	}

	_, err = os.Stat(cookieDir)
	if err != nil {
		os.Mkdir(cookieDir, 0777)
	}
}

func login(session anirip.Session, username, password, cookieDir string) {
	fmt.Printf("\n")
	if err := session.Login(username, password, cookieDir); err != nil {
		color.Green("Please login to the above provider.")
		getStandardUserInput("Username : ", &username)
		getStandardUserInput("Password : ", &password)
		login(session, username, password, cookieDir)
	}
	return
}
<|endoftext|>


<commit_before>package main

//import "github.com/andrewarrow/simplecoin.life/client"
import "github.com/andrewarrow/simplecoin.life/crypto"

import "fmt"

func main() {
	//client.Setup()
	//n, e, d := crypto.GenKeys()
	//n := uint64(828719)
	//e := uint64(412211)
	//d := uint64(252347)
	/*
		n0 := uint64(91)
		e0 := uint64(5)
		d0 := uint64(29)
	*/
	n1 := uint64(44817313)
	e1 := uint64(22946869)
	d1 := uint64(34788097)

	n2 := uint64(828719)
	e2 := uint64(412211)
	//d2 := uint64(252347)

	/*
		n3 := uint64(828719)
		e3 := uint64(412211)
		d3 := uint64(252347)
	*/

	fmt.Printf("Public Account Number: %d-%d\n", n1, e1)
	fmt.Printf("Private Account Number: %d-%d\n", n1, d1)

	// CryptoJS.SHA256(index + previousHash + timestamp + data).toString();
	//t0 := crypto.NewTransaction(0)

	t1 := crypto.NewTransaction(1)
	t1.Hash = fmt.Sprintf("%d-%d", n1, e1)
	t1.OwnersPublicAccount = []uint64{n1, e1}
	t1.PreviousOwnersSignature = []uint64{} // 0's sig of hash
	fmt.Println(t1.Encode())

	t2 := crypto.NewTransaction(2)
	t2.Hash = t1.Hash + fmt.Sprintf(",%d-%d", n2, e2)
	t2.OwnersPublicAccount = []uint64{n2, e2}
	t2.PreviousOwnersSignature = crypto.EncodeString(t2.Hash, n1, e1) // 1's sig of hash
	fmt.Println(t2.Encode())

	/*
		list := crypto.EncodeString("AHELLO", n, e)
		fmt.Println(list)
		for _, c := range list {
			p := crypto.Decode(c, n, d)
			fmt.Println(p)
		}
	*/
}
<commit_msg>block<commit_after>package main

//import "github.com/andrewarrow/simplecoin.life/client"
import "github.com/andrewarrow/simplecoin.life/crypto"

import "fmt"

func main() {
	//client.Setup()
	//n, e, d := crypto.GenKeys()
	//n := uint64(828719)
	//e := uint64(412211)
	//d := uint64(252347)
	/*
		n0 := uint64(91)
		e0 := uint64(5)
		d0 := uint64(29)
	*/
	n1 := uint64(44817313)
	e1 := uint64(22946869)
	d1 := uint64(34788097)

	n2 := uint64(828719)
	e2 := uint64(412211)
	//d2 := uint64(252347)

	n3 := uint64(828719)
	e3 := uint64(412211)
	//d3 := uint64(252347)

	fmt.Printf("Public Account Number: %d-%d\n", n1, e1)
	fmt.Printf("Private Account Number: %d-%d\n", n1, d1)

	// CryptoJS.SHA256(index + previousHash + timestamp + data).toString();
	//t0 := crypto.NewTransaction(0)

	t1 := crypto.NewTransaction(1)
	t1.Hash = fmt.Sprintf("%d-%d", n1, e1)
	t1.OwnersPublicAccount = []uint64{n1, e1}
	t1.PreviousOwnersSignature = []uint64{} // 0's sig of hash
	fmt.Println(t1.Encode())

	t2 := crypto.NewTransaction(2)
	t2.Hash = t1.Hash + fmt.Sprintf(",%d-%d", n2, e2)
	t2.OwnersPublicAccount = []uint64{n2, e2}
	t2.PreviousOwnersSignature = crypto.EncodeString(t2.Hash, n1, e1) // 1's sig of hash
	fmt.Println(t2.Encode())

	t3 := crypto.NewTransaction(3)
	t3.Hash = t2.Hash + fmt.Sprintf(",%d-%d", n3, e3)
	t3.OwnersPublicAccount = []uint64{n3, e3}
	t3.PreviousOwnersSignature = crypto.EncodeString(t3.Hash, n2, e2) // 1's sig of hash
	fmt.Println(t2.Encode())

	/*
		list := crypto.EncodeString("AHELLO", n, e)
		fmt.Println(list)
		for _, c := range list {
			p := crypto.Decode(c, n, d)
			fmt.Println(p)
		}
	*/
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"path"
	"strings"
	"sync"
)

const (
	idstring = "http://golang.org/pkg/http/#ListenAndServe"
	cacheSize = 2
	imgSizeLimit = 10 << 20
)

var (
	help  = flag.Bool("h", false, "show this help.")
	host  = flag.String("host", "localhost:8080", "listening port and hostname.")
	prefix = flag.String("prefix", "/", "URL prefix for which the server runs (as in http://foo:8080/prefix).")
)

func usage() {
	fmt.Fprintf(os.Stderr, "imgurproxy \n")
	flag.PrintDefaults()
	os.Exit(2)
}

var (
	mu sync.RWMutex
	cache map[string][]byte = make(map[string][]byte)
)

func main() {
	flag.Usage = usage
	flag.Parse()
	if *help {
		usage()
	}

	nargs := flag.NArg()
	if nargs > 0 {
		usage()
	}

	*prefix = path.Join("/" + *prefix)
	if *prefix != "/" {
		*prefix = *prefix + "/"
	}
	http.HandleFunc(*prefix, imgurHandler)
	if err := http.ListenAndServe(*host, nil); err != nil {
		log.Fatalf("Could not start http server: %v", err)
	}
}

const noImgHTML = `
<!DOCTYPE HTML>

	<title>imgurproxy</title>

Append an imgur image URL path to the URL.

`

func trimCache() {
	mu.Lock()
	defer mu.Unlock()
	size := len(cache)
	// TODO(mpl): this is a bit dumb since we could very well remove the
	// one(s) that were cached the latest, but oh well.
	for k,_ := range cache {
		if size <= cacheSize {
			break
		}
		delete(cache, k)
		size--
	}
}

func imgurHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != "GET" {
		http.Error(w, "Want GET", http.StatusMethodNotAllowed)
		return
	}
	if !strings.HasPrefix(r.URL.Path, *prefix) {
		http.Error(w, "NOPE", http.StatusNotFound)
		return
	}
	imgName := strings.TrimPrefix(r.URL.Path, *prefix)
	if imgName == "" {
		w.Write([]byte(noImgHTML))
		return
	}
	mu.Lock()
	defer mu.Unlock()
	b, ok := cache[imgName]
	if ok {
		w.Write(b)
		return
	}
	resp, err := http.Get("https://i.imgur.com/" + imgName)
	if err != nil {
		http.Error(w, "error fetching image", http.StatusInternalServerError)
		log.Printf("error fetching image %v: %v", imgName, err)
		return
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(io.LimitReader(resp.Body, imgSizeLimit))
	if err != nil {
		http.Error(w, "error fetching image", http.StatusInternalServerError)
		log.Printf("error fetching image %v: %v", imgName, err)
		return
	}
	if !strings.HasPrefix(http.DetectContentType(body), "image") {
		http.Error(w, "not an image", http.StatusNotFound)
		return
	}
	log.Printf("fetched %v\n", imgName)
	cache[imgName] = body
	w.Write(body)
	go func() {
		trimCache()
	}()
}
<commit_msg>wip fetching gallery<commit_after>package main

import (
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"path"
	"strings"
	"sync"
)

const (
	idstring = "http://golang.org/pkg/http/#ListenAndServe"
	cacheSize = 2
	imgSizeLimit = 10 << 20
)

var (
	help  = flag.Bool("h", false, "show this help.")
	host  = flag.String("host", "localhost:8080", "listening port and hostname.")
	prefix = flag.String("prefix", "/", "URL prefix for which the server runs (as in http://foo:8080/prefix).")
)

func usage() {
	fmt.Fprintf(os.Stderr, "imgurproxy \n")
	flag.PrintDefaults()
	os.Exit(2)
}

var (
	mu sync.RWMutex
	cache map[string][]byte = make(map[string][]byte)
)

func main() {
	flag.Usage = usage
	flag.Parse()
	if *help {
		usage()
	}

	nargs := flag.NArg()
	if nargs > 0 {
		usage()
	}

	*prefix = path.Join("/" + *prefix)
	if *prefix != "/" {
		*prefix = *prefix + "/"
	}

	http.HandleFunc(*prefix+"gallery/", galleryHandler)
	http.HandleFunc(*prefix, imgHandler)
	if err := http.ListenAndServe(*host, nil); err != nil {
		log.Fatalf("Could not start http server: %v", err)
	}
}

const noImgHTML = `
<!DOCTYPE HTML>

	<title>imgurproxy</title>

Append an imgur image or gallery URL path to the URL.

`

func trimCache() {
	mu.Lock()
	defer mu.Unlock()
	size := len(cache)
	// TODO(mpl): this is a bit dumb since we could very well remove the
	// one(s) that were cached the latest, but oh well.
	for k,_ := range cache {
		if size <= cacheSize {
			break
		}
		delete(cache, k)
		size--
	}
}

func imgHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != "GET" {
		http.Error(w, "Want GET", http.StatusMethodNotAllowed)
		return
	}
	if !strings.HasPrefix(r.URL.Path, *prefix) {
		http.Error(w, "NOPE", http.StatusNotFound)
		return
	}
	imgName := strings.TrimPrefix(r.URL.Path, *prefix)
	if imgName == "" {
		w.Write([]byte(noImgHTML))
		return
	}
	mu.Lock()
	defer mu.Unlock()
	b, ok := cache[imgName]
	if ok {
		w.Write(b)
		return
	}
	resp, err := http.Get("https://i.imgur.com/" + imgName)
	if err != nil {
		http.Error(w, "error fetching image", http.StatusInternalServerError)
		log.Printf("error fetching image %v: %v", imgName, err)
		return
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(io.LimitReader(resp.Body, imgSizeLimit))
	if err != nil {
		http.Error(w, "error fetching image", http.StatusInternalServerError)
		log.Printf("error fetching image %v: %v", imgName, err)
		return
	}
	if !strings.HasPrefix(http.DetectContentType(body), "image") {
		http.Error(w, "not an image", http.StatusNotFound)
		return
	}
	log.Printf("fetched %v\n", imgName)
	cache[imgName] = body
	w.Write(body)
	go func() {
		trimCache()
	}()
}

// TODO(mpl): add url parameter to control the number of images we slurp

func galleryHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != "GET" {
		http.Error(w, "Want GET", http.StatusMethodNotAllowed)
		return
	}
	if !strings.HasPrefix(r.URL.Path, *prefix) {
		http.Error(w, "NOPE", http.StatusNotFound)
		return
	}
	galleryName := strings.TrimPrefix(r.URL.Path, *prefix+"gallery")
	if galleryName == "" {
		w.Write([]byte(noImgHTML))
		return
	}

	resp, err := http.Get("https://imgur.com/gallery/" + galleryName)
	if err != nil {
		http.Error(w, "error fetching gallery", http.StatusInternalServerError)
		log.Printf("error fetching gallery %v: %v", galleryName, err)
		return
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		http.Error(w, "error reading gallery", http.StatusInternalServerError)
		log.Printf("error reading gallery %v: %v", galleryName, err)
		return
	}
	log.Printf("fetched %v\n", galleryName)

	sc := bufio.NewScanner(bytes.NewReader(body))
	for sc.Scan() {
		// TODO(mpl): get each i.imgur.com link, slurp the image (if not in cache)
		// serve a page with all the images.
	}
	if err := scanner.Err(); err != nil {
	}
}
<|endoftext|>


<commit_before>package main

import (
	"errors"
	"fmt"

	"github.com/codegangsta/envy/lib"
	"github.com/codegangsta/gin/lib"
	shellwords "github.com/mattn/go-shellwords"
	"gopkg.in/urfave/cli.v1"

	"github.com/0xAX/notificator"
	"log"
	"os"
	"os/signal"
	"path/filepath"
	"strconv"
	"strings"
	"syscall"
	"time"
)

var (
	startTime     = time.Now()
	logger        = log.New(os.Stdout, "[gin] ", 0)
	immediate     = false
	buildError    error
	colorGreen    = string([]byte{27, 91, 57, 55, 59, 51, 50, 59, 49, 109})
	colorRed      = string([]byte{27, 91, 57, 55, 59, 51, 49, 59, 49, 109})
	colorReset    = string([]byte{27, 91, 48, 109})
	notifier      = notificator.New(notificator.Options{AppName: "Gin Build"})
	notifications = false
)

func main() {
	app := cli.NewApp()
	app.Name = "gin"
	app.Usage = "A live reload utility for Go web applications."
	app.Action = MainAction
	app.Flags = []cli.Flag{
		cli.StringFlag{
			Name:   "laddr,l",
			Value:  "",
			EnvVar: "GIN_LADDR",
			Usage:  "listening address for the proxy server",
		},
		cli.IntFlag{
			Name:   "port,p",
			Value:  3000,
			EnvVar: "GIN_PORT",
			Usage:  "port for the proxy server",
		},
		cli.IntFlag{
			Name:   "appPort,a",
			Value:  3001,
			EnvVar: "BIN_APP_PORT",
			Usage:  "port for the Go web server",
		},
		cli.StringFlag{
			Name:   "bin,b",
			Value:  "gin-bin",
			EnvVar: "GIN_BIN",
			Usage:  "name of generated binary file",
		},
		cli.StringFlag{
			Name:   "path,t",
			Value:  ".",
			EnvVar: "GIN_PATH",
			Usage:  "Path to watch files from",
		},
		cli.StringFlag{
			Name:   "build,d",
			Value:  "",
			EnvVar: "GIN_BUILD",
			Usage:  "Path to build files from (defaults to same value as --path)",
		},
		cli.StringSliceFlag{
			Name:   "excludeDir,x",
			Value:  &cli.StringSlice{},
			EnvVar: "GIN_EXCLUDE_DIR",
			Usage:  "Relative directories to exclude",
		},
		cli.BoolFlag{
			Name:   "immediate,i",
			EnvVar: "GIN_IMMEDIATE",
			Usage:  "run the server immediately after it's built",
		},
		cli.BoolFlag{
			Name:   "all",
			EnvVar: "GIN_ALL",
			Usage:  "reloads whenever any file changes, as opposed to reloading only on .go file change",
		},
		cli.BoolFlag{
			Name:   "godep,g",
			EnvVar: "GIN_GODEP",
			Usage:  "use godep when building",
		},
		cli.StringFlag{
			Name:   "buildArgs",
			EnvVar: "GIN_BUILD_ARGS",
			Usage:  "Additional go build arguments",
		},
		cli.StringFlag{
			Name:   "certFile",
			EnvVar: "GIN_CERT_FILE",
			Usage:  "TLS Certificate",
		},
		cli.StringFlag{
			Name:   "keyFile",
			EnvVar: "GIN_KEY_FILE",
			Usage:  "TLS Certificate Key",
		},
		cli.StringFlag{
			Name:   "logPrefix",
			EnvVar: "GIN_LOG_PREFIX",
			Usage:  "Log prefix",
			Value:  "gin",
		},
		cli.BoolFlag{
			Name:   "notifications",
			EnvVar: "GIN_NOTIFICATIONS",
			Usage:  "Enables desktop notifications",
		},
	}
	app.Commands = []cli.Command{
		{
			Name:      "run",
			ShortName: "r",
			Usage:     "Run the gin proxy in the current working directory",
			Action:    MainAction,
		},
		{
			Name:      "env",
			ShortName: "e",
			Usage:     "Display environment variables set by the .env file",
			Action:    EnvAction,
		},
	}

	app.Run(os.Args)
}

func MainAction(c *cli.Context) {
	laddr := c.GlobalString("laddr")
	port := c.GlobalInt("port")
	all := c.GlobalBool("all")
	appPort := strconv.Itoa(c.GlobalInt("appPort"))
	immediate = c.GlobalBool("immediate")
	keyFile := c.GlobalString("keyFile")
	certFile := c.GlobalString("certFile")
	logPrefix := c.GlobalString("logPrefix")
	notifications = c.GlobalBool("notifications")

	logger.SetPrefix(fmt.Sprintf("[%s] ", logPrefix))

	// Bootstrap the environment
	envy.Bootstrap()

	// Set the PORT env
	os.Setenv("PORT", appPort)

	wd, err := os.Getwd()
	if err != nil {
		logger.Fatal(err)
	}

	buildArgs, err := shellwords.Parse(c.GlobalString("buildArgs"))
	if err != nil {
		logger.Fatal(err)
	}

	buildPath := c.GlobalString("build")
	if buildPath == "" {
		buildPath = c.GlobalString("path")
	}
	builder := gin.NewBuilder(buildPath, c.GlobalString("bin"), c.GlobalBool("godep"), wd, buildArgs)
	runner := gin.NewRunner(filepath.Join(wd, builder.Binary()), c.Args()...)
	runner.SetWriter(os.Stdout)
	proxy := gin.NewProxy(builder, runner)

	config := &gin.Config{
		Laddr:    laddr,
		Port:     port,
		ProxyTo:  "http://localhost:" + appPort,
		KeyFile:  keyFile,
		CertFile: certFile,
	}

	err = proxy.Run(config)
	if err != nil {
		logger.Fatal(err)
	}

	if laddr != "" {
		logger.Printf("Listening at %s:%d\n", laddr, port)
	} else {
		logger.Printf("Listening on port %d\n", port)
	}

	shutdown(runner)

	// build right now
	build(builder, runner, logger)

	// scan for changes
	scanChanges(c.GlobalString("path"), c.GlobalStringSlice("excludeDir"), all, func(path string) {
		runner.Kill()
		build(builder, runner, logger)
	})
}

func EnvAction(c *cli.Context) {
	logPrefix := c.GlobalString("logPrefix")
	logger.SetPrefix(fmt.Sprintf("[%s] ", logPrefix))

	// Bootstrap the environment
	env, err := envy.Bootstrap()
	if err != nil {
		logger.Fatalln(err)
	}

	for k, v := range env {
		fmt.Printf("%s: %s\n", k, v)
	}

}

func build(builder gin.Builder, runner gin.Runner, logger *log.Logger) {
	logger.Println("Building...")

	if notifications {
		notifier.Push("Build Started!", "Building "+builder.Binary()+"...", "", notificator.UR_NORMAL)
	}
	err := builder.Build()
	if err != nil {
		buildError = err
		logger.Printf("%sBuild failed%s\n", colorRed, colorReset)
		fmt.Println(builder.Errors())
		buildErrors := strings.Split(builder.Errors(), "\n")
		if notifications {
			if err := notifier.Push("Build FAILED!", buildErrors[1], "", notificator.UR_CRITICAL); err != nil {
				logger.Println("Notification send failed")
			}
		}
	} else {
		buildError = nil
		logger.Printf("%sBuild finished%s\n", colorGreen, colorReset)
		if immediate {
			runner.Run()
		}
		if notifications {
			if err := notifier.Push("Build Succeded", "Build Finished!", "", notificator.UR_CRITICAL); err != nil {
				logger.Println("Notification send failed")
			}
		}
	}

	time.Sleep(100 * time.Millisecond)
}

type scanCallback func(path string)

func scanChanges(watchPath string, excludeDirs []string, allFiles bool, cb scanCallback) {
	for {
		filepath.Walk(watchPath, func(path string, info os.FileInfo, err error) error {
			if path == ".git" && info.IsDir() {
				return filepath.SkipDir
			}
			for _, x := range excludeDirs {
				if x == path {
					return filepath.SkipDir
				}
			}

			// ignore hidden files
			if filepath.Base(path)[0] == '.' {
				return nil
			}

			if (allFiles || filepath.Ext(path) == ".go") && info.ModTime().After(startTime) {
				cb(path)
				startTime = time.Now()
				return errors.New("done")
			}

			return nil
		})
		time.Sleep(500 * time.Millisecond)
	}
}

func shutdown(runner gin.Runner) {
	c := make(chan os.Signal, 2)
	signal.Notify(c, os.Interrupt, syscall.SIGTERM)
	go func() {
		s := <-c
		log.Println("Got signal: ", s)
		err := runner.Kill()
		if err != nil {
			log.Print("Error killing: ", err)
		}
		os.Exit(1)
	}()
}
<commit_msg>fix #14<commit_after>package main

import (
	"errors"
	"fmt"

	"github.com/codegangsta/envy/lib"
	"github.com/codegangsta/gin/lib"
	shellwords "github.com/mattn/go-shellwords"
	"gopkg.in/urfave/cli.v1"

	"log"
	"os"
	"os/signal"
	"path/filepath"
	"strconv"
	"strings"
	"syscall"
	"time"

	"github.com/0xAX/notificator"
)

var (
	startTime     = time.Now()
	logger        = log.New(os.Stdout, "[gin] ", 0)
	immediate     = false
	buildError    error
	colorGreen    = string([]byte{27, 91, 57, 55, 59, 51, 50, 59, 49, 109})
	colorRed      = string([]byte{27, 91, 57, 55, 59, 51, 49, 59, 49, 109})
	colorReset    = string([]byte{27, 91, 48, 109})
	notifier      = notificator.New(notificator.Options{AppName: "Gin Build"})
	notifications = false
)

func main() {
	app := cli.NewApp()
	app.Name = "gin"
	app.Usage = "A live reload utility for Go web applications."
	app.Action = MainAction
	app.Flags = []cli.Flag{
		cli.StringFlag{
			Name:   "laddr,l",
			Value:  "",
			EnvVar: "GIN_LADDR",
			Usage:  "listening address for the proxy server",
		},
		cli.IntFlag{
			Name:   "port,p",
			Value:  3000,
			EnvVar: "GIN_PORT",
			Usage:  "port for the proxy server",
		},
		cli.IntFlag{
			Name:   "appPort,a",
			Value:  3001,
			EnvVar: "BIN_APP_PORT",
			Usage:  "port for the Go web server",
		},
		cli.StringFlag{
			Name:   "bin,b",
			Value:  "gin-bin",
			EnvVar: "GIN_BIN",
			Usage:  "name of generated binary file",
		},
		cli.StringFlag{
			Name:   "path,t",
			Value:  ".",
			EnvVar: "GIN_PATH",
			Usage:  "Path to watch files from",
		},
		cli.StringFlag{
			Name:   "build,d",
			Value:  "",
			EnvVar: "GIN_BUILD",
			Usage:  "Path to build files from (defaults to same value as --path)",
		},
		cli.StringSliceFlag{
			Name:   "excludeDir,x",
			Value:  &cli.StringSlice{},
			EnvVar: "GIN_EXCLUDE_DIR",
			Usage:  "Relative directories to exclude",
		},
		cli.BoolFlag{
			Name:   "immediate,i",
			EnvVar: "GIN_IMMEDIATE",
			Usage:  "run the server immediately after it's built",
		},
		cli.BoolFlag{
			Name:   "all",
			EnvVar: "GIN_ALL",
			Usage:  "reloads whenever any file changes, as opposed to reloading only on .go file change",
		},
		cli.BoolFlag{
			Name:   "godep,g",
			EnvVar: "GIN_GODEP",
			Usage:  "use godep when building",
		},
		cli.StringFlag{
			Name:   "buildArgs",
			EnvVar: "GIN_BUILD_ARGS",
			Usage:  "Additional go build arguments",
		},
		cli.StringFlag{
			Name:   "certFile",
			EnvVar: "GIN_CERT_FILE",
			Usage:  "TLS Certificate",
		},
		cli.StringFlag{
			Name:   "keyFile",
			EnvVar: "GIN_KEY_FILE",
			Usage:  "TLS Certificate Key",
		},
		cli.StringFlag{
			Name:   "logPrefix",
			EnvVar: "GIN_LOG_PREFIX",
			Usage:  "Log prefix",
			Value:  "gin",
		},
		cli.BoolFlag{
			Name:   "notifications",
			EnvVar: "GIN_NOTIFICATIONS",
			Usage:  "Enables desktop notifications",
		},
	}
	app.Commands = []cli.Command{
		{
			Name:            "run",
			ShortName:       "r",
			Usage:           "Run the gin proxy in the current working directory",
			Action:          MainAction,
			SkipFlagParsing: true,
		},
		{
			Name:      "env",
			ShortName: "e",
			Usage:     "Display environment variables set by the .env file",
			Action:    EnvAction,
		},
	}

	app.Run(os.Args)
}

func MainAction(c *cli.Context) {
	laddr := c.GlobalString("laddr")
	port := c.GlobalInt("port")
	all := c.GlobalBool("all")
	appPort := strconv.Itoa(c.GlobalInt("appPort"))
	immediate = c.GlobalBool("immediate")
	keyFile := c.GlobalString("keyFile")
	certFile := c.GlobalString("certFile")
	logPrefix := c.GlobalString("logPrefix")
	notifications = c.GlobalBool("notifications")

	logger.SetPrefix(fmt.Sprintf("[%s] ", logPrefix))

	// Bootstrap the environment
	envy.Bootstrap()

	// Set the PORT env
	os.Setenv("PORT", appPort)

	wd, err := os.Getwd()
	if err != nil {
		logger.Fatal(err)
	}

	buildArgs, err := shellwords.Parse(c.GlobalString("buildArgs"))
	if err != nil {
		logger.Fatal(err)
	}

	buildPath := c.GlobalString("build")
	if buildPath == "" {
		buildPath = c.GlobalString("path")
	}
	builder := gin.NewBuilder(buildPath, c.GlobalString("bin"), c.GlobalBool("godep"), wd, buildArgs)
	runner := gin.NewRunner(filepath.Join(wd, builder.Binary()), c.Args()...)
	runner.SetWriter(os.Stdout)
	proxy := gin.NewProxy(builder, runner)

	config := &gin.Config{
		Laddr:    laddr,
		Port:     port,
		ProxyTo:  "http://localhost:" + appPort,
		KeyFile:  keyFile,
		CertFile: certFile,
	}

	err = proxy.Run(config)
	if err != nil {
		logger.Fatal(err)
	}

	if laddr != "" {
		logger.Printf("Listening at %s:%d\n", laddr, port)
	} else {
		logger.Printf("Listening on port %d\n", port)
	}

	shutdown(runner)

	// build right now
	build(builder, runner, logger)

	// scan for changes
	scanChanges(c.GlobalString("path"), c.GlobalStringSlice("excludeDir"), all, func(path string) {
		runner.Kill()
		build(builder, runner, logger)
	})
}

func EnvAction(c *cli.Context) {
	logPrefix := c.GlobalString("logPrefix")
	logger.SetPrefix(fmt.Sprintf("[%s] ", logPrefix))

	// Bootstrap the environment
	env, err := envy.Bootstrap()
	if err != nil {
		logger.Fatalln(err)
	}

	for k, v := range env {
		fmt.Printf("%s: %s\n", k, v)
	}

}

func build(builder gin.Builder, runner gin.Runner, logger *log.Logger) {
	logger.Println("Building...")

	if notifications {
		notifier.Push("Build Started!", "Building "+builder.Binary()+"...", "", notificator.UR_NORMAL)
	}
	err := builder.Build()
	if err != nil {
		buildError = err
		logger.Printf("%sBuild failed%s\n", colorRed, colorReset)
		fmt.Println(builder.Errors())
		buildErrors := strings.Split(builder.Errors(), "\n")
		if notifications {
			if err := notifier.Push("Build FAILED!", buildErrors[1], "", notificator.UR_CRITICAL); err != nil {
				logger.Println("Notification send failed")
			}
		}
	} else {
		buildError = nil
		logger.Printf("%sBuild finished%s\n", colorGreen, colorReset)
		if immediate {
			runner.Run()
		}
		if notifications {
			if err := notifier.Push("Build Succeded", "Build Finished!", "", notificator.UR_CRITICAL); err != nil {
				logger.Println("Notification send failed")
			}
		}
	}

	time.Sleep(100 * time.Millisecond)
}

type scanCallback func(path string)

func scanChanges(watchPath string, excludeDirs []string, allFiles bool, cb scanCallback) {
	for {
		filepath.Walk(watchPath, func(path string, info os.FileInfo, err error) error {
			if path == ".git" && info.IsDir() {
				return filepath.SkipDir
			}
			for _, x := range excludeDirs {
				if x == path {
					return filepath.SkipDir
				}
			}

			// ignore hidden files
			if filepath.Base(path)[0] == '.' {
				return nil
			}

			if (allFiles || filepath.Ext(path) == ".go") && info.ModTime().After(startTime) {
				cb(path)
				startTime = time.Now()
				return errors.New("done")
			}

			return nil
		})
		time.Sleep(500 * time.Millisecond)
	}
}

func shutdown(runner gin.Runner) {
	c := make(chan os.Signal, 2)
	signal.Notify(c, os.Interrupt, syscall.SIGTERM)
	go func() {
		s := <-c
		log.Println("Got signal: ", s)
		err := runner.Kill()
		if err != nil {
			log.Print("Error killing: ", err)
		}
		os.Exit(1)
	}()
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"gopkg.in/yaml.v2"
	"io/ioutil"
	"log"
	"os"
)

func main() {
	defer func() {
		if err := recover(); err != nil {
			fmt.Fprintf(ColorStderrWriter, FgRB("[jitome] error: %v\n", err))
			os.Exit(1)
		}
	}()

	os.Exit(realMain())
}

var debug bool
var configFile string

func realMain() int {
	log.SetPrefix(FgGB("[jitome] "))
	log.SetOutput(ColorStdoutWriter)
	log.SetFlags(0)

	var initFlag bool

	flag.StringVar(&configFile, "c", "jitome.yml", "")
	flag.StringVar(&configFile, "config", "jitome.yml", "")
	flag.BoolVar(&initFlag, "i", false, "")
	flag.BoolVar(&initFlag, "init", false, "")
	flag.BoolVar(&debug, "d", false, "")
	flag.BoolVar(&debug, "debug", false, "")

	flag.Usage = printUsage
	flag.Parse()

	if initFlag {
		err := generateConfig()
		if err != nil {
			panic(err)
		}

		log.Printf("created '%s'", configFile)
		return 0
	}

	if _, err := os.Stat(configFile); os.IsNotExist(err) {
		panic(fmt.Errorf("'%s' is not found. If you want to get help, run 'jitome -h'.", configFile))
	}

	b, err := ioutil.ReadFile(configFile)
	if err != nil {
		panic(err)
	}

	config := &Config{}
	err = yaml.Unmarshal(b, &config.Targets)
	if err != nil {
		panic(err)
	}

	j := NewJitome(config)
	err = j.Start()
	if err != nil {
		panic(err)
	}

	return 0
}

func generateConfig() error {
	if _, err := os.Stat(configFile); err == nil {
		return fmt.Errorf("%s is already existed.", configFile)
	}

	err := ioutil.WriteFile(configFile, []byte(initialConfig), 0644)
	if err != nil {
		return err
	}

	return nil
}

func printUsage() {
	fmt.Println(`Usage: jitome [<options>]

Jitome is a simple file watcher.

Options:
  -c|-config    Specify a config file.
  -i|-init      Create initial config file.
  -d|-debug     Run on debug mode.
`)
}
<commit_msg>update usage message<commit_after>package main

import (
	"flag"
	"fmt"
	"gopkg.in/yaml.v2"
	"io/ioutil"
	"log"
	"os"
)

func main() {
	defer func() {
		if err := recover(); err != nil {
			fmt.Fprintf(ColorStderrWriter, FgRB("[jitome] error: %v\n", err))
			os.Exit(1)
		}
	}()

	os.Exit(realMain())
}

var debug bool
var configFile string

func realMain() int {
	log.SetPrefix(FgGB("[jitome] "))
	log.SetOutput(ColorStdoutWriter)
	log.SetFlags(0)

	var initFlag bool

	flag.StringVar(&configFile, "c", "jitome.yml", "")
	flag.StringVar(&configFile, "config", "jitome.yml", "")
	flag.BoolVar(&initFlag, "i", false, "")
	flag.BoolVar(&initFlag, "init", false, "")
	flag.BoolVar(&debug, "d", false, "")
	flag.BoolVar(&debug, "debug", false, "")

	flag.Usage = printUsage
	flag.Parse()

	if initFlag {
		err := generateConfig()
		if err != nil {
			panic(err)
		}

		log.Printf("created '%s'", configFile)
		return 0
	}

	if _, err := os.Stat(configFile); os.IsNotExist(err) {
		panic(fmt.Errorf("'%s' is not found. If you want to get help, run 'jitome -h'.", configFile))
	}

	b, err := ioutil.ReadFile(configFile)
	if err != nil {
		panic(err)
	}

	config := &Config{}
	err = yaml.Unmarshal(b, &config.Targets)
	if err != nil {
		panic(err)
	}

	j := NewJitome(config)
	err = j.Start()
	if err != nil {
		panic(err)
	}

	return 0
}

func generateConfig() error {
	if _, err := os.Stat(configFile); err == nil {
		return fmt.Errorf("%s is already existed.", configFile)
	}

	err := ioutil.WriteFile(configFile, []byte(initialConfig), 0644)
	if err != nil {
		return err
	}

	return nil
}

func printUsage() {
	fmt.Println(`Usage: jitome [<options>]

  Jitome is a simple file watcher.

Options:
  -c|-config    Specify a config file.
  -i|-init      Create initial config file.
  -d|-debug     Run on debug mode.
`)
}
<|endoftext|>


<commit_before>// Copyright © 2016 Zlatko Čalušić
//
// Use of this source code is governed by an MIT-style license that can be found in the LICENSE file.

// Update notifier displays notification and an icon in the panel tray area when Debian package updates are
// available. You can hover the mouse over the icon to check how many updates are available. It's especially suitable
// for Debian testing/unstable users, because it checks for updates very often. Developed and tested on Xfce desktop
// environment.
package main

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"log"
	"os/exec"
	"path"
	"regexp"
	"strconv"
	"time"

	"github.com/mattn/go-gtk/glib"
	"github.com/mattn/go-gtk/gtk"
	notify "github.com/mqu/go-notify"
)

var (
	aptRunning   bool
	aptLastCheck time.Time
	updLastCheck time.Time
	updAvailable int
	updTooltip   int
	updNotified  int
)

func showIcon(icon *gtk.StatusIcon) {
	if !icon.GetVisible() {
		icon.SetVisible(true)
	}
}

func hideIcon(icon *gtk.StatusIcon) {
	if icon.GetVisible() {
		icon.SetVisible(false)
	}
}

func showNotification(text string) {
	notify.Init("update-notifier")
	defer notify.UnInit()

	if notification := notify.NotificationNew("Software updates available", text,
		"/usr/share/icons/Adwaita/48x48/status/software-update-available.png"); notification != nil {
		notify.NotificationShow(notification) // ignore errors
	}
}

func userNotify(icon *gtk.StatusIcon) {
	var word1, word2 string

	if updAvailable == 1 {
		word1 = "is"
		word2 = "update"
	} else {
		word1 = "are"
		word2 = "updates"
	}

	if updAvailable != updTooltip {
		icon.SetTooltipMarkup(fmt.Sprintf("%d %s available", updAvailable, word2))
		updTooltip = updAvailable
	}

	showIcon(icon)

	if updAvailable != updNotified {
		showNotification(fmt.Sprintf("There %s %d %s ready to install.", word1, updAvailable, word2))
		updNotified = updAvailable
	}
}

// Check if package manager is running (every 5 seconds).
func isAptRunning() bool {
	if time.Since(aptLastCheck) >= 5*time.Second {
		aptRunning = false

		dir, err := ioutil.ReadDir("/proc")
		if err != nil {
			log.Fatal(err)
		}

		reApt := regexp.MustCompile(`(?m:^(apt-get|dselect|aptitude)$)`)
		reProcess := regexp.MustCompile(`^\d+$`)

		for _, v := range dir {
			if !v.IsDir() {
				continue
			}

			if !reProcess.MatchString(v.Name()) {
				continue
			}

			comm, err := ioutil.ReadFile(path.Join("/proc", v.Name(), "comm"))
			if err != nil {
				continue
			}

			if reApt.Match(comm) {
				aptRunning = true
				updLastCheck = time.Time{}
				break
			}
		}

		aptLastCheck = time.Now()
	}

	return aptRunning
}

// Check if updates are available (every 5 minutes).
func updatesAvailable() int {
	if time.Since(updLastCheck) >= 5*time.Minute {
		var out bytes.Buffer

		cmd := exec.Command("/usr/bin/apt-get", "-s", "dist-upgrade")
		cmd.Stdout = &out
		if err := cmd.Run(); err != nil {
			log.Fatal(err)
		}

		if sl := regexp.MustCompile(`(?m:^(\d+) upgraded, (\d+) newly installed)`).FindStringSubmatch(out.String()); len(sl) == 3 {
			if upgraded, err := strconv.Atoi(sl[1]); err == nil {
				if newInstall, err := strconv.Atoi(sl[2]); err == nil {
					updAvailable = upgraded + newInstall
				}
			}
		}

		updLastCheck = time.Now()
	}

	return updAvailable
}

func main() {
	// https://github.com/mattn/go-gtk/issues/251
	gtk.Init(nil)
	glib.SetApplicationName("update-notifier")

	icon := gtk.NewStatusIconFromFile("/usr/share/icons/Adwaita/24x24/status/software-update-available.png")
	icon.SetTitle("update-notifier")
	icon.SetVisible(false)

	// Don't fight with system notifications during login.
	time.Sleep(15 * time.Second)

	for {
		if !isAptRunning() && updatesAvailable() > 0 {
			userNotify(icon)
		} else {
			hideIcon(icon)
		}

		for gtk.EventsPending() {
			gtk.MainIteration()
		}

		time.Sleep(time.Second / 10)
	}
}
<commit_msg>Handle apt-get errors gracefully<commit_after>// Copyright © 2016 Zlatko Čalušić
//
// Use of this source code is governed by an MIT-style license that can be found in the LICENSE file.

// Update notifier displays notification and an icon in the panel tray area when Debian package updates are
// available. You can hover the mouse over the icon to check how many updates are available. It's especially suitable
// for Debian testing/unstable users, because it checks for updates very often. Developed and tested on Xfce desktop
// environment.
package main

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"log"
	"os/exec"
	"path"
	"regexp"
	"strconv"
	"time"

	"github.com/mattn/go-gtk/glib"
	"github.com/mattn/go-gtk/gtk"
	notify "github.com/mqu/go-notify"
)

var (
	aptRunning   bool
	aptLastCheck time.Time
	updLastCheck time.Time
	updAvailable int
	updTooltip   int
	updNotified  int
)

func showIcon(icon *gtk.StatusIcon) {
	if !icon.GetVisible() {
		icon.SetVisible(true)
	}
}

func hideIcon(icon *gtk.StatusIcon) {
	if icon.GetVisible() {
		icon.SetVisible(false)
	}
}

func showNotification(text string) {
	notify.Init("update-notifier")
	defer notify.UnInit()

	if notification := notify.NotificationNew("Software updates available", text,
		"/usr/share/icons/Adwaita/48x48/status/software-update-available.png"); notification != nil {
		notify.NotificationShow(notification) // ignore errors
	}
}

func userNotify(icon *gtk.StatusIcon) {
	var word1, word2 string

	if updAvailable == 1 {
		word1 = "is"
		word2 = "update"
	} else {
		word1 = "are"
		word2 = "updates"
	}

	if updAvailable != updTooltip {
		icon.SetTooltipMarkup(fmt.Sprintf("%d %s available", updAvailable, word2))
		updTooltip = updAvailable
	}

	showIcon(icon)

	if updAvailable != updNotified {
		showNotification(fmt.Sprintf("There %s %d %s ready to install.", word1, updAvailable, word2))
		updNotified = updAvailable
	}
}

// Check if package manager is running (every 5 seconds).
func isAptRunning() bool {
	if time.Since(aptLastCheck) >= 5*time.Second {
		aptRunning = false

		dir, err := ioutil.ReadDir("/proc")
		if err != nil {
			log.Fatal(err)
		}

		reApt := regexp.MustCompile(`(?m:^(apt-get|dselect|aptitude)$)`)
		reProcess := regexp.MustCompile(`^\d+$`)

		for _, v := range dir {
			if !v.IsDir() {
				continue
			}

			if !reProcess.MatchString(v.Name()) {
				continue
			}

			comm, err := ioutil.ReadFile(path.Join("/proc", v.Name(), "comm"))
			if err != nil {
				continue
			}

			if reApt.Match(comm) {
				aptRunning = true
				updLastCheck = time.Time{}
				break
			}
		}

		aptLastCheck = time.Now()
	}

	return aptRunning
}

// Check if updates are available (every 5 minutes).
func updatesAvailable() int {
	if time.Since(updLastCheck) >= 5*time.Minute {
		var out bytes.Buffer

		cmd := exec.Command("/usr/bin/apt-get", "-s", "dist-upgrade")
		cmd.Stdout = &out
		if err := cmd.Run(); err == nil {

			if sl := regexp.MustCompile(`(?m:^(\d+) upgraded, (\d+) newly installed)`).FindStringSubmatch(out.String()); len(sl) == 3 {
				if upgraded, err := strconv.Atoi(sl[1]); err == nil {
					if newInstall, err := strconv.Atoi(sl[2]); err == nil {
						updAvailable = upgraded + newInstall
					}
				}
			}
		}

		updLastCheck = time.Now()
	}

	return updAvailable
}

func main() {
	// https://github.com/mattn/go-gtk/issues/251
	gtk.Init(nil)
	glib.SetApplicationName("update-notifier")

	icon := gtk.NewStatusIconFromFile("/usr/share/icons/Adwaita/24x24/status/software-update-available.png")
	icon.SetTitle("update-notifier")
	icon.SetVisible(false)

	// Don't fight with system notifications during login.
	time.Sleep(15 * time.Second)

	for {
		if !isAptRunning() && updatesAvailable() > 0 {
			userNotify(icon)
		} else {
			hideIcon(icon)
		}

		for gtk.EventsPending() {
			gtk.MainIteration()
		}

		time.Sleep(time.Second / 10)
	}
}
<|endoftext|>