1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
|
/***************
FILE UPLOADING
(c) 2013 Samuel Erb
****************/
/* sending functionality, only allow 1 file to be sent out at a time */
this.chunks = {};
this.meta = {};
this.numOfChunksInFile = 10; /* set to some arbitrarily low number for right now */
this.CHUNK_SIZE = 1000;
this.timeout = 1000; /* time before resending request for next chunk */
/* recieving functionality, allow multiple at the same time! */
this.recieved_chunks = [];
this.recieved_meta = [];
this.recieved_timeout = [];
/* Document bind's to accept files copied. Don't accept until we have a connection */
function accept_inbound_files() {
$(document).bind('drop dragover dragenter', function (e) {
// todo: CSS signal?
e.preventDefault();
});
/* drop a file on the page! */
$(document).bind('drop', function (e) {
var file = e.originalEvent.dataTransfer.files[0];
reader = new FileReader();
systemMessage("now processing "+file.name);
//send out once FileReader reads in file
reader.onload = function (event) {
chunkify(event.target.result, file);
systemMessage("now uploading "+file.name);
send_meta();
systemMessage("file ready to send");
};
reader.readAsDataURL(file);
});
}
/* recursive re-request callback function for passing parameters through setTimeout */
function resend_chunk_request_CB(id, chunk_num) {
return function(){
resend_chunk_request(id, chunk_num);
};
}
/* recursivly re-request the next chunk until we get it, it isn't perfect, but it works */
function resend_chunk_request(id, chunk_num) {
console.log("re-requesting chunk " + chunk_num + " from " + id);
/* request the next chunk */
request_chunk(id, chunk_num);
/* reset the timeout */
clearTimeout(this.recieved_timeout[id]);
this.recieved_timeout[id] = setTimeout(resend_chunk_request_CB(id, chunk_num),this.timeout);
}
/* inbound - recieve data */
function process_data(data) {
window.URL = window.webkitURL || window.URL;
if (data.file) {
//if it has file_params, we are reciving a file chunk */
var chunk_length = this.recieved_chunks[data.id].length;
this.recieved_chunks[data.id][data.chunk] = data.file;
//request the next chunk, if we just didn't get the last
if (this.recieved_meta[data.id].numOfChunksInFile > (data.chunk + 1)) {
/* only send the request for the next chunk if the number of chunks we have went up (ie. we saw a new chunk) */
if (chunk_length < this.recieved_chunks[data.id].length) {
/* update the cointainer % */
update_container_percentage(data.id, data.chunk, this.recieved_meta[data.id].numOfChunksInFile, this.recieved_meta[data.id].size);
/* request the next chunk */
request_chunk(data.id, (data.chunk + 1));
/* reset the timeout */
clearTimeout(this.recieved_timeout[data.id]);
this.recieved_timeout[data.id] = setTimeout(resend_chunk_request_CB(data.id, (data.chunk + 1)),this.timeout);
}
} else {
console.log("done downloading file!");
/* reset the timeout so we don't recieve the same packet twice */
clearTimeout(this.recieved_timeout[data.id]);
/* now combine the chunks and form a link! */
var combine_chunks = '';
for (var i = 0; i < this.recieved_meta[data.id].numOfChunksInFile; i++) {
if (this.recieved_chunks[data.id][i] == ''){
console.log("missing chunk! " + i);
}
combine_chunks += this.recieved_chunks[data.id][i];
}
create_file_link (combine_chunks,this.recieved_meta[data.id], data.id, data.username);
}
} else if (data.file_meta) {
/* if it contains file_meta, must be meta data! */
this.recieved_meta[data.id] = data.file_meta;
console.log(this.recieved_meta[data.id]);
this.recieved_chunks[data.id] = []; //clear out our chunks
/* create a download link */
create_pre_file_link(this.recieved_meta[data.id], data.id, data.username);
/* if auto-download, start the process */
if ($("#auto_download").prop('checked')) {
download_file(data.id);
}
} else {
/* if it does not have file_params, must be request for next chunk, send it, don't broadcast */
sendchunk(data.id, data.chunk);
}
}
/* request chunk # chunk_num from id */
function request_chunk(id, chunk_num) {
//console.log("requesting chunk " + chunk_num + " from " + id);
dataChannelChat.send(id, JSON.stringify({
"eventName": "request_chunk",
"data": {
"chunk": chunk_num
}
}));
}
/* request id's file by sending request for block 0 */
function download_file(id) {
request_chunk(id, 0);
}
/* creates an entry in our filelist for a user, if it doesn't exist already */
function create_or_clear_container(id, username) {
var filelist = document.getElementById('filelist_cointainer');
var filecontainer = document.getElementById(id);
if (!filecontainer) {
/* if filecontainer doesn't exist, create it */
var fs = '<div id="' + id + '">' + username + ': </div>';
filelist.innerHTML = filelist.innerHTML + fs;
} else {
/* if filecontainer does exist, clear it */
filecontainer.innerHTML = username + ": ";
}
}
/* creates an entry in our filelist for a user, if it doesn't exist already */
function remove_container(id) {
var filecontainer = document.getElementById(id);
if (filecontainer) {
filecontainer.remove();
}
}
/* create a link that will let the user start the download */
function create_pre_file_link(meta, id, username) {
//create a place to store this if it does not already
create_or_clear_container(id, username);
var filecontainer = document.getElementById(id);
//create the link
var a = document.createElement('a');
a.download = meta.name;
a.id = id + '-download';
a.href = 'javascript:void(0);';
a.textContent = 'download ' + meta.name + ' ' + getReadableFileSizeString(meta.size);
a.draggable = true;
//onclick, download the file!
a.setAttribute('onclick','javascript:download_file("' + id + '");');
//append link!
var messages = document.getElementById('messages');
filecontainer.appendChild(a);
//append to chat
systemMessage(username +" is now offering file " + meta.name);
}
/* update a file container with a DL % */
function update_container_percentage(id, chunk_num, chunk_total, total_size) {
create_or_clear_container(id, username);
var filecontainer = document.getElementById(id);
/* create file % based on chunk # downloaded */
var percentage = (chunk_num / chunk_total) * 100;
filecontainer.innerHTML = filecontainer.innerHTML + percentage.toFixed(1) + "% of " + getReadableFileSizeString(total_size);
}
/* -h */
function getReadableFileSizeString(fileSizeInBytes) {
var i = -1;
var byteUnits = [' kB', ' MB', ' GB', ' TB', 'PB', 'EB', 'ZB', 'YB'];
do {
fileSizeInBytes = fileSizeInBytes / 1024;
i++;
} while (fileSizeInBytes > 1024);
return Math.max(fileSizeInBytes, 0.1).toFixed(1) + byteUnits[i];
};
/* create a link to this file */
function create_file_link (combine_chunks, meta, id, username) {
//grab the file type, should probably use a pattern match...
var remove_base = meta.filetype.split(";");
var remove_data = remove_base[0].split(":");
var filetype = remove_data[1];
//now handle the data
var debase64_data = base64.decode(combine_chunks);
var bb = new Blob([new Uint8Array(debase64_data)], {type: filetype});
//create a place to store this if it does not already
create_or_clear_container(id, username);
var filecontainer = document.getElementById(id);
//create the link
var a = document.createElement('a');
a.download = meta.name;
a.href = window.URL.createObjectURL(bb);
a.textContent = 'save ' + meta.name;
a.dataset.downloadurl = [filetype, a.download, a.href].join(':');
a.draggable = true;
//append link!
var messages = document.getElementById('messages');
filecontainer.appendChild(a);
//append to chat
systemMessage(username +"'s file " + meta.name + " is ready to save locally");
}
/* devide the base64'd file into chunks, also process meta data */
function chunkify(result, file) {
var split = result.split(',');
/* meta data */
this.meta.name = file.name;
this.meta.size = file.size;
this.meta.filetype = split[0];
/*chunkify */
var file = split[1];//base64
this.numOfChunksInFile = Math.ceil(file.length / this.CHUNK_SIZE);
this.meta.numOfChunksInFile = numOfChunksInFile;
console.log("number of chunks to send:"+this.numOfChunksInFile);
for (var i = 0; i < this.numOfChunksInFile; i++) {
var start = i * this.CHUNK_SIZE;
this.chunks[i] = file.slice(start, start + this.CHUNK_SIZE);
}
}
/* send out meta data, allow for id to be empty = broadcast */
function send_meta(id) {
if (jQuery.isEmptyObject(this.meta)) {
return;
}
console.log("sending meta data");
console.log(this.meta);
if (!id) {
dataChannelChat.broadcast(JSON.stringify({
"eventName": "data_msg",
"data": {
"file_meta": this.meta
}
}));
} else {
dataChannelChat.send(id, JSON.stringify({
"eventName": "data_msg",
"data": {
"file_meta": this.meta
}
}));
}
}
/* Please note that this works by sending a chunk, then waiting for a request for the next one */
function sendchunk(id, chunk_num) {
/* uncomment the following 6 lines and set breakpoints on them to similar an impaired connection */
/* if (chunk_num == 30) {
console.log("30 reached, breakpoint this line");
}
if (chunk_num == 50) {
console.log("30 reached");
}*/
//console.log("sending chunk " + chunk_num + " to " + id);
dataChannelChat.send(id, JSON.stringify({
"eventName": "data_msg",
"data": {
"chunk": chunk_num,
"file": this.chunks[chunk_num]
}
}));
}
|