<commit_before>package artifice

import (
	"log"
	"time"
)

func init() {
	registerTrigger("marketMaint", marketMaint, time.NewTicker(time.Second*3610))
	registerTrigger("discoveredAssetsMaint", discoveredAssetsMaint, time.NewTicker(time.Second*3620))
	registerTrigger("entityMaint", entityMaint, time.NewTicker(time.Second*3630*3))
	registerTrigger("killmailMaint", killmailMaint, time.NewTicker(time.Second*3640))
	registerTrigger("contactSyncMaint", contactSyncMaint, time.NewTicker(time.Second*3615*6))
}

func contactSyncMaint(s *Artifice) error {
	if err := s.doSQL(`
        DELETE S.* FROM evedata.contactSyncs S
        LEFT OUTER JOIN evedata.crestTokens T ON S.destination = T.tokenCharacterID
        WHERE tokenCharacterID IS NULL;`); err != nil {
		return err
	}
	if err := s.doSQL(`
        DELETE S.* FROM evedata.contactSyncs S
        LEFT OUTER JOIN evedata.crestTokens T ON S.source = T.tokenCharacterID
        WHERE tokenCharacterID IS NULL;`); err != nil {
		return err
	}

	return nil
}

func killmailMaint(s *Artifice) error { // Broken into smaller chunks so we have a chance of it getting completed.
	// Delete stuff older than 90 days, we do not care...
	/*	if err := s.RetryExecTillNoRows(`
					DELETE A.* FROM evedata.killmailAttackers A
			            JOIN (SELECT id FROM evedata.killmails WHERE killTime < DATE_SUB(UTC_TIMESTAMP, INTERVAL 365 DAY) LIMIT 5000) K ON A.id = K.id;
			            `); err != nil {
			return err
		}

		if err := s.RetryExecTillNoRows(`
					DELETE FROM evedata.killmails
			        WHERE killTime < DATE_SUB(UTC_TIMESTAMP, INTERVAL 365 DAY) LIMIT 5000;
			            `); err != nil {
			return err
		}

		// Remove any invalid items
		if err := s.RetryExecTillNoRows(`
			        DELETE D.* FROM evedata.killmailAttackers D
		            JOIN (SELECT A.id FROM evedata.killmailAttackers A
						 LEFT OUTER JOIN evedata.killmails K ON A.id = K.id
			             WHERE K.id IS NULL LIMIT 1000) S ON D.id = S.id;
			               `); err != nil {
			return err
		}*/

	// Prefill stats for known entities that may have no kills
	if err := s.doSQL(`
        INSERT IGNORE INTO evedata.entityKillStats (id)
	    (SELECT corporationID AS id FROM evedata.corporations WHERE memberCount > 0);
            `); err != nil {
		return err
	}

	if err := s.doSQL(`
        INSERT IGNORE INTO evedata.entityKillStats (id)
	    (SELECT allianceID AS id FROM evedata.alliances);
            `); err != nil {
		return err
	}

	// Build entity stats
	if err := s.doSQL(`
        INSERT INTO evedata.entityKillStats (id, losses)
            (SELECT
                victimCorporationID AS id,
                COUNT(DISTINCT K.id) AS losses
            FROM evedata.killmails K
            WHERE K.killTime > DATE_SUB(UTC_TIMESTAMP, INTERVAL 180 DAY)
            GROUP BY victimCorporationID
            ) ON DUPLICATE KEY UPDATE losses = values(losses);
            `); err != nil {
		return err
	}

	if err := s.doSQL(`
        INSERT INTO evedata.entityKillStats (id, losses)
            (SELECT
                victimAllianceID AS id,
                COUNT(DISTINCT K.id) AS losses
            FROM evedata.killmails K
            WHERE K.killTime > DATE_SUB(UTC_TIMESTAMP, INTERVAL 180 DAY)
            GROUP BY victimAllianceID
            ) ON DUPLICATE KEY UPDATE losses = values(losses);
            `); err != nil {
		return err
	}

	if err := s.doSQL(`
        INSERT INTO evedata.entityKillStats (id, kills)
            (SELECT
                corporationID AS id,
                COUNT(DISTINCT K.id) AS kills
            FROM evedata.killmails K
            INNER JOIN evedata.killmailAttackers A ON A.id = K.id
            WHERE K.killTime > DATE_SUB(UTC_TIMESTAMP, INTERVAL 180 DAY)
            GROUP BY A.corporationID
            ) ON DUPLICATE KEY UPDATE kills = values(kills);
            `); err != nil {
		return err
	}

	if err := s.doSQL(`
        INSERT INTO evedata.entityKillStats (id, kills)
            (SELECT
                allianceID AS id,
                COUNT(DISTINCT K.id) AS kills
            FROM evedata.killmails K
            INNER JOIN evedata.killmailAttackers A ON A.id = K.id
            WHERE K.killTime > DATE_SUB(UTC_TIMESTAMP, INTERVAL 180 DAY)
            GROUP BY A.allianceID
            ) ON DUPLICATE KEY UPDATE kills = values(kills);
            `); err != nil {
		return err
	}

	// Update everyone efficiency
	if err := s.doSQL(`
        UPDATE evedata.entityKillStats SET efficiency = IF(losses+kills, (kills/(kills+losses)) , 1.0000);
            `); err != nil {
		return err
	}

	return nil
}

func discoveredAssetsMaint(s *Artifice) error {
	if err := s.doSQL(`
        INSERT INTO evedata.discoveredAssets
            SELECT
                A.corporationID,
                C.allianceID,
                typeID,
                K.solarSystemID,
                K.x,
                K.y,
                K.z,
                evedata.closestCelestial(K.solarSystemID, K.x, K.y, K.z) AS locationID,
                MAX(killTime) as lastSeen
            FROM evedata.killmailAttackers A
            INNER JOIN invTypes T ON shipType = typeID
            INNER JOIN evedata.corporations C ON C.corporationID = A.corporationID
            INNER JOIN evedata.killmails K ON K.id = A.id
            INNER JOIN mapSolarSystems S ON S.solarSystemID = K.solarSystemID
            WHERE characterID = 0 AND groupID IN (365, 549, 1023, 1537, 1652, 1653, 1657, 2233)
            GROUP BY A.corporationID, solarSystemID, typeID
        ON DUPLICATE KEY UPDATE lastSeen = lastSeen;
            `); err != nil {
		return err
	}

	if err := s.doSQL(`
        INSERT INTO evedata.discoveredAssets
            SELECT
                K.victimCorporationID AS corporationID,
                C.allianceID,
                typeID,
                K.solarSystemID,
                K.x,
                K.y,
                K.z,
                evedata.closestCelestial(K.solarSystemID, K.x, K.y, K.z) AS locationID,
                MAX(killTime) as lastSeen
            FROM evedata.killmails K
            INNER JOIN invTypes T ON K.shipType = typeID
            INNER JOIN evedata.corporations C ON C.corporationID = K.victimCorporationID
            INNER JOIN mapSolarSystems S ON S.solarSystemID = K.solarSystemID
            WHERE victimCharacterID = 0 AND groupID IN (365, 549, 1023, 1537, 1652, 1653, 1657, 2233)
            GROUP BY K.victimCorporationID, solarSystemID, typeID
        ON DUPLICATE KEY UPDATE lastSeen = lastSeen;
            `); err != nil {
		return err
	}
	return nil
}

func entityMaint(s *Artifice) error {
	if err := s.doSQL(`
        UPDATE evedata.alliances A SET memberCount =
            IFNULL(
                    (SELECT sum(memberCount) AS memberCount FROM evedata.corporations  C
                    WHERE C.allianceID = A.allianceID
                    GROUP BY allianceID LIMIT 1),
                    0
            );
            `); err != nil {
		return err
	}
	return nil
}

type marketRegion struct {
	RegionID   int32  `db:"regionID"`
	RegionName string `db:"regionName"`
}

// [BENCHMARK] 0.000 sec / 0.000 sec
// Anywhere can now have a public market.
func getMarketRegions(s *Artifice) ([]marketRegion, error) {
	v := []marketRegion{}
	err := s.db.Select(&v, `
		SELECT 	regionID, regionName
		FROM 	mapRegions
		WHERE regionID < 11000000;
	`)
	if err != nil {
		return nil, err
	}
	return v, nil
}

func marketMaint(s *Artifice) error {
	regions, err := getMarketRegions(s)
	if err != nil {
		log.Println(err)
	}

	if err := s.RetryExecTillNoRows(`
        DELETE LOW_PRIORITY FROM evedata.market
            WHERE date_add(issued, INTERVAL duration DAY) < UTC_TIMESTAMP() OR
            reported < DATE_SUB(utc_timestamp(), INTERVAL 3 HOUR)
            ORDER BY regionID, typeID ASC LIMIT 50000;
            `); err != nil {
		log.Println(err)
	}

	if err := s.RetryExecTillNoRows(`
        DELETE LOW_PRIORITY FROM evedata.market_history
            WHERE date < date_sub(UTC_TIMESTAMP(), INTERVAL 365 DAY) LIMIT 5000;
            `); err != nil {
		log.Println(err)
	}

	if err := s.doSQL(`
        DELETE LOW_PRIORITY FROM evedata.marketStations ORDER BY stationName;
             `); err != nil {
		log.Println(err)
	}

	if err := s.doSQL(`
        INSERT IGNORE INTO evedata.marketStations SELECT  stationName, M.stationID, Count(*) as Count
        FROM    evedata.market M
                INNER JOIN staStations S ON M.stationID = S.stationID
        WHERE   reported >= DATE_SUB(UTC_TIMESTAMP(), INTERVAL 5 DAY)
        GROUP BY M.stationID
        HAVING count(*) > 2000
        ORDER BY stationName;
            `); err != nil {
		log.Println(err)
	}

	if err := s.doSQL(`
       UPDATE evedata.market_vol SET quantity = 0;
             `); err != nil {
		log.Println(err)
	}

	for _, region := range regions {
		if err := s.doSQL(`
        REPLACE INTO evedata.market_vol (
            SELECT count(*) as number,sum(quantity)/7 as quantity, regionID, itemID
                FROM evedata.market_history
                WHERE date > DATE_SUB(UTC_TIMESTAMP(),INTERVAL 7 DAY)
                AND regionID = ?
                GROUP BY regionID, itemID);
            `, region.RegionID); err != nil {
			log.Println(err)
			return err
		}
	}

	if err := s.doSQL(`
		DELETE FROM evedata.jitaPrice ORDER BY itemID;
			  `); err != nil {
		log.Println(err)
		return err
	}

	if err := s.doSQL(`
		 INSERT IGNORE INTO evedata.jitaPrice (
		 SELECT S.typeID as itemID, buy, sell, high, low, mean, quantity FROM
			 (SELECT typeID, min(price) AS sell FROM evedata.market WHERE regionID = 10000002 AND bid = 0 GROUP BY typeID) S
			 INNER JOIN (SELECT typeID, max(price) AS buy FROM evedata.market WHERE regionID = 10000002 AND bid = 1 GROUP BY typeID) B ON S.typeID = B.typeID
			 LEFT OUTER JOIN (SELECT itemID, max(high) AS high, avg(mean) AS mean, min(low) AS low, sum(quantity) AS quantity FROM evedata.market_history WHERE regionID = 10000002 AND date > DATE_SUB(UTC_DATE(), INTERVAL 4 DAY) GROUP BY itemID) H on H.itemID = S.typeID
		 HAVING mean IS NOT NULL
		 ) ORDER BY itemID;
			 `); err != nil {
		log.Println(err)
		return err
	}

	if err := s.doSQL(`
		DELETE FROM evedata.iskPerLp ORDER BY typeID;
			  `); err != nil {
		log.Println(err)
		return err
	}

	if err := s.doSQL(`
		 INSERT IGNORE INTO evedata.iskPerLp (
		 SELECT
				 N.itemName,
				 S.typeID,
				 T.typeName,
				 MIN(lpCost) AS lpCost,
				 MIN(iskCost) AS iskCost,
				 ROUND(MIN(C.buy),0) AS JitaPrice,
				 ROUND(MIN(C.quantity),0) AS JitaVolume,
				 ROUND(COALESCE(MIN(P.price),0) + iskCost, 0)  AS itemCost,
				 ROUND(
						 (
								 ( MIN(S.quantity) * AVG(C.buy) ) -
								 ( COALESCE( MIN(P.price), 0) + iskCost )
						 )
						 / MIN(lpCost)
				 , 0) AS ISKperLP,
				 P.offerID
		 FROM evedata.lpOffers S

		 INNER JOIN invNames N ON S.corporationID = N.itemID
		 INNER JOIN invTypes T ON S.typeID = T.typeID
		 INNER JOIN evedata.jitaPrice C ON C.itemID = S.typeID

		 LEFT OUTER JOIN         (
								 SELECT offerID, sum(H.sell * L.quantity) AS price
								 FROM evedata.lpOfferRequirements L
								 INNER JOIN evedata.jitaPrice H ON H.itemID = L.typeID
								 GROUP BY offerID
						 ) AS P ON S.offerID = P.offerID

		 GROUP BY S.offerID, S.corporationID
		 HAVING ISKperLP > 0) ORDER BY typeID;
			 `); err != nil {
		log.Println(err)
		return err
	}

	return nil
}
<commit_msg>fix and add new types<commit_after>package artifice

import (
	"log"
	"time"
)

func init() {
	registerTrigger("marketMaint", marketMaint, time.NewTicker(time.Second*3610))
	registerTrigger("discoveredAssetsMaint", discoveredAssetsMaint, time.NewTicker(time.Second*3620))
	registerTrigger("entityMaint", entityMaint, time.NewTicker(time.Second*3630*3))
	registerTrigger("killmailMaint", killmailMaint, time.NewTicker(time.Second*3640))
	registerTrigger("contactSyncMaint", contactSyncMaint, time.NewTicker(time.Second*3615*6))
}

func contactSyncMaint(s *Artifice) error {
	if err := s.doSQL(`
        DELETE S.* FROM evedata.contactSyncs S
        LEFT OUTER JOIN evedata.crestTokens T ON S.destination = T.tokenCharacterID
        WHERE tokenCharacterID IS NULL;`); err != nil {
		return err
	}
	if err := s.doSQL(`
        DELETE S.* FROM evedata.contactSyncs S
        LEFT OUTER JOIN evedata.crestTokens T ON S.source = T.tokenCharacterID
        WHERE tokenCharacterID IS NULL;`); err != nil {
		return err
	}

	return nil
}

