100 lines
3.4 KiB
Go
100 lines
3.4 KiB
Go
package filesharing
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"cwtch.im/cwtch/model"
|
|
"cwtch.im/cwtch/model/attr"
|
|
"cwtch.im/cwtch/peer"
|
|
"cwtch.im/cwtch/protocol/files"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"git.openprivacy.ca/openprivacy/log"
|
|
"io"
|
|
"math"
|
|
path "path/filepath"
|
|
"strconv"
|
|
)
|
|
|
|
// Functionality groups some common UI triggered functions for contacts...
|
|
type Functionality struct {
|
|
}
|
|
|
|
// FunctionalityGate returns contact.Functionality always
|
|
func FunctionalityGate(experimentMap map[string]bool) (*Functionality, error) {
|
|
if experimentMap["filesharing"] == true {
|
|
return new(Functionality), nil
|
|
}
|
|
return nil, errors.New("filesharing is not enabled")
|
|
}
|
|
|
|
// OverlayMessage presents the canonical format of the File Sharing functionality Overlay Message
|
|
// This is the format that the UI will parse to display the message
|
|
type OverlayMessage struct {
|
|
Name string `json:"f"`
|
|
Hash string `json:"h"`
|
|
Nonce string `json:"n"`
|
|
Size uint64 `json:"s"`
|
|
}
|
|
|
|
// DownloadFile given a profile, a conversation handle and a file sharing key, start off a download process
|
|
// to downloadFilePath
|
|
func (f *Functionality) DownloadFile(profile peer.CwtchPeer, handle string, downloadFilePath string, manifestFilePath string, key string) {
|
|
|
|
// Store local.filesharing.filekey.manifest as the location of the manifest
|
|
profile.SetScopedZonedAttribute(attr.LocalScope, attr.FilesharingZone, fmt.Sprintf("%s.manifest", key), manifestFilePath)
|
|
|
|
// Store local.filesharing.filekey as the location of the download
|
|
profile.SetScopedZonedAttribute(attr.LocalScope, attr.FilesharingZone, key, downloadFilePath)
|
|
|
|
// Get the value of conversation.filesharing.filekey.manifest.size from `handle`
|
|
profile.SendScopedZonedGetValToContact(handle, attr.ConversationScope, attr.FilesharingZone, fmt.Sprintf("%s.manifest.size", key))
|
|
}
|
|
|
|
// ShareFile given a profile and a conversation handle, sets up a file sharing process to share the file
|
|
// at filepath
|
|
func (f *Functionality) ShareFile(filepath string, profile peer.CwtchPeer, handle string) error {
|
|
manifest, err := files.CreateManifest(filepath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var nonce [24]byte
|
|
if _, err := io.ReadFull(rand.Reader, nonce[:]); err != nil {
|
|
log.Errorf("Cannot read from random: %v\n", err)
|
|
return err
|
|
}
|
|
|
|
message := OverlayMessage{
|
|
Name: path.Base(manifest.FileName),
|
|
Hash: hex.EncodeToString(manifest.RootHash),
|
|
Nonce: hex.EncodeToString(nonce[:]),
|
|
Size: manifest.FileSizeInBytes,
|
|
}
|
|
|
|
data, _ := json.Marshal(message)
|
|
|
|
wrapper := model.MessageWrapper{
|
|
Overlay: model.OverlayFileSharing,
|
|
Data: string(data),
|
|
}
|
|
|
|
wrapperJSON, _ := json.Marshal(wrapper)
|
|
key := fmt.Sprintf("%x.%x", manifest.RootHash, nonce)
|
|
serializedManifest, _ := json.Marshal(manifest)
|
|
|
|
// Store the size of the manifest (in chunks) as part of the public scope so contacts who we share the file with
|
|
// can fetch the manifest as if it were a file.
|
|
// manifest.FileName gets redacted in filesharing_subsystem (to remove the system-specific file hierarchy),
|
|
// but we need to *store* the full path because the sender also uses it to locate the file
|
|
lenDiff := len(filepath) - len(path.Base(filepath))
|
|
profile.SetScopedZonedAttribute(attr.ConversationScope, attr.FilesharingZone, fmt.Sprintf("%s.manifest.size", key), strconv.Itoa(int(math.Ceil(float64(len(serializedManifest)-lenDiff)/float64(files.DefaultChunkSize)))))
|
|
|
|
profile.ShareFile(key, string(serializedManifest))
|
|
|
|
profile.SendMessage(handle, string(wrapperJSON))
|
|
|
|
return nil
|
|
}
|