Skip to content

Commit

Permalink
added upload and download
Browse files Browse the repository at this point in the history
  • Loading branch information
celrenheit committed Mar 5, 2018
1 parent 0db7b1e commit 66b57c7
Show file tree
Hide file tree
Showing 3 changed files with 385 additions and 0 deletions.
113 changes: 113 additions & 0 deletions cmd/download.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// Copyright © 2018 Salim Alami Idrissi
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package cmd

import (
"io/ioutil"
"sort"
"strings"

"github.com/celrenheit/alfred/wallet"
"github.com/stellar/go/clients/horizon"

"github.com/spf13/cobra"
"github.com/spf13/viper"
)

// downloadCmd represents the import command
var downloadCmd = &cobra.Command{
Use: "download",
Short: "download",
Aliases: []string{"p"},
Long: `download file`,
Example: "alfred download mywallet text.txt",
PreRunE: middlewares(checkDB),
Run: func(cmd *cobra.Command, args []string) {
path := viper.GetString("db")
secret := viper.GetString("secret")
m, err := wallet.OpenSecretString(path, secret)
if err != nil {
fatal(err)
}

if len(args) != 2 {
fatal("account and file expected")
}

accName, filename := args[0], args[1]

kp := getAddress(m, accName)

client := getClient(viper.GetBool("testnet"))
acc, exists, err := getAccount(client, kp.Address())
if err != nil {
fatal(err)
}
if !exists {
fatal("account does not exist")
}

header, parts, err := parseAccountData(acc, filename)
if err != nil {
fatal(err)
}

data, err := GetData(header, parts)
if err != nil {
fatal(err)
}

err = ioutil.WriteFile(filename, data, 0775)
if err != nil {
fatal(err)
}
},
}

func init() {
RootCmd.AddCommand(downloadCmd)

viper.BindPFlags(downloadCmd.Flags())
}

func parseAccountData(acc horizon.Account, filename string) (header *Header, parts []*Part, err error) {
for key := range acc.Data {
value, err := acc.GetData(key)
if err != nil {
return nil, nil, err
}
switch {
case strings.HasPrefix(key, headerPrefix+filename):
header, err = ParseHeader(key, value)
if err != nil {
return nil, nil, err
}
case strings.HasPrefix(key, filename):
part, err := ParsePart(filename, key, value)
if err != nil {
return nil, nil, err
}
parts = append(parts, part)
default:
continue
}
}

sort.Slice(parts, func(i, j int) bool {
return parts[i].Offset < parts[j].Offset
})

return header, parts, nil
}
188 changes: 188 additions & 0 deletions cmd/parts.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
package cmd

import (
"bytes"
"crypto/sha256"
"encoding/binary"
"fmt"
"strconv"
"strings"

"github.com/manifoldco/promptui"
"github.com/stellar/go/build"
"github.com/stellar/go/keypair"
)

var (
limit = 64
headerPrefix = "alfred:"
)

func sha256sum(data []byte) []byte {
h := sha256.New()
h.Write(data)
return h.Sum(nil)
}

type KVData interface {
Key() string
Value() []byte
}

type Header struct {
Filename string
Length uint64
Checksum []byte
}

func (h *Header) Key() string {
return headerPrefix + h.Filename
}

func (h *Header) Value() []byte {
b := make([]byte, 8+32)
binary.BigEndian.PutUint64(b[:8], h.Length)

copy(b[8:], h.Checksum)

return b
}

func ParseHeader(key string, value []byte) (*Header, error) {
name := strings.TrimPrefix(key, headerPrefix)

h := &Header{
Filename: name,
Length: binary.BigEndian.Uint64(value[:8]),
Checksum: make([]byte, 32),
}

copy(h.Checksum, value[8:])

return h, nil
}

func ParsePart(prefix string, key string, value []byte) (*Part, error) {
if !strings.HasPrefix(key, prefix) {
return nil, fmt.Errorf("key: '%s' does not have prefix: '%s'", key, prefix)
}

offsetStr := strings.TrimPrefix(key, prefix+":")
offset, err := strconv.Atoi(offsetStr)
if err != nil {
return nil, err
}

return &Part{
Filename: prefix,
Offset: uint32(offset),
Data: value,
}, nil
}