func killmailMaint(s *Artifice) error { // Broken into smaller chunks so we have a chance of it getting completed.
	// Delete stuff older than 90 days, we do not care...
	/*	if err := s.RetryExecTillNoRows(`
					DELETE A.* FROM evedata.killmailAttackers A
			            JOIN (SELECT id FROM evedata.killmails WHERE killTime < DATE_SUB(UTC_TIMESTAMP, INTERVAL 365 DAY) LIMIT 5000) K ON A.id = K.id;
			            `); err != nil {
			return err
		}

		if err := s.RetryExecTillNoRows(`
					DELETE FROM evedata.killmails
			        WHERE killTime < DATE_SUB(UTC_TIMESTAMP, INTERVAL 365 DAY) LIMIT 5000;
			            `); err != nil {
			return err
		}

		// Remove any invalid items
		if err := s.RetryExecTillNoRows(`
			        DELETE D.* FROM evedata.killmailAttackers D
		            JOIN (SELECT A.id FROM evedata.killmailAttackers A
						 LEFT OUTER JOIN evedata.killmails K ON A.id = K.id
			             WHERE K.id IS NULL LIMIT 1000) S ON D.id = S.id;
			               `); err != nil {
			return err
		}*/

	// Prefill stats for known entities that may have no kills
	if err := s.doSQL(`
        INSERT IGNORE INTO evedata.entityKillStats (id)
	    (SELECT corporationID AS id FROM evedata.corporations WHERE memberCount > 0);
            `); err != nil {
		return err
	}

	if err := s.doSQL(`
        INSERT IGNORE INTO evedata.entityKillStats (id)
	    (SELECT allianceID AS id FROM evedata.alliances);
            `); err != nil {
		return err
	}

	// Build entity stats
	if err := s.doSQL(`
        INSERT INTO evedata.entityKillStats (id, losses)
            (SELECT
                victimCorporationID AS id,
                COUNT(DISTINCT K.id) AS losses
            FROM evedata.killmails K
            WHERE K.killTime > DATE_SUB(UTC_TIMESTAMP, INTERVAL 180 DAY)
            GROUP BY victimCorporationID
            ) ON DUPLICATE KEY UPDATE losses = values(losses);
            `); err != nil {
		return err
	}

	if err := s.doSQL(`
        INSERT INTO evedata.entityKillStats (id, losses)
            (SELECT
                victimAllianceID AS id,
                COUNT(DISTINCT K.id) AS losses
            FROM evedata.killmails K
            WHERE K.killTime > DATE_SUB(UTC_TIMESTAMP, INTERVAL 180 DAY)
            GROUP BY victimAllianceID
            ) ON DUPLICATE KEY UPDATE losses = values(losses);
            `); err != nil {
		return err
	}

	if err := s.doSQL(`
        INSERT INTO evedata.entityKillStats (id, kills)
            (SELECT
                corporationID AS id,
                COUNT(DISTINCT K.id) AS kills
            FROM evedata.killmails K
            INNER JOIN evedata.killmailAttackers A ON A.id = K.id
            WHERE K.killTime > DATE_SUB(UTC_TIMESTAMP, INTERVAL 180 DAY)
            GROUP BY A.corporationID
            ) ON DUPLICATE KEY UPDATE kills = values(kills);
            `); err != nil {
		return err
	}

	if err := s.doSQL(`
        INSERT INTO evedata.entityKillStats (id, kills)
            (SELECT
                allianceID AS id,
                COUNT(DISTINCT K.id) AS kills
            FROM evedata.killmails K
            INNER JOIN evedata.killmailAttackers A ON A.id = K.id
            WHERE K.killTime > DATE_SUB(UTC_TIMESTAMP, INTERVAL 180 DAY)
            GROUP BY A.allianceID
            ) ON DUPLICATE KEY UPDATE kills = values(kills);
            `); err != nil {
		return err
	}

	// Update everyone efficiency
	if err := s.doSQL(`
        UPDATE evedata.entityKillStats SET efficiency = IF(losses+kills, (kills/(kills+losses)) , 1.0000);
            `); err != nil {
		return err
	}

	return nil
}

func discoveredAssetsMaint(s *Artifice) error {
	if err := s.doSQL(`
        INSERT INTO evedata.discoveredAssets
            SELECT
                A.corporationID,
                C.allianceID,
                typeID,
                K.solarSystemID,
                K.x,
                K.y,
                K.z,
                evedata.closestCelestial(K.solarSystemID, K.x, K.y, K.z) AS locationID,
                MAX(killTime) as lastSeen
            FROM evedata.killmailAttackers A
            INNER JOIN invTypes T ON shipType = typeID
            INNER JOIN evedata.corporations C ON C.corporationID = A.corporationID
            INNER JOIN evedata.killmails K ON K.id = A.id
            INNER JOIN mapSolarSystems S ON S.solarSystemID = K.solarSystemID
			WHERE K.killTime > DATE_SUB(UTC_TIMESTAMP, INTERVAL 120 DAY) AND
				characterID = 0 AND groupID IN (365, 549, 1023, 1404, 1406, 1537, 1652, 1653, 1657, 2233)
            GROUP BY A.corporationID, solarSystemID, typeID
        ON DUPLICATE KEY UPDATE lastSeen = lastSeen;
            `); err != nil {
		return err
	}

	if err := s.doSQL(`
        INSERT INTO evedata.discoveredAssets
            SELECT
                K.victimCorporationID AS corporationID,
                C.allianceID,
                typeID,
                K.solarSystemID,
                K.x,
                K.y,
                K.z,
                evedata.closestCelestial(K.solarSystemID, K.x, K.y, K.z) AS locationID,
                MAX(killTime) as lastSeen
            FROM evedata.killmails K
            INNER JOIN invTypes T ON K.shipType = typeID
            INNER JOIN evedata.corporations C ON C.corporationID = K.victimCorporationID
            INNER JOIN mapSolarSystems S ON S.solarSystemID = K.solarSystemID
			WHERE K.killTime > DATE_SUB(UTC_TIMESTAMP, INTERVAL 120 DAY) AND
				victimCharacterID = 0 AND groupID IN (365, 549, 1023, 1404, 1406, 1537, 1652, 1653, 1657, 2233)
            GROUP BY K.victimCorporationID, solarSystemID, typeID
        ON DUPLICATE KEY UPDATE lastSeen = lastSeen;
            `); err != nil {
		return err
	}
	return nil
}

func entityMaint(s *Artifice) error {
	if err := s.doSQL(`
        UPDATE evedata.alliances A SET memberCount =
            IFNULL(
                    (SELECT sum(memberCount) AS memberCount FROM evedata.corporations  C
                    WHERE C.allianceID = A.allianceID
                    GROUP BY allianceID LIMIT 1),
                    0
            );
            `); err != nil {
		return err
	}
	return nil
}

type marketRegion struct {
	RegionID   int32  `db:"regionID"`
	RegionName string `db:"regionName"`
}

// [BENCHMARK] 0.000 sec / 0.000 sec
// Anywhere can now have a public market.
func getMarketRegions(s *Artifice) ([]marketRegion, error) {
	v := []marketRegion{}
	err := s.db.Select(&v, `
		SELECT 	regionID, regionName
		FROM 	mapRegions
		WHERE regionID < 11000000;
	`)
	if err != nil {
		return nil, err
	}
	return v, nil
}

func marketMaint(s *Artifice) error {
	regions, err := getMarketRegions(s)
	if err != nil {
		log.Println(err)
	}

	if err := s.RetryExecTillNoRows(`
        DELETE LOW_PRIORITY FROM evedata.market
            WHERE date_add(issued, INTERVAL duration DAY) < UTC_TIMESTAMP() OR
            reported < DATE_SUB(utc_timestamp(), INTERVAL 3 HOUR)
            ORDER BY regionID, typeID ASC LIMIT 50000;
            `); err != nil {
		log.Println(err)
	}

	if err := s.RetryExecTillNoRows(`
        DELETE LOW_PRIORITY FROM evedata.market_history
            WHERE date < date_sub(UTC_TIMESTAMP(), INTERVAL 365 DAY) LIMIT 5000;
            `); err != nil {
		log.Println(err)
	}

	if err := s.doSQL(`
        DELETE LOW_PRIORITY FROM evedata.marketStations ORDER BY stationName;
             `); err != nil {
		log.Println(err)
	}

	if err := s.doSQL(`
        INSERT IGNORE INTO evedata.marketStations SELECT  stationName, M.stationID, Count(*) as Count
        FROM    evedata.market M
                INNER JOIN staStations S ON M.stationID = S.stationID
        WHERE   reported >= DATE_SUB(UTC_TIMESTAMP(), INTERVAL 5 DAY)
        GROUP BY M.stationID
        HAVING count(*) > 2000
        ORDER BY stationName;
            `); err != nil {
		log.Println(err)
	}

	if err := s.doSQL(`
       UPDATE evedata.market_vol SET quantity = 0;
             `); err != nil {
		log.Println(err)
	}

	for _, region := range regions {
		if err := s.doSQL(`
        REPLACE INTO evedata.market_vol (
            SELECT count(*) as number,sum(quantity)/7 as quantity, regionID, itemID
                FROM evedata.market_history
                WHERE date > DATE_SUB(UTC_TIMESTAMP(),INTERVAL 7 DAY)
                AND regionID = ?
                GROUP BY regionID, itemID);
            `, region.RegionID); err != nil {
			log.Println(err)
			return err
		}
	}

	if err := s.doSQL(`
		DELETE FROM evedata.jitaPrice ORDER BY itemID;
			  `); err != nil {
		log.Println(err)
		return err
	}

	if err := s.doSQL(`
		 INSERT IGNORE INTO evedata.jitaPrice (
		 SELECT S.typeID as itemID, buy, sell, high, low, mean, quantity FROM
			 (SELECT typeID, min(price) AS sell FROM evedata.market WHERE regionID = 10000002 AND bid = 0 GROUP BY typeID) S
			 INNER JOIN (SELECT typeID, max(price) AS buy FROM evedata.market WHERE regionID = 10000002 AND bid = 1 GROUP BY typeID) B ON S.typeID = B.typeID
			 LEFT OUTER JOIN (SELECT itemID, max(high) AS high, avg(mean) AS mean, min(low) AS low, sum(quantity) AS quantity FROM evedata.market_history WHERE regionID = 10000002 AND date > DATE_SUB(UTC_DATE(), INTERVAL 4 DAY) GROUP BY itemID) H on H.itemID = S.typeID
		 HAVING mean IS NOT NULL
		 ) ORDER BY itemID;
			 `); err != nil {
		log.Println(err)
		return err
	}

	if err := s.doSQL(`
		DELETE FROM evedata.iskPerLp ORDER BY typeID;
			  `); err != nil {
		log.Println(err)
		return err
	}

	if err := s.doSQL(`
		 INSERT IGNORE INTO evedata.iskPerLp (
		 SELECT
				 N.itemName,
				 S.typeID,
				 T.typeName,
				 MIN(lpCost) AS lpCost,
				 MIN(iskCost) AS iskCost,
				 ROUND(MIN(C.buy),0) AS JitaPrice,
				 ROUND(MIN(C.quantity),0) AS JitaVolume,
				 ROUND(COALESCE(MIN(P.price),0) + iskCost, 0)  AS itemCost,
				 ROUND(
						 (
								 ( MIN(S.quantity) * AVG(C.buy) ) -
								 ( COALESCE( MIN(P.price), 0) + iskCost )
						 )
						 / MIN(lpCost)
				 , 0) AS ISKperLP,
				 P.offerID
		 FROM evedata.lpOffers S

		 INNER JOIN invNames N ON S.corporationID = N.itemID
		 INNER JOIN invTypes T ON S.typeID = T.typeID
		 INNER JOIN evedata.jitaPrice C ON C.itemID = S.typeID

		 LEFT OUTER JOIN         (
								 SELECT offerID, sum(H.sell * L.quantity) AS price
								 FROM evedata.lpOfferRequirements L
								 INNER JOIN evedata.jitaPrice H ON H.itemID = L.typeID
								 GROUP BY offerID
						 ) AS P ON S.offerID = P.offerID

		 GROUP BY S.offerID, S.corporationID
		 HAVING ISKperLP > 0) ORDER BY typeID;
			 `); err != nil {
		log.Println(err)
		return err
	}

	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"os/user"
	"strings"
)

// Conf represents Slack configuration data
type Conf struct {
	Token    string `json:"token"`
	Channel  string `json:"channel"`
	Username string `json:"username"`
}

var conf Conf

func loadConf() {
	usr, err := user.Current()
	if err != nil {
		os.Stderr.WriteString("Can't get current user info... Are you an alien?\n")
		os.Exit(1)
	}
	confFile := usr.HomeDir + "/.slack-cli.json"

	file, err := ioutil.ReadFile(confFile)
	if err != nil {
		os.Stderr.WriteString("Can't find ~/.slack-cli.json.\n")
		os.Exit(1)
	}

	err = json.Unmarshal(file, &conf)
	if err != nil {
		os.Stderr.WriteString("Can't parse ~/.slack-cli.json. Is the format correct?\n")
		os.Exit(1)
	}

	if len(os.Args) < 2 {
		os.Stderr.WriteString("Usage: slack-cli MESSAGE-TO-SEND\n")
		os.Exit(1)
	}
}

func main() {
	loadConf()

	query := fmt.Sprintf("token=%s&channel=%s&username=%s&text=%s",
		conf.Token,
		conf.Channel,
		conf.Username,
		strings.Join(os.Args[1:], " "))

	body := strings.NewReader(query)
	req, err := http.NewRequest("POST", "https://slack.com/api/chat.postMessage", body)
	if err != nil {
		os.Stderr.WriteString("Can't post to Slack API.\n")
		os.Exit(1)
	}
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		os.Stderr.WriteString("Can't post to Slack API.\n")
		os.Exit(1)
	}
	defer resp.Body.Close()
}
<commit_msg>Add a feature to understand commandline options<commit_after>package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"os/user"
	"strings"
)

// Conf represents Slack configuration data
type Conf struct {
	Token    string `json:"token"`
	Channel  string `json:"channel"`
	Username string `json:"username"`
}

var conf Conf

