├── .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 | ![SENDDEMO](docs/SharefSendDemo.gif) 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 | ![SENDDEMO](docs/SharefDeamonDemo.gif) 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 | --------------------------------------------------------------------------------