├── .gitignore
├── LICENSE.txt
├── Makefile
├── README.md
├── TODO.md
├── api
├── api.pb.go
└── api.proto
├── cli
├── conf
│ └── conf.go
├── deamon.go
├── pull.go
├── push.go
└── sdp
│ ├── receiver_pipe.go
│ └── sender_pipe.go
├── deamon
├── sender_daemon_server.go
└── sender_deamon_client.go
├── docs
├── SharefDeamonDemo.gif
└── SharefSendDemo.gif
├── errx
└── errx.go
├── fsx
└── readwrite.go
├── go.mod
├── go.sum
├── gui
├── index.html
└── main.js
├── itests
├── api_integration_test.go
├── helpers.go
├── internal
│ ├── send
│ │ └── testfile.txt
│ ├── senddir
│ │ ├── sub
│ │ │ └── subsub
│ │ │ │ └── subsub
│ │ │ │ └── nestedfile
│ │ ├── subdir
│ │ │ └── subfile.md
│ │ └── testfile.txt
│ ├── streamdir
│ │ ├── firstdir
│ │ │ ├── newfile.txt
│ │ │ └── subdir
│ │ │ │ └── newfile.txt
│ │ ├── newfile.txt
│ │ └── testfile.txt
│ └── testfile.txt
└── main_integration_test.go
├── main.go
├── streamer
├── bandwithcalc.go
├── bandwithcalc_test.go
├── datachannel.go
├── filestat.go
├── frame.go
├── frame.pb.go
├── frame.proto
├── frame_encode.go
├── frame_encode_test.go
├── receive_streamer.go
├── receive_streamer_test.go
├── receiver.go
├── sdp.go
├── send_streamer.go
├── send_streamer_test.go
├── sender.go
├── session.go
└── streamfile.go
└── watcher
└── watcher.go
/.gitignore:
--------------------------------------------------------------------------------
1 | /release
2 | /itests/internal/received
3 | /sharef
4 | /.vscode
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 |
2 | all: proto build
3 |
4 | proto:
5 | # protoc -I api/ api/api.proto --go_out=plugins=grpc:api
6 | # protoc -I streamer/ --gofast_out=streamer streamer/frame.proto
7 | protoc -I streamer/ --gogofaster_out=streamer streamer/frame.proto
8 | # protoc -I streamer/ --js_out=import_style=commonjs,binary:streamer streamer/frame.proto
9 |
10 | build:
11 | go build -o sharef
12 |
13 | install:
14 | go install
15 |
16 | test:
17 | go test ./streamer/...
18 |
19 | integrationtests:
20 | # cd itests/
21 | # go test -timeout 60s --tags integration ./itests/... -v
22 | cd itests/ && go test -timeout 60s --tags integration -v -cover
23 |
24 | release_linux:
25 | env GOOS=linux GOARCH=amd64 go build -o sharef
26 | strip sharef
27 | upx sharef
28 | #mv sharef release/linux/
29 |
30 | release_windows:
31 | env GOOS=windows GOARCH=amd64 go build -o sharef
32 | strip sharef
33 | upx sharef
34 | # mv sharef release/windows/sharef.exe
35 |
36 | release: release_linux
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Sharef
2 |
3 | Sharef is Command Line Tool for easy sharing files.
4 | Focus is to have tool that can sync/stream securely file between two machines/containers easily.
5 | It can help to avoid:
6 | - uploading files to webservice, just to download them on different machine
7 | - downloading file just to upload on different machine
8 | - making important files public
9 |
10 | It uses WebRTC technology underneath.
11 | Webrtc makes encrypted and p2p communication which makes sharing files and streaming highly secure. There is no third-party dependency.
12 | It is totally written in GO language using pion/webrtc library.
13 |
14 |
15 | ### Features list:
16 | - Sending files
17 | - Sending directories
18 | - Sending multiple files in one command
19 | - Sending and streaming changes of file/directory from sender
20 | - Making connection once send files on the fly
21 |
22 | **NOTE:** It is experimental right now. Code should be improved.
23 |
24 |
25 | # Install
26 |
27 | For now only linux users :)
28 |
29 | ```
30 | sudo wget -P /usr/bin https://github.com/emiraganov/sharef/releases/download/v0.3/sharef
31 | sudo chmod +x /usr/bin/sharef
32 | ```
33 |
34 | # Usage
35 |
36 | Before file streaming can begin, SDP offer and answer must be exchanged. This encoded string
37 | will establish **p2p** connection and it is unique for each session.
38 |
39 | ## > Send files/directories
40 | 
41 |
42 | **Sender:**
43 |
44 | ```
45 | sharef push file1 file2 dir1 dir2 ...
46 | ```
47 |
48 | **Receiver:**
49 |
50 | You just call pull in directory where you want to get files from sender.
51 | ```
52 | sharef pull
53 | ```
54 |
55 |
56 |
57 | ## >> Keep Us Synced
58 | #### #DEMO
59 |
60 | You can make sender to listen for file/dir changes after inital sending. Sender will listen for any changes under file/directory and automaticaly resend changed file. This will keep sender and receiver in sync.
61 | This is useful if you are working on some directory and you want
62 | those changes to be sent to receiver automatically.
63 |
64 | *Probably this will be improved but for now do not use this on large files*
65 |
66 | **Sender:**
67 |
68 | ```
69 | sharef push -f file/dir
70 | ```
71 |
72 |
73 |
74 |
75 | ## >|> Make connection once and send on the fly
76 |
77 | After exchanching SDP, all sending will be done by deamon running in background.
78 |
79 | 
80 |
81 | **Sender:**
82 |
83 | Calling this will deamonize sender
84 | ```
85 | sharef push -d
86 | ```
87 |
88 | After making connection with receiver, you send same as:
89 | ```
90 | sharef push file
91 | sharef push dir
92 | sharef push file1 file2
93 | ...
94 | ```
95 |
96 | Sharef will detect deamon is running and it will just tell deamon to do the job.
97 | Deamon is listening on 9876 port by default, using HTTP2 protocol.
98 | BE AWARE receiver will put everything in same directory where it is run.
99 |
100 |
101 | # Feedback
102 |
103 | Any feedback is welcome!
104 |
105 | # Contribute
106 |
107 | Prereqs
108 | - [Golang](https://golang.org/)
109 | - [proto-lens](https://google.github.io/proto-lens/)
110 | - [Go support for Protocol Buffers](https://github.com/golang/protobuf)
111 |
112 |
113 | Building sharef
114 |
115 | ```
116 | make //build sharef
117 | make test //run unit tests
118 | make integrationtest //run integration tests
119 | ```
120 |
121 | # References
122 |
123 | - https://github.com/pion/webrtc
124 | - https://github.com/Antonito/gfile
125 |
--------------------------------------------------------------------------------
/TODO.md:
--------------------------------------------------------------------------------
1 | ## TODO LIST:
2 |
3 | 1. Try to short SDP
4 | 1. For now no better solution
5 |
6 | 2. Sender must know when file is fully sent
7 | 1. We just know when sending is done
8 | 3. Stream sender
9 | 4. Close Sender when Receiver disconects
10 | 5. Close receiver when Sender closes
11 | 6. Close sender when finish sending file
--------------------------------------------------------------------------------
/api/api.pb.go:
--------------------------------------------------------------------------------
1 | // Code generated by protoc-gen-go. DO NOT EDIT.
2 | // versions:
3 | // protoc-gen-go v1.23.0
4 | // protoc v3.11.4
5 | // source: api.proto
6 |
7 | package api
8 |
9 | import (
10 | context "context"
11 | proto "github.com/golang/protobuf/proto"
12 | grpc "google.golang.org/grpc"
13 | codes "google.golang.org/grpc/codes"
14 | status "google.golang.org/grpc/status"
15 | protoreflect "google.golang.org/protobuf/reflect/protoreflect"
16 | protoimpl "google.golang.org/protobuf/runtime/protoimpl"
17 | reflect "reflect"
18 | sync "sync"
19 | )
20 |
21 | const (
22 | // Verify that this generated code is sufficiently up-to-date.
23 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
24 | // Verify that runtime/protoimpl is sufficiently up-to-date.
25 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
26 | )
27 |
28 | // This is a compile-time assertion that a sufficiently up-to-date version
29 | // of the legacy proto package is being used.
30 | const _ = proto.ProtoPackageIsVersion4
31 |
32 | // The request message containing the user's name.
33 | type HelloRequest struct {
34 | state protoimpl.MessageState
35 | sizeCache protoimpl.SizeCache
36 | unknownFields protoimpl.UnknownFields
37 |
38 | Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
39 | }
40 |
41 | func (x *HelloRequest) Reset() {
42 | *x = HelloRequest{}
43 | if protoimpl.UnsafeEnabled {
44 | mi := &file_api_proto_msgTypes[0]
45 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
46 | ms.StoreMessageInfo(mi)
47 | }
48 | }
49 |
50 | func (x *HelloRequest) String() string {
51 | return protoimpl.X.MessageStringOf(x)
52 | }
53 |
54 | func (*HelloRequest) ProtoMessage() {}
55 |
56 | func (x *HelloRequest) ProtoReflect() protoreflect.Message {
57 | mi := &file_api_proto_msgTypes[0]
58 | if protoimpl.UnsafeEnabled && x != nil {
59 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
60 | if ms.LoadMessageInfo() == nil {
61 | ms.StoreMessageInfo(mi)
62 | }
63 | return ms
64 | }
65 | return mi.MessageOf(x)
66 | }
67 |
68 | // Deprecated: Use HelloRequest.ProtoReflect.Descriptor instead.
69 | func (*HelloRequest) Descriptor() ([]byte, []int) {
70 | return file_api_proto_rawDescGZIP(), []int{0}
71 | }
72 |
73 | func (x *HelloRequest) GetName() string {
74 | if x != nil {
75 | return x.Name
76 | }
77 | return ""
78 | }
79 |
80 | type HelloReply struct {
81 | state protoimpl.MessageState
82 | sizeCache protoimpl.SizeCache
83 | unknownFields protoimpl.UnknownFields
84 |
85 | Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
86 | }
87 |
88 | func (x *HelloReply) Reset() {
89 | *x = HelloReply{}
90 | if protoimpl.UnsafeEnabled {
91 | mi := &file_api_proto_msgTypes[1]
92 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
93 | ms.StoreMessageInfo(mi)
94 | }
95 | }
96 |
97 | func (x *HelloReply) String() string {
98 | return protoimpl.X.MessageStringOf(x)
99 | }
100 |
101 | func (*HelloReply) ProtoMessage() {}
102 |
103 | func (x *HelloReply) ProtoReflect() protoreflect.Message {
104 | mi := &file_api_proto_msgTypes[1]
105 | if protoimpl.UnsafeEnabled && x != nil {
106 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
107 | if ms.LoadMessageInfo() == nil {
108 | ms.StoreMessageInfo(mi)
109 | }
110 | return ms
111 | }
112 | return mi.MessageOf(x)
113 | }
114 |
115 | // Deprecated: Use HelloReply.ProtoReflect.Descriptor instead.
116 | func (*HelloReply) Descriptor() ([]byte, []int) {
117 | return file_api_proto_rawDescGZIP(), []int{1}
118 | }
119 |
120 | func (x *HelloReply) GetName() string {
121 | if x != nil {
122 | return x.Name
123 | }
124 | return ""
125 | }
126 |
127 | type SendFileRequest struct {
128 | state protoimpl.MessageState
129 | sizeCache protoimpl.SizeCache
130 | unknownFields protoimpl.UnknownFields
131 |
132 | Filename string `protobuf:"bytes,1,opt,name=filename,proto3" json:"filename,omitempty"`
133 | }
134 |
135 | func (x *SendFileRequest) Reset() {
136 | *x = SendFileRequest{}
137 | if protoimpl.UnsafeEnabled {
138 | mi := &file_api_proto_msgTypes[2]
139 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
140 | ms.StoreMessageInfo(mi)
141 | }
142 | }
143 |
144 | func (x *SendFileRequest) String() string {
145 | return protoimpl.X.MessageStringOf(x)
146 | }
147 |
148 | func (*SendFileRequest) ProtoMessage() {}
149 |
150 | func (x *SendFileRequest) ProtoReflect() protoreflect.Message {
151 | mi := &file_api_proto_msgTypes[2]
152 | if protoimpl.UnsafeEnabled && x != nil {
153 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
154 | if ms.LoadMessageInfo() == nil {
155 | ms.StoreMessageInfo(mi)
156 | }
157 | return ms
158 | }
159 | return mi.MessageOf(x)
160 | }
161 |
162 | // Deprecated: Use SendFileRequest.ProtoReflect.Descriptor instead.
163 | func (*SendFileRequest) Descriptor() ([]byte, []int) {
164 | return file_api_proto_rawDescGZIP(), []int{2}
165 | }
166 |
167 | func (x *SendFileRequest) GetFilename() string {
168 | if x != nil {
169 | return x.Filename
170 | }
171 | return ""
172 | }
173 |
174 | // The response message containing the greetings
175 | type STDOutput struct {
176 | state protoimpl.MessageState
177 | sizeCache protoimpl.SizeCache
178 | unknownFields protoimpl.UnknownFields
179 |
180 | Line string `protobuf:"bytes,1,opt,name=line,proto3" json:"line,omitempty"`
181 | }
182 |
183 | func (x *STDOutput) Reset() {
184 | *x = STDOutput{}
185 | if protoimpl.UnsafeEnabled {
186 | mi := &file_api_proto_msgTypes[3]
187 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
188 | ms.StoreMessageInfo(mi)
189 | }
190 | }
191 |
192 | func (x *STDOutput) String() string {
193 | return protoimpl.X.MessageStringOf(x)
194 | }
195 |
196 | func (*STDOutput) ProtoMessage() {}
197 |
198 | func (x *STDOutput) ProtoReflect() protoreflect.Message {
199 | mi := &file_api_proto_msgTypes[3]
200 | if protoimpl.UnsafeEnabled && x != nil {
201 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
202 | if ms.LoadMessageInfo() == nil {
203 | ms.StoreMessageInfo(mi)
204 | }
205 | return ms
206 | }
207 | return mi.MessageOf(x)
208 | }
209 |
210 | // Deprecated: Use STDOutput.ProtoReflect.Descriptor instead.
211 | func (*STDOutput) Descriptor() ([]byte, []int) {
212 | return file_api_proto_rawDescGZIP(), []int{3}
213 | }
214 |
215 | func (x *STDOutput) GetLine() string {
216 | if x != nil {
217 | return x.Line
218 | }
219 | return ""
220 | }
221 |
222 | var File_api_proto protoreflect.FileDescriptor
223 |
224 | var file_api_proto_rawDesc = []byte{
225 | 0x0a, 0x09, 0x61, 0x70, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x22, 0x0a, 0x0c, 0x48,
226 | 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e,
227 | 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22,
228 | 0x20, 0x0a, 0x0a, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x12, 0x0a,
229 | 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d,
230 | 0x65, 0x22, 0x2d, 0x0a, 0x0f, 0x53, 0x65, 0x6e, 0x64, 0x46, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x71,
231 | 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x6e, 0x61, 0x6d, 0x65,
232 | 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x6e, 0x61, 0x6d, 0x65,
233 | 0x22, 0x1f, 0x0a, 0x09, 0x53, 0x54, 0x44, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x12, 0x12, 0x0a,
234 | 0x04, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6c, 0x69, 0x6e,
235 | 0x65, 0x32, 0x5d, 0x0a, 0x06, 0x53, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x12, 0x25, 0x0a, 0x05, 0x48,
236 | 0x65, 0x6c, 0x6c, 0x6f, 0x12, 0x0d, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x71, 0x75,
237 | 0x65, 0x73, 0x74, 0x1a, 0x0b, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79,
238 | 0x22, 0x00, 0x12, 0x2c, 0x0a, 0x08, 0x53, 0x65, 0x6e, 0x64, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x10,
239 | 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x46, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
240 | 0x1a, 0x0a, 0x2e, 0x53, 0x54, 0x44, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x22, 0x00, 0x30, 0x01,
241 | 0x42, 0x07, 0x5a, 0x05, 0x2e, 0x3b, 0x61, 0x70, 0x69, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f,
242 | 0x33,
243 | }
244 |
245 | var (
246 | file_api_proto_rawDescOnce sync.Once
247 | file_api_proto_rawDescData = file_api_proto_rawDesc
248 | )
249 |
250 | func file_api_proto_rawDescGZIP() []byte {
251 | file_api_proto_rawDescOnce.Do(func() {
252 | file_api_proto_rawDescData = protoimpl.X.CompressGZIP(file_api_proto_rawDescData)
253 | })
254 | return file_api_proto_rawDescData
255 | }
256 |
257 | var file_api_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
258 | var file_api_proto_goTypes = []interface{}{
259 | (*HelloRequest)(nil), // 0: HelloRequest
260 | (*HelloReply)(nil), // 1: HelloReply
261 | (*SendFileRequest)(nil), // 2: SendFileRequest
262 | (*STDOutput)(nil), // 3: STDOutput
263 | }
264 | var file_api_proto_depIdxs = []int32{
265 | 0, // 0: Sender.Hello:input_type -> HelloRequest
266 | 2, // 1: Sender.SendFile:input_type -> SendFileRequest
267 | 1, // 2: Sender.Hello:output_type -> HelloReply
268 | 3, // 3: Sender.SendFile:output_type -> STDOutput
269 | 2, // [2:4] is the sub-list for method output_type
270 | 0, // [0:2] is the sub-list for method input_type
271 | 0, // [0:0] is the sub-list for extension type_name
272 | 0, // [0:0] is the sub-list for extension extendee
273 | 0, // [0:0] is the sub-list for field type_name
274 | }
275 |
276 | func init() { file_api_proto_init() }
277 | func file_api_proto_init() {
278 | if File_api_proto != nil {
279 | return
280 | }
281 | if !protoimpl.UnsafeEnabled {
282 | file_api_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
283 | switch v := v.(*HelloRequest); i {
284 | case 0:
285 | return &v.state
286 | case 1:
287 | return &v.sizeCache
288 | case 2:
289 | return &v.unknownFields
290 | default:
291 | return nil
292 | }
293 | }
294 | file_api_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
295 | switch v := v.(*HelloReply); i {
296 | case 0:
297 | return &v.state
298 | case 1:
299 | return &v.sizeCache
300 | case 2:
301 | return &v.unknownFields
302 | default:
303 | return nil
304 | }
305 | }
306 | file_api_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
307 | switch v := v.(*SendFileRequest); i {
308 | case 0:
309 | return &v.state
310 | case 1:
311 | return &v.sizeCache
312 | case 2:
313 | return &v.unknownFields
314 | default:
315 | return nil
316 | }
317 | }
318 | file_api_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
319 | switch v := v.(*STDOutput); i {
320 | case 0:
321 | return &v.state
322 | case 1:
323 | return &v.sizeCache
324 | case 2:
325 | return &v.unknownFields
326 | default:
327 | return nil
328 | }
329 | }
330 | }
331 | type x struct{}
332 | out := protoimpl.TypeBuilder{
333 | File: protoimpl.DescBuilder{
334 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
335 | RawDescriptor: file_api_proto_rawDesc,
336 | NumEnums: 0,
337 | NumMessages: 4,
338 | NumExtensions: 0,
339 | NumServices: 1,
340 | },
341 | GoTypes: file_api_proto_goTypes,
342 | DependencyIndexes: file_api_proto_depIdxs,
343 | MessageInfos: file_api_proto_msgTypes,
344 | }.Build()
345 | File_api_proto = out.File
346 | file_api_proto_rawDesc = nil
347 | file_api_proto_goTypes = nil
348 | file_api_proto_depIdxs = nil
349 | }
350 |
351 | // Reference imports to suppress errors if they are not otherwise used.
352 | var _ context.Context
353 | var _ grpc.ClientConnInterface
354 |
355 | // This is a compile-time assertion to ensure that this generated file
356 | // is compatible with the grpc package it is being compiled against.
357 | const _ = grpc.SupportPackageIsVersion6
358 |
359 | // SenderClient is the client API for Sender service.
360 | //
361 | // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
362 | type SenderClient interface {
363 | // Sends a greeting
364 | Hello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error)
365 | // Sends another greeting
366 | SendFile(ctx context.Context, in *SendFileRequest, opts ...grpc.CallOption) (Sender_SendFileClient, error)
367 | }
368 |
369 | type senderClient struct {
370 | cc grpc.ClientConnInterface
371 | }
372 |
373 | func NewSenderClient(cc grpc.ClientConnInterface) SenderClient {
374 | return &senderClient{cc}
375 | }
376 |
377 | func (c *senderClient) Hello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error) {
378 | out := new(HelloReply)
379 | err := c.cc.Invoke(ctx, "/Sender/Hello", in, out, opts...)
380 | if err != nil {
381 | return nil, err
382 | }
383 | return out, nil
384 | }
385 |
386 | func (c *senderClient) SendFile(ctx context.Context, in *SendFileRequest, opts ...grpc.CallOption) (Sender_SendFileClient, error) {
387 | stream, err := c.cc.NewStream(ctx, &_Sender_serviceDesc.Streams[0], "/Sender/SendFile", opts...)
388 | if err != nil {
389 | return nil, err
390 | }
391 | x := &senderSendFileClient{stream}
392 | if err := x.ClientStream.SendMsg(in); err != nil {
393 | return nil, err
394 | }
395 | if err := x.ClientStream.CloseSend(); err != nil {
396 | return nil, err
397 | }
398 | return x, nil
399 | }
400 |
401 | type Sender_SendFileClient interface {
402 | Recv() (*STDOutput, error)
403 | grpc.ClientStream
404 | }
405 |
406 | type senderSendFileClient struct {
407 | grpc.ClientStream
408 | }
409 |
410 | func (x *senderSendFileClient) Recv() (*STDOutput, error) {
411 | m := new(STDOutput)
412 | if err := x.ClientStream.RecvMsg(m); err != nil {
413 | return nil, err
414 | }
415 | return m, nil
416 | }
417 |
418 | // SenderServer is the server API for Sender service.
419 | type SenderServer interface {
420 | // Sends a greeting
421 | Hello(context.Context, *HelloRequest) (*HelloReply, error)
422 | // Sends another greeting
423 | SendFile(*SendFileRequest, Sender_SendFileServer) error
424 | }
425 |
426 | // UnimplementedSenderServer can be embedded to have forward compatible implementations.
427 | type UnimplementedSenderServer struct {
428 | }
429 |
430 | func (*UnimplementedSenderServer) Hello(context.Context, *HelloRequest) (*HelloReply, error) {
431 | return nil, status.Errorf(codes.Unimplemented, "method Hello not implemented")
432 | }
433 | func (*UnimplementedSenderServer) SendFile(*SendFileRequest, Sender_SendFileServer) error {
434 | return status.Errorf(codes.Unimplemented, "method SendFile not implemented")
435 | }
436 |
437 | func RegisterSenderServer(s *grpc.Server, srv SenderServer) {
438 | s.RegisterService(&_Sender_serviceDesc, srv)
439 | }
440 |
441 | func _Sender_Hello_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
442 | in := new(HelloRequest)
443 | if err := dec(in); err != nil {
444 | return nil, err
445 | }
446 | if interceptor == nil {
447 | return srv.(SenderServer).Hello(ctx, in)
448 | }
449 | info := &grpc.UnaryServerInfo{
450 | Server: srv,
451 | FullMethod: "/Sender/Hello",
452 | }
453 | handler := func(ctx context.Context, req interface{}) (interface{}, error) {
454 | return srv.(SenderServer).Hello(ctx, req.(*HelloRequest))
455 | }
456 | return interceptor(ctx, in, info, handler)
457 | }
458 |
459 | func _Sender_SendFile_Handler(srv interface{}, stream grpc.ServerStream) error {
460 | m := new(SendFileRequest)
461 | if err := stream.RecvMsg(m); err != nil {
462 | return err
463 | }
464 | return srv.(SenderServer).SendFile(m, &senderSendFileServer{stream})
465 | }
466 |
467 | type Sender_SendFileServer interface {
468 | Send(*STDOutput) error
469 | grpc.ServerStream
470 | }
471 |
472 | type senderSendFileServer struct {
473 | grpc.ServerStream
474 | }
475 |
476 | func (x *senderSendFileServer) Send(m *STDOutput) error {
477 | return x.ServerStream.SendMsg(m)
478 | }
479 |
480 | var _Sender_serviceDesc = grpc.ServiceDesc{
481 | ServiceName: "Sender",
482 | HandlerType: (*SenderServer)(nil),
483 | Methods: []grpc.MethodDesc{
484 | {
485 | MethodName: "Hello",
486 | Handler: _Sender_Hello_Handler,
487 | },
488 | },
489 | Streams: []grpc.StreamDesc{
490 | {
491 | StreamName: "SendFile",
492 | Handler: _Sender_SendFile_Handler,
493 | ServerStreams: true,
494 | },
495 | },
496 | Metadata: "api.proto",
497 | }
498 |
--------------------------------------------------------------------------------
/api/api.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 | option go_package = ".;api";
3 | // The greeting service definition.
4 | service Sender {
5 | // Sends a greeting
6 | rpc Hello (HelloRequest) returns (HelloReply) {}
7 | // Sends another greeting
8 | rpc SendFile (SendFileRequest) returns (stream STDOutput) {}
9 | }
10 |
11 | // The request message containing the user's name.
12 | message HelloRequest {
13 | string name = 1;
14 | }
15 |
16 | message HelloReply {
17 | string name = 1;
18 | }
19 |
20 | message SendFileRequest {
21 | string filename = 1;
22 | }
23 |
24 | // The response message containing the greetings
25 | message STDOutput {
26 | string line = 1;
27 | }
--------------------------------------------------------------------------------
/cli/conf/conf.go:
--------------------------------------------------------------------------------
1 | package conf
2 |
3 | import (
4 | "fmt"
5 | "os"
6 | "strings"
7 |
8 | "github.com/emiraganov/sharef/streamer"
9 |
10 | "github.com/pion/webrtc/v3"
11 | )
12 |
13 | func ReadEnv() {
14 | // SHAREF_STUNS is enviroment variable to allow changin default stun server list
15 | if stunservers := os.Getenv("SHAREF_STUNS"); stunservers != "" {
16 | list := strings.Split(stunservers, ",")
17 | stuns := make([]string, 0, len(list))
18 | for _, st := range strings.Split(stunservers, ",") {
19 | stuns = append(stuns, fmt.Sprintf("stun:%s", st))
20 | }
21 | streamer.ICEServerList = []webrtc.ICEServer{
22 | {URLs: stuns},
23 | }
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/cli/deamon.go:
--------------------------------------------------------------------------------
1 | package cli
2 |
3 | import (
4 | "flag"
5 | "fmt"
6 | "os"
7 |
8 | "github.com/emiraganov/sharef/deamon"
9 | "github.com/emiraganov/sharef/streamer"
10 |
11 | log "github.com/sirupsen/logrus"
12 | )
13 |
14 | func Deamon(args []string) {
15 | flagset := flag.NewFlagSet("deamon", flag.ExitOnError)
16 | flagset.Parse(args)
17 |
18 | //Deamon start
19 | deamonizeMe()
20 | }
21 |
22 | func deamonizeMe() {
23 | sess := streamer.NewSession(os.Stdin, os.Stdout)
24 |
25 | s := streamer.NewSender(sess)
26 | if err := s.Dial(); err != nil {
27 | log.Fatal(err)
28 | }
29 |
30 | fmt.Println("Running in daemon mode...")
31 | if err := deamon.StartSenderDaemonServer(s, 9876); err != nil {
32 | fmt.Printf("Fail to star deamon: %s\n", err)
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/cli/pull.go:
--------------------------------------------------------------------------------
1 | package cli
2 |
3 | import (
4 | "flag"
5 | "fmt"
6 |
7 | "github.com/emiraganov/sharef/cli/sdp"
8 | "github.com/emiraganov/sharef/streamer"
9 |
10 | log "github.com/sirupsen/logrus"
11 | )
12 |
13 | func Pull(args []string) {
14 | flagset := flag.NewFlagSet("pull", flag.ExitOnError)
15 | flagset.Parse(args)
16 |
17 | //Receiver
18 | receiveFiles()
19 | }
20 |
21 | func receiveFiles() {
22 | reader, writer := sdp.ReceiverPipe() //This will send prompts and offer/answer from stdin,stdout
23 | sess := streamer.NewSession(reader, writer)
24 | r := streamer.NewReceiver(sess)
25 |
26 | if err := r.Dial(); err != nil {
27 | log.Fatal(err)
28 | }
29 | defer r.Close()
30 |
31 | streamer := r.NewFileStreamer("")
32 |
33 | fmt.Println("")
34 | fmt.Println("Receiving files:")
35 | <-streamer.Stream()
36 | }
37 |
--------------------------------------------------------------------------------
/cli/push.go:
--------------------------------------------------------------------------------
1 | package cli
2 |
3 | import (
4 | "context"
5 | "flag"
6 | "fmt"
7 | "os"
8 | "os/exec"
9 | "sync"
10 | "time"
11 |
12 | "github.com/emiraganov/sharef/cli/sdp"
13 | "github.com/emiraganov/sharef/deamon"
14 | "github.com/emiraganov/sharef/errx"
15 | "github.com/emiraganov/sharef/streamer"
16 | "github.com/emiraganov/sharef/watcher"
17 |
18 | "github.com/emiraganov/goextra/osx"
19 | log "github.com/sirupsen/logrus"
20 | )
21 |
22 | func Push(args []string) {
23 | flagset := flag.NewFlagSet("push", flag.ExitOnError)
24 | var daemonize = flagset.Bool("d", false, "- Daemonize Sender, you must kill it")
25 | var keepsync = flag.Bool("f", false, "- Stream/Sync files")
26 |
27 | flagset.Parse(args)
28 | args = flagset.Args()
29 |
30 | //Check do we deamonize
31 | if *daemonize {
32 | bootstrapSenderDeamon()
33 | return
34 | }
35 |
36 | //Check do we have deamon running
37 | cdaemon := deamon.InitSenderDeamonClient()
38 | if cdaemon != nil {
39 | cdaemon.ProcessArgs(args)
40 | return
41 | }
42 |
43 | //Proceed with normal streaming
44 | if err := sendFiles(args, *keepsync); err != nil {
45 | fmt.Println(err.Error())
46 | }
47 | }
48 |
49 | func bootstrapSenderDeamon() {
50 | name := os.Args[0]
51 | if name == "" {
52 | name = "sharef"
53 | }
54 |
55 | cmd := exec.Command(name, "deamon")
56 |
57 | cmd.Stdout = os.Stdout
58 | cmd.Stdin = os.Stdin
59 |
60 | fmt.Println("Starting deamon, please wait...")
61 | if err := cmd.Start(); err != nil {
62 | log.Fatal(err)
63 | }
64 |
65 | //Now client needs to fulfil SDP connections
66 | var running bool
67 | for i := 0; i < 300; i++ {
68 | time.Sleep(1 * time.Second) //Give some timeout for boot
69 |
70 | s := deamon.InitSenderDeamonClient()
71 | if s != nil {
72 | running = true
73 | fmt.Println("Deamon is up and running")
74 | s.Close() //Close connection
75 | break
76 | }
77 | }
78 |
79 | if !running {
80 | fmt.Println("Timeout")
81 | cmd.Process.Kill()
82 | return
83 | }
84 |
85 | if err := cmd.Process.Release(); err != nil {
86 | log.Fatal(err)
87 | }
88 | // cmd.Wait()
89 | }
90 |
91 | func sendFiles(args []string, keepsync bool) error {
92 | //Check do file exists
93 | for _, file := range args {
94 | if !osx.CheckFileExists(file) {
95 | return fmt.Errorf("File %s does not exist", file)
96 | }
97 | }
98 |
99 | //Sender
100 | reader, writer := sdp.SenderPipe() //This will send prompts and offer/answer from stdin,stdout
101 | sess := streamer.NewSession(reader, writer)
102 | s := streamer.NewSender(sess)
103 |
104 | if err := s.Dial(); err != nil {
105 | return errx.Wrapf(err, "Dial failed")
106 | }
107 | defer s.Close()
108 |
109 | //Stream files
110 | fmt.Println("")
111 | fmt.Println("Sending files:")
112 |
113 | ctx, cancel := context.WithCancel(context.Background())
114 | defer cancel()
115 | for _, file := range args {
116 | fi, err := os.Stat(file)
117 | if err != nil {
118 | return err
119 | }
120 |
121 | streamer := s.NewFileStreamer(file)
122 |
123 | if keepsync {
124 | w := watcher.New(file, fi)
125 | go w.ListenChangeFile(ctx, func(fin os.FileInfo, path string) error {
126 | return streamer.SubStream(fin, path)
127 | })
128 | }
129 |
130 | if err := streamer.Stream(ctx, fi); err != nil {
131 | return errx.Wrapf(err, "Streaming %s file failed", file)
132 | }
133 | }
134 | return nil
135 | }
136 |
137 | func sendStreamerWait(s *streamer.SendStreamer, wg *sync.WaitGroup) {
138 | <-s.Done
139 | wg.Done()
140 | }
141 |
--------------------------------------------------------------------------------
/cli/sdp/receiver_pipe.go:
--------------------------------------------------------------------------------
1 | package sdp
2 |
3 | import (
4 | "fmt"
5 | "io"
6 | "os"
7 | )
8 |
9 | const (
10 | SDP_ANSWER_PROMPT = "Send this answer:"
11 | SDP_ANSWER_WAITING_PROMPT = "Please, paste the remote offer:"
12 | )
13 |
14 | type STDReceiver struct {
15 | readPrompt bool
16 | }
17 |
18 | func ReceiverPipe() (io.Reader, io.Writer) {
19 | s := &STDReceiver{}
20 | return s, s
21 | }
22 |
23 | func (s *STDReceiver) Read(p []byte) (n int, err error) {
24 | if !s.readPrompt { //Read could happen multiple times, making sure we send prompt once
25 | fmt.Printf("%s\n\n", SDP_ANSWER_WAITING_PROMPT)
26 | s.readPrompt = true
27 | }
28 |
29 | n, err = os.Stdin.Read(p)
30 | if err != nil {
31 | return
32 | }
33 |
34 | return
35 | }
36 |
37 | func (s *STDReceiver) Write(p []byte) (n int, err error) {
38 | fmt.Printf("\n%s\n\n", SDP_ANSWER_PROMPT)
39 | n, err = os.Stdout.Write(p)
40 | if err != nil {
41 | return
42 | }
43 |
44 | return
45 | }
46 |
--------------------------------------------------------------------------------
/cli/sdp/sender_pipe.go:
--------------------------------------------------------------------------------
1 | package sdp
2 |
3 | import (
4 | "fmt"
5 | "io"
6 | "os"
7 | )
8 |
9 | const (
10 | SDP_OFFER_PROMPT = "Send this offer:"
11 | SDP_OFFER_WAITING_PROMPT = "Please, paste the remote offer:"
12 | )
13 |
14 | type STDSender struct {
15 | reader io.Reader
16 | writer io.Writer
17 | }
18 |
19 | func SenderPipe() (io.Reader, io.Writer) {
20 | s := &STDSender{
21 | reader: os.Stdin,
22 | writer: os.Stdout,
23 | }
24 | return os.Stdin, s
25 | }
26 |
27 | // func (s *STDSender) Read(p []byte) (n int, err error) {
28 | // n, err = s.reader.Read(p)
29 | // if err != nil {
30 | // return
31 | // }
32 | // return
33 | // }
34 |
35 | func (s *STDSender) Write(p []byte) (n int, err error) {
36 | fmt.Printf("%s\n\n", SDP_OFFER_PROMPT)
37 | n, err = s.writer.Write(p)
38 | if err != nil {
39 | return
40 | }
41 |
42 | fmt.Printf("\n%s\n\n", SDP_OFFER_WAITING_PROMPT)
43 | return
44 | }
45 |
--------------------------------------------------------------------------------
/deamon/sender_daemon_server.go:
--------------------------------------------------------------------------------
1 | package deamon
2 |
3 | import (
4 | context "context"
5 | "fmt"
6 | "io"
7 | "net"
8 | "os"
9 |
10 | "github.com/emiraganov/sharef/api"
11 | "github.com/emiraganov/sharef/streamer"
12 |
13 | grpc "google.golang.org/grpc"
14 | codes "google.golang.org/grpc/codes"
15 | status "google.golang.org/grpc/status"
16 | )
17 |
18 | // UnimplementedSenderServer can be embedded to have forward compatible implementations.
19 | type SenderDaemonServer struct {
20 | sender *streamer.Sender
21 | }
22 |
23 | func StartSenderDaemonServer(sender *streamer.Sender, port int) error {
24 | lis, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
25 | if err != nil {
26 | return err
27 | }
28 | grpcServer := grpc.NewServer()
29 | api.RegisterSenderServer(grpcServer, &SenderDaemonServer{sender: sender})
30 | // determine whether to use TLS
31 | return grpcServer.Serve(lis)
32 | }
33 |
34 | func (*SenderDaemonServer) Hello(context.Context, *api.HelloRequest) (*api.HelloReply, error) {
35 | return &api.HelloReply{}, nil
36 | }
37 | func (s *SenderDaemonServer) SendFile(req *api.SendFileRequest, stream api.Sender_SendFileServer) error {
38 | fi, err := os.Stat(req.Filename)
39 | if err != nil {
40 | return status.Errorf(codes.Internal, err.Error())
41 | }
42 |
43 | streamer := s.sender.NewFileStreamer(req.Filename)
44 |
45 | // reader := bytes.NewBuffer([]byte{})
46 | // writer := bytes.NewBuffer([]byte{})
47 | reader, writer := io.Pipe()
48 | streamer.SetOutput(writer)
49 |
50 | streamer.AsyncStream(fi)
51 | // defer streamer.Close()
52 | for {
53 | select {
54 | case <-streamer.Done:
55 | return nil
56 | default:
57 | }
58 |
59 | data := make([]byte, 4096)
60 | n, err := reader.Read(data)
61 | if err != nil && err != io.EOF {
62 | return status.Errorf(codes.Internal, err.Error())
63 | }
64 |
65 | out := &api.STDOutput{
66 | Line: string(data[:n]),
67 | }
68 |
69 | if err := stream.Send(out); err != nil {
70 | return status.Errorf(codes.Internal, err.Error())
71 | }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/deamon/sender_deamon_client.go:
--------------------------------------------------------------------------------
1 | package deamon
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "io"
7 | "path/filepath"
8 |
9 | "github.com/emiraganov/sharef/api"
10 |
11 | log "github.com/sirupsen/logrus"
12 | "google.golang.org/grpc"
13 | )
14 |
15 | type SenderDeamonClient struct {
16 | conn *grpc.ClientConn
17 | client api.SenderClient
18 | }
19 |
20 | func InitSenderDeamonClient() *SenderDeamonClient {
21 | conn, err := grpc.Dial(":9876", grpc.WithInsecure())
22 | if err != nil {
23 | return nil
24 | }
25 |
26 | client := api.NewSenderClient(conn)
27 |
28 | _, err = client.Hello(context.Background(), &api.HelloRequest{})
29 | if err != nil {
30 | return nil
31 | }
32 |
33 | s := &SenderDeamonClient{
34 | conn: conn,
35 | client: client,
36 | }
37 | return s
38 | }
39 |
40 | func (s *SenderDeamonClient) ProcessArgs(args []string) {
41 | for _, file := range args {
42 | file, err := filepath.Abs(file)
43 | if err != nil {
44 | log.Fatal(err)
45 | }
46 |
47 | out, err := s.client.SendFile(context.Background(), &api.SendFileRequest{Filename: file})
48 | if err != nil {
49 | log.Fatal(err)
50 | }
51 |
52 | for {
53 | stdout, err := out.Recv()
54 | if err == io.EOF {
55 | break
56 | }
57 |
58 | if err != nil {
59 | log.Fatal(err)
60 | }
61 | fmt.Print(stdout.Line)
62 | }
63 | }
64 | }
65 |
66 | func (s *SenderDeamonClient) Close() error {
67 | return s.conn.Close()
68 | }
69 |
--------------------------------------------------------------------------------
/docs/SharefDeamonDemo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emiago/sharef/8b5a478b1e276bd409e3d75b0b93a3a1dccae162/docs/SharefDeamonDemo.gif
--------------------------------------------------------------------------------
/docs/SharefSendDemo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emiago/sharef/8b5a478b1e276bd409e3d75b0b93a3a1dccae162/docs/SharefSendDemo.gif
--------------------------------------------------------------------------------
/errx/errx.go:
--------------------------------------------------------------------------------
1 | package errx
2 |
3 | import "fmt"
4 |
5 | func Wrapf(err error, format string, args ...interface{}) error {
6 | return fmt.Errorf("%s: %w", fmt.Sprintf(format, args...), err)
7 | }
8 |
--------------------------------------------------------------------------------
/fsx/readwrite.go:
--------------------------------------------------------------------------------
1 | package fsx
2 |
3 | import (
4 | "io"
5 | "io/ioutil"
6 | "os"
7 | )
8 |
9 | type FileReader struct {
10 | }
11 |
12 | func NewFileReader() *FileReader {
13 | return &FileReader{}
14 | }
15 |
16 | func (s *FileReader) OpenFile(path string) (io.ReadCloser, error) {
17 | file, err := os.Open(path)
18 | return file, err
19 | }
20 |
21 | func (s *FileReader) ReadDir(path string) ([]os.FileInfo, error) {
22 | return ioutil.ReadDir(path)
23 | }
24 |
25 | type FileWriter struct {
26 | }
27 |
28 | func NewFileWriter() *FileWriter {
29 | return &FileWriter{}
30 | }
31 |
32 | func (s *FileWriter) OpenFile(path string, mode os.FileMode) (io.WriteCloser, error) {
33 | file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, mode)
34 | return file, err
35 | }
36 |
37 | func (s *FileWriter) Mkdir(path string, mode os.FileMode) error {
38 | return os.MkdirAll(path, mode)
39 | }
40 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/emiraganov/sharef
2 |
3 | go 1.13
4 |
5 | require (
6 | github.com/chappjc/logrus-prefix v0.0.0-20180227015900-3a1d64819adb
7 | github.com/emiraganov/goextra/osx v0.0.0-20200623213359-21cf6b1c1580
8 | github.com/gogo/protobuf v1.3.2 // indirect
9 | github.com/golang/protobuf v1.4.3
10 | github.com/google/go-cmp v0.5.0
11 | github.com/google/uuid v1.2.0 // indirect
12 | github.com/klauspost/compress v1.10.10
13 | github.com/kr/text v0.2.0 // indirect
14 | github.com/mattn/go-colorable v0.1.7 // indirect
15 | github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
16 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
17 | github.com/pion/ice/v2 v2.0.15 // indirect
18 | github.com/pion/webrtc/v3 v3.0.4
19 | github.com/sirupsen/logrus v1.6.0
20 | github.com/stretchr/testify v1.7.0
21 | github.com/x-cray/logrus-prefixed-formatter v0.5.2 // indirect
22 | golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad // indirect
23 | golang.org/x/mobile v0.0.0-20201217150744-e6ae53a27f4f // indirect
24 | golang.org/x/net v0.0.0-20210119194325-5f4716e94777 // indirect
25 | golang.org/x/sys v0.0.0-20210123111255-9b0068b26619 // indirect
26 | google.golang.org/grpc v1.30.0
27 | google.golang.org/protobuf v1.25.0
28 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
29 | gopkg.in/fsnotify.v1 v1.4.7
30 | gopkg.in/yaml.v3 v3.0.0-20210106172901-c476de37821d // indirect
31 | gotest.tools v2.2.0+incompatible
32 | )
33 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
2 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
3 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
4 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
5 | github.com/chappjc/logrus-prefix v0.0.0-20180227015900-3a1d64819adb h1:aZTKxMminKeQWHtzJBbV8TttfTxzdJ+7iEJFE6FmUzg=
6 | github.com/chappjc/logrus-prefix v0.0.0-20180227015900-3a1d64819adb/go.mod h1:xzXc1S/L+64uglB3pw54o8kqyM6KFYpTeC9Q6+qZIu8=
7 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
8 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
9 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
10 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
11 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
12 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
13 | github.com/emiraganov/goextra/osx v0.0.0-20200623213359-21cf6b1c1580 h1:yZ634Gqx4gQ5jRw2pwFfgCc2rswEsnorBfbe+mQ2t1g=
14 | github.com/emiraganov/goextra/osx v0.0.0-20200623213359-21cf6b1c1580/go.mod h1:LkJrz5F7Rzo20ikJRJWOwmp4+EW24QpNumXdj8d6iAM=
15 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
16 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
17 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
18 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
19 | github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
20 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
21 | github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
22 | github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
23 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
24 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
25 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
26 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
27 | github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
28 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
29 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
30 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
31 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
32 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
33 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
34 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
35 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
36 | github.com/golang/protobuf v1.4.1 h1:ZFgWrT+bLgsYPirOnRfKLYJLvssAegOj/hgyMFdJZe0=
37 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
38 | github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
39 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
40 | github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
41 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
42 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
43 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
44 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
45 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
46 | github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w=
47 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
48 | github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
49 | github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
50 | github.com/google/uuid v1.1.5/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
51 | github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
52 | github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
53 | github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
54 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
55 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
56 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
57 | github.com/klauspost/compress v1.10.10 h1:a/y8CglcM7gLGYmlbP/stPE5sR3hbhFRUjCBfd/0B3I=
58 | github.com/klauspost/compress v1.10.10/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
59 | github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8=
60 | github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
61 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
62 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
63 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
64 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
65 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
66 | github.com/mattn/go-colorable v0.1.7 h1:bQGKb3vps/j0E9GfJQ03JyhRuxsvdAanXlT9BTw3mdw=
67 | github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
68 | github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
69 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
70 | github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI=
71 | github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
72 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
73 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
74 | github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
75 | github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
76 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
77 | github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
78 | github.com/onsi/ginkgo v1.14.2 h1:8mVmC9kjFFmA8H4pKMUhcblgifdkOIXPvbhN1T36q1M=
79 | github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
80 | github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
81 | github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE=
82 | github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
83 | github.com/onsi/gomega v1.10.3 h1:gph6h/qe9GSUw1NhH1gp+qb+h8rXD8Cy60Z32Qw3ELA=
84 | github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc=
85 | github.com/pion/datachannel v1.4.21 h1:3ZvhNyfmxsAqltQrApLPQMhSFNA+aT87RqyCq4OXmf0=
86 | github.com/pion/datachannel v1.4.21/go.mod h1:oiNyP4gHx2DIwRzX/MFyH0Rz/Gz05OgBlayAI2hAWjg=
87 | github.com/pion/dtls/v2 v2.0.4 h1:WuUcqi6oYMu/noNTz92QrF1DaFj4eXbhQ6dzaaAwOiI=
88 | github.com/pion/dtls/v2 v2.0.4/go.mod h1:qAkFscX0ZHoI1E07RfYPoRw3manThveu+mlTDdOxoGI=
89 | github.com/pion/ice/v2 v2.0.14 h1:FxXxauyykf89SWAtkQCfnHkno6G8+bhRkNguSh9zU+4=
90 | github.com/pion/ice/v2 v2.0.14/go.mod h1:wqaUbOq5ObDNU5ox1hRsEst0rWfsKuH1zXjQFEWiZwM=
91 | github.com/pion/ice/v2 v2.0.15 h1:KZrwa2ciL9od8+TUVJiYTNsCW9J5lktBjGwW1MacEnQ=
92 | github.com/pion/ice/v2 v2.0.15/go.mod h1:ZIiVGevpgAxF/cXiIVmuIUtCb3Xs4gCzCbXB6+nFkSI=
93 | github.com/pion/interceptor v0.0.9 h1:fk5hTdyLO3KURQsf/+RjMpEm4NE3yeTY9Kh97b5BvwA=
94 | github.com/pion/interceptor v0.0.9/go.mod h1:dHgEP5dtxOTf21MObuBAjJeAayPxLUAZjerGH8Xr07c=
95 | github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
96 | github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
97 | github.com/pion/mdns v0.0.4 h1:O4vvVqr4DGX63vzmO6Fw9vpy3lfztVWHGCQfyw0ZLSY=
98 | github.com/pion/mdns v0.0.4/go.mod h1:R1sL0p50l42S5lJs91oNdUL58nm0QHrhxnSegr++qC0=
99 | github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
100 | github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
101 | github.com/pion/rtcp v1.2.6 h1:1zvwBbyd0TeEuuWftrd/4d++m+/kZSeiguxU61LFWpo=
102 | github.com/pion/rtcp v1.2.6/go.mod h1:52rMNPWFsjr39z9B9MhnkqhPLoeHTv1aN63o/42bWE0=
103 | github.com/pion/rtp v1.6.2 h1:iGBerLX6JiDjB9NXuaPzHyxHFG9JsIEdgwTC0lp5n/U=
104 | github.com/pion/rtp v1.6.2/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko=
105 | github.com/pion/sctp v1.7.10 h1:o3p3/hZB5Cx12RMGyWmItevJtZ6o2cpuxaw6GOS4x+8=
106 | github.com/pion/sctp v1.7.10/go.mod h1:EhpTUQu1/lcK3xI+eriS6/96fWetHGCvBi9MSsnaBN0=
107 | github.com/pion/sctp v1.7.11 h1:UCnj7MsobLKLuP/Hh+JMiI/6W5Bs/VF45lWKgHFjSIE=
108 | github.com/pion/sctp v1.7.11/go.mod h1:EhpTUQu1/lcK3xI+eriS6/96fWetHGCvBi9MSsnaBN0=
109 | github.com/pion/sdp/v3 v3.0.4 h1:2Kf+dgrzJflNCSw3TV5v2VLeI0s/qkzy2r5jlR0wzf8=
110 | github.com/pion/sdp/v3 v3.0.4/go.mod h1:bNiSknmJE0HYBprTHXKPQ3+JjacTv5uap92ueJZKsRk=
111 | github.com/pion/srtp/v2 v2.0.1 h1:kgfh65ob3EcnFYA4kUBvU/menCp9u7qaJLXwWgpobzs=
112 | github.com/pion/srtp/v2 v2.0.1/go.mod h1:c8NWHhhkFf/drmHTAblkdu8++lsISEBBdAuiyxgqIsE=
113 | github.com/pion/stun v0.3.5 h1:uLUCBCkQby4S1cf6CGuR9QrVOKcvUwFeemaC865QHDg=
114 | github.com/pion/stun v0.3.5/go.mod h1:gDMim+47EeEtfWogA37n6qXZS88L5V6LqFcf+DZA2UA=
115 | github.com/pion/transport v0.8.10/go.mod h1:tBmha/UCjpum5hqTWhfAEs3CO4/tHSg0MYRhSzR+CZ8=
116 | github.com/pion/transport v0.10.0 h1:9M12BSneJm6ggGhJyWpDveFOstJsTiQjkLf4M44rm80=
117 | github.com/pion/transport v0.10.0/go.mod h1:BnHnUipd0rZQyTVB2SBGojFHT9CBt5C5TcsJSQGkvSE=
118 | github.com/pion/transport v0.10.1 h1:2W+yJT+0mOQ160ThZYUx5Zp2skzshiNgxrNE9GUfhJM=
119 | github.com/pion/transport v0.10.1/go.mod h1:PBis1stIILMiis0PewDw91WJeLJkyIMcEk+DwKOzf4A=
120 | github.com/pion/transport v0.12.0 h1:UFmOBBZkTZ3LgvLRf/NGrfWdZEubcU6zkLU3PsA9YvU=
121 | github.com/pion/transport v0.12.0/go.mod h1:N3+vZQD9HlDP5GWkZ85LohxNsDcNgofQmyL6ojX5d8Q=
122 | github.com/pion/transport v0.12.1/go.mod h1:N3+vZQD9HlDP5GWkZ85LohxNsDcNgofQmyL6ojX5d8Q=
123 | github.com/pion/transport v0.12.2 h1:WYEjhloRHt1R86LhUKjC5y+P52Y11/QqEUalvtzVoys=
124 | github.com/pion/transport v0.12.2/go.mod h1:N3+vZQD9HlDP5GWkZ85LohxNsDcNgofQmyL6ojX5d8Q=
125 | github.com/pion/turn/v2 v2.0.5 h1:iwMHqDfPEDEOFzwWKT56eFmh6DYC6o/+xnLAEzgISbA=
126 | github.com/pion/turn/v2 v2.0.5/go.mod h1:APg43CFyt/14Uy7heYUOGWdkem/Wu4PhCO/bjyrTqMw=
127 | github.com/pion/udp v0.1.0 h1:uGxQsNyrqG3GLINv36Ff60covYmfrLoxzwnCsIYspXI=
128 | github.com/pion/udp v0.1.0/go.mod h1:BPELIjbwE9PRbd/zxI/KYBnbo7B6+oA6YuEaNE8lths=
129 | github.com/pion/webrtc/v3 v3.0.4 h1:Tiw3H9fpfcwkvaxonB+Gv1DG9tmgYBQaM1vBagDHP40=
130 | github.com/pion/webrtc/v3 v3.0.4/go.mod h1:1TmFSLpPYFTFXFHPtoq9eGP1ASTa9LC6FBh7sUY8cd4=
131 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
132 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
133 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
134 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
135 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
136 | github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
137 | github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I=
138 | github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
139 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
140 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
141 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
142 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
143 | github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
144 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
145 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
146 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
147 | github.com/x-cray/logrus-prefixed-formatter v0.5.2 h1:00txxvfBM9muc0jiLIEAkAcIMJzfthRT6usrui8uGmg=
148 | github.com/x-cray/logrus-prefixed-formatter v0.5.2/go.mod h1:2duySbKsL6M18s5GU7VPsoEPHyzalCE06qoARUCeBBE=
149 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
150 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
151 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
152 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
153 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
154 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
155 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
156 | golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
157 | golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY=
158 | golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
159 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
160 | golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4=
161 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
162 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
163 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
164 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
165 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
166 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
167 | golang.org/x/mobile v0.0.0-20201217150744-e6ae53a27f4f h1:kgfVkAEEQXXQ0qc6dH7n6y37NAYmTFmz0YRwrRjgxKw=
168 | golang.org/x/mobile v0.0.0-20201217150744-e6ae53a27f4f/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4=
169 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
170 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
171 | golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd h1:ePuNC7PZ6O5BzgPn9bZayERXBdfZjUYoXEf5BTfDfh8=
172 | golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
173 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
174 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
175 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
176 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
177 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
178 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
179 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
180 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
181 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
182 | golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
183 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
184 | golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
185 | golang.org/x/net v0.0.0-20200625001655-4c5254603344 h1:vGXIOMxbNfDTk/aXCmfdLgkrSV+Z2tcbze+pEc3v5W4=
186 | golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
187 | golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
188 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
189 | golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
190 | golang.org/x/net v0.0.0-20201201195509-5d6afe98e0b7/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
191 | golang.org/x/net v0.0.0-20210119194325-5f4716e94777 h1:003p0dJM77cxMSyCPFphvZf/Y5/NXf5fzg6ufd1/Oew=
192 | golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
193 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
194 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
195 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
196 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
197 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
198 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
199 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
200 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
201 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
202 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
203 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
204 | golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
205 | golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
206 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
207 | golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
208 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
209 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
210 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
211 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
212 | golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
213 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
214 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
215 | golang.org/x/sys v0.0.0-20210123111255-9b0068b26619 h1:yLLDsUUPDliIQpKl7BjVb1igwngIMH2GBjo1VpwLTE0=
216 | golang.org/x/sys v0.0.0-20210123111255-9b0068b26619/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
217 | golang.org/x/term v0.0.0-20201117132131-f5c789dd3221 h1:/ZHdbVpdR/jk3g30/d4yUL0JU9kksj8+F/bnQUVLGDM=
218 | golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
219 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
220 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
221 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
222 | golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
223 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
224 | golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
225 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
226 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
227 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
228 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
229 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
230 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
231 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
232 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
233 | golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69 h1:yBHHx+XZqXJBm6Exke3N7V9gnlsyXxoCPEb1yVenjfk=
234 | golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
235 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
236 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
237 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
238 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
239 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
240 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
241 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
242 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
243 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
244 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
245 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
246 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
247 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY=
248 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
249 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
250 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
251 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
252 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
253 | google.golang.org/grpc v1.30.0 h1:M5a8xTlYTxwMn5ZFkwhRabsygDY5G8TYLyQDBxJNAxE=
254 | google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
255 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
256 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
257 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
258 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
259 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
260 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
261 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
262 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
263 | google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
264 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
265 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
266 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
267 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
268 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
269 | gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
270 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
271 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
272 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
273 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
274 | gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
275 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
276 | gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
277 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
278 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
279 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
280 | gopkg.in/yaml.v3 v3.0.0-20210106172901-c476de37821d h1:827r06Ng1EGlK/5Qb/mj+yHDj6pgKf5CjoX4v24FRJ0=
281 | gopkg.in/yaml.v3 v3.0.0-20210106172901-c476de37821d/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
282 | gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
283 | gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
284 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
285 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
286 |
--------------------------------------------------------------------------------
/gui/index.html:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | gUM audio
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
Warning: if you're not using headphones, pressing play will cause feedback.
38 |
39 |
Render the audio stream from an audio-only getUserMedia()
call with an audio element.
40 |
41 |
The MediaStream
object stream
passed to the getUserMedia()
42 | callback is in global scope, so you can inspect it from the console.
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/gui/main.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
3 | *
4 | * Use of this source code is governed by a BSD-style license
5 | * that can be found in the LICENSE file in the root of the source
6 | * tree.
7 | */
8 |
9 | 'use strict';
10 |
11 | // Put variables in global scope to make them available to the browser console.
12 | // const audio = document.querySelector('audio');
13 |
14 | // const constraints = window.constraints = {
15 | // audio: true,
16 | // video: false
17 | // };
18 |
19 | // function handleSuccess(stream) {
20 | // const audioTracks = stream.getAudioTracks();
21 | // console.log('Got stream with constraints:', constraints);
22 | // console.log('Using audio device: ' + audioTracks[0].label);
23 | // stream.oninactive = function() {
24 | // console.log('Stream ended');
25 | // };
26 | // window.stream = stream; // make variable available to browser console
27 | // audio.srcObject = stream;
28 | // }
29 |
30 | // function handleError(error) {
31 | // const errorMessage = 'navigator.MediaDevices.getUserMedia error: ' + error.message + ' ' + error.name;
32 | // var errorMsgElement = document.getElementById("errorMsg");
33 | // errorMsgElement.innerHTML = errorMessage;
34 | // console.log(errorMessage);
35 | // }
36 |
37 | // navigator.mediaDevices.getUserMedia(constraints).then(handleSuccess).catch(handleError);
38 |
39 |
40 | // peerConnection.setRemoteDescription(new RTCSessionDescription(message));
41 | // const answer = await peerConnection.createAnswer();
42 | // await peerConnection.setLocalDescription(answer);
43 |
44 | //This should wait for track stream and then we should embed
45 |
46 | // var audioElem = document.querySelector('audio');
47 | // peerConnectionpc.ontrack = ev => {
48 | // audioElem.srcObject = ev.streams[0];
49 | // }
50 |
51 |
52 | function handleSDPSubmit() {
53 | console.log("Submit pressed");
54 | var sdpelem = document.getElementById("sdp");
55 | var sdp = sdpelem.value;
56 | console.log(sdp);
57 |
58 |
59 |
60 | }
--------------------------------------------------------------------------------
/itests/api_integration_test.go:
--------------------------------------------------------------------------------
1 | // +build integration
2 |
3 | package itests
4 |
5 | import (
6 | "context"
7 | "fmt"
8 | "io/ioutil"
9 | "os"
10 | "path"
11 | "testing"
12 | "time"
13 |
14 | "github.com/stretchr/testify/assert"
15 | "github.com/stretchr/testify/require"
16 | "github.com/stretchr/testify/suite"
17 | )
18 |
19 | type SuiteApiSender struct {
20 | suite.Suite
21 |
22 | Sendfile string
23 | OutputDir string
24 |
25 | //Do not initialize this
26 | SenderReceiverConnector
27 | outputFile string
28 | }
29 |
30 | func (suite *SuiteApiSender) SetupTest() {
31 | t := suite.T()
32 | outputDir := suite.OutputDir
33 | if err := suite.SetupConnection(outputDir); err != nil {
34 | t.Fatal(err)
35 | }
36 | suite.outputFile = fmt.Sprintf("%s/%s", outputDir, path.Base(suite.Sendfile))
37 | }
38 |
39 | func (suite *SuiteApiSender) TestReceiveFile() {
40 | t := suite.T()
41 | sen := suite.sender
42 | sendfile := suite.Sendfile
43 | outputFile := suite.outputFile
44 |
45 | //Make some content
46 | ioutil.WriteFile(sendfile, []byte("Hello My Friend"), 0644)
47 |
48 | //Send our file
49 | fi, err := os.Stat(sendfile)
50 | require.Nil(t, err)
51 |
52 | t.Log("Starting sending file", sendfile)
53 | sender := sen.NewFileStreamer(sendfile)
54 |
55 | err = sender.Stream(context.Background(), fi)
56 | require.Nil(t, err)
57 |
58 | //Compare data received
59 | senddata, err := ioutil.ReadFile(sendfile)
60 | require.Nil(t, err)
61 |
62 | assert.Eventually(t, func() bool {
63 | return testFileContentAreSame(t, senddata, outputFile)
64 | }, 5*time.Second, 1*time.Second, "File is not received")
65 | }
66 |
67 | func TestApiSenderSendFile(t *testing.T) {
68 | suite.Run(t, &SuiteApiSender{
69 | Sendfile: "./internal/send/testfile.txt",
70 | OutputDir: "./internal/received",
71 | })
72 | }
73 |
--------------------------------------------------------------------------------
/itests/helpers.go:
--------------------------------------------------------------------------------
1 | package itests
2 |
3 | import (
4 | "bufio"
5 | "bytes"
6 | "io"
7 | "io/ioutil"
8 | "os"
9 | "path/filepath"
10 | "strings"
11 | "sync"
12 | "testing"
13 | "time"
14 |
15 | "github.com/emiraganov/sharef/streamer"
16 |
17 | "github.com/emiraganov/goextra/osx"
18 | "github.com/stretchr/testify/assert"
19 | "github.com/stretchr/testify/require"
20 | )
21 |
22 | type SafeBuffer struct {
23 | *bytes.Buffer
24 | mu sync.RWMutex
25 | }
26 |
27 | func (b *SafeBuffer) Write(p []byte) (n int, err error) {
28 | b.mu.Lock()
29 | defer b.mu.Unlock()
30 | return b.Buffer.Write(p)
31 | }
32 |
33 | func (b *SafeBuffer) WriteString(s string) (n int, err error) {
34 | b.mu.Lock()
35 | defer b.mu.Unlock()
36 | return b.Buffer.WriteString(s)
37 | }
38 |
39 | func (b *SafeBuffer) Read(p []byte) (n int, err error) {
40 | b.mu.Lock()
41 | defer b.mu.Unlock()
42 | return b.Buffer.Read(p)
43 | }
44 |
45 | // func (b *SafeBuffer) ReadString(p []byte) (n int, err error) {
46 | // b.mu.Lock()
47 | // defer b.mu.Unlock()
48 | // return b.Buffer.Read(p)
49 | // }
50 |
51 | func startSender() (sen *streamer.Sender, term *bufio.Reader, sdpInput *SafeBuffer, sdpOutput *SafeBuffer, connected chan error) {
52 | // reader, writer := io.Pipe()
53 | // term = bufio.NewReader(reader)
54 |
55 | sdpInput = &SafeBuffer{
56 | Buffer: bytes.NewBuffer([]byte{}),
57 | }
58 |
59 | sdpOutput = &SafeBuffer{
60 | Buffer: bytes.NewBuffer([]byte{}),
61 | }
62 |
63 | conn := streamer.NewSession(sdpInput, sdpOutput)
64 | sen = streamer.NewSender(conn)
65 |
66 | connected = make(chan error)
67 | go func() {
68 | err := sen.Dial()
69 | connected <- err
70 | close(connected)
71 | }()
72 |
73 | return sen, term, sdpInput, sdpOutput, connected
74 | }
75 |
76 | func startReceiver(sdpInput *SafeBuffer, sdpOutput *SafeBuffer) (rec *streamer.Receiver, term *bufio.Reader, connected chan error) {
77 | // reader, writer := io.Pipe()
78 | // term = bufio.NewReader(reader)
79 |
80 | conn := streamer.NewSession(sdpInput, sdpOutput)
81 | rec = streamer.NewReceiver(conn)
82 |
83 | connected = make(chan error)
84 | go func() {
85 | err := rec.Dial()
86 | connected <- err
87 | close(connected)
88 | }()
89 |
90 | return
91 | }
92 |
93 | func readUntil(t *testing.T, term *bufio.Reader, match string) error {
94 | for {
95 | s, err := term.ReadString('\n')
96 |
97 | if err == io.EOF {
98 | continue
99 | }
100 |
101 | if err != nil {
102 | return err
103 | }
104 |
105 | t.Log(s)
106 | if strings.HasPrefix(s, match) {
107 | break
108 | }
109 | }
110 | return nil
111 | }
112 |
113 | func IsEqualDirectory(aroot, broot string, noSize bool) bool {
114 | res := true
115 |
116 | filepath.Walk(aroot, func(path string, fi os.FileInfo, err error) error {
117 | if err != nil {
118 | res = false
119 | return err
120 | }
121 |
122 | if path == "" {
123 | //Skip root
124 | return nil
125 | }
126 |
127 | dest := filepath.Join(broot, strings.TrimPrefix(path, filepath.Dir(aroot)))
128 | fo, err := os.Stat(dest)
129 | if err != nil {
130 | res = false
131 | return err
132 | }
133 |
134 | res = res && fi.IsDir() == fo.IsDir()
135 | res = res && (fi.Size() == fo.Size() || noSize)
136 | res = res && fi.Mode() == fo.Mode()
137 | return nil
138 | })
139 |
140 | return res
141 | }
142 |
143 | func testFileContentAreSame(t *testing.T, senddata []byte, filename string) bool {
144 | file, err := os.Open(filename)
145 | if err == os.ErrNotExist {
146 | t.Log("File does not exists")
147 | return false
148 | }
149 | require.Nil(t, err)
150 | defer file.Close()
151 |
152 | data, err := ioutil.ReadAll(file)
153 | require.Nil(t, err)
154 |
155 | if string(data) != string(senddata) {
156 | t.Log("Data in files is not same")
157 | return false
158 | }
159 |
160 | return true
161 | }
162 |
163 | func assertSendfile2Outputfile(t *testing.T, newdata []byte, sendfile string, outputFile string) {
164 | err := ioutil.WriteFile(sendfile, newdata, 0644)
165 | if err != nil {
166 | t.Fatal(err, err.Error())
167 | }
168 |
169 | assert.Eventually(t, func() bool {
170 | return testFileContentAreSame(t, newdata, outputFile)
171 | }, 5*time.Second, 1*time.Second, "File is not synced sendfile=%s outputfile=%s", sendfile, outputFile)
172 | }
173 |
174 | func assertSendDir2OutputDir(t *testing.T, sendfile string, outputFile string) {
175 | err := os.MkdirAll(sendfile, 0744)
176 | if err != nil {
177 | t.Fatal(err, err.Error())
178 | }
179 |
180 | assert.Eventually(t, func() bool {
181 | return osx.CheckFileExists(outputFile)
182 | }, 5*time.Second, 1*time.Second, "File is not synced sendfile=%s outputfile=%s", sendfile, outputFile)
183 | }
184 |
185 | func assertOutputFileContent(t *testing.T, newdata []byte, outputFile string) {
186 | assert.Eventually(t, func() bool {
187 | return testFileContentAreSame(t, newdata, outputFile)
188 | }, 5*time.Second, 1*time.Second, "File is not received", outputFile)
189 | }
190 |
191 | //This Class should just be extended
192 | type SenderReceiverConnector struct {
193 | sender *streamer.Sender
194 | receiver *streamer.Receiver
195 | }
196 |
197 | func (s *SenderReceiverConnector) SetupConnection(outputDir string) error {
198 | if _, err := os.Stat(outputDir); os.IsNotExist(err) {
199 | os.MkdirAll(outputDir, 0774)
200 | } else {
201 | if err := osx.RemoveContents(outputDir); err != nil {
202 | return err
203 | }
204 | }
205 |
206 | sen, _, sin, sout, sconnected := startSender()
207 | rec, _, rconnected := startReceiver(sout, sin)
208 |
209 | if err := <-sconnected; err != nil {
210 | return err
211 | }
212 |
213 | if err := <-rconnected; err != nil {
214 | return err
215 | }
216 |
217 | streamer := rec.NewFileStreamer(outputDir)
218 | streamer.Stream()
219 |
220 | s.sender = sen
221 | s.receiver = rec
222 | return nil
223 | }
224 |
--------------------------------------------------------------------------------
/itests/internal/send/testfile.txt:
--------------------------------------------------------------------------------
1 | Hello My Friend
2 | Remlines
--------------------------------------------------------------------------------
/itests/internal/senddir/sub/subsub/subsub/nestedfile:
--------------------------------------------------------------------------------
1 | I am so neesteeed
2 | I am so neesteeed
3 | I am so neesteeed
4 | I am so neesteeed
5 | I am so neesteeed
6 | I am so neesteeed
7 | I am so neesteeed
8 | I am so neesteeed
9 | I am so neesteeed
10 | I am so neesteeed
11 |
--------------------------------------------------------------------------------
/itests/internal/senddir/subdir/subfile.md:
--------------------------------------------------------------------------------
1 | this is some md file
2 | ## Has some title
3 |
4 | Has some paragraph
--------------------------------------------------------------------------------
/itests/internal/senddir/testfile.txt:
--------------------------------------------------------------------------------
1 | Hello My Friend
--------------------------------------------------------------------------------
/itests/internal/streamdir/firstdir/newfile.txt:
--------------------------------------------------------------------------------
1 | Some data
2 | Moreaaaaaaaa sssss
--------------------------------------------------------------------------------
/itests/internal/streamdir/firstdir/subdir/newfile.txt:
--------------------------------------------------------------------------------
1 | Some data
2 | Moreaaaaaaaa sssss
--------------------------------------------------------------------------------
/itests/internal/streamdir/newfile.txt:
--------------------------------------------------------------------------------
1 | Some data
--------------------------------------------------------------------------------
/itests/internal/streamdir/testfile.txt:
--------------------------------------------------------------------------------
1 |
2 | Removed lines
--------------------------------------------------------------------------------
/itests/internal/testfile.txt:
--------------------------------------------------------------------------------
1 | third lineasdads
2 | aaaaaaaaaaaaaaaaaaaaaaaa
3 | asdasdas asdasd as
4 | pp[[[[[[[[[[[[daaaaaaaaaaaaaaaaaasdasd
5 | aaaaaaaaaaaa
6 |
7 | aasdad a
--------------------------------------------------------------------------------
/itests/main_integration_test.go:
--------------------------------------------------------------------------------
1 | // +build integration
2 |
3 | package itests
4 |
5 | import (
6 | "bytes"
7 | "context"
8 | "fmt"
9 | "io/ioutil"
10 | "os"
11 | "path"
12 | "path/filepath"
13 | "strings"
14 | "syscall"
15 | "testing"
16 | "time"
17 |
18 | "github.com/emiraganov/sharef/streamer"
19 | "github.com/emiraganov/sharef/watcher"
20 |
21 | log_prefixed "github.com/chappjc/logrus-prefix"
22 | "github.com/emiraganov/goextra/osx"
23 | "github.com/sirupsen/logrus"
24 | "github.com/stretchr/testify/assert"
25 | "github.com/stretchr/testify/require"
26 | "github.com/stretchr/testify/suite"
27 |
28 | cliconf "github.com/emiraganov/sharef/cli/conf"
29 | )
30 |
31 | func init() {
32 | logrus.SetLevel(logrus.DebugLevel)
33 |
34 | logrus.SetFormatter(&log_prefixed.TextFormatter{
35 | FullTimestamp: true,
36 | })
37 |
38 | cliconf.ReadEnv()
39 | }
40 |
41 | type SuiteSendFile struct {
42 | suite.Suite
43 |
44 | Sendfile string
45 | OutputDir string
46 |
47 | //Do not initialize this
48 | SenderReceiverConnector
49 | outputFile string
50 | }
51 |
52 | func (suite *SuiteSendFile) SetupTest() {
53 | t := suite.T()
54 | outputDir := suite.OutputDir
55 | if err := suite.SetupConnection(outputDir); err != nil {
56 | t.Fatal(err)
57 | }
58 | suite.outputFile = fmt.Sprintf("%s/%s", outputDir, path.Base(suite.Sendfile))
59 | }
60 |
61 | func (suite *SuiteSendFile) TestReceiveFile() {
62 | t := suite.T()
63 | sen := suite.sender
64 | sendfile := suite.Sendfile
65 | outputFile := suite.outputFile
66 |
67 | //Make some content
68 | ioutil.WriteFile(sendfile, []byte("Hello My Friend"), 0644)
69 |
70 | //Send our file
71 | t.Log("Starting sending file", sendfile)
72 | err := sen.SendFile(sendfile)
73 | require.Nil(t, err)
74 |
75 | //Compare data received
76 | senddata, err := ioutil.ReadFile(sendfile)
77 | require.Nil(t, err)
78 |
79 | assert.Eventually(t, func() bool {
80 | return testFileContentAreSame(t, senddata, outputFile)
81 | }, 5*time.Second, 1*time.Second, "File is not received")
82 | }
83 |
84 | func (suite *SuiteSendFile) TestStringReader() {
85 | t := suite.T()
86 | sen := suite.sender
87 | sendfile := suite.Sendfile
88 | outputFile := suite.outputFile
89 |
90 | sender := sen.NewFileStreamer(sendfile)
91 |
92 | senddata := []byte("This some of my content\nSome rows\nSome content")
93 | readersize := bytes.NewReader(senddata)
94 | reader := bytes.NewReader(senddata)
95 | fi := streamer.StreamFile{
96 | Name: path.Base(sendfile),
97 | Path: sendfile,
98 | // FullPath: sendfile,
99 | SizeLen: readersize.Size(),
100 | Mode: 0664,
101 | ModTime: time.Now().String(),
102 | }
103 |
104 | err := sender.StreamReader(context.Background(), reader, fi)
105 | require.Nil(t, err)
106 | assert.Eventually(t, func() bool {
107 | return testFileContentAreSame(t, senddata, outputFile)
108 | }, 5*time.Second, 1*time.Second, "File is not received")
109 | }
110 |
111 | type SuiteStreamFile struct {
112 | suite.Suite
113 |
114 | Sendfile string
115 | OutputDir string
116 |
117 | //Do not initialize this
118 | SenderReceiverConnector
119 | outputFile string
120 | }
121 |
122 | func (suite *SuiteStreamFile) SetupTest() {
123 | t := suite.T()
124 | outputDir := suite.OutputDir
125 | if err := suite.SetupConnection(outputDir); err != nil {
126 | t.Fatal(err)
127 | }
128 | suite.outputFile = fmt.Sprintf("%s/%s", outputDir, path.Base(suite.Sendfile))
129 |
130 | //Here we want to prepare for streaming. So file needs two be sent prior that
131 | sen := suite.sender
132 | sendfile := suite.Sendfile
133 | //Send our file
134 | t.Log("Starting sending file", sendfile)
135 | fi, err := os.Stat(sendfile)
136 | require.Nil(t, err)
137 |
138 | //Start listener
139 | w := watcher.New(sendfile, fi)
140 | ctx := context.Background()
141 | sender := sen.NewFileStreamer(sendfile)
142 | err = sender.Stream(ctx, fi)
143 | go w.ListenChangeFile(ctx, func(fi os.FileInfo, path string) error {
144 | return sender.SubStream(fi, path)
145 | })
146 | require.Nil(t, err)
147 | }
148 |
149 | func (suite *SuiteStreamFile) TestStreamFile() {
150 | t := suite.T()
151 | sendfile := suite.Sendfile
152 | outputfile := suite.outputFile
153 |
154 | //Compare data received
155 | senddata, err := ioutil.ReadFile(sendfile)
156 | require.Nil(t, err)
157 | assertSendfile2Outputfile(t, senddata, sendfile, outputfile)
158 |
159 | //Add line
160 | newdata := append(senddata, []byte("\nSomething on new line")...)
161 | assertSendfile2Outputfile(t, newdata, sendfile, outputfile)
162 |
163 | // newdata = append(newdata, []byte("\nSomething on new second line")...)
164 | // assertSendfile2Outputfile(t, newdata, sendfile, outputfile)
165 |
166 | newdata = append(newdata, []byte(" MOre on second line")...)
167 | assertSendfile2Outputfile(t, newdata, sendfile, outputfile)
168 |
169 | //Remove lines
170 | newdata = append(senddata, []byte("\nRemoved lines")...)
171 | assertSendfile2Outputfile(t, newdata, sendfile, outputfile)
172 |
173 | //Remove couple characters in middle
174 | newdata = append(newdata[0:len(newdata)-10], newdata[len(newdata)-5:]...)
175 | assertSendfile2Outputfile(t, newdata, sendfile, outputfile)
176 | }
177 |
178 | type SuiteSendDir struct {
179 | suite.Suite
180 |
181 | SendDir string
182 | OutputDir string
183 |
184 | //Do not initialize this
185 | SenderReceiverConnector
186 | }
187 |
188 | func (suite *SuiteSendDir) SetupTest() {
189 | t := suite.T()
190 | outputDir := suite.OutputDir
191 | if err := suite.SetupConnection(outputDir); err != nil {
192 | t.Fatal(err)
193 | }
194 | }
195 |
196 | func (suite *SuiteSendDir) checkFileContent(senddata []byte, filename string) bool {
197 | t := suite.T()
198 | _, err := os.Stat(filename)
199 | if err == os.ErrNotExist {
200 | t.Log("File does not exists")
201 | return false
202 | }
203 |
204 | if e, ok := err.(*os.PathError); ok && e.Err == syscall.ENOENT {
205 | t.Log("File does not exists")
206 | return false
207 | }
208 |
209 | require.Nil(t, err)
210 | data, err := ioutil.ReadFile(filename)
211 | require.Nil(t, err)
212 |
213 | if string(data) != string(senddata) {
214 | t.Log("Data in files is not same")
215 | return false
216 | }
217 |
218 | return true
219 | }
220 |
221 | func (suite *SuiteSendDir) TestReceiveFile() {
222 | t := suite.T()
223 | sen := suite.sender
224 | senddir := suite.SendDir
225 | outputDir := suite.OutputDir
226 | //Send our file
227 | t.Log("Starting sending file", senddir)
228 | err := sen.SendFile(senddir)
229 | require.Nil(t, err)
230 |
231 | //Compare data received
232 | var sendfiles []string
233 |
234 | filepath.Walk(senddir, func(path string, info os.FileInfo, err error) error {
235 | if path != "" {
236 | sendfiles = append(sendfiles, path)
237 | }
238 | return nil
239 | })
240 |
241 | assert.Eventually(t, func() bool {
242 | for _, sendfile := range sendfiles {
243 | outf := filepath.Base(senddir) + strings.TrimPrefix(sendfile, senddir)
244 | outputFile := fmt.Sprintf("%s/%s", outputDir, outf)
245 |
246 | t.Logf("Comparing content %s vs %s", sendfile, outputFile)
247 |
248 | if !osx.CompareFilesAreSame(sendfile, outputFile) {
249 | return false
250 | }
251 | }
252 | return true
253 | }, 5*time.Second, 1*time.Second, "File is not received")
254 | }
255 |
256 | type SuiteStreamDir struct {
257 | suite.Suite
258 |
259 | SendDir string
260 | OutputDir string
261 |
262 | //Do not initialize this
263 | SenderReceiverConnector
264 | }
265 |
266 | func (suite *SuiteStreamDir) SetupTest() {
267 | t := suite.T()
268 | outputDir := suite.OutputDir
269 | if err := suite.SetupConnection(outputDir); err != nil {
270 | t.Fatal(err)
271 | }
272 |
273 | //Clear also our stream dir
274 | senddir := suite.SendDir
275 | osx.RemoveContents(senddir)
276 |
277 | //Create some existing files
278 | sendfile := filepath.Join(senddir, "testfile.txt")
279 | err := ioutil.WriteFile(sendfile, []byte("Here some lines\nThis should be second line\nThirdline"), 0644)
280 | require.Nil(t, err)
281 |
282 | }
283 |
284 | func (suite *SuiteStreamDir) TestSendingDirAndChanges() {
285 | t := suite.T()
286 | sen := suite.sender
287 | senddir := suite.SendDir
288 | outputDir := suite.OutputDir
289 | outputsenddir := filepath.Join(outputDir, filepath.Base(senddir))
290 | // outputFile := suite.outputFile
291 |
292 | //Send our file
293 | t.Log("Starting sending file", senddir)
294 | // err := sen.SendFile(senddir)
295 | // require.Nil(t, err)
296 |
297 | fi, err := os.Stat(senddir)
298 | require.Nil(t, err)
299 |
300 | w := watcher.New(senddir, fi)
301 | ctx := context.Background()
302 |
303 | sender := sen.NewFileStreamer(senddir)
304 | sender.Stream(context.Background(), fi)
305 |
306 | go w.ListenChangeFile(ctx, func(fin os.FileInfo, path string) error {
307 | return sender.SubStream(fin, path)
308 | })
309 |
310 | //Compare data received
311 | sendfile := filepath.Join(senddir, "testfile.txt")
312 | outputfile := filepath.Join(outputsenddir, "testfile.txt")
313 |
314 | err = os.Truncate(sendfile, 0)
315 | require.Nil(t, err)
316 |
317 | suite.assertSimpleFileChanges(sendfile, outputfile)
318 |
319 | //Add new file
320 | sendfile = filepath.Join(senddir, "newfile.txt")
321 | outputfile = filepath.Join(outputsenddir, "newfile.txt")
322 | assertSendfile2Outputfile(t, []byte("Some data"), sendfile, outputfile)
323 |
324 | //Add new dir
325 | sendfirstdir := filepath.Join(senddir, "firstdir")
326 | outputfile = filepath.Join(outputsenddir, "firstdir")
327 | assertSendDir2OutputDir(t, sendfirstdir, outputfile)
328 |
329 | //Add new file in firstdir
330 | sendfile = filepath.Join(sendfirstdir, "newfile.txt")
331 | outputfile = filepath.Join(outputsenddir, filepath.Base(sendfirstdir), "newfile.txt")
332 | assertSendfile2Outputfile(t, []byte("Some data\nMoreaaaaaaaa sssss"), sendfile, outputfile)
333 |
334 | //Add subdir in firstdir
335 | sendfirstsubdir := filepath.Join(sendfirstdir, "subdir")
336 | outputfile = filepath.Join(outputsenddir, filepath.Base(sendfirstdir), "subdir")
337 | assertSendDir2OutputDir(t, sendfirstsubdir, outputfile)
338 |
339 | //Add new file in firstdir/subdir
340 | sendfile = filepath.Join(sendfirstsubdir, "newfile.txt")
341 | outputfile = filepath.Join(outputsenddir, filepath.Base(sendfirstdir), filepath.Base(sendfirstsubdir), "newfile.txt")
342 | assertSendfile2Outputfile(t, []byte("Some data\nMoreaaaaaaaa sssss"), sendfile, outputfile)
343 | }
344 |
345 | func (suite *SuiteStreamDir) assertSimpleFileChanges(sendfile string, outputfile string) {
346 | t := suite.T()
347 | senddata, err := ioutil.ReadFile(sendfile)
348 | require.Nil(t, err)
349 | assertSendfile2Outputfile(t, senddata, sendfile, outputfile)
350 |
351 | //Add line
352 | newdata := append(senddata, []byte("\nSomething on new line")...)
353 | assertSendfile2Outputfile(t, newdata, sendfile, outputfile)
354 |
355 | //Remove lines
356 | newdata = append(senddata, []byte("\nRemoved lines")...)
357 | assertSendfile2Outputfile(t, newdata, sendfile, outputfile)
358 | }
359 |
360 | func TestSendFile(t *testing.T) {
361 | suite.Run(t, &SuiteSendFile{
362 | Sendfile: "./internal/send/testfile.txt",
363 | OutputDir: "./internal/received",
364 | })
365 | }
366 |
367 | func TestStreamFile(t *testing.T) {
368 | suite.Run(t, &SuiteStreamFile{
369 | Sendfile: "./internal/send/testfile.txt",
370 | OutputDir: "./internal/received",
371 | })
372 | }
373 |
374 | func TestSendDir(t *testing.T) {
375 | suite.Run(t, &SuiteSendDir{
376 | SendDir: "internal/senddir",
377 | OutputDir: "internal/received",
378 | })
379 | }
380 |
381 | func TestStreamDir(t *testing.T) {
382 | suite.Run(t, &SuiteStreamDir{
383 | SendDir: "internal/streamdir",
384 | OutputDir: "internal/received",
385 | })
386 | }
387 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "flag"
5 | "fmt"
6 | "os"
7 |
8 | "github.com/emiraganov/sharef/cli"
9 | cliconf "github.com/emiraganov/sharef/cli/conf"
10 |
11 | log_prefixed "github.com/chappjc/logrus-prefix"
12 | "github.com/sirupsen/logrus"
13 | )
14 |
15 | var verbose = flag.Int("v", 3, "verbosity")
16 |
17 | func init() {
18 | logrus.SetFormatter(&log_prefixed.TextFormatter{
19 | FullTimestamp: true,
20 | })
21 | }
22 |
23 | func main() {
24 | // receive := flag.NewFlagSet("receive", flag.ExitOnError)
25 | flag.Usage = func() {
26 | s := `Usage:
27 | push - Start sending/streaming files. More options are available.
28 | pull - Start receiving files.
29 | `
30 |
31 | fmt.Fprintln(os.Stderr, s)
32 | fmt.Fprintln(os.Stderr, "Options:")
33 | flag.PrintDefaults()
34 | }
35 | flag.Parse()
36 | logrus.SetLevel(logrus.Level(*verbose))
37 | // Read any enviroment vars for
38 | cliconf.ReadEnv()
39 |
40 | args := flag.Args()
41 |
42 | // We expect always some action argument:
43 | if len(args) == 0 {
44 | flag.Usage()
45 | return
46 | }
47 | ParseArgs(args)
48 | }
49 |
50 | func ParseArgs(args []string) {
51 | action := args[0]
52 | args = args[1:]
53 | switch action {
54 | case "push":
55 | cli.Push(args)
56 | case "pull":
57 | cli.Pull(args)
58 | case "deamon":
59 | cli.Deamon(args)
60 | default:
61 | fmt.Println("Unknown action")
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/streamer/bandwithcalc.go:
--------------------------------------------------------------------------------
1 | package streamer
2 |
3 | import (
4 | "fmt"
5 | "io"
6 | "time"
7 | )
8 |
9 | const (
10 | KB int64 = 1024
11 | MB int64 = 1024 * 1024
12 | GB int64 = 1024 * 1024 * 1024
13 | )
14 |
15 | type StreamBandwithCalculator interface {
16 | // NewStream called for each new stream
17 | NewStream(streamname string, size uint64)
18 | // Add adds amount of bytes read from network
19 | Add(n uint64)
20 | // Finish is called when stream is closed
21 | Finish()
22 | }
23 |
24 | type BandwithCalc struct {
25 | n uint64 //Bytes
26 | start time.Time
27 | lastprint time.Time
28 | duration time.Duration
29 | size uint64
30 | w io.Writer
31 | streamname string
32 | }
33 |
34 | func NewBandwithCalc(w io.Writer) *BandwithCalc {
35 | b := &BandwithCalc{
36 | start: time.Now(),
37 | lastprint: time.Time{},
38 | w: w,
39 | }
40 |
41 | return b
42 | }
43 |
44 | func (b *BandwithCalc) calcIn(munit int64) float64 {
45 | duration := b.duration.Seconds()
46 | if duration < 1 {
47 | duration = 1
48 | }
49 |
50 | bandwidth := float64(b.n) / float64(munit) / float64(duration)
51 | return bandwidth
52 | }
53 |
54 | func (b *BandwithCalc) total(munit int64) float64 {
55 | bandwidth := float64(b.n) / float64(munit)
56 | return bandwidth
57 | }
58 |
59 | func (b *BandwithCalc) percentage() int64 {
60 | if b.n == 0 {
61 | return 0
62 | }
63 | bandwidth := float64(b.n) / float64(b.size) * 100
64 | return int64(bandwidth)
65 | }
66 |
67 | func (b *BandwithCalc) printOnSecond() {
68 | since := time.Since(b.lastprint)
69 | if since.Seconds() > 1 { //Printing only if there are changes
70 | b.print()
71 | b.lastprint = time.Now()
72 | }
73 | }
74 |
75 | func (b *BandwithCalc) print() {
76 | speed := b.calcIn(MB)
77 | total := b.total(MB)
78 | percentage := b.percentage()
79 | fmt.Fprintf(b.w, "\033[999D%s %d%% %.2fMB %.2f MB/s\033[K", b.streamname, percentage, total, speed)
80 | }
81 |
82 | func (b *BandwithCalc) NewStream(streamname string, n uint64) {
83 | b.start = time.Now()
84 | b.lastprint = time.Time{}
85 | b.streamname = streamname
86 | b.size = n
87 | b.n = 0
88 | }
89 |
90 | //When adding bytes we calculate duration, to be more precise
91 | func (b *BandwithCalc) Add(n uint64) {
92 | b.n += n
93 | b.duration = time.Since(b.start)
94 | b.printOnSecond()
95 | }
96 |
97 | func (b *BandwithCalc) Finish() {
98 | b.print()
99 | fmt.Fprintln(b.w) //Print new line on finish. So that next stream does now overwrite output
100 | }
101 |
--------------------------------------------------------------------------------
/streamer/bandwithcalc_test.go:
--------------------------------------------------------------------------------
1 | package streamer
2 |
3 | import (
4 | "math"
5 | "math/rand"
6 | "os"
7 | "testing"
8 | "time"
9 |
10 | "gotest.tools/assert"
11 | )
12 |
13 | func bandwithCalcFakeData(b *BandwithCalc, n uint64, t time.Duration) {
14 | b.Add(n)
15 | b.duration = t
16 | }
17 |
18 | func TestBandWithCalc(t *testing.T) {
19 | b := NewBandwithCalc(os.Stdout)
20 | b.NewStream("", 4096)
21 |
22 | bandwithCalcFakeData(b, 4096, 1*time.Second)
23 | //Lets calculate
24 | res := b.calcIn(KB)
25 | assert.Equal(t, math.Round(res), math.Round(4.00))
26 | }
27 |
28 | func TestBandWithLoopCalc(t *testing.T) {
29 | b := NewBandwithCalc(os.Stdout)
30 | b.NewStream("", 0)
31 |
32 | var totalbytes uint64
33 | for i := 1; i <= 10; i++ {
34 | //Make some random bytes between to 1 to 64K
35 | nbytes := uint64(1 + rand.Intn(int(64*KB)))
36 | totalbytes += nbytes
37 | duration := time.Duration(i) * time.Second
38 |
39 | bandwithCalcFakeData(b, nbytes, duration)
40 |
41 | res := b.calcIn(KB)
42 | expected := float64(totalbytes) / float64(KB) / float64(duration.Seconds())
43 | assert.Equal(t, math.Round(res), math.Round(expected))
44 | }
45 | }
46 |
47 | func TestBandWithPercentage(t *testing.T) {
48 | var third uint64 = 51234
49 | var totalbytes uint64 = 3 * third
50 |
51 | b := NewBandwithCalc(os.Stdout)
52 | b.NewStream("", totalbytes)
53 |
54 | b.Add(third)
55 | res := b.percentage()
56 | assert.Equal(t, res, int64(33), "Not 33%")
57 |
58 | b.Add(third)
59 | res = b.percentage()
60 | assert.Equal(t, res, int64(66), "Not 66%")
61 |
62 | b.Add(third)
63 | res = b.percentage()
64 | assert.Equal(t, res, int64(100), "Not 100%")
65 | }
66 |
--------------------------------------------------------------------------------
/streamer/datachannel.go:
--------------------------------------------------------------------------------
1 | package streamer
2 |
3 | import (
4 | webrtc "github.com/pion/webrtc/v3"
5 | )
6 |
7 | const (
8 | DatachannelLabelJsonstream = "jsonstream"
9 | DatachannelLabelProtobufstream = "protostream"
10 | )
11 |
12 | func DataChannelInitFileStream() *webrtc.DataChannelInit {
13 | ordered := true
14 | maxPacketLifeTime := uint16(10000)
15 | return &webrtc.DataChannelInit{
16 | Ordered: &ordered,
17 | MaxPacketLifeTime: &maxPacketLifeTime,
18 | }
19 | }
20 |
21 | type ReadWriteFramer interface {
22 | SendFrame(t int, f Framer) (n uint64, err error)
23 | ReadFrame(msg []byte) (f Framer, err error)
24 | }
25 |
26 | type DataChannelFramer struct {
27 | channel *webrtc.DataChannel
28 | encoder FrameEncoder
29 | }
30 |
31 | func NewDataChannelFramer(channel *webrtc.DataChannel) *DataChannelFramer {
32 | switch channel.Label() {
33 | case DatachannelLabelJsonstream:
34 | return &DataChannelFramer{
35 | channel,
36 | &JSONFrameEncoder{},
37 | }
38 | default:
39 | return &DataChannelFramer{
40 | channel,
41 | &ProtobufFrameEncoder{},
42 | }
43 | }
44 | }
45 |
46 | func (s *DataChannelFramer) SendFrame(t int, f Framer) (n uint64, err error) {
47 | data, err := s.encoder.MarshalFramer(f, t)
48 | if err != nil {
49 | return 0, err
50 | }
51 |
52 | n = uint64(len(data))
53 | err = s.channel.Send(data)
54 | return
55 | }
56 |
57 | func (s *DataChannelFramer) ReadFrame(msg []byte) (f Framer, err error) {
58 | return s.encoder.UnmarshalFramer(msg)
59 | }
60 |
--------------------------------------------------------------------------------
/streamer/filestat.go:
--------------------------------------------------------------------------------
1 | package streamer
2 |
3 | import (
4 | "os"
5 | "time"
6 | )
7 |
8 | type FileStat struct {
9 | name string
10 | size int64
11 | mode os.FileMode
12 | modtime time.Time
13 | }
14 |
15 | func NewFileStat(name string, size int64, mode os.FileMode, modtime time.Time) *FileStat {
16 | return &FileStat{name, size, mode, modtime}
17 | }
18 |
19 | func (s *FileStat) Name() string {
20 | return s.name
21 | }
22 |
23 | func (s *FileStat) Size() int64 {
24 | return s.size
25 | }
26 |
27 | func (s *FileStat) Mode() os.FileMode {
28 | return s.mode
29 | }
30 |
31 | func (s *FileStat) ModTime() time.Time {
32 | return s.modtime
33 | }
34 |
35 | func (s *FileStat) IsDir() bool {
36 | return s.mode.IsDir()
37 | }
38 |
39 | func (s *FileStat) Sys() interface{} {
40 | return nil
41 | }
42 |
--------------------------------------------------------------------------------
/streamer/frame.go:
--------------------------------------------------------------------------------
1 | package streamer
2 |
3 | import (
4 | "google.golang.org/protobuf/runtime/protoiface"
5 | )
6 |
7 | const (
8 | FRAME_NEWSTREAM = iota
9 | FRAME_DATA
10 | FRAME_OK
11 | FRAME_ERROR
12 | )
13 |
14 | type Framer interface {
15 | // proto.Message
16 | protoiface.MessageV1
17 | //protoreflect.ProtoMessage
18 | SetT(t int32)
19 | GetT() int32
20 | }
21 |
22 |
23 |
24 | // type Frame struct {
25 | // Type int
26 | // }
27 |
28 | func (f *Frame) SetT(t int32) {
29 | f.T = t
30 | }
31 |
32 | func (f *FrameError) SetT(t int32) {
33 | f.T = t
34 | }
35 |
36 | func (f *FrameNewStream) SetT(t int32) {
37 | f.T = t
38 | }
39 |
40 | func (f *FrameData) SetT(t int32) {
41 | f.T = t
42 | }
43 |
44 | // func (f *Frame) GetT() int {
45 | // return f.Type
46 | // }
47 |
48 | // type FrameError struct {
49 | // Frame
50 | // Err string
51 | // }
52 |
53 | // type FrameNewStream struct {
54 | // Frame
55 | // Info StreamFile
56 | // }
57 |
58 | // type FrameData struct {
59 | // Frame
60 | // Data []byte
61 | // }
62 |
--------------------------------------------------------------------------------
/streamer/frame.pb.go:
--------------------------------------------------------------------------------
1 | // Code generated by protoc-gen-gogo. DO NOT EDIT.
2 | // source: frame.proto
3 |
4 | package streamer
5 |
6 | import (
7 | fmt "fmt"
8 | proto "github.com/gogo/protobuf/proto"
9 | io "io"
10 | math "math"
11 | math_bits "math/bits"
12 | )
13 |
14 | // Reference imports to suppress errors if they are not otherwise used.
15 | var _ = proto.Marshal
16 | var _ = fmt.Errorf
17 | var _ = math.Inf
18 |
19 | // This is a compile-time assertion to ensure that this generated file
20 | // is compatible with the proto package it is being compiled against.
21 | // A compilation error at this line likely means your copy of the
22 | // proto package needs to be updated.
23 | const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package
24 |
25 | type StreamFile struct {
26 | Name string `protobuf:"bytes,1,opt,name=Name,proto3" json:"Name,omitempty"`
27 | Path string `protobuf:"bytes,2,opt,name=Path,proto3" json:"Path,omitempty"`
28 | SizeLen int64 `protobuf:"varint,3,opt,name=SizeLen,proto3" json:"SizeLen,omitempty"`
29 | Mode uint32 `protobuf:"varint,4,opt,name=Mode,proto3" json:"Mode,omitempty"`
30 | ModTime string `protobuf:"bytes,5,opt,name=ModTime,proto3" json:"ModTime,omitempty"`
31 | FullPath string `protobuf:"bytes,6,opt,name=FullPath,proto3" json:"FullPath,omitempty"`
32 | }
33 |
34 | func (m *StreamFile) Reset() { *m = StreamFile{} }
35 | func (m *StreamFile) String() string { return proto.CompactTextString(m) }
36 | func (*StreamFile) ProtoMessage() {}
37 | func (*StreamFile) Descriptor() ([]byte, []int) {
38 | return fileDescriptor_5379e2b825e15002, []int{0}
39 | }
40 | func (m *StreamFile) XXX_Unmarshal(b []byte) error {
41 | return m.Unmarshal(b)
42 | }
43 | func (m *StreamFile) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
44 | if deterministic {
45 | return xxx_messageInfo_StreamFile.Marshal(b, m, deterministic)
46 | } else {
47 | b = b[:cap(b)]
48 | n, err := m.MarshalToSizedBuffer(b)
49 | if err != nil {
50 | return nil, err
51 | }
52 | return b[:n], nil
53 | }
54 | }
55 | func (m *StreamFile) XXX_Merge(src proto.Message) {
56 | xxx_messageInfo_StreamFile.Merge(m, src)
57 | }
58 | func (m *StreamFile) XXX_Size() int {
59 | return m.Size()
60 | }
61 | func (m *StreamFile) XXX_DiscardUnknown() {
62 | xxx_messageInfo_StreamFile.DiscardUnknown(m)
63 | }
64 |
65 | var xxx_messageInfo_StreamFile proto.InternalMessageInfo
66 |
67 | func (m *StreamFile) GetName() string {
68 | if m != nil {
69 | return m.Name
70 | }
71 | return ""
72 | }
73 |
74 | func (m *StreamFile) GetPath() string {
75 | if m != nil {
76 | return m.Path
77 | }
78 | return ""
79 | }
80 |
81 | func (m *StreamFile) GetSizeLen() int64 {
82 | if m != nil {
83 | return m.SizeLen
84 | }
85 | return 0
86 | }
87 |
88 | func (m *StreamFile) GetMode() uint32 {
89 | if m != nil {
90 | return m.Mode
91 | }
92 | return 0
93 | }
94 |
95 | func (m *StreamFile) GetModTime() string {
96 | if m != nil {
97 | return m.ModTime
98 | }
99 | return ""
100 | }
101 |
102 | func (m *StreamFile) GetFullPath() string {
103 | if m != nil {
104 | return m.FullPath
105 | }
106 | return ""
107 | }
108 |
109 | type Frame struct {
110 | T int32 `protobuf:"varint,1,opt,name=T,proto3" json:"T,omitempty"`
111 | }
112 |
113 | func (m *Frame) Reset() { *m = Frame{} }
114 | func (m *Frame) String() string { return proto.CompactTextString(m) }
115 | func (*Frame) ProtoMessage() {}
116 | func (*Frame) Descriptor() ([]byte, []int) {
117 | return fileDescriptor_5379e2b825e15002, []int{1}
118 | }
119 | func (m *Frame) XXX_Unmarshal(b []byte) error {
120 | return m.Unmarshal(b)
121 | }
122 | func (m *Frame) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
123 | if deterministic {
124 | return xxx_messageInfo_Frame.Marshal(b, m, deterministic)
125 | } else {
126 | b = b[:cap(b)]
127 | n, err := m.MarshalToSizedBuffer(b)
128 | if err != nil {
129 | return nil, err
130 | }
131 | return b[:n], nil
132 | }
133 | }
134 | func (m *Frame) XXX_Merge(src proto.Message) {
135 | xxx_messageInfo_Frame.Merge(m, src)
136 | }
137 | func (m *Frame) XXX_Size() int {
138 | return m.Size()
139 | }
140 | func (m *Frame) XXX_DiscardUnknown() {
141 | xxx_messageInfo_Frame.DiscardUnknown(m)
142 | }
143 |
144 | var xxx_messageInfo_Frame proto.InternalMessageInfo
145 |
146 | func (m *Frame) GetT() int32 {
147 | if m != nil {
148 | return m.T
149 | }
150 | return 0
151 | }
152 |
153 | type FrameError struct {
154 | T int32 `protobuf:"varint,1,opt,name=T,proto3" json:"T,omitempty"`
155 | Err string `protobuf:"bytes,2,opt,name=Err,proto3" json:"Err,omitempty"`
156 | }
157 |
158 | func (m *FrameError) Reset() { *m = FrameError{} }
159 | func (m *FrameError) String() string { return proto.CompactTextString(m) }
160 | func (*FrameError) ProtoMessage() {}
161 | func (*FrameError) Descriptor() ([]byte, []int) {
162 | return fileDescriptor_5379e2b825e15002, []int{2}
163 | }
164 | func (m *FrameError) XXX_Unmarshal(b []byte) error {
165 | return m.Unmarshal(b)
166 | }
167 | func (m *FrameError) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
168 | if deterministic {
169 | return xxx_messageInfo_FrameError.Marshal(b, m, deterministic)
170 | } else {
171 | b = b[:cap(b)]
172 | n, err := m.MarshalToSizedBuffer(b)
173 | if err != nil {
174 | return nil, err
175 | }
176 | return b[:n], nil
177 | }
178 | }
179 | func (m *FrameError) XXX_Merge(src proto.Message) {
180 | xxx_messageInfo_FrameError.Merge(m, src)
181 | }
182 | func (m *FrameError) XXX_Size() int {
183 | return m.Size()
184 | }
185 | func (m *FrameError) XXX_DiscardUnknown() {
186 | xxx_messageInfo_FrameError.DiscardUnknown(m)
187 | }
188 |
189 | var xxx_messageInfo_FrameError proto.InternalMessageInfo
190 |
191 | func (m *FrameError) GetT() int32 {
192 | if m != nil {
193 | return m.T
194 | }
195 | return 0
196 | }
197 |
198 | func (m *FrameError) GetErr() string {
199 | if m != nil {
200 | return m.Err
201 | }
202 | return ""
203 | }
204 |
205 | type FrameNewStream struct {
206 | T int32 `protobuf:"varint,1,opt,name=T,proto3" json:"T,omitempty"`
207 | Info *StreamFile `protobuf:"bytes,3,opt,name=Info,proto3" json:"Info,omitempty"`
208 | }
209 |
210 | func (m *FrameNewStream) Reset() { *m = FrameNewStream{} }
211 | func (m *FrameNewStream) String() string { return proto.CompactTextString(m) }
212 | func (*FrameNewStream) ProtoMessage() {}
213 | func (*FrameNewStream) Descriptor() ([]byte, []int) {
214 | return fileDescriptor_5379e2b825e15002, []int{3}
215 | }
216 | func (m *FrameNewStream) XXX_Unmarshal(b []byte) error {
217 | return m.Unmarshal(b)
218 | }
219 | func (m *FrameNewStream) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
220 | if deterministic {
221 | return xxx_messageInfo_FrameNewStream.Marshal(b, m, deterministic)
222 | } else {
223 | b = b[:cap(b)]
224 | n, err := m.MarshalToSizedBuffer(b)
225 | if err != nil {
226 | return nil, err
227 | }
228 | return b[:n], nil
229 | }
230 | }
231 | func (m *FrameNewStream) XXX_Merge(src proto.Message) {
232 | xxx_messageInfo_FrameNewStream.Merge(m, src)
233 | }
234 | func (m *FrameNewStream) XXX_Size() int {
235 | return m.Size()
236 | }
237 | func (m *FrameNewStream) XXX_DiscardUnknown() {
238 | xxx_messageInfo_FrameNewStream.DiscardUnknown(m)
239 | }
240 |
241 | var xxx_messageInfo_FrameNewStream proto.InternalMessageInfo
242 |
243 | func (m *FrameNewStream) GetT() int32 {
244 | if m != nil {
245 | return m.T
246 | }
247 | return 0
248 | }
249 |
250 | func (m *FrameNewStream) GetInfo() *StreamFile {
251 | if m != nil {
252 | return m.Info
253 | }
254 | return nil
255 | }
256 |
257 | type FrameData struct {
258 | T int32 `protobuf:"varint,1,opt,name=T,proto3" json:"T,omitempty"`
259 | Data []byte `protobuf:"bytes,2,opt,name=Data,proto3" json:"Data,omitempty"`
260 | }
261 |
262 | func (m *FrameData) Reset() { *m = FrameData{} }
263 | func (m *FrameData) String() string { return proto.CompactTextString(m) }
264 | func (*FrameData) ProtoMessage() {}
265 | func (*FrameData) Descriptor() ([]byte, []int) {
266 | return fileDescriptor_5379e2b825e15002, []int{4}
267 | }
268 | func (m *FrameData) XXX_Unmarshal(b []byte) error {
269 | return m.Unmarshal(b)
270 | }
271 | func (m *FrameData) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
272 | if deterministic {
273 | return xxx_messageInfo_FrameData.Marshal(b, m, deterministic)
274 | } else {
275 | b = b[:cap(b)]
276 | n, err := m.MarshalToSizedBuffer(b)
277 | if err != nil {
278 | return nil, err
279 | }
280 | return b[:n], nil
281 | }
282 | }
283 | func (m *FrameData) XXX_Merge(src proto.Message) {
284 | xxx_messageInfo_FrameData.Merge(m, src)
285 | }
286 | func (m *FrameData) XXX_Size() int {
287 | return m.Size()
288 | }
289 | func (m *FrameData) XXX_DiscardUnknown() {
290 | xxx_messageInfo_FrameData.DiscardUnknown(m)
291 | }
292 |
293 | var xxx_messageInfo_FrameData proto.InternalMessageInfo
294 |
295 | func (m *FrameData) GetT() int32 {
296 | if m != nil {
297 | return m.T
298 | }
299 | return 0
300 | }
301 |
302 | func (m *FrameData) GetData() []byte {
303 | if m != nil {
304 | return m.Data
305 | }
306 | return nil
307 | }
308 |
309 | func init() {
310 | proto.RegisterType((*StreamFile)(nil), "StreamFile")
311 | proto.RegisterType((*Frame)(nil), "Frame")
312 | proto.RegisterType((*FrameError)(nil), "FrameError")
313 | proto.RegisterType((*FrameNewStream)(nil), "FrameNewStream")
314 | proto.RegisterType((*FrameData)(nil), "FrameData")
315 | }
316 |
317 | func init() { proto.RegisterFile("frame.proto", fileDescriptor_5379e2b825e15002) }
318 |
319 | var fileDescriptor_5379e2b825e15002 = []byte{
320 | // 282 bytes of a gzipped FileDescriptorProto
321 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x5c, 0x50, 0xc1, 0x4a, 0xc3, 0x40,
322 | 0x10, 0xcd, 0x9a, 0xa4, 0xda, 0x49, 0x15, 0x59, 0x10, 0x16, 0x0f, 0x6b, 0x08, 0x1e, 0x72, 0xd0,
323 | 0x1c, 0xf4, 0xe8, 0x41, 0x10, 0x1b, 0x10, 0x6c, 0x91, 0x6d, 0x4e, 0xde, 0x56, 0x3a, 0xc5, 0x40,
324 | 0xd2, 0x95, 0x35, 0x22, 0xf8, 0x15, 0x1e, 0xfd, 0x24, 0x8f, 0x3d, 0x7a, 0x94, 0xe4, 0x47, 0x64,
325 | 0xa7, 0x56, 0x69, 0x6f, 0x6f, 0xde, 0x7b, 0xfb, 0xf6, 0xcd, 0x40, 0x34, 0xb3, 0xba, 0xc6, 0xec,
326 | 0xc9, 0x9a, 0xc6, 0x24, 0x1f, 0x0c, 0x60, 0xd2, 0x58, 0xd4, 0x75, 0x5e, 0x56, 0xc8, 0x39, 0x04,
327 | 0x63, 0x5d, 0xa3, 0x60, 0x31, 0x4b, 0xfb, 0x8a, 0xb0, 0xe3, 0xee, 0x74, 0xf3, 0x28, 0xb6, 0x96,
328 | 0x9c, 0xc3, 0x5c, 0xc0, 0xf6, 0xa4, 0x7c, 0xc3, 0x5b, 0x9c, 0x0b, 0x3f, 0x66, 0xa9, 0xaf, 0x56,
329 | 0xa3, 0x73, 0x8f, 0xcc, 0x14, 0x45, 0x10, 0xb3, 0x74, 0x57, 0x11, 0x76, 0xee, 0x91, 0x99, 0x16,
330 | 0x65, 0x8d, 0x22, 0xa4, 0x90, 0xd5, 0xc8, 0x0f, 0x61, 0x27, 0x7f, 0xa9, 0x2a, 0xca, 0xef, 0x91,
331 | 0xf4, 0x37, 0x27, 0x07, 0x10, 0xe6, 0xae, 0x29, 0x1f, 0x00, 0x2b, 0xa8, 0x51, 0xa8, 0x58, 0x91,
332 | 0x9c, 0x00, 0x10, 0x3d, 0xb4, 0xd6, 0xd8, 0x75, 0x8d, 0xef, 0x83, 0x3f, 0xb4, 0xf6, 0xb7, 0xa9,
333 | 0x83, 0xc9, 0x25, 0xec, 0x91, 0x7b, 0x8c, 0xaf, 0xcb, 0x35, 0x37, 0x5e, 0x1c, 0x41, 0x70, 0x33,
334 | 0x9f, 0x19, 0xda, 0x22, 0x3a, 0x8b, 0xb2, 0xff, 0x5b, 0x28, 0x12, 0x92, 0x53, 0xe8, 0x53, 0xc0,
335 | 0xb5, 0x6e, 0xf4, 0xc6, 0x5b, 0x0e, 0x81, 0x63, 0xe9, 0xbb, 0x81, 0x22, 0x7c, 0x75, 0xfc, 0xd9,
336 | 0x4a, 0xb6, 0x68, 0x25, 0xfb, 0x6e, 0x25, 0x7b, 0xef, 0xa4, 0xb7, 0xe8, 0xa4, 0xf7, 0xd5, 0x49,
337 | 0xef, 0x1e, 0xb2, 0x8b, 0x67, 0x0a, 0x47, 0xfb, 0xd0, 0xa3, 0xe3, 0x9f, 0xff, 0x04, 0x00, 0x00,
338 | 0xff, 0xff, 0x6d, 0xff, 0x7e, 0x06, 0x8b, 0x01, 0x00, 0x00,
339 | }
340 |
341 | func (m *StreamFile) Marshal() (dAtA []byte, err error) {
342 | size := m.Size()
343 | dAtA = make([]byte, size)
344 | n, err := m.MarshalToSizedBuffer(dAtA[:size])
345 | if err != nil {
346 | return nil, err
347 | }
348 | return dAtA[:n], nil
349 | }
350 |
351 | func (m *StreamFile) MarshalTo(dAtA []byte) (int, error) {
352 | size := m.Size()
353 | return m.MarshalToSizedBuffer(dAtA[:size])
354 | }
355 |
356 | func (m *StreamFile) MarshalToSizedBuffer(dAtA []byte) (int, error) {
357 | i := len(dAtA)
358 | _ = i
359 | var l int
360 | _ = l
361 | if len(m.FullPath) > 0 {
362 | i -= len(m.FullPath)
363 | copy(dAtA[i:], m.FullPath)
364 | i = encodeVarintFrame(dAtA, i, uint64(len(m.FullPath)))
365 | i--
366 | dAtA[i] = 0x32
367 | }
368 | if len(m.ModTime) > 0 {
369 | i -= len(m.ModTime)
370 | copy(dAtA[i:], m.ModTime)
371 | i = encodeVarintFrame(dAtA, i, uint64(len(m.ModTime)))
372 | i--
373 | dAtA[i] = 0x2a
374 | }
375 | if m.Mode != 0 {
376 | i = encodeVarintFrame(dAtA, i, uint64(m.Mode))
377 | i--
378 | dAtA[i] = 0x20
379 | }
380 | if m.SizeLen != 0 {
381 | i = encodeVarintFrame(dAtA, i, uint64(m.SizeLen))
382 | i--
383 | dAtA[i] = 0x18
384 | }
385 | if len(m.Path) > 0 {
386 | i -= len(m.Path)
387 | copy(dAtA[i:], m.Path)
388 | i = encodeVarintFrame(dAtA, i, uint64(len(m.Path)))
389 | i--
390 | dAtA[i] = 0x12
391 | }
392 | if len(m.Name) > 0 {
393 | i -= len(m.Name)
394 | copy(dAtA[i:], m.Name)
395 | i = encodeVarintFrame(dAtA, i, uint64(len(m.Name)))
396 | i--
397 | dAtA[i] = 0xa
398 | }
399 | return len(dAtA) - i, nil
400 | }
401 |
402 | func (m *Frame) Marshal() (dAtA []byte, err error) {
403 | size := m.Size()
404 | dAtA = make([]byte, size)
405 | n, err := m.MarshalToSizedBuffer(dAtA[:size])
406 | if err != nil {
407 | return nil, err
408 | }
409 | return dAtA[:n], nil
410 | }
411 |
412 | func (m *Frame) MarshalTo(dAtA []byte) (int, error) {
413 | size := m.Size()
414 | return m.MarshalToSizedBuffer(dAtA[:size])
415 | }
416 |
417 | func (m *Frame) MarshalToSizedBuffer(dAtA []byte) (int, error) {
418 | i := len(dAtA)
419 | _ = i
420 | var l int
421 | _ = l
422 | if m.T != 0 {
423 | i = encodeVarintFrame(dAtA, i, uint64(m.T))
424 | i--
425 | dAtA[i] = 0x8
426 | }
427 | return len(dAtA) - i, nil
428 | }
429 |
430 | func (m *FrameError) Marshal() (dAtA []byte, err error) {
431 | size := m.Size()
432 | dAtA = make([]byte, size)
433 | n, err := m.MarshalToSizedBuffer(dAtA[:size])
434 | if err != nil {
435 | return nil, err
436 | }
437 | return dAtA[:n], nil
438 | }
439 |
440 | func (m *FrameError) MarshalTo(dAtA []byte) (int, error) {
441 | size := m.Size()
442 | return m.MarshalToSizedBuffer(dAtA[:size])
443 | }
444 |
445 | func (m *FrameError) MarshalToSizedBuffer(dAtA []byte) (int, error) {
446 | i := len(dAtA)
447 | _ = i
448 | var l int
449 | _ = l
450 | if len(m.Err) > 0 {
451 | i -= len(m.Err)
452 | copy(dAtA[i:], m.Err)
453 | i = encodeVarintFrame(dAtA, i, uint64(len(m.Err)))
454 | i--
455 | dAtA[i] = 0x12
456 | }
457 | if m.T != 0 {
458 | i = encodeVarintFrame(dAtA, i, uint64(m.T))
459 | i--
460 | dAtA[i] = 0x8
461 | }
462 | return len(dAtA) - i, nil
463 | }
464 |
465 | func (m *FrameNewStream) Marshal() (dAtA []byte, err error) {
466 | size := m.Size()
467 | dAtA = make([]byte, size)
468 | n, err := m.MarshalToSizedBuffer(dAtA[:size])
469 | if err != nil {
470 | return nil, err
471 | }
472 | return dAtA[:n], nil
473 | }
474 |
475 | func (m *FrameNewStream) MarshalTo(dAtA []byte) (int, error) {
476 | size := m.Size()
477 | return m.MarshalToSizedBuffer(dAtA[:size])
478 | }
479 |
480 | func (m *FrameNewStream) MarshalToSizedBuffer(dAtA []byte) (int, error) {
481 | i := len(dAtA)
482 | _ = i
483 | var l int
484 | _ = l
485 | if m.Info != nil {
486 | {
487 | size, err := m.Info.MarshalToSizedBuffer(dAtA[:i])
488 | if err != nil {
489 | return 0, err
490 | }
491 | i -= size
492 | i = encodeVarintFrame(dAtA, i, uint64(size))
493 | }
494 | i--
495 | dAtA[i] = 0x1a
496 | }
497 | if m.T != 0 {
498 | i = encodeVarintFrame(dAtA, i, uint64(m.T))
499 | i--
500 | dAtA[i] = 0x8
501 | }
502 | return len(dAtA) - i, nil
503 | }
504 |
505 | func (m *FrameData) Marshal() (dAtA []byte, err error) {
506 | size := m.Size()
507 | dAtA = make([]byte, size)
508 | n, err := m.MarshalToSizedBuffer(dAtA[:size])
509 | if err != nil {
510 | return nil, err
511 | }
512 | return dAtA[:n], nil
513 | }
514 |
515 | func (m *FrameData) MarshalTo(dAtA []byte) (int, error) {
516 | size := m.Size()
517 | return m.MarshalToSizedBuffer(dAtA[:size])
518 | }
519 |
520 | func (m *FrameData) MarshalToSizedBuffer(dAtA []byte) (int, error) {
521 | i := len(dAtA)
522 | _ = i
523 | var l int
524 | _ = l
525 | if len(m.Data) > 0 {
526 | i -= len(m.Data)
527 | copy(dAtA[i:], m.Data)
528 | i = encodeVarintFrame(dAtA, i, uint64(len(m.Data)))
529 | i--
530 | dAtA[i] = 0x12
531 | }
532 | if m.T != 0 {
533 | i = encodeVarintFrame(dAtA, i, uint64(m.T))
534 | i--
535 | dAtA[i] = 0x8
536 | }
537 | return len(dAtA) - i, nil
538 | }
539 |
540 | func encodeVarintFrame(dAtA []byte, offset int, v uint64) int {
541 | offset -= sovFrame(v)
542 | base := offset
543 | for v >= 1<<7 {
544 | dAtA[offset] = uint8(v&0x7f | 0x80)
545 | v >>= 7
546 | offset++
547 | }
548 | dAtA[offset] = uint8(v)
549 | return base
550 | }
551 | func (m *StreamFile) Size() (n int) {
552 | if m == nil {
553 | return 0
554 | }
555 | var l int
556 | _ = l
557 | l = len(m.Name)
558 | if l > 0 {
559 | n += 1 + l + sovFrame(uint64(l))
560 | }
561 | l = len(m.Path)
562 | if l > 0 {
563 | n += 1 + l + sovFrame(uint64(l))
564 | }
565 | if m.SizeLen != 0 {
566 | n += 1 + sovFrame(uint64(m.SizeLen))
567 | }
568 | if m.Mode != 0 {
569 | n += 1 + sovFrame(uint64(m.Mode))
570 | }
571 | l = len(m.ModTime)
572 | if l > 0 {
573 | n += 1 + l + sovFrame(uint64(l))
574 | }
575 | l = len(m.FullPath)
576 | if l > 0 {
577 | n += 1 + l + sovFrame(uint64(l))
578 | }
579 | return n
580 | }
581 |
582 | func (m *Frame) Size() (n int) {
583 | if m == nil {
584 | return 0
585 | }
586 | var l int
587 | _ = l
588 | if m.T != 0 {
589 | n += 1 + sovFrame(uint64(m.T))
590 | }
591 | return n
592 | }
593 |
594 | func (m *FrameError) Size() (n int) {
595 | if m == nil {
596 | return 0
597 | }
598 | var l int
599 | _ = l
600 | if m.T != 0 {
601 | n += 1 + sovFrame(uint64(m.T))
602 | }
603 | l = len(m.Err)
604 | if l > 0 {
605 | n += 1 + l + sovFrame(uint64(l))
606 | }
607 | return n
608 | }
609 |
610 | func (m *FrameNewStream) Size() (n int) {
611 | if m == nil {
612 | return 0
613 | }
614 | var l int
615 | _ = l
616 | if m.T != 0 {
617 | n += 1 + sovFrame(uint64(m.T))
618 | }
619 | if m.Info != nil {
620 | l = m.Info.Size()
621 | n += 1 + l + sovFrame(uint64(l))
622 | }
623 | return n
624 | }
625 |
626 | func (m *FrameData) Size() (n int) {
627 | if m == nil {
628 | return 0
629 | }
630 | var l int
631 | _ = l
632 | if m.T != 0 {
633 | n += 1 + sovFrame(uint64(m.T))
634 | }
635 | l = len(m.Data)
636 | if l > 0 {
637 | n += 1 + l + sovFrame(uint64(l))
638 | }
639 | return n
640 | }
641 |
642 | func sovFrame(x uint64) (n int) {
643 | return (math_bits.Len64(x|1) + 6) / 7
644 | }
645 | func sozFrame(x uint64) (n int) {
646 | return sovFrame(uint64((x << 1) ^ uint64((int64(x) >> 63))))
647 | }
648 | func (m *StreamFile) Unmarshal(dAtA []byte) error {
649 | l := len(dAtA)
650 | iNdEx := 0
651 | for iNdEx < l {
652 | preIndex := iNdEx
653 | var wire uint64
654 | for shift := uint(0); ; shift += 7 {
655 | if shift >= 64 {
656 | return ErrIntOverflowFrame
657 | }
658 | if iNdEx >= l {
659 | return io.ErrUnexpectedEOF
660 | }
661 | b := dAtA[iNdEx]
662 | iNdEx++
663 | wire |= uint64(b&0x7F) << shift
664 | if b < 0x80 {
665 | break
666 | }
667 | }
668 | fieldNum := int32(wire >> 3)
669 | wireType := int(wire & 0x7)
670 | if wireType == 4 {
671 | return fmt.Errorf("proto: StreamFile: wiretype end group for non-group")
672 | }
673 | if fieldNum <= 0 {
674 | return fmt.Errorf("proto: StreamFile: illegal tag %d (wire type %d)", fieldNum, wire)
675 | }
676 | switch fieldNum {
677 | case 1:
678 | if wireType != 2 {
679 | return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType)
680 | }
681 | var stringLen uint64
682 | for shift := uint(0); ; shift += 7 {
683 | if shift >= 64 {
684 | return ErrIntOverflowFrame
685 | }
686 | if iNdEx >= l {
687 | return io.ErrUnexpectedEOF
688 | }
689 | b := dAtA[iNdEx]
690 | iNdEx++
691 | stringLen |= uint64(b&0x7F) << shift
692 | if b < 0x80 {
693 | break
694 | }
695 | }
696 | intStringLen := int(stringLen)
697 | if intStringLen < 0 {
698 | return ErrInvalidLengthFrame
699 | }
700 | postIndex := iNdEx + intStringLen
701 | if postIndex < 0 {
702 | return ErrInvalidLengthFrame
703 | }
704 | if postIndex > l {
705 | return io.ErrUnexpectedEOF
706 | }
707 | m.Name = string(dAtA[iNdEx:postIndex])
708 | iNdEx = postIndex
709 | case 2:
710 | if wireType != 2 {
711 | return fmt.Errorf("proto: wrong wireType = %d for field Path", wireType)
712 | }
713 | var stringLen uint64
714 | for shift := uint(0); ; shift += 7 {
715 | if shift >= 64 {
716 | return ErrIntOverflowFrame
717 | }
718 | if iNdEx >= l {
719 | return io.ErrUnexpectedEOF
720 | }
721 | b := dAtA[iNdEx]
722 | iNdEx++
723 | stringLen |= uint64(b&0x7F) << shift
724 | if b < 0x80 {
725 | break
726 | }
727 | }
728 | intStringLen := int(stringLen)
729 | if intStringLen < 0 {
730 | return ErrInvalidLengthFrame
731 | }
732 | postIndex := iNdEx + intStringLen
733 | if postIndex < 0 {
734 | return ErrInvalidLengthFrame
735 | }
736 | if postIndex > l {
737 | return io.ErrUnexpectedEOF
738 | }
739 | m.Path = string(dAtA[iNdEx:postIndex])
740 | iNdEx = postIndex
741 | case 3:
742 | if wireType != 0 {
743 | return fmt.Errorf("proto: wrong wireType = %d for field SizeLen", wireType)
744 | }
745 | m.SizeLen = 0
746 | for shift := uint(0); ; shift += 7 {
747 | if shift >= 64 {
748 | return ErrIntOverflowFrame
749 | }
750 | if iNdEx >= l {
751 | return io.ErrUnexpectedEOF
752 | }
753 | b := dAtA[iNdEx]
754 | iNdEx++
755 | m.SizeLen |= int64(b&0x7F) << shift
756 | if b < 0x80 {
757 | break
758 | }
759 | }
760 | case 4:
761 | if wireType != 0 {
762 | return fmt.Errorf("proto: wrong wireType = %d for field Mode", wireType)
763 | }
764 | m.Mode = 0
765 | for shift := uint(0); ; shift += 7 {
766 | if shift >= 64 {
767 | return ErrIntOverflowFrame
768 | }
769 | if iNdEx >= l {
770 | return io.ErrUnexpectedEOF
771 | }
772 | b := dAtA[iNdEx]
773 | iNdEx++
774 | m.Mode |= uint32(b&0x7F) << shift
775 | if b < 0x80 {
776 | break
777 | }
778 | }
779 | case 5:
780 | if wireType != 2 {
781 | return fmt.Errorf("proto: wrong wireType = %d for field ModTime", wireType)
782 | }
783 | var stringLen uint64
784 | for shift := uint(0); ; shift += 7 {
785 | if shift >= 64 {
786 | return ErrIntOverflowFrame
787 | }
788 | if iNdEx >= l {
789 | return io.ErrUnexpectedEOF
790 | }
791 | b := dAtA[iNdEx]
792 | iNdEx++
793 | stringLen |= uint64(b&0x7F) << shift
794 | if b < 0x80 {
795 | break
796 | }
797 | }
798 | intStringLen := int(stringLen)
799 | if intStringLen < 0 {
800 | return ErrInvalidLengthFrame
801 | }
802 | postIndex := iNdEx + intStringLen
803 | if postIndex < 0 {
804 | return ErrInvalidLengthFrame
805 | }
806 | if postIndex > l {
807 | return io.ErrUnexpectedEOF
808 | }
809 | m.ModTime = string(dAtA[iNdEx:postIndex])
810 | iNdEx = postIndex
811 | case 6:
812 | if wireType != 2 {
813 | return fmt.Errorf("proto: wrong wireType = %d for field FullPath", wireType)
814 | }
815 | var stringLen uint64
816 | for shift := uint(0); ; shift += 7 {
817 | if shift >= 64 {
818 | return ErrIntOverflowFrame
819 | }
820 | if iNdEx >= l {
821 | return io.ErrUnexpectedEOF
822 | }
823 | b := dAtA[iNdEx]
824 | iNdEx++
825 | stringLen |= uint64(b&0x7F) << shift
826 | if b < 0x80 {
827 | break
828 | }
829 | }
830 | intStringLen := int(stringLen)
831 | if intStringLen < 0 {
832 | return ErrInvalidLengthFrame
833 | }
834 | postIndex := iNdEx + intStringLen
835 | if postIndex < 0 {
836 | return ErrInvalidLengthFrame
837 | }
838 | if postIndex > l {
839 | return io.ErrUnexpectedEOF
840 | }
841 | m.FullPath = string(dAtA[iNdEx:postIndex])
842 | iNdEx = postIndex
843 | default:
844 | iNdEx = preIndex
845 | skippy, err := skipFrame(dAtA[iNdEx:])
846 | if err != nil {
847 | return err
848 | }
849 | if (skippy < 0) || (iNdEx+skippy) < 0 {
850 | return ErrInvalidLengthFrame
851 | }
852 | if (iNdEx + skippy) > l {
853 | return io.ErrUnexpectedEOF
854 | }
855 | iNdEx += skippy
856 | }
857 | }
858 |
859 | if iNdEx > l {
860 | return io.ErrUnexpectedEOF
861 | }
862 | return nil
863 | }
864 | func (m *Frame) Unmarshal(dAtA []byte) error {
865 | l := len(dAtA)
866 | iNdEx := 0
867 | for iNdEx < l {
868 | preIndex := iNdEx
869 | var wire uint64
870 | for shift := uint(0); ; shift += 7 {
871 | if shift >= 64 {
872 | return ErrIntOverflowFrame
873 | }
874 | if iNdEx >= l {
875 | return io.ErrUnexpectedEOF
876 | }
877 | b := dAtA[iNdEx]
878 | iNdEx++
879 | wire |= uint64(b&0x7F) << shift
880 | if b < 0x80 {
881 | break
882 | }
883 | }
884 | fieldNum := int32(wire >> 3)
885 | wireType := int(wire & 0x7)
886 | if wireType == 4 {
887 | return fmt.Errorf("proto: Frame: wiretype end group for non-group")
888 | }
889 | if fieldNum <= 0 {
890 | return fmt.Errorf("proto: Frame: illegal tag %d (wire type %d)", fieldNum, wire)
891 | }
892 | switch fieldNum {
893 | case 1:
894 | if wireType != 0 {
895 | return fmt.Errorf("proto: wrong wireType = %d for field T", wireType)
896 | }
897 | m.T = 0
898 | for shift := uint(0); ; shift += 7 {
899 | if shift >= 64 {
900 | return ErrIntOverflowFrame
901 | }
902 | if iNdEx >= l {
903 | return io.ErrUnexpectedEOF
904 | }
905 | b := dAtA[iNdEx]
906 | iNdEx++
907 | m.T |= int32(b&0x7F) << shift
908 | if b < 0x80 {
909 | break
910 | }
911 | }
912 | default:
913 | iNdEx = preIndex
914 | skippy, err := skipFrame(dAtA[iNdEx:])
915 | if err != nil {
916 | return err
917 | }
918 | if (skippy < 0) || (iNdEx+skippy) < 0 {
919 | return ErrInvalidLengthFrame
920 | }
921 | if (iNdEx + skippy) > l {
922 | return io.ErrUnexpectedEOF
923 | }
924 | iNdEx += skippy
925 | }
926 | }
927 |
928 | if iNdEx > l {
929 | return io.ErrUnexpectedEOF
930 | }
931 | return nil
932 | }
933 | func (m *FrameError) Unmarshal(dAtA []byte) error {
934 | l := len(dAtA)
935 | iNdEx := 0
936 | for iNdEx < l {
937 | preIndex := iNdEx
938 | var wire uint64
939 | for shift := uint(0); ; shift += 7 {
940 | if shift >= 64 {
941 | return ErrIntOverflowFrame
942 | }
943 | if iNdEx >= l {
944 | return io.ErrUnexpectedEOF
945 | }
946 | b := dAtA[iNdEx]
947 | iNdEx++
948 | wire |= uint64(b&0x7F) << shift
949 | if b < 0x80 {
950 | break
951 | }
952 | }
953 | fieldNum := int32(wire >> 3)
954 | wireType := int(wire & 0x7)
955 | if wireType == 4 {
956 | return fmt.Errorf("proto: FrameError: wiretype end group for non-group")
957 | }
958 | if fieldNum <= 0 {
959 | return fmt.Errorf("proto: FrameError: illegal tag %d (wire type %d)", fieldNum, wire)
960 | }
961 | switch fieldNum {
962 | case 1:
963 | if wireType != 0 {
964 | return fmt.Errorf("proto: wrong wireType = %d for field T", wireType)
965 | }
966 | m.T = 0
967 | for shift := uint(0); ; shift += 7 {
968 | if shift >= 64 {
969 | return ErrIntOverflowFrame
970 | }
971 | if iNdEx >= l {
972 | return io.ErrUnexpectedEOF
973 | }
974 | b := dAtA[iNdEx]
975 | iNdEx++
976 | m.T |= int32(b&0x7F) << shift
977 | if b < 0x80 {
978 | break
979 | }
980 | }
981 | case 2:
982 | if wireType != 2 {
983 | return fmt.Errorf("proto: wrong wireType = %d for field Err", wireType)
984 | }
985 | var stringLen uint64
986 | for shift := uint(0); ; shift += 7 {
987 | if shift >= 64 {
988 | return ErrIntOverflowFrame
989 | }
990 | if iNdEx >= l {
991 | return io.ErrUnexpectedEOF
992 | }
993 | b := dAtA[iNdEx]
994 | iNdEx++
995 | stringLen |= uint64(b&0x7F) << shift
996 | if b < 0x80 {
997 | break
998 | }
999 | }
1000 | intStringLen := int(stringLen)
1001 | if intStringLen < 0 {
1002 | return ErrInvalidLengthFrame
1003 | }
1004 | postIndex := iNdEx + intStringLen
1005 | if postIndex < 0 {
1006 | return ErrInvalidLengthFrame
1007 | }
1008 | if postIndex > l {
1009 | return io.ErrUnexpectedEOF
1010 | }
1011 | m.Err = string(dAtA[iNdEx:postIndex])
1012 | iNdEx = postIndex
1013 | default:
1014 | iNdEx = preIndex
1015 | skippy, err := skipFrame(dAtA[iNdEx:])
1016 | if err != nil {
1017 | return err
1018 | }
1019 | if (skippy < 0) || (iNdEx+skippy) < 0 {
1020 | return ErrInvalidLengthFrame
1021 | }
1022 | if (iNdEx + skippy) > l {
1023 | return io.ErrUnexpectedEOF
1024 | }
1025 | iNdEx += skippy
1026 | }
1027 | }
1028 |
1029 | if iNdEx > l {
1030 | return io.ErrUnexpectedEOF
1031 | }
1032 | return nil
1033 | }
1034 | func (m *FrameNewStream) Unmarshal(dAtA []byte) error {
1035 | l := len(dAtA)
1036 | iNdEx := 0
1037 | for iNdEx < l {
1038 | preIndex := iNdEx
1039 | var wire uint64
1040 | for shift := uint(0); ; shift += 7 {
1041 | if shift >= 64 {
1042 | return ErrIntOverflowFrame
1043 | }
1044 | if iNdEx >= l {
1045 | return io.ErrUnexpectedEOF
1046 | }
1047 | b := dAtA[iNdEx]
1048 | iNdEx++
1049 | wire |= uint64(b&0x7F) << shift
1050 | if b < 0x80 {
1051 | break
1052 | }
1053 | }
1054 | fieldNum := int32(wire >> 3)
1055 | wireType := int(wire & 0x7)
1056 | if wireType == 4 {
1057 | return fmt.Errorf("proto: FrameNewStream: wiretype end group for non-group")
1058 | }
1059 | if fieldNum <= 0 {
1060 | return fmt.Errorf("proto: FrameNewStream: illegal tag %d (wire type %d)", fieldNum, wire)
1061 | }
1062 | switch fieldNum {
1063 | case 1:
1064 | if wireType != 0 {
1065 | return fmt.Errorf("proto: wrong wireType = %d for field T", wireType)
1066 | }
1067 | m.T = 0
1068 | for shift := uint(0); ; shift += 7 {
1069 | if shift >= 64 {
1070 | return ErrIntOverflowFrame
1071 | }
1072 | if iNdEx >= l {
1073 | return io.ErrUnexpectedEOF
1074 | }
1075 | b := dAtA[iNdEx]
1076 | iNdEx++
1077 | m.T |= int32(b&0x7F) << shift
1078 | if b < 0x80 {
1079 | break
1080 | }
1081 | }
1082 | case 3:
1083 | if wireType != 2 {
1084 | return fmt.Errorf("proto: wrong wireType = %d for field Info", wireType)
1085 | }
1086 | var msglen int
1087 | for shift := uint(0); ; shift += 7 {
1088 | if shift >= 64 {
1089 | return ErrIntOverflowFrame
1090 | }
1091 | if iNdEx >= l {
1092 | return io.ErrUnexpectedEOF
1093 | }
1094 | b := dAtA[iNdEx]
1095 | iNdEx++
1096 | msglen |= int(b&0x7F) << shift
1097 | if b < 0x80 {
1098 | break
1099 | }
1100 | }
1101 | if msglen < 0 {
1102 | return ErrInvalidLengthFrame
1103 | }
1104 | postIndex := iNdEx + msglen
1105 | if postIndex < 0 {
1106 | return ErrInvalidLengthFrame
1107 | }
1108 | if postIndex > l {
1109 | return io.ErrUnexpectedEOF
1110 | }
1111 | if m.Info == nil {
1112 | m.Info = &StreamFile{}
1113 | }
1114 | if err := m.Info.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
1115 | return err
1116 | }
1117 | iNdEx = postIndex
1118 | default:
1119 | iNdEx = preIndex
1120 | skippy, err := skipFrame(dAtA[iNdEx:])
1121 | if err != nil {
1122 | return err
1123 | }
1124 | if (skippy < 0) || (iNdEx+skippy) < 0 {
1125 | return ErrInvalidLengthFrame
1126 | }
1127 | if (iNdEx + skippy) > l {
1128 | return io.ErrUnexpectedEOF
1129 | }
1130 | iNdEx += skippy
1131 | }
1132 | }
1133 |
1134 | if iNdEx > l {
1135 | return io.ErrUnexpectedEOF
1136 | }
1137 | return nil
1138 | }
1139 | func (m *FrameData) Unmarshal(dAtA []byte) error {
1140 | l := len(dAtA)
1141 | iNdEx := 0
1142 | for iNdEx < l {
1143 | preIndex := iNdEx
1144 | var wire uint64
1145 | for shift := uint(0); ; shift += 7 {
1146 | if shift >= 64 {
1147 | return ErrIntOverflowFrame
1148 | }
1149 | if iNdEx >= l {
1150 | return io.ErrUnexpectedEOF
1151 | }
1152 | b := dAtA[iNdEx]
1153 | iNdEx++
1154 | wire |= uint64(b&0x7F) << shift
1155 | if b < 0x80 {
1156 | break
1157 | }
1158 | }
1159 | fieldNum := int32(wire >> 3)
1160 | wireType := int(wire & 0x7)
1161 | if wireType == 4 {
1162 | return fmt.Errorf("proto: FrameData: wiretype end group for non-group")
1163 | }
1164 | if fieldNum <= 0 {
1165 | return fmt.Errorf("proto: FrameData: illegal tag %d (wire type %d)", fieldNum, wire)
1166 | }
1167 | switch fieldNum {
1168 | case 1:
1169 | if wireType != 0 {
1170 | return fmt.Errorf("proto: wrong wireType = %d for field T", wireType)
1171 | }
1172 | m.T = 0
1173 | for shift := uint(0); ; shift += 7 {
1174 | if shift >= 64 {
1175 | return ErrIntOverflowFrame
1176 | }
1177 | if iNdEx >= l {
1178 | return io.ErrUnexpectedEOF
1179 | }
1180 | b := dAtA[iNdEx]
1181 | iNdEx++
1182 | m.T |= int32(b&0x7F) << shift
1183 | if b < 0x80 {
1184 | break
1185 | }
1186 | }
1187 | case 2:
1188 | if wireType != 2 {
1189 | return fmt.Errorf("proto: wrong wireType = %d for field Data", wireType)
1190 | }
1191 | var byteLen int
1192 | for shift := uint(0); ; shift += 7 {
1193 | if shift >= 64 {
1194 | return ErrIntOverflowFrame
1195 | }
1196 | if iNdEx >= l {
1197 | return io.ErrUnexpectedEOF
1198 | }
1199 | b := dAtA[iNdEx]
1200 | iNdEx++
1201 | byteLen |= int(b&0x7F) << shift
1202 | if b < 0x80 {
1203 | break
1204 | }
1205 | }
1206 | if byteLen < 0 {
1207 | return ErrInvalidLengthFrame
1208 | }
1209 | postIndex := iNdEx + byteLen
1210 | if postIndex < 0 {
1211 | return ErrInvalidLengthFrame
1212 | }
1213 | if postIndex > l {
1214 | return io.ErrUnexpectedEOF
1215 | }
1216 | m.Data = append(m.Data[:0], dAtA[iNdEx:postIndex]...)
1217 | if m.Data == nil {
1218 | m.Data = []byte{}
1219 | }
1220 | iNdEx = postIndex
1221 | default:
1222 | iNdEx = preIndex
1223 | skippy, err := skipFrame(dAtA[iNdEx:])
1224 | if err != nil {
1225 | return err
1226 | }
1227 | if (skippy < 0) || (iNdEx+skippy) < 0 {
1228 | return ErrInvalidLengthFrame
1229 | }
1230 | if (iNdEx + skippy) > l {
1231 | return io.ErrUnexpectedEOF
1232 | }
1233 | iNdEx += skippy
1234 | }
1235 | }
1236 |
1237 | if iNdEx > l {
1238 | return io.ErrUnexpectedEOF
1239 | }
1240 | return nil
1241 | }
1242 | func skipFrame(dAtA []byte) (n int, err error) {
1243 | l := len(dAtA)
1244 | iNdEx := 0
1245 | depth := 0
1246 | for iNdEx < l {
1247 | var wire uint64
1248 | for shift := uint(0); ; shift += 7 {
1249 | if shift >= 64 {
1250 | return 0, ErrIntOverflowFrame
1251 | }
1252 | if iNdEx >= l {
1253 | return 0, io.ErrUnexpectedEOF
1254 | }
1255 | b := dAtA[iNdEx]
1256 | iNdEx++
1257 | wire |= (uint64(b) & 0x7F) << shift
1258 | if b < 0x80 {
1259 | break
1260 | }
1261 | }
1262 | wireType := int(wire & 0x7)
1263 | switch wireType {
1264 | case 0:
1265 | for shift := uint(0); ; shift += 7 {
1266 | if shift >= 64 {
1267 | return 0, ErrIntOverflowFrame
1268 | }
1269 | if iNdEx >= l {
1270 | return 0, io.ErrUnexpectedEOF
1271 | }
1272 | iNdEx++
1273 | if dAtA[iNdEx-1] < 0x80 {
1274 | break
1275 | }
1276 | }
1277 | case 1:
1278 | iNdEx += 8
1279 | case 2:
1280 | var length int
1281 | for shift := uint(0); ; shift += 7 {
1282 | if shift >= 64 {
1283 | return 0, ErrIntOverflowFrame
1284 | }
1285 | if iNdEx >= l {
1286 | return 0, io.ErrUnexpectedEOF
1287 | }
1288 | b := dAtA[iNdEx]
1289 | iNdEx++
1290 | length |= (int(b) & 0x7F) << shift
1291 | if b < 0x80 {
1292 | break
1293 | }
1294 | }
1295 | if length < 0 {
1296 | return 0, ErrInvalidLengthFrame
1297 | }
1298 | iNdEx += length
1299 | case 3:
1300 | depth++
1301 | case 4:
1302 | if depth == 0 {
1303 | return 0, ErrUnexpectedEndOfGroupFrame
1304 | }
1305 | depth--
1306 | case 5:
1307 | iNdEx += 4
1308 | default:
1309 | return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
1310 | }
1311 | if iNdEx < 0 {
1312 | return 0, ErrInvalidLengthFrame
1313 | }
1314 | if depth == 0 {
1315 | return iNdEx, nil
1316 | }
1317 | }
1318 | return 0, io.ErrUnexpectedEOF
1319 | }
1320 |
1321 | var (
1322 | ErrInvalidLengthFrame = fmt.Errorf("proto: negative length found during unmarshaling")
1323 | ErrIntOverflowFrame = fmt.Errorf("proto: integer overflow")
1324 | ErrUnexpectedEndOfGroupFrame = fmt.Errorf("proto: unexpected end of group")
1325 | )
1326 |
--------------------------------------------------------------------------------
/streamer/frame.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 | option go_package = ".;streamer";
3 |
4 | // The request message containing the user's name.
5 |
6 | message StreamFile {
7 | string Name = 1;
8 | string Path = 2; //Relative path depending on what is sharing
9 | int64 SizeLen = 3; // File size in byte
10 | uint32 Mode = 4;
11 | string ModTime = 5;
12 | string FullPath = 6; // This is only used to maintain fullpath reference from sender side
13 | }
14 |
15 | message Frame {
16 | int32 T = 1;
17 | }
18 |
19 | message FrameError {
20 | int32 T = 1;
21 | string Err = 2;
22 | }
23 |
24 | message FrameNewStream {
25 | int32 T = 1;
26 | StreamFile Info = 3;
27 | }
28 |
29 | message FrameData {
30 | int32 T = 1;
31 | bytes Data = 2;
32 | }
--------------------------------------------------------------------------------
/streamer/frame_encode.go:
--------------------------------------------------------------------------------
1 | package streamer
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 |
7 | "github.com/golang/protobuf/proto"
8 | )
9 |
10 | type FrameEncoding int
11 |
12 | const (
13 | FrameEncodingJSON FrameEncoding = 0
14 | FrameEncodingProto FrameEncoding = 1
15 | )
16 |
17 | type FrameEncoder interface {
18 | MarshalFramer(f Framer, t int) ([]byte, error)
19 | UnmarshalFramer(data []byte) (Framer, error)
20 | }
21 |
22 | func NewFrameByType(t int32) Framer {
23 | switch t {
24 | case FRAME_DATA:
25 | return &FrameData{}
26 |
27 | case FRAME_NEWSTREAM:
28 | return &FrameNewStream{}
29 | case FRAME_OK:
30 | return &Frame{}
31 | case FRAME_ERROR:
32 | return &FrameError{}
33 | default:
34 | return nil
35 | }
36 | }
37 |
38 | type JSONFrameEncoder struct {
39 | }
40 |
41 | func (enc *JSONFrameEncoder) MarshalFramer(f Framer, t int) ([]byte, error) {
42 | f.SetT(int32(t))
43 | data, err := json.Marshal(f)
44 | return data, err
45 | }
46 |
47 | func (enc *JSONFrameEncoder) UnmarshalFramer(data []byte) (Framer, error) {
48 | var frame Framer = &Frame{}
49 | if err := json.Unmarshal(data, frame); err != nil {
50 | return nil, err
51 | }
52 |
53 | frame = NewFrameByType(frame.GetT())
54 | if frame == nil {
55 | return nil, fmt.Errorf("Unknown type")
56 | }
57 |
58 | err := json.Unmarshal(data, frame)
59 | return frame, err
60 | }
61 |
62 | type ProtobufFrameEncoder struct {
63 | }
64 |
65 | func (enc *ProtobufFrameEncoder) MarshalFramer(f Framer, t int) ([]byte, error) {
66 | f.SetT(int32(t))
67 | data, err := proto.Marshal(f)
68 | return data, err
69 | }
70 |
71 | func (enc *ProtobufFrameEncoder) UnmarshalFramer(data []byte) (Framer, error) {
72 | var frame Framer = &Frame{}
73 | if err := proto.Unmarshal(data, frame); err != nil {
74 | return nil, err
75 | }
76 |
77 | frame = NewFrameByType(frame.GetT())
78 | if frame == nil {
79 | return nil, fmt.Errorf("Unknown type")
80 | }
81 |
82 | // err := json.Unmarshal(data, frame)
83 | err := proto.Unmarshal(data, frame)
84 | return frame, err
85 | }
86 |
--------------------------------------------------------------------------------
/streamer/frame_encode_test.go:
--------------------------------------------------------------------------------
1 | package streamer
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/google/go-cmp/cmp/cmpopts"
7 | "github.com/stretchr/testify/require"
8 | "gotest.tools/assert"
9 | )
10 |
11 | func TestFrameMarshal(t *testing.T) {
12 | encoder := &ProtobufFrameEncoder{}
13 |
14 | var compare = func(f Framer, ftype int) {
15 | marshaled, err := encoder.MarshalFramer(f, ftype)
16 | require.Nil(t, err)
17 | unmarshaled, err := encoder.UnmarshalFramer(marshaled)
18 | require.Nil(t, err)
19 |
20 | assert.DeepEqual(t, f, unmarshaled, cmpopts.IgnoreUnexported(StreamFile{}))
21 | }
22 |
23 | compare(&Frame{}, FRAME_OK)
24 | compare(&FrameError{Err: "Some error"}, FRAME_ERROR)
25 | compare(&FrameNewStream{Info: &StreamFile{Name: "test.txt"}}, FRAME_NEWSTREAM)
26 | compare(&FrameData{Data: []byte("aaabbbcc")}, FRAME_DATA)
27 | }
28 |
--------------------------------------------------------------------------------
/streamer/receive_streamer.go:
--------------------------------------------------------------------------------
1 | package streamer
2 |
3 | import (
4 | "io"
5 | "os"
6 | "path/filepath"
7 |
8 | webrtc "github.com/pion/webrtc/v3"
9 | "github.com/sirupsen/logrus"
10 | )
11 |
12 | type WriteFileStreamer interface {
13 | // SendFrame(t int, f Framer) (n uint64, err error)
14 | OpenFile(path string, mode os.FileMode) (io.WriteCloser, error)
15 | Mkdir(path string, mode os.FileMode) (err error)
16 | }
17 |
18 | type ReceiveStreamer struct {
19 | WriteFileStreamer
20 | ReadWriteFramer
21 |
22 | channel *webrtc.DataChannel
23 | stream io.WriteCloser
24 | streamInfo StreamFile
25 | bandwidthCalc StreamBandwithCalculator
26 |
27 | bytesWritten int64
28 | log logrus.FieldLogger
29 | outputDir string
30 | output io.Writer
31 |
32 | // Done chan struct{}
33 |
34 | // FilesCount is number of files received, which can be read at the end of stream. Not safe to read during streaming
35 | FilesCount int
36 | }
37 |
38 | func NewReceiveStreamer(channel *webrtc.DataChannel, outputDir string, fwriter WriteFileStreamer) *ReceiveStreamer {
39 | if outputDir == "" {
40 | outputDir = "."
41 | }
42 |
43 | s := &ReceiveStreamer{
44 | channel: channel,
45 | // stream: stream,
46 | // streamInfo: streamInfo,
47 | outputDir: outputDir,
48 | log: logrus.WithField("prefix", "receivestream"),
49 | output: os.Stdout,
50 | // Done: make(chan struct{}),
51 | }
52 |
53 | s.bandwidthCalc = NewBandwithCalc(s.output)
54 | s.WriteFileStreamer = fwriter
55 | s.ReadWriteFramer = NewDataChannelFramer(channel)
56 | return s
57 | }
58 |
59 | func (s *ReceiveStreamer) Stream() (done chan struct{}) {
60 | s.channel.OnOpen(s.OnOpen)
61 | s.channel.OnMessage(s.OnMessage)
62 |
63 | done = make(chan struct{})
64 | s.channel.OnClose(func() {
65 | s.log.Infof("Recive Streamer %s closed", s.channel.Label())
66 | close(done)
67 | })
68 | return done
69 | }
70 |
71 | func (s *ReceiveStreamer) OnOpen() {
72 | s.log.Infof("Receive send streamer open")
73 | // fmt.Fprintln(s.output, "\nReceiving files:")
74 | }
75 |
76 | // func (s *ReceiveStreamer) OnClose() {
77 | // s.log.Infof("Recive Streamer %s closed", s.channel.Label())
78 | // close(s.Done)
79 | // }
80 |
81 | func (s *ReceiveStreamer) OnMessage(msg webrtc.DataChannelMessage) {
82 | f, err := s.ReadFrame(msg.Data)
83 | if err != nil {
84 | s.log.Error(err)
85 | return
86 | }
87 |
88 | // Each stream must be fully sent.
89 | // If sender wants to send new stream before finish sending current that will endup with error.
90 | switch m := f.(type) {
91 | case *FrameData:
92 | s.streamFrameData(m.Data)
93 | return
94 | case *FrameNewStream:
95 | s.log.WithField("name", m.Info.Name).Debug("Receiver new stream")
96 | if !s.isCurrentStreamSynced() {
97 | s.SendFrame(FRAME_ERROR, &FrameError{Err: "Current Stream not synced"})
98 | return
99 | }
100 |
101 | if err := s.handleNewStreamFrame(*m.Info); err != nil {
102 | s.SendFrame(FRAME_ERROR, &FrameError{Err: err.Error()})
103 | return
104 | }
105 | }
106 |
107 | s.SendFrame(FRAME_OK, &Frame{})
108 | }
109 |
110 | func (s *ReceiveStreamer) streamFrameData(data []byte) {
111 | n, err := s.stream.Write(data)
112 | s.bytesWritten += int64(n)
113 | b := s.bandwidthCalc
114 |
115 | if err != nil {
116 | s.log.Errorln(err)
117 | return
118 | }
119 |
120 | b.Add(uint64(n))
121 |
122 | if s.bytesWritten >= s.streamInfo.SizeLen {
123 | s.stream.Close()
124 | b.Finish()
125 | }
126 | }
127 |
128 | func (s *ReceiveStreamer) isCurrentStreamSynced() bool {
129 | if s.bytesWritten >= s.streamInfo.SizeLen {
130 | s.log.Info("File is fully send")
131 | return true
132 | }
133 | return false
134 | }
135 |
136 | func (s *ReceiveStreamer) handleNewStreamFrame(info StreamFile) error {
137 | // info.FullPath = fmt.Sprintf("%s/%s", s.outputDir, info.Name)
138 | info.FullPath = filepath.Join(s.outputDir, info.Name)
139 | s.log.Infof("Opening file %s %s", info.FullPath, info.Mode)
140 |
141 | if info.IsDir() {
142 | //If this is a directory, just create it
143 | if err := s.Mkdir(info.FullPath, info.FileMode()); err != nil {
144 | return err
145 | }
146 | return nil
147 | }
148 |
149 | //Here is problem. What if file exists, we have no way to return it.
150 | file, err := s.OpenFile(info.FullPath, info.FileMode())
151 | if err != nil {
152 | return err
153 | }
154 |
155 | s.stream = file
156 | s.streamInfo = info
157 | s.bandwidthCalc.NewStream(info.Name, uint64(info.SizeLen))
158 | s.bytesWritten = 0
159 |
160 | // Track some stats
161 | s.FilesCount++
162 | return nil
163 | }
164 |
--------------------------------------------------------------------------------
/streamer/receive_streamer_test.go:
--------------------------------------------------------------------------------
1 | package streamer
2 |
3 | import (
4 | "bytes"
5 | "io"
6 | "os"
7 | "testing"
8 | "time"
9 |
10 | webrtc "github.com/pion/webrtc/v3"
11 | "github.com/stretchr/testify/assert"
12 | "github.com/stretchr/testify/require"
13 | )
14 |
15 | type MyWriteCloser struct {
16 | buf *bytes.Buffer
17 | // buf []byte
18 | }
19 |
20 | func (mwc *MyWriteCloser) Write(data []byte) (int, error) {
21 | return mwc.buf.Write(data)
22 | // log.Printf("Write %p", mwc.buf)
23 | // n := copy(mwc.buf, data)
24 | // return n, nil
25 | }
26 |
27 | func (mwc *MyWriteCloser) Close() error {
28 | return nil
29 | }
30 |
31 | type MockWriteFileStreamer struct {
32 | sentFrames []Framer
33 | writedata *bytes.Buffer
34 | readN uint64
35 | openfiles map[string][]os.FileInfo
36 | encoder FrameEncoder
37 | }
38 |
39 | func (s *MockWriteFileStreamer) SendFrame(t int, f Framer) (n uint64, err error) {
40 | s.sentFrames = append(s.sentFrames, f)
41 | return s.readN, nil
42 | }
43 |
44 | func (s *MockWriteFileStreamer) ReadFrame(msg []byte) (f Framer, err error) {
45 | return s.encoder.UnmarshalFramer(msg)
46 | }
47 |
48 | func (s *MockWriteFileStreamer) OpenFile(path string, mode os.FileMode) (io.WriteCloser, error) {
49 | fi := &FileStat{
50 | name: path,
51 | size: 0,
52 | mode: mode,
53 | modtime: time.Now(),
54 | }
55 | s.openfiles[path] = append(s.openfiles[path], fi)
56 |
57 | // s.writedata = make([]byte, 0, 1024)
58 | // buf := bytes.NewBuffer(s.writedata)
59 | // buf.Write()
60 | file := &MyWriteCloser{s.writedata}
61 | return file, nil
62 | }
63 |
64 | func (s *MockWriteFileStreamer) Mkdir(path string, mode os.FileMode) error {
65 | fi := &FileStat{
66 | name: path,
67 | size: 0,
68 | mode: mode,
69 | modtime: time.Now(),
70 | }
71 | s.openfiles[path] = append(s.openfiles[path], fi)
72 | return nil
73 | }
74 |
75 | func NewMockReceiveStreamer(name string, fwriter *MockWriteFileStreamer) (*ReceiveStreamer, error) {
76 | conn, err := webrtc.NewPeerConnection(webrtc.Configuration{})
77 | if err != nil {
78 | return nil, err
79 | }
80 |
81 | dataChannel, err := conn.CreateDataChannel(name, DataChannelInitFileStream())
82 | sender := NewReceiveStreamer(dataChannel, "", fwriter)
83 | sender.ReadWriteFramer = fwriter
84 | return sender, nil
85 | }
86 |
87 | func checkNewStreamFrameSingle(t *testing.T, sf StreamFile, receiver *ReceiveStreamer, mocker *MockWriteFileStreamer) {
88 | err := receiver.handleNewStreamFrame(sf)
89 | require.Nil(t, err)
90 |
91 | opened, ok := mocker.openfiles[receiver.streamInfo.FullPath]
92 | require.True(t, ok)
93 | require.Equal(t, 1, len(opened))
94 |
95 | file := opened[0]
96 | assert.Equal(t, file.Name(), sf.Name)
97 | }
98 |
99 | func TestReceiveStreamerNewStreamFrame(t *testing.T) {
100 | mocker := &MockWriteFileStreamer{
101 | openfiles: make(map[string][]os.FileInfo),
102 | }
103 |
104 | receiver, err := NewMockReceiveStreamer("test", mocker)
105 | require.Nil(t, err)
106 |
107 | t.Run("File", func(t *testing.T) {
108 | checkNewStreamFrameSingle(t, StreamFile{Name: "file.txt", SizeLen: int64(512), Mode: 0644}, receiver, mocker)
109 | })
110 |
111 | t.Run("FileUnderDir", func(t *testing.T) {
112 | checkNewStreamFrameSingle(t, StreamFile{Name: "subdir/file.txt", SizeLen: int64(512), Mode: 0644}, receiver, mocker)
113 | })
114 | }
115 |
116 | func TestReceiveStreamerStreamData(t *testing.T) {
117 |
118 | mocker := &MockWriteFileStreamer{
119 | openfiles: make(map[string][]os.FileInfo),
120 | writedata: bytes.NewBuffer([]byte{}),
121 | }
122 | receiver, err := NewMockReceiveStreamer("test", mocker)
123 | require.Nil(t, err)
124 |
125 | content := []byte("Here some content of file\n Giving some breaks \nNew lines")
126 | sf := StreamFile{Name: "file.txt", SizeLen: int64(len(content)), Mode: 0644}
127 |
128 | err = receiver.handleNewStreamFrame(sf)
129 | require.Nil(t, err)
130 | require.NotNil(t, receiver.stream)
131 |
132 | receiver.streamFrameData(content)
133 |
134 | assert.Equal(t, mocker.writedata.Len(), len(content))
135 | assert.Equal(t, mocker.writedata.Bytes(), content)
136 | }
137 |
--------------------------------------------------------------------------------
/streamer/receiver.go:
--------------------------------------------------------------------------------
1 | package streamer
2 |
3 | import (
4 | "fmt"
5 | "time"
6 |
7 | "github.com/emiraganov/sharef/fsx"
8 |
9 | webrtc "github.com/pion/webrtc/v3"
10 | "github.com/sirupsen/logrus"
11 | log "github.com/sirupsen/logrus"
12 | )
13 |
14 | // Session is a receiver session
15 | type Receiver struct {
16 | Session
17 | Done chan struct{}
18 | log logrus.FieldLogger
19 |
20 | filechannel *webrtc.DataChannel
21 | }
22 |
23 | func NewReceiver(s Session) *Receiver {
24 |
25 | r := &Receiver{
26 | Session: s,
27 | log: logrus.WithField("prefix", "receiver"),
28 | }
29 |
30 | return r
31 | }
32 |
33 | func (s *Receiver) Dial() error {
34 | onfilechannel := make(chan struct{})
35 | if err := s.CreateConnection(s.onConnectionStateChange()); err != nil {
36 | log.Errorln(err)
37 | return err
38 | }
39 |
40 | s.OnDataChannel(func(d *webrtc.DataChannel) {
41 | s.log.Infof("New DataChannel %s %d\n", d.Label(), d.ID())
42 | s.filechannel = d
43 | close(onfilechannel)
44 | })
45 |
46 | if err := s.ReadSDP(); err != nil {
47 | s.log.Errorln(err)
48 | return err
49 | }
50 |
51 | if err := s.CreateAnswer(); err != nil {
52 | s.log.Errorln(err)
53 | return err
54 | }
55 |
56 | s.log.Debug("Waiting for connection before receiving")
57 | select {
58 | case <-onfilechannel:
59 | case <-time.After(10 * time.Second):
60 | return fmt.Errorf("Fail to get connected")
61 | }
62 |
63 | return nil
64 | }
65 |
66 | func (s *Receiver) DialReverse() error {
67 | connected := make(chan struct{})
68 | if err := s.CreateConnection(s.onConnectionStateConnected(connected)); err != nil {
69 | return err
70 | }
71 |
72 | d, err := s.createFileChannel()
73 | if err != nil {
74 | return err
75 | }
76 | s.log.Debugf("Channel created %s", d.Label())
77 |
78 | if err := s.CreateOffer(); err != nil {
79 | s.log.Errorln(err)
80 | return err
81 | }
82 |
83 | if err := s.ReadSDP(); err != nil {
84 | s.log.Errorln(err)
85 | return err
86 | }
87 |
88 | s.log.Debug("Waiting for connection before receiving")
89 | select {
90 | case <-connected:
91 | case <-time.After(10 * time.Second):
92 | return fmt.Errorf("Fail to get connected")
93 | }
94 |
95 | s.filechannel = d
96 | return nil
97 | }
98 |
99 | func (s *Receiver) NewFileStreamer(rootpath string) *ReceiveStreamer {
100 | return NewReceiveStreamer(s.filechannel, rootpath, fsx.NewFileWriter())
101 | }
102 |
103 | func (s *Receiver) NewFileStreamerWithWritter(rootpath string, fwriter WriteFileStreamer) *ReceiveStreamer {
104 | return NewReceiveStreamer(s.filechannel, rootpath, fwriter)
105 | }
106 |
107 | func (s *Receiver) onConnectionStateChange() func(connectionState webrtc.ICEConnectionState) {
108 | return func(connectionState webrtc.ICEConnectionState) {
109 | s.log.Infof("ICE Connection State has changed: %s\n", connectionState.String())
110 | if connectionState == webrtc.ICEConnectionStateDisconnected {
111 | close(s.Done)
112 | }
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/streamer/sdp.go:
--------------------------------------------------------------------------------
1 | package streamer
2 |
3 | import (
4 | "bytes"
5 | "compress/gzip"
6 | "encoding/base64"
7 | "encoding/json"
8 | "io"
9 | "io/ioutil"
10 | "strings"
11 |
12 | "github.com/klauspost/compress/s2"
13 | )
14 |
15 | // StripSDP remove useless elements from an SDP
16 | func StripSDP(originalSDP string) string {
17 | finalSDP := strings.Replace(originalSDP, "a=group:BUNDLE audio video data", "a=group:BUNDLE data", -1)
18 | tmp := strings.Split(finalSDP, "m=audio")
19 | beginningSdp := tmp[0]
20 |
21 | var endSdp string
22 | if len(tmp) > 1 {
23 | tmp = strings.Split(tmp[1], "a=end-of-candidates")
24 | endSdp = strings.Join(tmp[2:], "a=end-of-candidates")
25 | } else {
26 | endSdp = strings.Join(tmp[1:], "a=end-of-candidates")
27 | }
28 |
29 | finalSDP = beginningSdp + endSdp
30 | finalSDP = strings.Replace(finalSDP, "\r\n\r\n", "\r\n", -1)
31 | finalSDP = strings.Replace(finalSDP, "\n\n", "\n", -1)
32 | return finalSDP
33 | }
34 |
35 | // Encode encodes the input in base64
36 | // It can optionally zip the input before encoding
37 | func Encode(obj interface{}) (string, error) {
38 | b, err := json.Marshal(obj)
39 | if err != nil {
40 | return "", err
41 | }
42 | var gzbuff bytes.Buffer
43 | gz, err := gzip.NewWriterLevel(&gzbuff, gzip.BestCompression)
44 | if err != nil {
45 | return "", err
46 | }
47 | if _, err := gz.Write(b); err != nil {
48 | return "", err
49 | }
50 | if err := gz.Flush(); err != nil {
51 | return "", err
52 | }
53 | if err := gz.Close(); err != nil {
54 | return "", err
55 | }
56 |
57 | encoded := base64.StdEncoding.EncodeToString(gzbuff.Bytes())
58 |
59 | // var s2buff bytes.Buffer
60 | // S2EncodeStream(b, &s2buff)
61 |
62 | // log.Info("JSON ", len(b))
63 | // log.Info("Compressed ", len(gzbuff.Bytes()))
64 | // log.Info("base64 ", len(encoded))
65 | // log.Info("s2 ", len(s2buff.Bytes()))
66 |
67 | return encoded, nil
68 | }
69 |
70 | // Decode decodes the input from base64
71 | // It can optionally unzip the input after decoding
72 | func Decode(in string, obj interface{}) error {
73 | b, err := base64.StdEncoding.DecodeString(in)
74 | if err != nil {
75 | return err
76 | }
77 |
78 | gz, err := gzip.NewReader(bytes.NewReader(b))
79 | if err != nil {
80 | return err
81 | }
82 | defer gz.Close()
83 | s, err := ioutil.ReadAll(gz)
84 | if err != nil {
85 | return err
86 | }
87 |
88 | return json.Unmarshal(s, obj)
89 | }
90 |
91 | func S2EncodeStream(data []byte, dst io.Writer) error {
92 | // src := bytes.NewReader([]byte)
93 | // dst := bytes.NewWriter([]byte)
94 |
95 | enc := s2.NewWriter(dst)
96 | err := enc.EncodeBuffer(data)
97 | // _, err := io.Copy(enc, src)
98 | if err != nil {
99 | enc.Close()
100 | return err
101 | }
102 | // Blocks until compression is done.
103 | return enc.Close()
104 | }
105 |
--------------------------------------------------------------------------------
/streamer/send_streamer.go:
--------------------------------------------------------------------------------
1 | package streamer
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "io"
7 | "os"
8 | "path/filepath"
9 | "strings"
10 | "sync"
11 | "time"
12 |
13 | "github.com/emiraganov/sharef/errx"
14 |
15 | webrtc "github.com/pion/webrtc/v3"
16 | "github.com/sirupsen/logrus"
17 | )
18 |
19 | const (
20 | SEND_BUFFER_SIZE = 16384 // 16 * 1024
21 | SEND_BUFFER_AMOUNT_LOW_TRESHOLD = 524288 //512 * 1024
22 | )
23 |
24 | type ReadFileStreamer interface {
25 | // SendFrame(t int, f Framer) (n uint64, err error)
26 | OpenFile(name string) (io.ReadCloser, error)
27 | ReadDir(name string) ([]os.FileInfo, error)
28 | }
29 |
30 | type SendStreamer struct {
31 | ReadFileStreamer
32 | ReadWriteFramer
33 |
34 | channel *webrtc.DataChannel
35 | streamPath string
36 | destPath string
37 |
38 | bytesWritten int64
39 | frameCh chan Framer
40 | log logrus.FieldLogger
41 | Done chan struct{}
42 | DoneSending chan struct{}
43 | wg sync.WaitGroup
44 | //Optional variables
45 | output io.Writer
46 | streamChanges bool
47 |
48 | bandwithCalc StreamBandwithCalculator
49 | }
50 |
51 | // func NewSendStreamer(channel *webrtc.DataChannel, streamInfo os.FileInfo, rootpath string, options ...SendStreamerOption) *SendStreamer {
52 | func NewSendStreamer(channel *webrtc.DataChannel, rootpath string, freader ReadFileStreamer) *SendStreamer {
53 | r := &SendStreamer{
54 | channel: channel,
55 | streamPath: filepath.Clean(rootpath),
56 | frameCh: make(chan Framer),
57 | log: logrus.WithField("prefix", "sendstream"),
58 | output: os.Stdout,
59 | Done: make(chan struct{}),
60 | DoneSending: make(chan struct{}),
61 | wg: sync.WaitGroup{},
62 | }
63 |
64 | r.bandwithCalc = NewBandwithCalc(r.output)
65 | r.channel.SetBufferedAmountLowThreshold(SEND_BUFFER_AMOUNT_LOW_TRESHOLD)
66 | // r.sendFrameCb = r.sendFrame //Neded for mocking
67 | r.ReadFileStreamer = freader
68 | r.ReadWriteFramer = NewDataChannelFramer(channel)
69 | return r
70 | }
71 |
72 | func (s *SendStreamer) SetOutput(w io.Writer) {
73 | s.output = w
74 | }
75 |
76 | // SetBandwithCalc allows changing default bandwithcalc. Must be called before streaming
77 | func (s *SendStreamer) SetBandwithCalc(calc StreamBandwithCalculator) {
78 | s.bandwithCalc = calc
79 | }
80 |
81 | // SetDestinationPath will prefix path on destination side.
82 | func (s *SendStreamer) SetDestinationPath(destpath string) {
83 | s.destPath = filepath.Clean(destpath)
84 | }
85 |
86 | func (s *SendStreamer) AsyncStream(streamInfo os.FileInfo) error {
87 | s.channel.OnMessage(s.OnMessage)
88 | s.channel.OnClose(s.OnClose)
89 |
90 | s.channel.OnOpen(func() {
91 | s.log.Infof("Send receive streamer open")
92 |
93 | if err := s.SubStream(streamInfo, s.streamPath); err != nil {
94 | s.log.WithError(err).Error("Failed to process file ", s.streamPath)
95 | }
96 | // close(s.DoneSending)
97 | close(s.Done)
98 | }) //On open we streaming will start
99 |
100 | return nil
101 | }
102 |
103 | func (s *SendStreamer) Stream(ctx context.Context, streamInfo os.FileInfo) error {
104 | if !s.openChannel(ctx) {
105 | return fmt.Errorf("Channel not opened, or it too early exit")
106 | }
107 | err := s.SubStream(streamInfo, s.streamPath)
108 | return err
109 | }
110 |
111 | // StreamReader is more generic function that can stream any io reader as file on other side
112 | func (s *SendStreamer) StreamReader(ctx context.Context, reader io.Reader, info StreamFile) error {
113 | if !s.openChannel(ctx) {
114 | return fmt.Errorf("Channel not opened, or it too early exit")
115 | }
116 | err := s.processNewStream(reader, info)
117 | return err
118 | }
119 |
120 | func (s *SendStreamer) openChannel(ctx context.Context) bool {
121 | s.channel.OnMessage(s.OnMessage)
122 | s.channel.OnClose(s.OnClose)
123 |
124 | opened := make(chan struct{})
125 | s.channel.OnOpen(func() {
126 | close(opened)
127 | }) //On open we streaming will start
128 |
129 | select {
130 | case <-ctx.Done():
131 | case <-opened:
132 | return true
133 | }
134 | return false
135 | }
136 |
137 | func (s *SendStreamer) OnClose() {
138 | s.log.Infof("Send Streamer %s closed", s.channel.Label())
139 | }
140 |
141 | func (s *SendStreamer) SubStream(streamInfo os.FileInfo, path string) error {
142 | if err := s.processFile(streamInfo, path); err != nil {
143 | return err
144 | }
145 |
146 | //Need to slove this better, but for now we should not close our self until buffer is empty
147 | for {
148 | if s.channel.BufferedAmount() == 0 {
149 | break
150 | }
151 | time.Sleep(100 * time.Millisecond)
152 | }
153 | return nil
154 | }
155 |
156 | func (s *SendStreamer) processFile(fi os.FileInfo, path string) error {
157 | if fi.IsDir() {
158 | if err := s.processFileDir(fi, path); err != nil {
159 | return err
160 | }
161 | return nil
162 | }
163 |
164 | if err := s.processFileStream(fi, path); err != nil {
165 | return err
166 | }
167 |
168 | return nil
169 | }
170 |
171 | func (s *SendStreamer) processFileDir(fi os.FileInfo, root string) error {
172 | root = strings.TrimSuffix(root, string(os.PathSeparator))
173 |
174 | if err := s.processFileStream(fi, root); err != nil {
175 | return err
176 | }
177 |
178 | finfos, err := s.ReadDir(root)
179 | if err != nil {
180 | return err
181 | }
182 |
183 | for _, fi := range finfos {
184 | path := filepath.Join(root, fi.Name())
185 | if err := s.processFile(fi, path); err != nil {
186 | s.log.WithError(err).Error("Failed to process file")
187 | }
188 | }
189 |
190 | return nil
191 | }
192 |
193 | // func (s *SendStreamer) openFile(path string) (io.ReadCloser, error) {
194 | // file, err := os.Open(path)
195 | // return file, err
196 | // }
197 |
198 | func (s *SendStreamer) processFileStream(fi os.FileInfo, path string) error {
199 | file, err := s.OpenFile(path)
200 | if err != nil {
201 | return errx.Wrapf(err, "Fail to open file %s", path)
202 | }
203 | defer file.Close()
204 |
205 | info := s.prepareNewStream(fi, path)
206 |
207 | return s.processNewStream(file, info)
208 | }
209 |
210 | func (s *SendStreamer) prepareNewStream(fi os.FileInfo, path string) StreamFile {
211 | path = filepath.Clean(path)
212 | //Here we need to send file, root must be our stream name
213 | info := FileInfo2StreamFile(fi, "")
214 | //Get base of our main stream
215 | base := filepath.Base(s.streamPath)
216 | //Strip our path
217 | mainpath := strings.TrimPrefix(s.streamPath, ".")
218 | mainpath = strings.TrimPrefix(mainpath, string(os.PathSeparator))
219 | path = strings.TrimPrefix(path, ".")
220 | path = strings.TrimPrefix(path, string(os.PathSeparator))
221 |
222 | stripped := strings.TrimPrefix(path, mainpath)
223 |
224 | //Construct base path
225 | info.Name = filepath.Join(base, stripped)
226 |
227 | s.log.Infof("Sending file stream %s %s", info.Name, s.streamPath)
228 |
229 | return info
230 | }
231 |
232 | func (s *SendStreamer) processNewStream(file io.Reader, info StreamFile) error {
233 | //Relative path for receiver must be constructed
234 | info.Name = filepath.Join(s.destPath, info.Name)
235 |
236 | if _, err := s.postFrame(FRAME_NEWSTREAM, &FrameNewStream{Info: &info}); err != nil {
237 | return errx.Wrapf(err, "Fail to post frame for file %s", info.Name)
238 | }
239 |
240 | if info.IsDir() {
241 | //No need to stream dir
242 | return nil
243 | }
244 |
245 | if err := s.streamReader(file, info.SizeLen, info.Name); err != nil {
246 | return errx.Wrapf(err, "Fail to stream file %s", info.Name)
247 | }
248 | return nil
249 | }
250 |
251 | func (s *SendStreamer) streamReader(file io.Reader, size int64, fname string) error {
252 | s.log.Infof("Starting stream name=%s", fname)
253 |
254 | data := make([]byte, SEND_BUFFER_SIZE)
255 | b := s.bandwithCalc
256 | b.NewStream(fname, uint64(size))
257 |
258 | bufflock := make(chan struct{})
259 | s.channel.OnBufferedAmountLow(func() {
260 | <-bufflock
261 | })
262 |
263 | for {
264 | if s.channel.BufferedAmount() >= s.channel.BufferedAmountLowThreshold() {
265 | bufflock <- struct{}{}
266 | }
267 |
268 | n, err := file.Read(data)
269 |
270 | if err != nil && err != io.EOF {
271 | return err
272 | }
273 |
274 | if n == 0 {
275 | break
276 | }
277 |
278 | // _, err = s.sendFrame(FRAME_DATA, &FrameData{Data: data[:n]})
279 | _, err = s.SendFrame(FRAME_DATA, &FrameData{Data: data[:n]})
280 | if err != nil {
281 | return err
282 | }
283 |
284 | b.Add(uint64(n))
285 | }
286 |
287 | b.Finish()
288 | s.log.Infof("File %s is successfully sent", fname)
289 | return nil
290 | }
291 |
292 | func (s *SendStreamer) OnMessage(msg webrtc.DataChannelMessage) {
293 | // s.log.Infof("Sender on message called")
294 |
295 | f, err := s.ReadFrame(msg.Data)
296 | if err != nil {
297 | s.log.Error(err)
298 | return
299 | }
300 | s.log.Infof("Sender on message called %d", f.GetT())
301 |
302 | select {
303 | case s.frameCh <- f:
304 | default:
305 | s.log.Errorf("Frame missed %s", f.GetT())
306 | }
307 | }
308 |
309 | func (s *SendStreamer) postFrame(t int, f Framer) (Framer, error) {
310 | if _, err := s.SendFrame(t, f); err != nil {
311 | return nil, err
312 | }
313 |
314 | var res Framer
315 | select {
316 | case res = <-s.frameCh:
317 | case <-time.After(60 * time.Second):
318 | return nil, fmt.Errorf("Timeout")
319 | }
320 |
321 | if res.GetT() == FRAME_ERROR {
322 | frame := res.(*FrameError)
323 | return nil, fmt.Errorf(frame.Err)
324 | }
325 |
326 | return res, nil
327 | }
328 |
--------------------------------------------------------------------------------
/streamer/send_streamer_test.go:
--------------------------------------------------------------------------------
1 | package streamer
2 |
3 | import (
4 | "bytes"
5 | "io"
6 | "io/ioutil"
7 | "os"
8 | "path/filepath"
9 | "testing"
10 | "time"
11 |
12 | webrtc "github.com/pion/webrtc/v3"
13 | "github.com/stretchr/testify/require"
14 | "gotest.tools/assert"
15 | )
16 |
17 | type MockReadFileStreamer struct {
18 | sentFrames []Framer
19 | fakedata []byte
20 | readN uint64
21 | fakeDir map[string][]os.FileInfo
22 | encoder FrameEncoder
23 | }
24 |
25 | func (s *MockReadFileStreamer) SendFrame(t int, f Framer) (n uint64, err error) {
26 | s.sentFrames = append(s.sentFrames, f)
27 | return s.readN, nil
28 | }
29 |
30 | func (s *MockReadFileStreamer) ReadFrame(msg []byte) (f Framer, err error) {
31 | return s.encoder.UnmarshalFramer(msg)
32 | }
33 |
34 | func (s *MockReadFileStreamer) OpenFile(path string) (io.ReadCloser, error) {
35 | buf := bytes.NewReader(s.fakedata)
36 | file := ioutil.NopCloser(buf)
37 | return file, nil
38 | }
39 |
40 | func (s *MockReadFileStreamer) ReadDir(path string) ([]os.FileInfo, error) {
41 | return s.fakeDir[path], nil
42 | }
43 |
44 | func NewMockSendStreamer(name string, rootpath string, freader *MockReadFileStreamer) (*SendStreamer, error) {
45 | conn, err := webrtc.NewPeerConnection(webrtc.Configuration{})
46 | if err != nil {
47 | return nil, err
48 | }
49 |
50 | dataChannel, err := conn.CreateDataChannel(name, DataChannelInitFileStream())
51 | sender := NewSendStreamer(dataChannel, rootpath, freader)
52 | sender.ReadWriteFramer = freader
53 | return sender, nil
54 | }
55 | func TestSendStreamerPrepareNewStream(t *testing.T) {
56 | // streamInfo := &FileStat{name: "mydir"}
57 | mocker := &MockReadFileStreamer{}
58 |
59 | sender, err := NewMockSendStreamer("test", "/opt/my/some/mydir", mocker)
60 | require.Nil(t, err)
61 |
62 | fi := &FileStat{
63 | name: "file.txt",
64 | }
65 |
66 | t.Run("Simple", func(t *testing.T) {
67 | info := sender.prepareNewStream(fi, "/opt/my/some/mydir/subdir/sub/file.txt")
68 | assert.Equal(t, info.Name, "mydir/subdir/sub/file.txt")
69 | })
70 |
71 | t.Run("BadFormat", func(t *testing.T) {
72 | //Some badly formated
73 | info := sender.prepareNewStream(fi, "./opt/my/some//mydir/subdir/sub//file.txt")
74 | assert.Equal(t, info.Name, "mydir/subdir/sub/file.txt")
75 | })
76 | }
77 |
78 | func TestSendStreamerStreamfile(t *testing.T) {
79 | fi := &FileStat{
80 | name: "file.txt",
81 | }
82 |
83 | mocker := &MockReadFileStreamer{
84 | fakedata: []byte("0123456789"),
85 | readN: 10,
86 | }
87 |
88 | sender, err := NewMockSendStreamer("test", "/opt/file.txt", mocker)
89 | require.Nil(t, err)
90 |
91 | file, err := sender.OpenFile("file.txt")
92 | require.Nil(t, err)
93 |
94 | err = sender.streamReader(file, fi.Size(), fi.Name())
95 | require.Nil(t, err)
96 |
97 | sentFrames := mocker.sentFrames
98 | require.Equal(t, int(1), len(sentFrames))
99 |
100 | //FirstFrame should be new frame data
101 | frame, ok := sentFrames[0].(*FrameData)
102 | require.True(t, ok)
103 | assert.DeepEqual(t, frame.Data, mocker.fakedata)
104 | }
105 |
106 | func TestSendStreamerProcessFile(t *testing.T) {
107 | rootname := "mydir"
108 | rootpath := "/opt/mydir"
109 |
110 | fi := &FileStat{
111 | name: rootname,
112 | mode: os.ModeDir,
113 | }
114 |
115 | mocker := &MockReadFileStreamer{
116 | fakedata: []byte("0123456789"), //This is for file.txt
117 | readN: 10,
118 | fakeDir: map[string][]os.FileInfo{
119 | rootpath: {
120 | &FileStat{name: "subdir1", size: 0, mode: os.ModeDir, modtime: time.Now()},
121 | &FileStat{name: "subdir2", size: 0, mode: os.ModeDir, modtime: time.Now()},
122 | },
123 | rootpath + "/subdir1": {
124 | &FileStat{name: "file.txt", size: 10, mode: 0, modtime: time.Now()},
125 | },
126 | },
127 | }
128 |
129 | sender, err := NewMockSendStreamer("test", rootpath, mocker)
130 | require.Nil(t, err)
131 |
132 | go func() {
133 | //Fake response from receiver, and check is every processed
134 | for {
135 | sender.frameCh <- &Frame{T: FRAME_OK}
136 | }
137 | }()
138 |
139 | err = sender.processFile(fi, sender.streamPath)
140 | require.Nil(t, err)
141 |
142 | sentFrames := mocker.sentFrames
143 |
144 | var checkFrame func(subfi os.FileInfo, rname string, rpath string)
145 | checkFrame = func(subfi os.FileInfo, rname string, rpath string) {
146 | if len(sentFrames) == 0 {
147 | t.Fatalf("Missing frames")
148 | }
149 | frame := sentFrames[0]
150 | newframe, ok := frame.(*FrameNewStream)
151 | if !ok {
152 | t.Fatalf("Not New stream frame")
153 | }
154 |
155 | //If this is not file we should expect data flow
156 | if !subfi.IsDir() {
157 | sentFrames = sentFrames[1:]
158 | frame, ok := sentFrames[0].(*FrameData)
159 | require.True(t, ok)
160 | assert.DeepEqual(t, frame.Data, mocker.fakedata)
161 | }
162 |
163 | //Each Send stream must begin from root of stream, not from our relative path
164 | fname := filepath.Join(rname, subfi.Name())
165 |
166 | assert.Equal(t, fname, newframe.Info.Name)
167 | sentFrames = sentFrames[1:]
168 |
169 | fpath := filepath.Join(rpath, subfi.Name())
170 | for _, subfi := range mocker.fakeDir[fpath] {
171 | checkFrame(subfi, fname, fname)
172 | }
173 | }
174 |
175 | checkFrame(fi, "", "")
176 | }
177 |
--------------------------------------------------------------------------------
/streamer/sender.go:
--------------------------------------------------------------------------------
1 | package streamer
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "os"
7 | "sync"
8 | "time"
9 |
10 | "github.com/emiraganov/sharef/fsx"
11 |
12 | webrtc "github.com/pion/webrtc/v3"
13 |
14 | "github.com/sirupsen/logrus"
15 | )
16 |
17 | const (
18 | // Must be <= 16384
19 | senderBuffSize = 16384
20 | )
21 |
22 | type outputMsg struct {
23 | n int
24 | buff []byte
25 | }
26 |
27 | // Session is a sender session
28 | type Sender struct {
29 | Session
30 | log logrus.FieldLogger
31 | filechannel *webrtc.DataChannel
32 | }
33 |
34 | // New creates a new sender session
35 | func NewSender(s Session) *Sender {
36 | return &Sender{
37 | Session: s,
38 | log: logrus.WithField("prefix", "sender"),
39 | }
40 | }
41 |
42 | // Change encoding.
43 | // proto = binary encoding with protobuf
44 | // json = json encoding
45 |
46 | // Start the connection and the file transfer
47 | func (s *Sender) Dial() error {
48 | connected := make(chan struct{})
49 |
50 | if err := s.CreateConnection(s.onConnectionStateChange(connected)); err != nil {
51 | return err
52 | }
53 |
54 | channel, err := s.createFileChannel()
55 | if err != nil {
56 | return err
57 | }
58 | s.log.Debugf("Channel created %s", channel.Label())
59 |
60 | if err := s.CreateOffer(); err != nil {
61 | return err
62 | }
63 |
64 | if err := s.ReadSDP(); err != nil {
65 | return err
66 | }
67 |
68 | s.log.Debug("Waiting for connection before sending")
69 | select {
70 | case <-connected:
71 | case <-time.After(10 * time.Second):
72 | return fmt.Errorf("Fail to get connected")
73 | }
74 |
75 | s.filechannel = channel
76 |
77 | return nil
78 | }
79 |
80 | func (s *Sender) DialReverse() error {
81 | onfilechannel := make(chan struct{})
82 | if err := s.CreateConnection(nil); err != nil {
83 | return err
84 | }
85 |
86 | s.OnDataChannel(func(d *webrtc.DataChannel) {
87 | s.log.Infof("New DataChannel %s %d\n", d.Label(), d.ID())
88 | s.filechannel = d
89 | close(onfilechannel)
90 | })
91 |
92 | if err := s.ReadSDP(); err != nil {
93 | s.log.Errorln(err)
94 | return err
95 | }
96 |
97 | if err := s.CreateAnswer(); err != nil {
98 | s.log.Errorln(err)
99 | return err
100 | }
101 |
102 | s.log.Debug("Waiting for connection before sending")
103 | select {
104 | case <-onfilechannel:
105 | case <-time.After(10 * time.Second):
106 | return fmt.Errorf("Fail to get connected")
107 | }
108 |
109 | return nil
110 | }
111 |
112 | func (s *Sender) SendFile(dest string) (err error) {
113 | fi, err := os.Stat(dest)
114 | if err != nil {
115 | return err
116 | }
117 |
118 | sender := s.NewFileStreamer(dest)
119 |
120 | if err := sender.Stream(context.Background(), fi); err != nil {
121 | return err
122 | }
123 |
124 | return err
125 | }
126 |
127 | func (s *Sender) NewFileStreamer(dest string) (streamer *SendStreamer) {
128 | return NewSendStreamer(s.filechannel, dest, fsx.NewFileReader())
129 | }
130 |
131 | func (s *Sender) NewFileStreamerWithReader(dest string, freader ReadFileStreamer) (streamer *SendStreamer) {
132 | return NewSendStreamer(s.filechannel, dest, freader)
133 | }
134 |
135 | func (s *Sender) onConnectionStateChange(connected chan struct{}) func(connectionState webrtc.ICEConnectionState) {
136 | once := &sync.Once{}
137 | return func(sig webrtc.ICEConnectionState) {
138 | s.log.Debug("ICE STATE: ", sig.String())
139 | if sig == webrtc.ICEConnectionStateConnected {
140 | once.Do(func() {
141 | close(connected)
142 | })
143 | }
144 | }
145 | }
146 |
147 | // func (s *Sender) onConnectionStateChange(connected chan struct{}) func(connectionState webrtc.ICEConnectionState) {
148 | // once := &sync.Once{}
149 | // return func(sig webrtc.ICEConnectionState) {
150 | // s.log.Debug("ICE STATE: ", sig.String())
151 | // if sig == webrtc.ICEConnectionStateConnected {
152 | // once.Do(func() {
153 | // close(connected)
154 | // })
155 | // }
156 | // }
157 | // }
158 |
--------------------------------------------------------------------------------
/streamer/session.go:
--------------------------------------------------------------------------------
1 | package streamer
2 |
3 | import (
4 | "bufio"
5 | "fmt"
6 | "io"
7 | "strings"
8 | "sync"
9 |
10 | webrtc "github.com/pion/webrtc/v3"
11 | log "github.com/sirupsen/logrus"
12 | )
13 |
14 | const (
15 | SDP_OFFER_PROMPT = "Send this offer:"
16 | SDP_OFFER_WAITING_PROMPT = "Please, paste the remote offer:"
17 | SDP_ANSWER_PROMPT = "Send this answer:"
18 | SDP_ANSWER_WAITING_PROMPT = "Please, paste the remote answer:"
19 | )
20 |
21 | var (
22 | ICEServerList = []webrtc.ICEServer{
23 | {
24 | URLs: []string{
25 | "stun:stun.l.google.com:19302",
26 | },
27 | },
28 | }
29 | )
30 |
31 | type CompletionHandler func()
32 |
33 | // Session contains common elements to perform send/receive
34 | type Session struct {
35 | sdpReader io.Reader
36 | sdpWriter io.Writer
37 | peerConnection *webrtc.PeerConnection
38 | onCompletion CompletionHandler
39 | encoding FrameEncoding
40 | }
41 |
42 | // New creates a new Session
43 | func NewSession(SDPReader io.Reader, SDPWriter io.Writer) Session {
44 | sess := Session{
45 | sdpReader: SDPReader,
46 | sdpWriter: SDPWriter,
47 | encoding: FrameEncodingProto,
48 | }
49 |
50 | return sess
51 | }
52 |
53 | func (s *Session) Close() error {
54 | if s.peerConnection != nil {
55 | return s.peerConnection.Close()
56 | }
57 |
58 | return nil
59 | }
60 |
61 | func (s *Session) SetEncoding(enc FrameEncoding) {
62 | s.encoding = enc
63 | }
64 |
65 | // CreateConnection prepares a WebRTC connection
66 | func (s *Session) CreateConnection(onConnectionStateChange func(connectionState webrtc.ICEConnectionState)) error {
67 | config := webrtc.Configuration{
68 | ICEServers: ICEServerList,
69 | ICETransportPolicy: webrtc.ICETransportPolicyAll,
70 | BundlePolicy: webrtc.BundlePolicyBalanced,
71 | // SDPSemantics: webrtc.SDPSemanticsPlanB,
72 | }
73 |
74 | // Create a new RTCPeerConnection
75 | peerConnection, err := webrtc.NewPeerConnection(config)
76 | if err != nil {
77 | return err
78 | }
79 | s.peerConnection = peerConnection
80 | peerConnection.OnICEConnectionStateChange(onConnectionStateChange)
81 | return nil
82 | }
83 |
84 | // ReadSDP from the SDP input stream
85 | func (s *Session) ReadSDP() error {
86 | var sdp webrtc.SessionDescription
87 |
88 | for {
89 | encoded, err := MustReadStream(s.sdpReader)
90 | if err == nil {
91 | if err := Decode(encoded, &sdp); err == nil {
92 | break
93 | }
94 | }
95 | return err
96 | }
97 |
98 | return s.peerConnection.SetRemoteDescription(sdp)
99 | }
100 |
101 | // OnDataChannel sets an OnDataChannel handler
102 | func (s *Session) OnDataChannel(handler func(d *webrtc.DataChannel)) {
103 | s.peerConnection.OnDataChannel(handler)
104 | }
105 |
106 | // CreateAnswer set the local description and print the answer SDP
107 | func (s *Session) CreateAnswer() error {
108 | // Create an answer
109 | // s.peerConnection.AddTransceiverFromKind(webrtc.RTPCodecType(webrtc.RTPTransceiverDirectionSendrecv))
110 |
111 | answer, err := s.peerConnection.CreateAnswer(nil)
112 | if err != nil {
113 | return err
114 | }
115 |
116 | if err := s.createSessionDescription(answer, SDP_ANSWER_PROMPT); err != nil {
117 | return err
118 | }
119 |
120 | return nil
121 | }
122 |
123 | // CreateOffer set the local description and print the offer SDP
124 | func (s *Session) CreateOffer() error {
125 | // Create an offer
126 | // s.peerConnection.AddTransceiverFromKind(webrtc.RTPCodecType(webrtc.RTPTransceiverDirectionSendrecv))
127 | // s.peerConnection.AddTransceiverFromKind(webrtc.RTPCodecType(webrtc.))
128 |
129 | offer, err := s.peerConnection.CreateOffer(nil)
130 | if err != nil {
131 | return err
132 | }
133 |
134 | if err := s.createSessionDescription(offer, SDP_OFFER_PROMPT); err != nil {
135 | return err
136 | }
137 |
138 | return nil
139 | }
140 |
141 | // createSessionDescription set the local description and print the SDP
142 | func (s *Session) createSessionDescription(desc webrtc.SessionDescription, prompt string) error {
143 | // Sets the LocalDescription, and starts our UDP listeners
144 | // Create channel that is blocked until ICE Gathering is complete
145 | gatherComplete := webrtc.GatheringCompletePromise(s.peerConnection)
146 |
147 | if err := s.peerConnection.SetLocalDescription(desc); err != nil {
148 | return err
149 | }
150 |
151 | <-gatherComplete
152 |
153 | desc = *s.peerConnection.LocalDescription()
154 | log.Debugf("Gather ICE completed %s\n", desc.SDP)
155 |
156 | // writer the SDP in base64 so we can paste it in browser
157 | resp, err := Encode(desc)
158 | if err != nil {
159 | return err
160 | }
161 | fmt.Fprintf(s.sdpWriter, "%s\n", resp)
162 | return nil
163 | }
164 |
165 | func (s *Session) onConnectionStateConnected(connected chan struct{}) func(connectionState webrtc.ICEConnectionState) {
166 | once := &sync.Once{}
167 | return func(sig webrtc.ICEConnectionState) {
168 | log.Debug("ICE STATE: ", sig.String())
169 | if sig == webrtc.ICEConnectionStateConnected {
170 | once.Do(func() {
171 | close(connected)
172 | })
173 | }
174 | }
175 | }
176 |
177 | func (s *Session) createFileChannel() (*webrtc.DataChannel, error) {
178 | label := DatachannelLabelProtobufstream
179 | switch s.encoding {
180 | case FrameEncodingJSON:
181 | label = DatachannelLabelJsonstream
182 | }
183 |
184 | dataChannel, err := s.peerConnection.CreateDataChannel(label, DataChannelInitFileStream())
185 | return dataChannel, err
186 | }
187 |
188 | // MustReadStream blocks until input is received from the stream
189 | func MustReadStream(stream io.Reader) (string, error) {
190 | r := bufio.NewReader(stream)
191 |
192 | var in string
193 | for {
194 | var err error
195 | in, err = r.ReadString('\n')
196 | if err != io.EOF {
197 | if err != nil {
198 | return "", err
199 | }
200 | }
201 | in = strings.TrimSpace(in)
202 | if len(in) > 0 {
203 | break
204 | }
205 | }
206 |
207 | return in, nil
208 | }
209 |
--------------------------------------------------------------------------------
/streamer/streamfile.go:
--------------------------------------------------------------------------------
1 | package streamer
2 |
3 | import (
4 | "os"
5 | "path/filepath"
6 | "time"
7 | )
8 |
9 | // type StreamFile struct {
10 | // Name string //Relative path depending on what is sharing
11 | // Path string //This should be removed, it is used on sender receiver to keep original path
12 |
13 | // Size int64
14 | // Mode os.FileMode
15 | // ModTime time.Time
16 | // // Data []byte
17 | // // sys syscall.Stat_t
18 | // //Used on receiver side
19 | // fullPath string
20 | // }
21 |
22 | func (s *StreamFile) IsDir() bool {
23 | return s.FileMode().IsDir()
24 | }
25 |
26 | func (s *StreamFile) FileMode() os.FileMode {
27 | fmode := os.FileMode(s.Mode)
28 | return fmode
29 | }
30 |
31 | func StreamFile2FileInfo(fi StreamFile) os.FileInfo {
32 | t, _ := time.Parse(time.RFC3339, fi.ModTime)
33 | return &FileStat{
34 | name: fi.Name,
35 | size: fi.SizeLen,
36 | mode: fi.FileMode(),
37 | modtime: t,
38 | }
39 | }
40 |
41 | func FileInfo2StreamFile(fi os.FileInfo, path string) StreamFile {
42 | return StreamFile{
43 | Name: fi.Name(),
44 | Path: filepath.Clean(path),
45 | SizeLen: fi.Size(),
46 | Mode: uint32(fi.Mode()),
47 | ModTime: fi.ModTime().Format(time.RFC3339),
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/watcher/watcher.go:
--------------------------------------------------------------------------------
1 | package watcher
2 |
3 | import (
4 | "context"
5 | "os"
6 |
7 | "github.com/sirupsen/logrus"
8 | "gopkg.in/fsnotify.v1"
9 | )
10 |
11 | type Watcher struct {
12 | streamPath string
13 | streamInfo os.FileInfo
14 | log logrus.FieldLogger
15 | }
16 |
17 | type OnChangeFile func(fi os.FileInfo, path string) error
18 |
19 | func New(fullpath string, fi os.FileInfo) *Watcher {
20 | return &Watcher{
21 | streamPath: fullpath,
22 | streamInfo: fi,
23 | log: logrus.WithField("prefix", "watcher"),
24 | }
25 | }
26 |
27 | func (s *Watcher) ListenChangeFile(ctx context.Context, f OnChangeFile) {
28 | watcher, err := fsnotify.NewWatcher()
29 | if err != nil {
30 | s.log.Println("ERROR", err)
31 | return
32 | }
33 | defer watcher.Close()
34 |
35 | if err := watcher.Add(s.streamPath); err != nil {
36 | s.log.Info("ERROR ", err)
37 | }
38 |
39 | for {
40 | select {
41 | // watch for events
42 | case event := <-watcher.Events:
43 | s.log.Infof("EVENT! %s\n", event.String())
44 | s.checkFileChanges(event, watcher, f)
45 |
46 | // watch for errors
47 | case err := <-watcher.Errors:
48 | s.log.Info("ERROR ", err)
49 |
50 | case <-ctx.Done():
51 | return
52 | }
53 | }
54 | }
55 |
56 | func (s *Watcher) checkFileChanges(event fsnotify.Event, watcher *fsnotify.Watcher, f OnChangeFile) {
57 | // if s.bytesWritten < s.streamInfo.Size {
58 | // return
59 | // }
60 |
61 | switch {
62 | case event.Op&fsnotify.Write == fsnotify.Write:
63 | case event.Op&fsnotify.Create == fsnotify.Create:
64 | if !s.streamInfo.IsDir() { //Only streaming dir we follow create changes
65 | return
66 | }
67 | default:
68 | return
69 | }
70 |
71 | path := event.Name
72 |
73 | fi, err := os.Stat(path)
74 | if err != nil {
75 | s.log.Error(err)
76 | return
77 | }
78 |
79 | s.log.WithField("path", path).Info("Sending file changes")
80 |
81 | if err := f(fi, path); err != nil {
82 | s.log.Error(err)
83 | return
84 | }
85 |
86 | if fi.IsDir() {
87 | //Add tracking changes for this dir
88 | if err := watcher.Add(path); err != nil {
89 | s.log.Error(err)
90 | }
91 | }
92 | }
93 |
--------------------------------------------------------------------------------