func loadConf() {
	usr, err := user.Current()
	if err != nil {
		os.Stderr.WriteString("Can't get current user info... Are you an alien?\n")
		os.Exit(1)
	}
	confFile := usr.HomeDir + "/.slack-cli.json"

	file, err := ioutil.ReadFile(confFile)
	if err != nil {
		os.Stderr.WriteString("Can't find ~/.slack-cli.json.\n")
		os.Exit(1)
	}

	err = json.Unmarshal(file, &conf)
	if err != nil {
		os.Stderr.WriteString("Can't parse ~/.slack-cli.json. Is the format correct?\n")
		os.Exit(1)
	}

	if len(os.Args) < 2 {
		os.Stderr.WriteString("Usage: slack-cli MESSAGE-TO-SEND\n")
		os.Exit(1)
	}
}

func main() {
	loadConf()

	var (
		ch    = flag.String("ch", conf.Channel, "Channel to post message to")
		uname = flag.String("uname", conf.Channel, "User name to use for posting message")
	)
	flag.Parse()

	query := fmt.Sprintf("token=%s&channel=%s&username=%s&text=%s",
		conf.Token,
		*ch,
		*uname,
		strings.Join(os.Args[1:], " "))

	body := strings.NewReader(query)
	req, err := http.NewRequest("POST", "https://slack.com/api/chat.postMessage", body)
	if err != nil {
		os.Stderr.WriteString("Can't post to Slack API.\n")
		os.Exit(1)
	}
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		os.Stderr.WriteString("Can't post to Slack API.\n")
		os.Exit(1)
	}
	defer resp.Body.Close()
}
<|endoftext|>


<commit_before>package scamp

import (
  "encoding/json"
  "encoding/pem"
  "crypto/x509"
  "crypto/rsa"

  "strconv"

  "fmt"
  "errors"
  "strings"

  "sync"

  "net"
  u "net/url"
)

// Example:
// {"vmin":0,"vmaj":4,"acsec":[[7,"background"]],"acname":["_evaluate","_execute","_evaluate","_execute","_munge","_evaluate","_execute"],"acver":[[7,1]],"acenv":[[7,"json,jsonstore,extdirect"]],"acflag":[[7,""]],"acns":[[2,"Channel.Amazon.FeedInterchange"],[3,"Channel.Amazon.InvPush"],[2,"Channel.Amazon.OrderImport"]]}
type ServiceProxyDiscoveryExtension struct {
	Vmin   int           `json:"vmin"`
	Vmaj   int           `json:"vmaj"`
	AcSec  []interface{} `json:"acsec"`
	AcName []string      `json:"acname"`
	AcVer  []interface{} `json:acver"`
	AcEnv  []interface{} `json:acenv"`
	AcFlag []interface{} `json:"acflag"`
	AcNs   []interface{} `json:"acns"`
}

type ServiceProxy struct {
	version int
	ident string
	sector string
	weight int
	announceInterval int
	connspec string
	protocols []string
	classes []ServiceProxyClass

	extension *ServiceProxyDiscoveryExtension

	rawClassRecords []byte
	rawCert []byte
	rawSig []byte

	timestamp HighResTimestamp

	clientM sync.Mutex
	client *Client
}

func (sp ServiceProxy)GetClient() (client *Client, err error) {
	sp.clientM.Lock()
	defer sp.clientM.Unlock()

	if sp.client == nil {
		var url *u.URL
		url,err = u.Parse(sp.connspec)
		if err != nil {
			return nil, err
		}

		sp.client,err = Dial(url.Host)
	}

	client = sp.client

	return
}

func (sp ServiceProxy)Ident() string {
	return sp.ident
}

func (sp ServiceProxy)BaseIdent() string {
	baseAndRest := strings.SplitN(sp.ident, ":", 2)
	if len(baseAndRest) != 2 {
		return sp.ident
	} else {
		return baseAndRest[0]
	}
}

func (sp ServiceProxy)ShortHostname() string {
	url,err := u.Parse(sp.connspec)
	if err != nil {
		panic("BOMBING OUT")
		return sp.connspec
	}

	hostParts := strings.Split(url.Host,":")
	if len(hostParts) != 2 {
		return sp.connspec
	}
	host := hostParts[0]

	names,err := net.LookupAddr(host)
	if err != nil {
		return host
	} else if len(names) == 0 {
		return host
	}

	return names[0]
}

func (sp ServiceProxy)ConnSpec() string {
	return sp.connspec
}

func (sp ServiceProxy)Sector() string {
	return sp.sector
}

func (sp *ServiceProxy)Classes() ([]ServiceProxyClass) {
	return sp.classes
}

type ServiceProxyClass struct {
	className string
	actions []ActionDescription
}

func (spc ServiceProxyClass)Name() string {
	return spc.className
}

func (spc ServiceProxyClass)Actions() ([]ActionDescription) {
	return spc.actions
}

type ActionDescription struct {
	actionName string
	crudTags string
	version int
}

func (ad ActionDescription)Name() string {
	return ad.actionName
}

func (ad ActionDescription)Version() int {
	return ad.version
}

func ServiceAsServiceProxy(serv *Service) (proxy *ServiceProxy) {
	proxy = new(ServiceProxy)
	proxy.version = 3
	proxy.ident = serv.name
	proxy.sector = serv.sector
	proxy.weight = 1
	proxy.announceInterval = defaultAnnounceInterval * 500
	proxy.connspec = fmt.Sprintf("beepish+tls://%s:%d", serv.listenerIP.To4().String(), serv.listenerPort)
	proxy.protocols = make([]string, 1, 1)
	proxy.protocols[0] = "json"
	proxy.classes = make([]ServiceProxyClass, 0)
	proxy.rawClassRecords = []byte("rawClassRecords")
	proxy.rawCert = []byte("rawCert")
	proxy.rawSig = []byte("rawSig")

	// { "Logger.info": [{ "name": "blah", "callback": foo() }] }
	for classAndActionName, serviceAction := range serv.actions {
		actionDotIndex := strings.LastIndex(classAndActionName, ".")
		// TODO: this is the only spot that could fail? shouldn't happen in any usage...
		if actionDotIndex == -1 {
			panic("bad action name")
		}
		className := classAndActionName[0:actionDotIndex]
		actionName := classAndActionName[actionDotIndex+1:len(classAndActionName)]

		newServiceProxyClass := ServiceProxyClass {
			className: className,
			actions: make([]ActionDescription, 0),
		}
		newServiceProxyClass.actions = append(newServiceProxyClass.actions, ActionDescription {
			actionName: actionName,
			crudTags: serviceAction.crudTags,
			version: serviceAction.version,
		})

		proxy.classes = append(proxy.classes, newServiceProxyClass)
	}

	timestamp,err := Gettimeofday()
	if err != nil {
		Error.Printf("error with high-res timestamp: `%s`", err)
		return nil
	}
	proxy.timestamp = timestamp

	return
}

func NewServiceProxy(classRecordsRaw []byte, certRaw []byte, sigRaw []byte) (proxy *ServiceProxy, err error) {
	proxy = new(ServiceProxy)
	proxy.rawClassRecords = classRecordsRaw
	proxy.rawCert = certRaw
	proxy.rawSig = sigRaw
	proxy.protocols = make([]string,0)

	var classRecords []json.RawMessage
	err = json.Unmarshal(classRecordsRaw, &classRecords)
	if err != nil {
		return
	}
	if len(classRecords) != 9 {
		err = errors.New( fmt.Sprintf("expected 9 entries in class record. got %d", len(classRecords)) )
	}

	// OMG, position-based, heterogenously typed values in an array suck to deal with.
	err = json.Unmarshal(classRecords[0], &proxy.version)
	if err != nil {
		return
	}

	err = json.Unmarshal(classRecords[1], &proxy.ident)
	if err != nil {
		return
	}

	err = json.Unmarshal(classRecords[2], &proxy.sector)
	if err != nil {
		return
	}

	err = json.Unmarshal(classRecords[3], &proxy.weight)
	if err != nil {
		return
	}

	err = json.Unmarshal(classRecords[4], &proxy.announceInterval)
	if err != nil {
		return
	}

	err = json.Unmarshal(classRecords[5], &proxy.connspec)
	if err != nil {
		return
	}

	var rawProtocols []*json.RawMessage
	err = json.Unmarshal(classRecords[6], &rawProtocols)
	if err != nil {
		return
	}

	// Skip object-looking stuff. We only care about strings for now
	for _,rawProtocol := range rawProtocols	{
		var tempStr string
		err := json.Unmarshal(*rawProtocol, &tempStr)
		if err != nil {

			var extension ServiceProxyDiscoveryExtension
			err = json.Unmarshal(*rawProtocol, &extension)
			if err != nil {
				fmt.Printf("could not parse: %s\n", string(*rawProtocol))
				continue
			}

			proxy.extension = &extension
		} else {
			proxy.protocols = append(proxy.protocols, tempStr)
		}
	}

	// fmt.Printf("proxy.protocols: %s\n", proxy.protocols)

	var rawClasses [][]json.RawMessage
	err = json.Unmarshal(classRecords[7], &rawClasses)
	if err != nil {
		return
	}
	classes := make([]ServiceProxyClass, len(rawClasses), len(rawClasses))
	proxy.classes = classes

	for i,rawClass := range rawClasses {
		if len(rawClass) < 2 {
			err = errors.New( fmt.Sprintf("expected rawClass to have at least 2 entries. was: `%s`", rawClass) )
			return nil, err
		}

		err = json.Unmarshal(rawClass[0], &classes[i].className)
		if err != nil {
			return nil, err
		}

		rawActionsSlice := rawClass[1:]
		classes[i].actions = make([]ActionDescription, len(rawActionsSlice), len(rawActionsSlice))

		for j,rawActionSpec := range rawActionsSlice {
			var actionsRawMessages []json.RawMessage
			err = json.Unmarshal(rawActionSpec, &actionsRawMessages)
			if err != nil {
				Error.Printf("could not parse: %s", rawActionSpec)
				return nil, err
			} else if len(actionsRawMessages) != 2 && len(actionsRawMessages) != 3 {
				err = errors.New( fmt.Sprintf("expected action spec to have 2 or 3 entries. got `%s` (%d)", actionsRawMessages, len(actionsRawMessages) ) )
			}

			err = json.Unmarshal(actionsRawMessages[0], &classes[i].actions[j].actionName)
			if err != nil {
				return nil, err
			}

			err = json.Unmarshal(actionsRawMessages[1], &classes[i].actions[j].crudTags)
			if err != nil {
				return nil, err
			}

			// TODO: it's gross that some of the services announce version
			// as a string.
			if len(actionsRawMessages) < 3 {
				// TODO: safe to assume a version-less thing is version 0?
				classes[i].actions[j].version = 0
			} else {
				err = json.Unmarshal(actionsRawMessages[2], &classes[i].actions[j].version)
				if err != nil {
					var versionStr string
					err = json.Unmarshal(actionsRawMessages[2], &versionStr)
					if err != nil {
						return nil, err
					}

					versionInt,err := strconv.ParseInt(versionStr, 10, 64)
					if err != nil {
						return nil, err
					}

					classes[i].actions[j].version = int(versionInt)
				}
			}
		}
	}

	proxy.client = nil // we connect on demand
	return
}

// 1) Verify signature of classRecords
// 2) Make sure the fingerprint is in authorized_services
// 3) Filter announced actions against authorized actions
func (proxy *ServiceProxy)Validate() (err error) {
	_, err = proxy.validateSignature()
	if err != nil {
		return
	}

	// See if we have this fingerprint in our authorized_services
	// TODO

	return
}

func (proxy *ServiceProxy)validateSignature() (hexSha1 string, err error) {
	decoded,_ := pem.Decode(proxy.rawCert)
	if decoded == nil {
		err = errors.New( fmt.Sprintf("could not find valid cert in `%s`", proxy.rawCert) )
		return
	}

	// Put pem in form useful for fingerprinting
	cert,err := x509.ParseCertificate(decoded.Bytes)
	if err != nil {
		err = fmt.Errorf("failed to parse certificate: `%s`", err)
		return
	}

	pkixInterface := cert.PublicKey
	rsaPubKey, ok := pkixInterface.(*rsa.PublicKey)
	if !ok {
		err = errors.New("could not cast parsed value to rsa.PublicKey")
		return
	}

	err = VerifySHA256(proxy.rawClassRecords, rsaPubKey, proxy.rawSig, false)
	if err != nil {
		return
	}

	hexSha1 = sha1FingerPrint(cert)
	return
}

func (proxy *ServiceProxy)GetConnection() (client *Client, err error) {
	if proxy.client != nil {
		client = proxy.client
		return
	}

	proxy.client,err = Dial(proxy.connspec)
	if err != nil {
		return
	}

	return
}

func (proxy *ServiceProxy)MarshalJSON() (b []byte, err error) {
	arr := make([]interface{},9)
	arr[0] = &proxy.version
  arr[1] = &proxy.ident
  arr[2] = &proxy.sector
  arr[3] = &proxy.weight
  arr[4] = &proxy.announceInterval
  arr[5] = &proxy.connspec
  arr[6] = &proxy.protocols

  // TODO: move this to two MarshalJSON interfaces for `ServiceProxyClass` and `ActionDescription`
  // doing so should remove manual copies and separate concerns
  //
  // Serialize actions in this format:
  // 	["bgdispatcher",["poll","",1],["reboot","",1],["report","",1]]
  classSpecs := make([][]interface{}, len(proxy.classes), len(proxy.classes))
  for i,class := range proxy.classes {
  	entry := make([]interface{}, 1+len(class.actions), 1+len(class.actions))
  	entry[0] = &class.className
  	for j,action := range class.actions {
  		actions := make([]interface{},3,3)

  		actionNameCopy := make([]byte, len(action.actionName))
  		copy(actionNameCopy, action.actionName)
  		actions[0] = string(actionNameCopy)
  		actions[1] = &action.crudTags
  		actions[2] = &action.version
  		entry[j+1] = &actions
  	}

  	classSpecs[i] = entry
  }
  arr[7] = &classSpecs

  arr[8] = &proxy.timestamp

	return json.Marshal(arr)
}
<commit_msg>Better explosion string<commit_after>package scamp

import (
  "encoding/json"
  "encoding/pem"
  "crypto/x509"
  "crypto/rsa"

  "strconv"

  "fmt"
  "errors"
  "strings"

  "sync"

  "net"
  u "net/url"
)

// Example:
// {"vmin":0,"vmaj":4,"acsec":[[7,"background"]],"acname":["_evaluate","_execute","_evaluate","_execute","_munge","_evaluate","_execute"],"acver":[[7,1]],"acenv":[[7,"json,jsonstore,extdirect"]],"acflag":[[7,""]],"acns":[[2,"Channel.Amazon.FeedInterchange"],[3,"Channel.Amazon.InvPush"],[2,"Channel.Amazon.OrderImport"]]}
type ServiceProxyDiscoveryExtension struct {
	Vmin   int           `json:"vmin"`
	Vmaj   int           `json:"vmaj"`
	AcSec  []interface{} `json:"acsec"`
	AcName []string      `json:"acname"`
	AcVer  []interface{} `json:acver"`
	AcEnv  []interface{} `json:acenv"`
	AcFlag []interface{} `json:"acflag"`
	AcNs   []interface{} `json:"acns"`
}