func SplitIntoParts(filename string, data []byte) []*Part {
parts := make([]*Part, len(data)/limit+1)
var i uint32
for len(data) >= limit {
parts[i] = &Part{
Filename: filename,
Offset: i,
Data: data[:limit],
}
i++
data = data[limit:]
}

if len(data) > 0 {
parts[i] = &Part{
Filename: filename,
Offset: i,
Data: data,
}
}

return parts
}

type Part struct {
Filename string
Offset uint32
Data []byte
}

func (p *Part) Key() string {
return p.Filename + ":" + strconv.Itoa(int(p.Offset))
}

func (p *Part) Value() []byte {
return p.Data
}

func submitData(testnet, yes bool, src *keypair.Full, kvs []KVData) error {
var sopts []build.TransactionMutator
for _, kv := range kvs {
sopts = append(sopts, build.SetData(kv.Key(), kv.Value()))
}

client := getClient(testnet)

opts := []build.TransactionMutator{
build.SourceAccount{src.Seed()},
build.AutoSequence{SequenceProvider: client},
}

opts = append(opts, sopts...)

if testnet {
opts = append(opts, build.TestNetwork)
} else {
opts = append(opts, build.PublicNetwork)
}

tx, err := build.Transaction(opts...)
if err != nil {
return err
}

txe, err := tx.Sign(src.Seed())
if err != nil {
return err
}

txeB64, err := txe.Base64()
if err != nil {
return err
}

if !yes {
_, err = (&promptui.Prompt{
Label: "Are you sure",
IsConfirm: true,
}).Run()
if err != nil {
return err
}
}

resp, err := client.SubmitTransaction(txeB64)
if err != nil {
return err
}

fmt.Println(resp.Hash)
return nil
}

func GetData(header *Header, parts []*Part) ([]byte, error) {
data := make([]byte, header.Length)
for _, p := range parts {
offset := int(p.Offset) * 64
copy(data[offset:offset+len(p.Data)], p.Data)
}

if !bytes.Equal(header.Checksum, sha256sum(data)) {
return nil, fmt.Errorf("checksum invalid")
}

return data, nil
}
84 changes: 84 additions & 0 deletions cmd/upload.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// Copyright © 2018 Salim Alami Idrissi
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package cmd

import (
"io/ioutil"
"path/filepath"

"github.com/celrenheit/alfred/wallet"

"github.com/spf13/cobra"
"github.com/spf13/viper"
)

// uploadCmd represents the import command
var uploadCmd = &cobra.Command{
Use: "upload",
Short: "upload",
Aliases: []string{"p"},
Long: `send XLM to someone`,
Example: "alfred upload ./text.txt",
PreRunE: middlewares(checkDB, checkSecret),
Run: func(cmd *cobra.Command, args []string) {
path := viper.GetString("db")
secret := viper.GetString("secret")
m, err := wallet.OpenSecretString(path, secret)
if err != nil {
fatal(err)
}

if len(args) != 1 {
fatal("only one file allowed")
}

fpath := args[0]
data, err := ioutil.ReadFile(fpath)
if err != nil {
fatal(err)
}

name := filepath.Base(fpath)
header := &Header{
Filename: name,
Length: uint64(len(data)),
Checksum: sha256sum(data),
}

parts := SplitIntoParts(name, data)

kvs := make([]KVData, len(parts)+1)
kvs[0] = header
for i := 0; i < len(parts); i++ {
kvs[i+1] = parts[i]
}

src, err := selectWallet(m)
if err != nil {
fatal(err)
}

err = submitData(viper.GetBool("testnet"), viper.GetBool("yes"), src, kvs)
if err != nil {
fatal(err)
}
},
}

func init() {
RootCmd.AddCommand(uploadCmd)

viper.BindPFlags(uploadCmd.Flags())
}

0 comments on commit 66b57c7

Please sign in to comment.