type ServiceProxy struct {
	version int
	ident string
	sector string
	weight int
	announceInterval int
	connspec string
	protocols []string
	classes []ServiceProxyClass

	extension *ServiceProxyDiscoveryExtension

	rawClassRecords []byte
	rawCert []byte
	rawSig []byte

	timestamp HighResTimestamp

	clientM sync.Mutex
	client *Client
}

func (sp ServiceProxy)GetClient() (client *Client, err error) {
	sp.clientM.Lock()
	defer sp.clientM.Unlock()

	if sp.client == nil {
		var url *u.URL
		url,err = u.Parse(sp.connspec)
		if err != nil {
			return nil, err
		}

		sp.client,err = Dial(url.Host)
	}

	client = sp.client

	return
}

func (sp ServiceProxy)Ident() string {
	return sp.ident
}

func (sp ServiceProxy)BaseIdent() string {
	baseAndRest := strings.SplitN(sp.ident, ":", 2)
	if len(baseAndRest) != 2 {
		return sp.ident
	} else {
		return baseAndRest[0]
	}
}

func (sp ServiceProxy)ShortHostname() string {
	url,err := u.Parse(sp.connspec)
	if err != nil {
		panic("BOMBING OUT")
		return sp.connspec
	}

	hostParts := strings.Split(url.Host,":")
	if len(hostParts) != 2 {
		return sp.connspec
	}
	host := hostParts[0]

	names,err := net.LookupAddr(host)
	if err != nil {
		return host
	} else if len(names) == 0 {
		return host
	}

	return names[0]
}

func (sp ServiceProxy)ConnSpec() string {
	return sp.connspec
}

func (sp ServiceProxy)Sector() string {
	return sp.sector
}

func (sp *ServiceProxy)Classes() ([]ServiceProxyClass) {
	return sp.classes
}

type ServiceProxyClass struct {
	className string
	actions []ActionDescription
}

func (spc ServiceProxyClass)Name() string {
	return spc.className
}

func (spc ServiceProxyClass)Actions() ([]ActionDescription) {
	return spc.actions
}

type ActionDescription struct {
	actionName string
	crudTags string
	version int
}

func (ad ActionDescription)Name() string {
	return ad.actionName
}

func (ad ActionDescription)Version() int {
	return ad.version
}

func ServiceAsServiceProxy(serv *Service) (proxy *ServiceProxy) {
	proxy = new(ServiceProxy)
	proxy.version = 3
	proxy.ident = serv.name
	proxy.sector = serv.sector
	proxy.weight = 1
	proxy.announceInterval = defaultAnnounceInterval * 500
	proxy.connspec = fmt.Sprintf("beepish+tls://%s:%d", serv.listenerIP.To4().String(), serv.listenerPort)
	proxy.protocols = make([]string, 1, 1)
	proxy.protocols[0] = "json"
	proxy.classes = make([]ServiceProxyClass, 0)
	proxy.rawClassRecords = []byte("rawClassRecords")
	proxy.rawCert = []byte("rawCert")
	proxy.rawSig = []byte("rawSig")

	// { "Logger.info": [{ "name": "blah", "callback": foo() }] }
	for classAndActionName, serviceAction := range serv.actions {
		actionDotIndex := strings.LastIndex(classAndActionName, ".")
		// TODO: this is the only spot that could fail? shouldn't happen in any usage...
		if actionDotIndex == -1 {
			panic(fmt.Sprintf("bad action name: `%s` (no dot found)", classAndActionName))
		}
		className := classAndActionName[0:actionDotIndex]
		actionName := classAndActionName[actionDotIndex+1:len(classAndActionName)]

		newServiceProxyClass := ServiceProxyClass {
			className: className,
			actions: make([]ActionDescription, 0),
		}
		newServiceProxyClass.actions = append(newServiceProxyClass.actions, ActionDescription {
			actionName: actionName,
			crudTags: serviceAction.crudTags,
			version: serviceAction.version,
		})

		proxy.classes = append(proxy.classes, newServiceProxyClass)
	}

	timestamp,err := Gettimeofday()
	if err != nil {
		Error.Printf("error with high-res timestamp: `%s`", err)
		return nil
	}
	proxy.timestamp = timestamp

	return
}

func NewServiceProxy(classRecordsRaw []byte, certRaw []byte, sigRaw []byte) (proxy *ServiceProxy, err error) {
	proxy = new(ServiceProxy)
	proxy.rawClassRecords = classRecordsRaw
	proxy.rawCert = certRaw
	proxy.rawSig = sigRaw
	proxy.protocols = make([]string,0)

	var classRecords []json.RawMessage
	err = json.Unmarshal(classRecordsRaw, &classRecords)
	if err != nil {
		return
	}
	if len(classRecords) != 9 {
		err = errors.New( fmt.Sprintf("expected 9 entries in class record. got %d", len(classRecords)) )
	}

	// OMG, position-based, heterogenously typed values in an array suck to deal with.
	err = json.Unmarshal(classRecords[0], &proxy.version)
	if err != nil {
		return
	}

	err = json.Unmarshal(classRecords[1], &proxy.ident)
	if err != nil {
		return
	}

	err = json.Unmarshal(classRecords[2], &proxy.sector)
	if err != nil {
		return
	}

	err = json.Unmarshal(classRecords[3], &proxy.weight)
	if err != nil {
		return
	}

	err = json.Unmarshal(classRecords[4], &proxy.announceInterval)
	if err != nil {
		return
	}

	err = json.Unmarshal(classRecords[5], &proxy.connspec)
	if err != nil {
		return
	}

	var rawProtocols []*json.RawMessage
	err = json.Unmarshal(classRecords[6], &rawProtocols)
	if err != nil {
		return
	}

	// Skip object-looking stuff. We only care about strings for now
	for _,rawProtocol := range rawProtocols	{
		var tempStr string
		err := json.Unmarshal(*rawProtocol, &tempStr)
		if err != nil {

			var extension ServiceProxyDiscoveryExtension
			err = json.Unmarshal(*rawProtocol, &extension)
			if err != nil {
				fmt.Printf("could not parse: %s\n", string(*rawProtocol))
				continue
			}

			proxy.extension = &extension
		} else {
			proxy.protocols = append(proxy.protocols, tempStr)
		}
	}

	// fmt.Printf("proxy.protocols: %s\n", proxy.protocols)

	var rawClasses [][]json.RawMessage
	err = json.Unmarshal(classRecords[7], &rawClasses)
	if err != nil {
		return
	}
	classes := make([]ServiceProxyClass, len(rawClasses), len(rawClasses))
	proxy.classes = classes

	for i,rawClass := range rawClasses {
		if len(rawClass) < 2 {
			err = errors.New( fmt.Sprintf("expected rawClass to have at least 2 entries. was: `%s`", rawClass) )
			return nil, err
		}

		err = json.Unmarshal(rawClass[0], &classes[i].className)
		if err != nil {
			return nil, err
		}

		rawActionsSlice := rawClass[1:]
		classes[i].actions = make([]ActionDescription, len(rawActionsSlice), len(rawActionsSlice))

		for j,rawActionSpec := range rawActionsSlice {
			var actionsRawMessages []json.RawMessage
			err = json.Unmarshal(rawActionSpec, &actionsRawMessages)
			if err != nil {
				Error.Printf("could not parse: %s", rawActionSpec)
				return nil, err
			} else if len(actionsRawMessages) != 2 && len(actionsRawMessages) != 3 {
				err = errors.New( fmt.Sprintf("expected action spec to have 2 or 3 entries. got `%s` (%d)", actionsRawMessages, len(actionsRawMessages) ) )
			}

			err = json.Unmarshal(actionsRawMessages[0], &classes[i].actions[j].actionName)
			if err != nil {
				return nil, err
			}

			err = json.Unmarshal(actionsRawMessages[1], &classes[i].actions[j].crudTags)
			if err != nil {
				return nil, err
			}

			// TODO: it's gross that some of the services announce version
			// as a string.
			if len(actionsRawMessages) < 3 {
				// TODO: safe to assume a version-less thing is version 0?
				classes[i].actions[j].version = 0
			} else {
				err = json.Unmarshal(actionsRawMessages[2], &classes[i].actions[j].version)
				if err != nil {
					var versionStr string
					err = json.Unmarshal(actionsRawMessages[2], &versionStr)
					if err != nil {
						return nil, err
					}

					versionInt,err := strconv.ParseInt(versionStr, 10, 64)
					if err != nil {
						return nil, err
					}

					classes[i].actions[j].version = int(versionInt)
				}
			}
		}
	}

	proxy.client = nil // we connect on demand
	return
}

// 1) Verify signature of classRecords
// 2) Make sure the fingerprint is in authorized_services
// 3) Filter announced actions against authorized actions
func (proxy *ServiceProxy)Validate() (err error) {
	_, err = proxy.validateSignature()
	if err != nil {
		return
	}

	// See if we have this fingerprint in our authorized_services
	// TODO

	return
}

func (proxy *ServiceProxy)validateSignature() (hexSha1 string, err error) {
	decoded,_ := pem.Decode(proxy.rawCert)
	if decoded == nil {
		err = errors.New( fmt.Sprintf("could not find valid cert in `%s`", proxy.rawCert) )
		return
	}

	// Put pem in form useful for fingerprinting
	cert,err := x509.ParseCertificate(decoded.Bytes)
	if err != nil {
		err = fmt.Errorf("failed to parse certificate: `%s`", err)
		return
	}

	pkixInterface := cert.PublicKey
	rsaPubKey, ok := pkixInterface.(*rsa.PublicKey)
	if !ok {
		err = errors.New("could not cast parsed value to rsa.PublicKey")
		return
	}

	err = VerifySHA256(proxy.rawClassRecords, rsaPubKey, proxy.rawSig, false)
	if err != nil {
		return
	}

	hexSha1 = sha1FingerPrint(cert)
	return
}

func (proxy *ServiceProxy)GetConnection() (client *Client, err error) {
	if proxy.client != nil {
		client = proxy.client
		return
	}

	proxy.client,err = Dial(proxy.connspec)
	if err != nil {
		return
	}

	return
}

func (proxy *ServiceProxy)MarshalJSON() (b []byte, err error) {
	arr := make([]interface{},9)
	arr[0] = &proxy.version
  arr[1] = &proxy.ident
  arr[2] = &proxy.sector
  arr[3] = &proxy.weight
  arr[4] = &proxy.announceInterval
  arr[5] = &proxy.connspec
  arr[6] = &proxy.protocols

  // TODO: move this to two MarshalJSON interfaces for `ServiceProxyClass` and `ActionDescription`
  // doing so should remove manual copies and separate concerns
  //
  // Serialize actions in this format:
  // 	["bgdispatcher",["poll","",1],["reboot","",1],["report","",1]]
  classSpecs := make([][]interface{}, len(proxy.classes), len(proxy.classes))
  for i,class := range proxy.classes {
  	entry := make([]interface{}, 1+len(class.actions), 1+len(class.actions))
  	entry[0] = &class.className
  	for j,action := range class.actions {
  		actions := make([]interface{},3,3)

  		actionNameCopy := make([]byte, len(action.actionName))
  		copy(actionNameCopy, action.actionName)
  		actions[0] = string(actionNameCopy)
  		actions[1] = &action.crudTags
  		actions[2] = &action.version
  		entry[j+1] = &actions
  	}

  	classSpecs[i] = entry
  }
  arr[7] = &classSpecs

  arr[8] = &proxy.timestamp

	return json.Marshal(arr)
}
<|endoftext|>


<commit_before>// common.go
//
// This file implements the configuration part for when you need the API
// key to modify things in the Atlas configuration and manage measurements.

package atlas

import (
	"encoding/json"
	"errors"
	"fmt"
	"github.com/sendgrid/rest"
	"log"
	"regexp"
)

const (
	apiEndpoint = "https://atlas.ripe.net/api/v2"

	ourVersion = "0.11"
)

// APIKey is the API key
var APIKey string

// ErrInvalidMeasurementType is a new error
var ErrInvalidMeasurementType = errors.New("invalid measurement type")

// ErrInvalidAPIKey is returned when the key is invalid
var ErrInvalidAPIKey = errors.New("invalid API key")

// SetAuth stores the credentials for later use
func SetAuth(key string) {
	APIKey = key
}

// HasAPIKey returns whether an API key is stored
func HasAPIKey() (string, bool) {
	if APIKey == "" {
		return "", false
	}
	return APIKey, true
}

// GetVersion returns the API wrapper version
func GetVersion() string {
	return ourVersion
}

// getPageNum returns the value of the page= parameter
func getPageNum(url string) (page string) {
	re := regexp.MustCompile(`page=(\d+)`)
	if m := re.FindStringSubmatch(url); len(m) >= 1 {
		return m[1]
	}
	return ""
}

// prepareRequest insert all pre-defined stuff
func prepareRequest(what string) (req rest.Request) {
	endPoint := apiEndpoint + fmt.Sprintf("/%s/", what)
	key, ok := HasAPIKey()

	// Add at least one option, the APIkey if present
	hdrs := make(map[string]string)
	opts := make(map[string]string)

	// Insert our sig
	hdrs["User-Agent"] = fmt.Sprintf("ripe-atlas/%s", ourVersion)

	// Insert key
	if ok {
		opts["key"] = key
	}

	req = rest.Request{
		BaseURL:     endPoint,
		Headers:     hdrs,
		QueryParams: opts,
	}
	return
}

// handleAPIResponse check status code & errors from the API
func handleAPIResponse(r *rest.Response) (err error) {
	if r == nil {
		return fmt.Errorf("Error: r is nil!")
	}

	// Everything is fine
	if r.StatusCode == 0 {
		return nil
	}

	// Everything is fine too
	if r.StatusCode >= 200 && r.StatusCode <= 299 {
		return nil
	}

	// Check this condition
	if r.StatusCode >= 300 && r.StatusCode <= 399 {
		var aerr APIError

		err = json.Unmarshal([]byte(r.Body), &aerr)
		log.Printf("Info 3XX status: %d code: %d - r:%v\n",
			aerr.Error.Status,
			aerr.Error.Code,
			aerr.Error.Detail)
		return nil
	}

	// EVerything else is an error
	var aerr APIError

	err = json.Unmarshal([]byte(r.Body), &aerr)
	err = fmt.Errorf("status: %d code: %d - r:%v",
		aerr.Error.Status,
		aerr.Error.Code,
		aerr.Error.Detail)
	return
}
<commit_msg>Use new config.  Increase API version.<commit_after>// common.go
//
// This file implements the configuration part for when you need the API
// key to modify things in the Atlas configuration and manage measurements.

package atlas

import (
	"encoding/json"
	"fmt"
	"github.com/sendgrid/rest"
	"log"
	"regexp"
)

const (
	apiEndpoint = "https://atlas.ripe.net/api/v2"

	ourVersion = "0.12"
)

// HasAPIKey returns whether an API key is stored
func HasAPIKey() (string, bool) {
	if ctx.config.APIKey == "" {
		return "", false
	}
	return ctx.config.APIKey, true
}

// GetVersion returns the API wrapper version
func GetVersion() string {
	return ourVersion
}

// getPageNum returns the value of the page= parameter
func getPageNum(url string) (page string) {
	re := regexp.MustCompile(`page=(\d+)`)
	if m := re.FindStringSubmatch(url); len(m) >= 1 {
		return m[1]
	}
	return ""
}

// prepareRequest insert all pre-defined stuff
func prepareRequest(what string) (req rest.Request) {
	endPoint := apiEndpoint + fmt.Sprintf("/%s/", what)
	key, ok := HasAPIKey()

	// Add at least one option, the APIkey if present
	hdrs := make(map[string]string)
	opts := make(map[string]string)

	// Insert our sig
	hdrs["User-Agent"] = fmt.Sprintf("ripe-atlas/%s", ourVersion)

	// Insert key
	if ok {
		opts["key"] = key
	}

	req = rest.Request{
		BaseURL:     endPoint,
		Headers:     hdrs,
		QueryParams: opts,
	}
	return
}

// handleAPIResponse check status code & errors from the API
func handleAPIResponse(r *rest.Response) (err error) {
	if r == nil {
		return fmt.Errorf("Error: r is nil!")
	}

	// Everything is fine
	if r.StatusCode == 0 {
		return nil
	}

	// Everything is fine too
	if r.StatusCode >= 200 && r.StatusCode <= 299 {
		return nil
	}

	// Check this condition
	if r.StatusCode >= 300 && r.StatusCode <= 399 {
		var aerr APIError

		err = json.Unmarshal([]byte(r.Body), &aerr)
		log.Printf("Info 3XX status: %d code: %d - r:%v\n",
			aerr.Error.Status,
			aerr.Error.Code,
			aerr.Error.Detail)
		return nil
	}

	// EVerything else is an error
	var aerr APIError

	err = json.Unmarshal([]byte(r.Body), &aerr)
	err = fmt.Errorf("status: %d code: %d - r:%v",
		aerr.Error.Status,
		aerr.Error.Code,
		aerr.Error.Detail)
	return
}
<|endoftext|>


<commit_before>package main

import (
	"crypto/sha256"
	"encoding/xml"
	"fmt"
	"io"
	"reflect"
	"sort"
	"strconv"
	"strings"
)

type Configuration struct {
	Version      int                       `xml:"version,attr" default:"1"`
	Repositories []RepositoryConfiguration `xml:"repository"`
	Options      OptionsConfiguration      `xml:"options"`
	XMLName      xml.Name                  `xml:"configuration" json:"-"`
}

type RepositoryConfiguration struct {
	Directory string              `xml:"directory,attr"`
	Nodes     []NodeConfiguration `xml:"node"`
}

type NodeConfiguration struct {
	NodeID    string   `xml:"id,attr"`
	Name      string   `xml:"name,attr"`
	Addresses []string `xml:"address"`
}

type OptionsConfiguration struct {
	ListenAddress      []string `xml:"listenAddress" default:":22000" ini:"listen-address"`
	ReadOnly           bool     `xml:"readOnly" ini:"read-only"`
	AllowDelete        bool     `xml:"allowDelete" default:"true" ini:"allow-delete"`
	FollowSymlinks     bool     `xml:"followSymlinks" default:"true" ini:"follow-symlinks"`
	GUIEnabled         bool     `xml:"guiEnabled" default:"true" ini:"gui-enabled"`
	GUIAddress         string   `xml:"guiAddress" default:"127.0.0.1:8080" ini:"gui-address"`
	GlobalAnnServer    string   `xml:"globalAnnounceServer" default:"syncthing.nym.se:22025" ini:"global-announce-server"`
	GlobalAnnEnabled   bool     `xml:"globalAnnounceEnabled" default:"true" ini:"global-announce-enabled"`
	LocalAnnEnabled    bool     `xml:"localAnnounceEnabled" default:"true" ini:"local-announce-enabled"`
	ParallelRequests   int      `xml:"parallelRequests" default:"16" ini:"parallel-requests"`
	MaxSendKbps        int      `xml:"maxSendKbps" ini:"max-send-kbps"`
	RescanIntervalS    int      `xml:"rescanIntervalS" default:"60" ini:"rescan-interval"`
	ReconnectIntervalS int      `xml:"reconnectionIntervalS" default:"60" ini:"reconnection-interval"`
	MaxChangeKbps      int      `xml:"maxChangeKbps" default:"1000" ini:"max-change-bw"`
}

func setDefaults(data interface{}) error {
	s := reflect.ValueOf(data).Elem()
	t := s.Type()

	for i := 0; i < s.NumField(); i++ {
		f := s.Field(i)
		tag := t.Field(i).Tag

		v := tag.Get("default")
		if len(v) > 0 {
			switch f.Interface().(type) {
			case string:
				f.SetString(v)

			case []string:
				rv := reflect.MakeSlice(reflect.TypeOf([]string{}), 1, 1)
				rv.Index(0).SetString(v)
				f.Set(rv)

			case int:
				i, err := strconv.ParseInt(v, 10, 64)
				if err != nil {
					return err
				}
				f.SetInt(i)

			case bool:
				f.SetBool(v == "true")

			default:
				panic(f.Type())
			}
		}
	}
	return nil
}

func readConfigINI(m map[string]string, data interface{}) error {
	s := reflect.ValueOf(data).Elem()
	t := s.Type()

	for i := 0; i < s.NumField(); i++ {
		f := s.Field(i)
		tag := t.Field(i).Tag

		name := tag.Get("ini")
		if len(name) == 0 {
			name = strings.ToLower(t.Field(i).Name)
		}

		if v, ok := m[name]; ok {
			switch f.Interface().(type) {
			case string:
				f.SetString(v)

			case int:
				i, err := strconv.ParseInt(v, 10, 64)
				if err == nil {
					f.SetInt(i)
				}

			case bool:
				f.SetBool(v == "true")

			default:
				panic(f.Type())
			}
		}
	}
	return nil
}

func writeConfigXML(wr io.Writer, cfg Configuration) error {
	e := xml.NewEncoder(wr)
	e.Indent("", "    ")
	err := e.Encode(cfg)
	if err != nil {
		return err
	}
	_, err = wr.Write([]byte("\n"))
	return err
}

func uniqueStrings(ss []string) []string {
	var m = make(map[string]bool, len(ss))
	for _, s := range ss {
		m[s] = true
	}

	var us = make([]string, 0, len(m))
	for k := range m {
		us = append(us, k)
	}

	return us
}

func readConfigXML(rd io.Reader) (Configuration, error) {
	var cfg Configuration

	setDefaults(&cfg)
	setDefaults(&cfg.Options)

	var err error
	if rd != nil {
		err = xml.NewDecoder(rd).Decode(&cfg)
	}

	cfg.Options.ListenAddress = uniqueStrings(cfg.Options.ListenAddress)
	return cfg, err
}

type NodeConfigurationList []NodeConfiguration

func (l NodeConfigurationList) Less(a, b int) bool {
	return l[a].NodeID < l[b].NodeID
}
func (l NodeConfigurationList) Swap(a, b int) {
	l[a], l[b] = l[b], l[a]
}
func (l NodeConfigurationList) Len() int {
	return len(l)
}

func clusterHash(nodes []NodeConfiguration) string {
	sort.Sort(NodeConfigurationList(nodes))
	h := sha256.New()
	for _, n := range nodes {
		h.Write([]byte(n.NodeID))
	}
	return fmt.Sprintf("%x", h.Sum(nil))
}

func cleanNodeList(nodes []NodeConfiguration, myID string) []NodeConfiguration {
	var myIDExists bool
	for _, node := range nodes {
		if node.NodeID == myID {
			myIDExists = true
			break
		}
	}

	if !myIDExists {
		nodes = append(nodes, NodeConfiguration{
			NodeID:    myID,
			Addresses: []string{"dynamic"},
			Name:      "",
		})
	}

	sort.Sort(NodeConfigurationList(nodes))

	return nodes
}
<commit_msg>Change default announce server to announce.syncthing.net<commit_after>package main

import (
	"crypto/sha256"
	"encoding/xml"
	"fmt"
	"io"
	"reflect"
	"sort"
	"strconv"
	"strings"
)

type Configuration struct {
	Version      int                       `xml:"version,attr" default:"1"`
	Repositories []RepositoryConfiguration `xml:"repository"`
	Options      OptionsConfiguration      `xml:"options"`
	XMLName      xml.Name                  `xml:"configuration" json:"-"`
}

type RepositoryConfiguration struct {
	Directory string              `xml:"directory,attr"`
	Nodes     []NodeConfiguration `xml:"node"`
}

type NodeConfiguration struct {
	NodeID    string   `xml:"id,attr"`
	Name      string   `xml:"name,attr"`
	Addresses []string `xml:"address"`
}

type OptionsConfiguration struct {
	ListenAddress      []string `xml:"listenAddress" default:":22000" ini:"listen-address"`
	ReadOnly           bool     `xml:"readOnly" ini:"read-only"`
	AllowDelete        bool     `xml:"allowDelete" default:"true" ini:"allow-delete"`
	FollowSymlinks     bool     `xml:"followSymlinks" default:"true" ini:"follow-symlinks"`
	GUIEnabled         bool     `xml:"guiEnabled" default:"true" ini:"gui-enabled"`
	GUIAddress         string   `xml:"guiAddress" default:"127.0.0.1:8080" ini:"gui-address"`
	GlobalAnnServer    string   `xml:"globalAnnounceServer" default:"announce.syncthing.net:22025" ini:"global-announce-server"`
	GlobalAnnEnabled   bool     `xml:"globalAnnounceEnabled" default:"true" ini:"global-announce-enabled"`
	LocalAnnEnabled    bool     `xml:"localAnnounceEnabled" default:"true" ini:"local-announce-enabled"`
	ParallelRequests   int      `xml:"parallelRequests" default:"16" ini:"parallel-requests"`
	MaxSendKbps        int      `xml:"maxSendKbps" ini:"max-send-kbps"`
	RescanIntervalS    int      `xml:"rescanIntervalS" default:"60" ini:"rescan-interval"`
	ReconnectIntervalS int      `xml:"reconnectionIntervalS" default:"60" ini:"reconnection-interval"`
	MaxChangeKbps      int      `xml:"maxChangeKbps" default:"1000" ini:"max-change-bw"`
}

func setDefaults(data interface{}) error {
	s := reflect.ValueOf(data).Elem()
	t := s.Type()

	for i := 0; i < s.NumField(); i++ {
		f := s.Field(i)
		tag := t.Field(i).Tag

		v := tag.Get("default")
		if len(v) > 0 {
			switch f.Interface().(type) {
			case string:
				f.SetString(v)

			case []string:
				rv := reflect.MakeSlice(reflect.TypeOf([]string{}), 1, 1)
				rv.Index(0).SetString(v)
				f.Set(rv)

			case int:
				i, err := strconv.ParseInt(v, 10, 64)
				if err != nil {
					return err
				}
				f.SetInt(i)

			case bool:
				f.SetBool(v == "true")

			default:
				panic(f.Type())
			}
		}
	}
	return nil
}

func readConfigINI(m map[string]string, data interface{}) error {
	s := reflect.ValueOf(data).Elem()
	t := s.Type()

	for i := 0; i < s.NumField(); i++ {
		f := s.Field(i)
		tag := t.Field(i).Tag

		name := tag.Get("ini")
		if len(name) == 0 {
			name = strings.ToLower(t.Field(i).Name)
		}

		if v, ok := m[name]; ok {
			switch f.Interface().(type) {
			case string:
				f.SetString(v)

			case int:
				i, err := strconv.ParseInt(v, 10, 64)
				if err == nil {
					f.SetInt(i)
				}

			case bool:
				f.SetBool(v == "true")

			default:
				panic(f.Type())
			}
		}
	}
	return nil
}

func writeConfigXML(wr io.Writer, cfg Configuration) error {
	e := xml.NewEncoder(wr)
	e.Indent("", "    ")
	err := e.Encode(cfg)
	if err != nil {
		return err
	}
	_, err = wr.Write([]byte("\n"))
	return err
}

func uniqueStrings(ss []string) []string {
	var m = make(map[string]bool, len(ss))
	for _, s := range ss {
		m[s] = true
	}

	var us = make([]string, 0, len(m))
	for k := range m {
		us = append(us, k)
	}

	return us
}

func readConfigXML(rd io.Reader) (Configuration, error) {
	var cfg Configuration

	setDefaults(&cfg)
	setDefaults(&cfg.Options)

	var err error
	if rd != nil {
		err = xml.NewDecoder(rd).Decode(&cfg)
	}

	cfg.Options.ListenAddress = uniqueStrings(cfg.Options.ListenAddress)
	return cfg, err
}

type NodeConfigurationList []NodeConfiguration

func (l NodeConfigurationList) Less(a, b int) bool {
	return l[a].NodeID < l[b].NodeID
}
func (l NodeConfigurationList) Swap(a, b int) {
	l[a], l[b] = l[b], l[a]
}
func (l NodeConfigurationList) Len() int {
	return len(l)
}

func clusterHash(nodes []NodeConfiguration) string {
	sort.Sort(NodeConfigurationList(nodes))
	h := sha256.New()
	for _, n := range nodes {
		h.Write([]byte(n.NodeID))
	}
	return fmt.Sprintf("%x", h.Sum(nil))
}

func cleanNodeList(nodes []NodeConfiguration, myID string) []NodeConfiguration {
	var myIDExists bool
	for _, node := range nodes {
		if node.NodeID == myID {
			myIDExists = true
			break
		}
	}

	if !myIDExists {
		nodes = append(nodes, NodeConfiguration{
			NodeID:    myID,
			Addresses: []string{"dynamic"},
			Name:      "",
		})
	}

	sort.Sort(NodeConfigurationList(nodes))

	return nodes
}
<|endoftext|>


<commit_before>package gold

import (
	"code.google.com/p/gcfg"
)

type Config struct {
	Main
}

type Main struct {
	DataBase          string
	BlogTheme         string
	BlogTitle         string
	BlogSubtitle      string
	BlogUrl           string
	BlogOwner         string
	BlogRights        string
	CaptchaPublic     string
	CaptchaPrivate    string
	CommentLength     int
	CommentsAllowed   bool
	SmtpServer        string
	SmtpSender        string
	ArticlesPerPage   int
	GoogleAnalyticsId string
	GoogleWebmasterId string
	TagsInCloud       int
}

func ReadConf(fname string) (*Config, error) {
	c := new(Config)
	if err := gcfg.ReadFileInto(c, fname); err != nil {
		return &Config{}, err
	}
	return c, nil
}
<commit_msg>split config<commit_after>package gold

import (
	"code.google.com/p/gcfg"
)

type settings struct {
	ArticlesPerPage int
	TagsInCloud int
	Db string
}

type blog struct {
	Title string
	Subtitle string
	Url string
	Theme string
	Owner string
	Rights string
}

type captcha struct {
	Public string
	Private string
}

type comments struct {
	Maxlen int
	Allowed bool
}

type smtp struct {
	Server string
	Sender string
}

type google struct {
	AnalyticsID string
	WebmasterID string
}

type Config struct {
	Settings settings
	Blog blog
	Captcha captcha
	Comments comments
	Smtp smtp
	Google google
}

func ReadConf(fname string) (*Config, error) {
	c := new(Config)
	if err := gcfg.ReadFileInto(c, fname); err != nil {
		return &Config{}, err
	}
	return c, nil
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"fmt"
	"net"
	"os"
	"path/filepath"
	"regexp"
	"runtime"

	toml "github.com/BurntSushi/toml"
	"github.com/boot2docker/boot2docker-cli/driver"
	flag "github.com/ogier/pflag"
)

var (
	// Pattern to parse a key=value line in config profile.
	reFlagLine = regexp.MustCompile(`^\s*(\w+)\s*=\s*([^#;]+)`)
	B2D        = driver.MachineConfig{}
)

func homeDir() (string, error) {
	dir := ""

	// *nix and MSYS Windows
	if dir = os.Getenv("HOME"); dir == "" {
		// Windows (if not running under MSYS)
		dir = os.Getenv("USERPROFILE")
	}
	if _, err := os.Stat(dir); err != nil {
		return "", err
	}

	return dir, nil
}

func cfgDir(name string) (string, error) {
	if name == ".boot2docker" {
		if b2dDir := os.Getenv("BOOT2DOCKER_DIR"); b2dDir != "" {
			return b2dDir, nil
		}
	}

	dir, err := homeDir()
	if err != nil {
		return "", err
	}
	dir = filepath.Join(dir, name)
	if err := os.MkdirAll(dir, 0755); err != nil {
		return "", err
	}
	return dir, nil
}

func cfgFilename(dir string) string {
	filename := os.Getenv("BOOT2DOCKER_PROFILE")
	if filename == "" {
		filename = filepath.Join(dir, "profile")
	}
	return filename
}

// Write configuration set by the combination of profile and flags
//    Should result in a format that can be piped into a profile file
func printConfig() string {
	var buf bytes.Buffer
	e := toml.NewEncoder(&buf)
	err := e.Encode(B2D)
	if err != nil {
		return ""
	}
	return buf.String()
}

// Read configuration from both profile and flags. Flags override profile.
func config() (*flag.FlagSet, error) {
	dir, err := cfgDir(".boot2docker")
	if err != nil {
		return nil, fmt.Errorf("failed to get boot2docker directory: %s", err)
	}

	flags := flag.NewFlagSet(os.Args[0], flag.ContinueOnError)
	flags.Usage = func() { usageLong(flags) }

	driver.ConfigFlags(&B2D, flags)

	// Add the generic flags

	flags.StringVar(&B2D.VM, "vm", "boot2docker-vm", "virtual machine name.")
	// removed for now, requires re-parsing a new config file which is too messy
	//flags.StringVarP(&B2D.Dir, "dir", "d", dir, "boot2docker config directory.")
	B2D.Dir = dir
	flags.StringVar(&B2D.ISOURL, "iso-url", "https://api.github.com/repos/boot2docker/boot2docker/releases", "source URL to provision the boot2docker ISO image.")
	flags.StringVar(&B2D.ISO, "iso", filepath.Join(dir, "boot2docker.iso"), "path to boot2docker ISO image.")

	// Sven disabled this, as it is broken - if I user with a fresh computer downloads
	// just the boot2docker-cli, and then runs `boot2docker --init ip`, we create a vm
	// which cannot run, because it fails to have have the boot2docker.iso and the ssh keys
	B2D.Init = false
	//flags.BoolVarP(&B2D.Init, "init", "i", false, "auto initialize vm instance.")

	flags.BoolVarP(&B2D.Verbose, "verbose", "v", false, "display verbose command invocations.")
	flags.StringVar(&B2D.Driver, "driver", "virtualbox", "hypervisor driver.")
	flags.StringVar(&B2D.SSH, "ssh", "ssh", "path to SSH client utility.")
	flags.StringVar(&B2D.SSHGen, "ssh-keygen", "ssh-keygen", "path to ssh-keygen utility.")

	sshdir, _ := cfgDir(".ssh")
	flags.StringVar(&B2D.SSHKey, "sshkey", filepath.Join(sshdir, "id_boot2docker"), "path to SSH key to use.")
	flags.UintVarP(&B2D.DiskSize, "disksize", "s", 20000, "boot2docker disk image size (in MB).")
	flags.UintVarP(&B2D.Memory, "memory", "m", 2048, "virtual machine memory size (in MB).")
	flags.Uint16Var(&B2D.SSHPort, "sshport", 2022, "host SSH port (forward to port 22 in VM).")
	flags.Uint16Var(&B2D.DockerPort, "dockerport", 0, "host Docker port (forward to port 2375 in VM). (deprecated - use with care)")
	flags.IPVar(&B2D.HostIP, "hostip", net.ParseIP("192.168.59.3"), "VirtualBox host-only network IP address.")
	flags.IPMaskVar(&B2D.NetMask, "netmask", flag.ParseIPv4Mask("255.255.255.0"), "VirtualBox host-only network mask.")
	flags.BoolVar(&B2D.DHCPEnabled, "dhcp", true, "enable VirtualBox host-only network DHCP.")
	flags.IPVar(&B2D.DHCPIP, "dhcpip", net.ParseIP("192.168.59.99"), "VirtualBox host-only network DHCP server address.")
	flags.IPVar(&B2D.LowerIP, "lowerip", net.ParseIP("192.168.59.103"), "VirtualBox host-only network DHCP lower bound.")
	flags.IPVar(&B2D.UpperIP, "upperip", net.ParseIP("192.168.59.254"), "VirtualBox host-only network DHCP upper bound.")

	flags.IntVar(&B2D.Waittime, "waittime", 300, "Time in milliseconds to wait between port knocking retries during 'start'")
	flags.IntVar(&B2D.Retries, "retries", 40, "number of port knocking retries during 'start'")

	if runtime.GOOS != "windows" {
		//SerialFile ~~ filepath.Join(dir, B2D.vm+".sock")
		flags.StringVar(&B2D.SerialFile, "serialfile", "", "path to the serial socket/pipe.")
		flags.BoolVar(&B2D.Serial, "serial", false, "try serial console to get IP address (experimental)")
	} else {
		B2D.Serial = false
	}

	// Set the defaults
	if err := flags.Parse([]string{}); err != nil {
		return nil, err
	}
	// Over-ride from the profile file
	filename := cfgFilename(B2D.Dir)
	if _, err := os.Lstat(filename); err == nil {
		if _, err := toml.DecodeFile(filename, &B2D); err != nil {
			return nil, err
		}
	}

	if B2D.SerialFile == "" {
		if runtime.GOOS == "windows" {
			//SerialFile ~~ filepath.Join(dir, B2D.vm+".sock")
			B2D.SerialFile = `\\.\pipe\` + B2D.VM
		} else {
			B2D.SerialFile = filepath.Join(dir, B2D.VM+".sock")
		}
	}
	// for cmd==ssh only:
	// only pass the params up to and including the `ssh` command - after that,
	// there might be other -flags that are destined for the ssh cmd
	sshIdx := 1
	for sshIdx < len(os.Args) && os.Args[sshIdx-1] != "ssh" {
		sshIdx++
	}
	// Command-line overrides profile config.
	if err := flags.Parse(os.Args[1:sshIdx]); err != nil {
		return nil, err
	}

	leftovers := flags.Args()

	if B2D.Verbose || (len(leftovers) > 0 && leftovers[0] == "version") {
		fmt.Printf("Boot2Docker-cli version: %s\nGit commit: %s\n", Version, GitSHA)
	}

	return flags, nil
}

func usageShort() {
	fmt.Fprintf(os.Stderr, "Usage: %s [<options>] {help|init|up|ssh|save|down|poweroff|reset|restart|config|status|info|ip|socket|shellinit|delete|download|upgrade|version} [<args>]\n", os.Args[0])
}

func usageLong(flags *flag.FlagSet) {
	// NOTE: the help message uses spaces, not tabs for indentation!
	fmt.Fprintf(os.Stderr, `Usage: %s [<options>] <command> [<args>]

Boot2Docker management utility.

Commands:
   init                Create a new Boot2Docker VM.
   up|start|boot       Start VM from any states.
   ssh [ssh-command]   Login to VM via SSH.
   save|suspend        Suspend VM and save state to disk.
   down|stop|halt      Gracefully shutdown the VM.
   restart             Gracefully reboot the VM.
   poweroff            Forcefully power off the VM (may corrupt disk image).
   reset               Forcefully power cycle the VM (may corrupt disk image).
   delete|destroy      Delete Boot2Docker VM and its disk image.
   config|cfg          Show selected profile file settings.
   info                Display detailed information of VM.
   ip                  Display the IP address of the VM's Host-only network.
   socket              Display the DOCKER_HOST socket to connect to.
   shellinit           Display the shell command to set up the Docker client.
   status              Display current state of VM.
   download            Download Boot2Docker ISO image.
   upgrade             Upgrade the Boot2Docker ISO image (restart if running).
   version             Display version information.

Options:
`, os.Args[0])
	flags.PrintDefaults()
}
<commit_msg>Bump retries up to 75 for extra insanity<commit_after>package main

import (
	"bytes"
	"fmt"
	"net"
	"os"
	"path/filepath"
	"regexp"
	"runtime"

	toml "github.com/BurntSushi/toml"
	"github.com/boot2docker/boot2docker-cli/driver"
	flag "github.com/ogier/pflag"
)

var (
	// Pattern to parse a key=value line in config profile.
	reFlagLine = regexp.MustCompile(`^\s*(\w+)\s*=\s*([^#;]+)`)
	B2D        = driver.MachineConfig{}
)

func homeDir() (string, error) {
	dir := ""

	// *nix and MSYS Windows
	if dir = os.Getenv("HOME"); dir == "" {
		// Windows (if not running under MSYS)
		dir = os.Getenv("USERPROFILE")
	}
	if _, err := os.Stat(dir); err != nil {
		return "", err
	}

	return dir, nil
}

func cfgDir(name string) (string, error) {
	if name == ".boot2docker" {
		if b2dDir := os.Getenv("BOOT2DOCKER_DIR"); b2dDir != "" {
			return b2dDir, nil
		}
	}

	dir, err := homeDir()
	if err != nil {
		return "", err
	}
	dir = filepath.Join(dir, name)
	if err := os.MkdirAll(dir, 0755); err != nil {
		return "", err
	}
	return dir, nil
}

func cfgFilename(dir string) string {
	filename := os.Getenv("BOOT2DOCKER_PROFILE")
	if filename == "" {
		filename = filepath.Join(dir, "profile")
	}
	return filename
}

// Write configuration set by the combination of profile and flags
//    Should result in a format that can be piped into a profile file
func printConfig() string {
	var buf bytes.Buffer
	e := toml.NewEncoder(&buf)
	err := e.Encode(B2D)
	if err != nil {
		return ""
	}
	return buf.String()
}

// Read configuration from both profile and flags. Flags override profile.
func config() (*flag.FlagSet, error) {
	dir, err := cfgDir(".boot2docker")
	if err != nil {
		return nil, fmt.Errorf("failed to get boot2docker directory: %s", err)
	}

	flags := flag.NewFlagSet(os.Args[0], flag.ContinueOnError)
	flags.Usage = func() { usageLong(flags) }

	driver.ConfigFlags(&B2D, flags)

	// Add the generic flags

	flags.StringVar(&B2D.VM, "vm", "boot2docker-vm", "virtual machine name.")
	// removed for now, requires re-parsing a new config file which is too messy
	//flags.StringVarP(&B2D.Dir, "dir", "d", dir, "boot2docker config directory.")
	B2D.Dir = dir
	flags.StringVar(&B2D.ISOURL, "iso-url", "https://api.github.com/repos/boot2docker/boot2docker/releases", "source URL to provision the boot2docker ISO image.")
	flags.StringVar(&B2D.ISO, "iso", filepath.Join(dir, "boot2docker.iso"), "path to boot2docker ISO image.")

	// Sven disabled this, as it is broken - if I user with a fresh computer downloads
	// just the boot2docker-cli, and then runs `boot2docker --init ip`, we create a vm
	// which cannot run, because it fails to have have the boot2docker.iso and the ssh keys
	B2D.Init = false
	//flags.BoolVarP(&B2D.Init, "init", "i", false, "auto initialize vm instance.")

	flags.BoolVarP(&B2D.Verbose, "verbose", "v", false, "display verbose command invocations.")
	flags.StringVar(&B2D.Driver, "driver", "virtualbox", "hypervisor driver.")
	flags.StringVar(&B2D.SSH, "ssh", "ssh", "path to SSH client utility.")
	flags.StringVar(&B2D.SSHGen, "ssh-keygen", "ssh-keygen", "path to ssh-keygen utility.")

	sshdir, _ := cfgDir(".ssh")
	flags.StringVar(&B2D.SSHKey, "sshkey", filepath.Join(sshdir, "id_boot2docker"), "path to SSH key to use.")
	flags.UintVarP(&B2D.DiskSize, "disksize", "s", 20000, "boot2docker disk image size (in MB).")
	flags.UintVarP(&B2D.Memory, "memory", "m", 2048, "virtual machine memory size (in MB).")
	flags.Uint16Var(&B2D.SSHPort, "sshport", 2022, "host SSH port (forward to port 22 in VM).")
	flags.Uint16Var(&B2D.DockerPort, "dockerport", 0, "host Docker port (forward to port 2375 in VM). (deprecated - use with care)")
	flags.IPVar(&B2D.HostIP, "hostip", net.ParseIP("192.168.59.3"), "VirtualBox host-only network IP address.")
	flags.IPMaskVar(&B2D.NetMask, "netmask", flag.ParseIPv4Mask("255.255.255.0"), "VirtualBox host-only network mask.")
	flags.BoolVar(&B2D.DHCPEnabled, "dhcp", true, "enable VirtualBox host-only network DHCP.")
	flags.IPVar(&B2D.DHCPIP, "dhcpip", net.ParseIP("192.168.59.99"), "VirtualBox host-only network DHCP server address.")
	flags.IPVar(&B2D.LowerIP, "lowerip", net.ParseIP("192.168.59.103"), "VirtualBox host-only network DHCP lower bound.")
	flags.IPVar(&B2D.UpperIP, "upperip", net.ParseIP("192.168.59.254"), "VirtualBox host-only network DHCP upper bound.")

	flags.IntVar(&B2D.Waittime, "waittime", 300, "Time in milliseconds to wait between port knocking retries during 'start'")
	flags.IntVar(&B2D.Retries, "retries", 75, "number of port knocking retries during 'start'")

	if runtime.GOOS != "windows" {
		//SerialFile ~~ filepath.Join(dir, B2D.vm+".sock")
		flags.StringVar(&B2D.SerialFile, "serialfile", "", "path to the serial socket/pipe.")
		flags.BoolVar(&B2D.Serial, "serial", false, "try serial console to get IP address (experimental)")
	} else {
		B2D.Serial = false
	}

	// Set the defaults
	if err := flags.Parse([]string{}); err != nil {
		return nil, err
	}
	// Over-ride from the profile file
	filename := cfgFilename(B2D.Dir)
	if _, err := os.Lstat(filename); err == nil {
		if _, err := toml.DecodeFile(filename, &B2D); err != nil {
			return nil, err
		}
	}

	if B2D.SerialFile == "" {
		if runtime.GOOS == "windows" {
			//SerialFile ~~ filepath.Join(dir, B2D.vm+".sock")
			B2D.SerialFile = `\\.\pipe\` + B2D.VM
		} else {
			B2D.SerialFile = filepath.Join(dir, B2D.VM+".sock")
		}
	}
	// for cmd==ssh only:
	// only pass the params up to and including the `ssh` command - after that,
	// there might be other -flags that are destined for the ssh cmd
	sshIdx := 1
	for sshIdx < len(os.Args) && os.Args[sshIdx-1] != "ssh" {
		sshIdx++
	}
	// Command-line overrides profile config.
	if err := flags.Parse(os.Args[1:sshIdx]); err != nil {
		return nil, err
	}

	leftovers := flags.Args()

	if B2D.Verbose || (len(leftovers) > 0 && leftovers[0] == "version") {
		fmt.Printf("Boot2Docker-cli version: %s\nGit commit: %s\n", Version, GitSHA)
	}

	return flags, nil
}

func usageShort() {
	fmt.Fprintf(os.Stderr, "Usage: %s [<options>] {help|init|up|ssh|save|down|poweroff|reset|restart|config|status|info|ip|socket|shellinit|delete|download|upgrade|version} [<args>]\n", os.Args[0])
}

func usageLong(flags *flag.FlagSet) {
	// NOTE: the help message uses spaces, not tabs for indentation!
	fmt.Fprintf(os.Stderr, `Usage: %s [<options>] <command> [<args>]

Boot2Docker management utility.

Commands:
   init                Create a new Boot2Docker VM.
   up|start|boot       Start VM from any states.
   ssh [ssh-command]   Login to VM via SSH.
   save|suspend        Suspend VM and save state to disk.
   down|stop|halt      Gracefully shutdown the VM.
   restart             Gracefully reboot the VM.
   poweroff            Forcefully power off the VM (may corrupt disk image).
   reset               Forcefully power cycle the VM (may corrupt disk image).
   delete|destroy      Delete Boot2Docker VM and its disk image.
   config|cfg          Show selected profile file settings.
   info                Display detailed information of VM.
   ip                  Display the IP address of the VM's Host-only network.
   socket              Display the DOCKER_HOST socket to connect to.
   shellinit           Display the shell command to set up the Docker client.
   status              Display current state of VM.
   download            Download Boot2Docker ISO image.
   upgrade             Upgrade the Boot2Docker ISO image (restart if running).
   version             Display version information.

Options:
`, os.Args[0])
	flags.PrintDefaults()
}
<|endoftext|>


<commit_before>package configuration

import (
	"math/big"
	"strings"
	"time"

	"github.com/go-akka/configuration/hocon"
)

type Config struct {
	root          *hocon.HoconValue
	substitutions []*hocon.HoconSubstitution
	fallback      *Config
}

func NewConfigFromRoot(root *hocon.HoconRoot) *Config {
	if root.Value() == nil {
		panic("The root value cannot be null.")
	}

	return &Config{
		root:          root.Value(),
		substitutions: root.Substitutions(),
	}
}

func NewConfigFromConfig(source, fallback *Config) *Config {
	if source == nil {
		panic("The source configuration cannot be null.")
	}

	return &Config{
		root:     source.root,
		fallback: fallback,
	}
}

func (p *Config) IsEmpty() bool {
	return p.root == nil || p.root.IsEmpty()
}

func (p *Config) Root() *hocon.HoconValue {
	return p.root
}

func (p *Config) Copy(fallback ...*Config) *Config {

	var fb *Config

	if p.fallback != nil {
		fb = p.fallback.Copy()
	} else {
		if len(fallback) > 0 {
			fb = fallback[0]
		}
	}
	return &Config{
		fallback:      fb,
		root:          p.root,
		substitutions: p.substitutions,
	}
}

func (p *Config) GetNode(path string) *hocon.HoconValue {
	elements := splitDottedPathHonouringQuotes(path)
	currentNode := p.root

	if currentNode == nil {
		panic("Current node should not be null")
	}

	for _, key := range elements {
		currentNode = currentNode.GetChildObject(key)
		if currentNode == nil {
			if p.fallback != nil {
				return p.fallback.GetNode(path)
			}
			return nil
		}
	}
	return currentNode
}

func (p *Config) GetBoolean(path string, defaultVal ...bool) bool {
	obj := p.GetNode(path)
	if obj := p.GetNode(path); obj == nil {
		if len(defaultVal) > 0 {
			return defaultVal[0]
		}
		return false
	}
	return obj.GetBoolean()
}

func (p *Config) GetByteSize(path string) *big.Int {
	obj := p.GetNode(path)
	if obj == nil {
		return big.NewInt(-1)
	}
	return obj.GetByteSize()
}

func (p *Config) GetInt32(path string, defaultVal ...int32) int32 {
	obj := p.GetNode(path)
	if obj := p.GetNode(path); obj == nil {
		if len(defaultVal) > 0 {
			return defaultVal[0]
		}
		return 0
	}
	return obj.GetInt32()
}

func (p *Config) GetInt64(path string, defaultVal ...int64) int64 {
	obj := p.GetNode(path)
	if obj := p.GetNode(path); obj == nil {
		if len(defaultVal) > 0 {
			return defaultVal[0]
		}
		return 0
	}
	return obj.GetInt64()
}

func (p *Config) GetString(path string, defaultVal ...string) string {
	obj := p.GetNode(path)
	if obj := p.GetNode(path); obj == nil {
		if len(defaultVal) > 0 {
			return defaultVal[0]
		}
		return ""
	}
	return obj.GetString()
}

func (p *Config) GetFloat32(path string, defaultVal ...float32) float32 {
	obj := p.GetNode(path)
	if obj := p.GetNode(path); obj == nil {
		if len(defaultVal) > 0 {
			return defaultVal[0]
		}
	}
	return obj.GetFloat32()
}

func (p *Config) GetFloat64(path string, defaultVal ...float64) float64 {
	obj := p.GetNode(path)
	if obj := p.GetNode(path); obj == nil {
		if len(defaultVal) > 0 {
			return defaultVal[0]
		}
		return 0
	}
	return obj.GetFloat64()
}

func (p *Config) GetTimeDuration(path string, defaultVal ...time.Duration) time.Duration {
	obj := p.GetNode(path)
	if obj := p.GetNode(path); obj == nil {
		if len(defaultVal) > 0 {
			return defaultVal[0]
		}
		return 0
	}
	return obj.GetTimeDuration(true)
}

func (p *Config) GetTimeDurationInfiniteNotAllowed(path string, defaultVal ...time.Duration) time.Duration {
	obj := p.GetNode(path)
	if obj := p.GetNode(path); obj == nil {
		if len(defaultVal) > 0 {
			return defaultVal[0]
		}
		return 0
	}
	return obj.GetTimeDuration(false)
}

func (p *Config) GetBooleanList(path string) []bool {
	obj := p.GetNode(path)
	if obj := p.GetNode(path); obj == nil {
		return nil
	}
	return obj.GetBooleanList()
}

func (p *Config) GetFloat32List(path string) []float32 {
	obj := p.GetNode(path)
	if obj := p.GetNode(path); obj == nil {
		return nil
	}
	return obj.GetFloat32List()
}

func (p *Config) GetFloat64List(path string) []float64 {
	obj := p.GetNode(path)
	if obj := p.GetNode(path); obj == nil {
		return nil
	}
	return obj.GetFloat64List()
}

func (p *Config) GetInt32List(path string) []int32 {
	obj := p.GetNode(path)
	if obj := p.GetNode(path); obj == nil {
		return nil
	}
	return obj.GetInt32List()
}

func (p *Config) GetInt64List(path string) []int64 {
	obj := p.GetNode(path)
	if obj := p.GetNode(path); obj == nil {
		return nil
	}
	return obj.GetInt64List()
}

func (p *Config) GetByteList(path string) []byte {
	obj := p.GetNode(path)
	if obj := p.GetNode(path); obj == nil {
		return nil
	}
	return obj.GetByteList()
}

func (p *Config) GetStringList(path string) []string {
	obj := p.GetNode(path)
	if obj := p.GetNode(path); obj == nil {
		return nil
	}
	return obj.GetStringList()
}

func (p *Config) GetConfig(path string) *Config {
	value := p.GetNode(path)
	if p.fallback != nil {
		f := p.fallback.GetConfig(path)
		if value == nil && f == nil {
			return nil
		}
		if value == nil {
			return f
		}
		return NewConfigFromRoot(hocon.NewHoconRoot(value)).WithFallback(f)
	}

	if value == nil {
		return nil
	}
	return NewConfigFromRoot(hocon.NewHoconRoot(value))
}

func (p *Config) GetValue(path string) *hocon.HoconValue {
	return p.GetNode(path)
}

func (p *Config) WithFallback(fallback *Config) *Config {
	if fallback == p {
		panic("Config can not have itself as fallback")
	}

	if fallback == nil {
		return p
	}

	mergedRoot := p.root.GetObject().MergeImmutable(fallback.root.GetObject())
	newRoot := hocon.NewHoconValue()

	newRoot.AppendValue(mergedRoot)

	mergedConfig := p.Copy(fallback)

	mergedConfig.root = newRoot

	return mergedConfig
}

func (p *Config) HasPath(path string) bool {
	return p.GetNode(path) != nil
}

func (p *Config) AddConfig(textConfig string, fallbackConfig *Config) *Config {
	root := hocon.Parse(textConfig, nil)
	config := NewConfigFromRoot(root)
	return config.WithFallback(fallbackConfig)
}

func (p *Config) AddConfigWithTextFallback(config *Config, textFallback string) *Config {
	fallbackRoot := hocon.Parse(textFallback, nil)
	fallbackConfig := NewConfigFromRoot(fallbackRoot)
	return config.WithFallback(fallbackConfig)
}

func (p Config) String() string {
	return p.root.String()
}

func splitDottedPathHonouringQuotes(path string) []string {
	tmp1 := strings.Split(path, "\"")
	var values []string
	for i := 0; i < len(tmp1); i++ {
		tmp2 := strings.Split(tmp1[i], ".")
		for j := 0; j < len(tmp2); j++ {
			if len(tmp2[j]) > 0 {
				values = append(values, tmp2[j])
			}
		}
	}
	return values
}
<commit_msg>fix panic if config is nil<commit_after>package configuration

import (
	"math/big"
	"strings"
	"time"

	"github.com/go-akka/configuration/hocon"
)

type Config struct {
	root          *hocon.HoconValue
	substitutions []*hocon.HoconSubstitution
	fallback      *Config
}

func NewConfigFromRoot(root *hocon.HoconRoot) *Config {
	if root.Value() == nil {
		panic("The root value cannot be null.")
	}

	return &Config{
		root:          root.Value(),
		substitutions: root.Substitutions(),
	}
}

func NewConfigFromConfig(source, fallback *Config) *Config {
	if source == nil {
		panic("The source configuration cannot be null.")
	}

	return &Config{
		root:     source.root,
		fallback: fallback,
	}
}

func (p *Config) IsEmpty() bool {
	return p == nil || p.root == nil || p.root.IsEmpty()
}

func (p *Config) Root() *hocon.HoconValue {
	return p.root
}

func (p *Config) Copy(fallback ...*Config) *Config {

	var fb *Config

	if p.fallback != nil {
		fb = p.fallback.Copy()
	} else {
		if len(fallback) > 0 {
			fb = fallback[0]
		}
	}
	return &Config{
		fallback:      fb,
		root:          p.root,
		substitutions: p.substitutions,
	}
}

func (p *Config) GetNode(path string) *hocon.HoconValue {
	if p == nil {
		return nil
	}

	elements := splitDottedPathHonouringQuotes(path)
	currentNode := p.root

	if currentNode == nil {
		panic("Current node should not be null")
	}

	for _, key := range elements {
		currentNode = currentNode.GetChildObject(key)
		if currentNode == nil {
			if p.fallback != nil {
				return p.fallback.GetNode(path)
			}
			return nil
		}
	}
	return currentNode
}

func (p *Config) GetBoolean(path string, defaultVal ...bool) bool {
	obj := p.GetNode(path)
	if obj == nil {
		if len(defaultVal) > 0 {
			return defaultVal[0]
		}
		return false
	}
	return obj.GetBoolean()
}

func (p *Config) GetByteSize(path string) *big.Int {
	obj := p.GetNode(path)
	if obj == nil {
		return big.NewInt(-1)
	}
	return obj.GetByteSize()
}

func (p *Config) GetInt32(path string, defaultVal ...int32) int32 {
	obj := p.GetNode(path)
	if obj == nil {
		if len(defaultVal) > 0 {
			return defaultVal[0]
		}
		return 0
	}
	return obj.GetInt32()
}

func (p *Config) GetInt64(path string, defaultVal ...int64) int64 {
	obj := p.GetNode(path)
	if obj == nil {
		if len(defaultVal) > 0 {
			return defaultVal[0]
		}
		return 0
	}
	return obj.GetInt64()
}

func (p *Config) GetString(path string, defaultVal ...string) string {
	obj := p.GetNode(path)
	if obj == nil {
		if len(defaultVal) > 0 {
			return defaultVal[0]
		}
		return ""
	}
	return obj.GetString()
}

func (p *Config) GetFloat32(path string, defaultVal ...float32) float32 {
	obj := p.GetNode(path)
	if obj == nil {
		if len(defaultVal) > 0 {
			return defaultVal[0]
		}
	}
	return obj.GetFloat32()
}

func (p *Config) GetFloat64(path string, defaultVal ...float64) float64 {
	obj := p.GetNode(path)
	if obj == nil {
		if len(defaultVal) > 0 {
			return defaultVal[0]
		}
		return 0
	}
	return obj.GetFloat64()
}

func (p *Config) GetTimeDuration(path string, defaultVal ...time.Duration) time.Duration {
	obj := p.GetNode(path)
	if obj == nil {
		if len(defaultVal) > 0 {
			return defaultVal[0]
		}
		return 0
	}
	return obj.GetTimeDuration(true)
}

func (p *Config) GetTimeDurationInfiniteNotAllowed(path string, defaultVal ...time.Duration) time.Duration {
	obj := p.GetNode(path)
	if obj == nil {
		if len(defaultVal) > 0 {
			return defaultVal[0]
		}
		return 0
	}
	return obj.GetTimeDuration(false)
}

func (p *Config) GetBooleanList(path string) []bool {
	obj := p.GetNode(path)
	if obj == nil {
		return nil
	}
	return obj.GetBooleanList()
}

func (p *Config) GetFloat32List(path string) []float32 {
	obj := p.GetNode(path)
	if obj == nil {
		return nil
	}
	return obj.GetFloat32List()
}

func (p *Config) GetFloat64List(path string) []float64 {
	obj := p.GetNode(path)
	if obj == nil {
		return nil
	}
	return obj.GetFloat64List()
}

func (p *Config) GetInt32List(path string) []int32 {
	obj := p.GetNode(path)
	if obj == nil {
		return nil
	}
	return obj.GetInt32List()
}

func (p *Config) GetInt64List(path string) []int64 {
	obj := p.GetNode(path)
	if obj == nil {
		return nil
	}
	return obj.GetInt64List()
}

func (p *Config) GetByteList(path string) []byte {
	obj := p.GetNode(path)
	if obj == nil {
		return nil
	}
	return obj.GetByteList()
}

func (p *Config) GetStringList(path string) []string {
	obj := p.GetNode(path)
	if obj == nil {
		return nil
	}
	return obj.GetStringList()
}

func (p *Config) GetConfig(path string) *Config {
	if p == nil {
		return nil
	}

	value := p.GetNode(path)
	if p.fallback != nil {
		f := p.fallback.GetConfig(path)
		if value == nil && f == nil {
			return nil
		}
		if value == nil {
			return f
		}
		return NewConfigFromRoot(hocon.NewHoconRoot(value)).WithFallback(f)
	}

	if value == nil {
		return nil
	}
	return NewConfigFromRoot(hocon.NewHoconRoot(value))
}

func (p *Config) GetValue(path string) *hocon.HoconValue {
	return p.GetNode(path)
}

func (p *Config) WithFallback(fallback *Config) *Config {
	if fallback == p {
		panic("Config can not have itself as fallback")
	}

	if fallback == nil {
		return p
	}

	mergedRoot := p.root.GetObject().MergeImmutable(fallback.root.GetObject())
	newRoot := hocon.NewHoconValue()

	newRoot.AppendValue(mergedRoot)

	mergedConfig := p.Copy(fallback)

	mergedConfig.root = newRoot

	return mergedConfig
}

func (p *Config) HasPath(path string) bool {
	return p.GetNode(path) != nil
}

func (p *Config) AddConfig(textConfig string, fallbackConfig *Config) *Config {
	root := hocon.Parse(textConfig, nil)
	config := NewConfigFromRoot(root)
	return config.WithFallback(fallbackConfig)
}

func (p *Config) AddConfigWithTextFallback(config *Config, textFallback string) *Config {
	fallbackRoot := hocon.Parse(textFallback, nil)
	fallbackConfig := NewConfigFromRoot(fallbackRoot)
	return config.WithFallback(fallbackConfig)
}

func (p Config) String() string {
	return p.root.String()
}

func splitDottedPathHonouringQuotes(path string) []string {
	tmp1 := strings.Split(path, "\"")
	var values []string
	for i := 0; i < len(tmp1); i++ {
		tmp2 := strings.Split(tmp1[i], ".")
		for j := 0; j < len(tmp2); j++ {
			if len(tmp2[j]) > 0 {
				values = append(values, tmp2[j])
			}
		}
	}
	return values
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"fmt"
	"os"
	"regexp"
	"strings"

	"github.com/BurntSushi/toml"
	"github.com/skratchdot/open-golang/open"
)

const AuthURL = "http://slackcat.chat/configure"

// Slack team and channel read from file
type Config struct {
	Teams          map[string]string `toml:"teams"`
	DefaultTeam    string            `toml:"default_team"`
	DefaultChannel string            `toml:"default_channel"`
}

// Return new default config
func NewConfig() *Config {
	return &Config{
		Teams: make(map[string]string),
	}
}

// Return config read from file
func ReadConfig(path string) *Config {
	config := NewConfig()
	lines, err := readLines(path)
	failOnError(err, "unable to read config")

	// simple config file
	if len(lines) == 1 {
		config.Teams["default"] = lines[0]
		config.DefaultTeam = "default"
		return config
	}

	// advanced config file
	body := strings.Join(lines, "\n")
	_, err = toml.Decode(body, &config)
	failOnError(err, "failed to parse config")

	return config
}

func (c *Config) Write(path string) {
	cfgdir := basedir(path)
	// create config dir if not exist
	if _, err := os.Stat(cfgdir); err != nil {
		err = os.Mkdir(cfgdir, 0755)
		if err != nil {
			exitErr(fmt.Errorf("failed to initialize config dir: %s", err))
		}
	}

	file, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0644)
	if err != nil {
		exitErr(fmt.Errorf("failed to open config for writing: %s", err))
	}

	writer := toml.NewEncoder(file)
	err = writer.Encode(c)
	if err != nil {
		exitErr(fmt.Errorf("failed to write config: %s", err))
	}
}

func (c *Config) parseChannelOpt(channel string) (string, string, error) {
	// use default channel if none provided
	if channel == "" {
		if c.DefaultChannel == "" {
			return "", "", fmt.Errorf("no channel provided")
		}
		return c.DefaultTeam, c.DefaultChannel, nil
	}
	// if channel is prefixed with a team
	if strings.Contains(channel, ":") {
		s := strings.Split(channel, ":")
		return s[0], s[1], nil
	}
	// use default team with provided channel
	return c.DefaultTeam, channel, nil
}

// determine config path from environment
func getConfigPath() (path string, exists bool) {
	userHome, ok := os.LookupEnv("HOME")
	if !ok {
		exitErr(fmt.Errorf("$HOME not set"))
	}

	if xdgSupport() {
		xdgHome, ok := os.LookupEnv("XDG_CONFIG_HOME")
		if !ok {
			xdgHome = fmt.Sprintf("%s/.config", userHome)
		}
		path = fmt.Sprintf("%s/slackcat/config", xdgHome)
	} else {
		fmt.Sprintf("%s/.slackcat", userHome)
	}

	if _, err := os.Stat(path); err == nil {
		exists = true
	}

	return path, exists
}

func basedir(path string) string {
	parts := strings.Split(path, "/")
	return strings.Join((parts[0 : len(parts)-1]), "/")
}

// Test for environemnt supporting XDG spec
func xdgSupport() bool {
	re := regexp.MustCompile("^XDG_*")
	for _, e := range os.Environ() {
		if re.FindAllString(e, 1) != nil {
			return true
		}
	}
	return false
}

func configureOA() {
	var nick, token string
	var config *Config

	cfgPath, cfgExists := getConfigPath()
	if !cfgExists {
		config = NewConfig()
	} else {
		config = ReadConfig(cfgPath)
	}

	fmt.Printf("nickname for team: ")
	fmt.Scanf("%s", &nick)
	if nick == "" {
		exitErr(fmt.Errorf("no name provided"))
	}

	output("creating token request for slackcat")
	open.Run(AuthURL)
	output("Use the below URL to authorize slackcat if browser fails to launch")
	output(AuthURL)

	fmt.Printf("token issued: ")
	fmt.Scanf("%s", &token)
	if token == "" {
		exitErr(fmt.Errorf("no token provided"))
	}

	// creating a new config file
	if !cfgExists {
		config.DefaultTeam = nick
	}
	config.Teams[nick] = token
	config.Write(cfgPath)

	output(fmt.Sprintf("added team to config file at %s", cfgPath))
}

func readLines(path string) (lines []string, err error) {
	file, err := os.Open(path)
	if err != nil {
		return lines, err
	}
	defer file.Close()

	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		if scanner.Text() != "" {
			lines = append(lines, scanner.Text())
		}
	}
	return lines, nil
}
<commit_msg>set path to Sprintf return value<commit_after>package main

import (
	"bufio"
	"fmt"
	"os"
	"regexp"
	"strings"

	"github.com/BurntSushi/toml"
	"github.com/skratchdot/open-golang/open"
)

const AuthURL = "http://slackcat.chat/configure"

// Slack team and channel read from file
type Config struct {
	Teams          map[string]string `toml:"teams"`
	DefaultTeam    string            `toml:"default_team"`
	DefaultChannel string            `toml:"default_channel"`
}

// Return new default config
func NewConfig() *Config {
	return &Config{
		Teams: make(map[string]string),
	}
}

// Return config read from file
func ReadConfig(path string) *Config {
	config := NewConfig()
	lines, err := readLines(path)
	failOnError(err, "unable to read config")

	// simple config file
	if len(lines) == 1 {
		config.Teams["default"] = lines[0]
		config.DefaultTeam = "default"
		return config
	}

	// advanced config file
	body := strings.Join(lines, "\n")
	_, err = toml.Decode(body, &config)
	failOnError(err, "failed to parse config")

	return config
}

func (c *Config) Write(path string) {
	cfgdir := basedir(path)
	// create config dir if not exist
	if _, err := os.Stat(cfgdir); err != nil {
		err = os.Mkdir(cfgdir, 0755)
		if err != nil {
			exitErr(fmt.Errorf("failed to initialize config dir [%s]: %s", cfgdir, err))
		}
	}

	file, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0644)
	if err != nil {
		exitErr(fmt.Errorf("failed to open config for writing: %s", err))
	}

	writer := toml.NewEncoder(file)
	err = writer.Encode(c)
	if err != nil {
		exitErr(fmt.Errorf("failed to write config: %s", err))
	}
}

func (c *Config) parseChannelOpt(channel string) (string, string, error) {
	// use default channel if none provided
	if channel == "" {
		if c.DefaultChannel == "" {
			return "", "", fmt.Errorf("no channel provided")
		}
		return c.DefaultTeam, c.DefaultChannel, nil
	}
	// if channel is prefixed with a team
	if strings.Contains(channel, ":") {
		s := strings.Split(channel, ":")
		return s[0], s[1], nil
	}
	// use default team with provided channel
	return c.DefaultTeam, channel, nil
}

// determine config path from environment
func getConfigPath() (path string, exists bool) {
	userHome, ok := os.LookupEnv("HOME")
	if !ok {
		exitErr(fmt.Errorf("$HOME not set"))
	}

	if xdgSupport() {
		xdgHome, ok := os.LookupEnv("XDG_CONFIG_HOME")
		if !ok {
			xdgHome = fmt.Sprintf("%s/.config", userHome)
		}
		path = fmt.Sprintf("%s/slackcat/config", xdgHome)
	} else {
		path = fmt.Sprintf("%s/.slackcat", userHome)
	}

	if _, err := os.Stat(path); err == nil {
		exists = true
	}

	return path, exists
}

func basedir(path string) string {
	parts := strings.Split(path, "/")
	return strings.Join((parts[0 : len(parts)-1]), "/")
}

// Test for environemnt supporting XDG spec
func xdgSupport() bool {
	re := regexp.MustCompile("^XDG_*")
	for _, e := range os.Environ() {
		if re.FindAllString(e, 1) != nil {
			return true
		}
	}
	return false
}

func configureOA() {
	var nick, token string
	var config *Config

	cfgPath, cfgExists := getConfigPath()
	if !cfgExists {
		config = NewConfig()
	} else {
		config = ReadConfig(cfgPath)
	}

	fmt.Printf("nickname for team: ")
	fmt.Scanf("%s", &nick)
	if nick == "" {
		exitErr(fmt.Errorf("no name provided"))
	}

	output("creating token request for slackcat")
	open.Run(AuthURL)
	output("Use the below URL to authorize slackcat if browser fails to launch")
	output(AuthURL)

	fmt.Printf("token issued: ")
	fmt.Scanf("%s", &token)
	if token == "" {
		exitErr(fmt.Errorf("no token provided"))
	}

	// creating a new config file
	if !cfgExists {
		config.DefaultTeam = nick
	}
	config.Teams[nick] = token
	config.Write(cfgPath)

	output(fmt.Sprintf("added team to config file at %s", cfgPath))
}

func readLines(path string) (lines []string, err error) {
	file, err := os.Open(path)
	if err != nil {
		return lines, err
	}
	defer file.Close()

	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		if scanner.Text() != "" {
			lines = append(lines, scanner.Text())
		}
	}
	return lines, nil
}
<|endoftext|>