My favourite way to copy files to my phone over WiFi used to be Portal Pushbullet. It is super easy to install and using it is just drag & drop. You start from installing an app on your phone and then go to their website and pointing camera of your phone on QR code. That process suppose to link your mobile with the website and you should be redirected to an empty page with drag & drop box in the middle. Easy! I really liked that until something happened and one of my phones refused to connect. Very frustrating I started searching for alternatives, what I've found wasn't as good as Portal Pushbullet. I've downloaded few suggestions from google top results. Everything requires downloading an app give them all possible suspicious permissions it's also required to create an account.

I needed those files on my phone really fast, it happened I had IIS open on my machine so I turned one of my local pages into file server. This is how I got the idea of how to make your own tool to transfer files to you mobile. No need to install anything on your mobile. 

This is what you need:

  • Installed IIS
  • Access to your WiFi router with port forwarding feature

First we have to create empty files and folder structure for our page

C:\page\index.html

C:\page\main.js

C:\page\files\

Next we need to create a page in IIS and use C:\Page as location then add binding with some random port number

Select that page and open Directory Browsing and enable it.

If you copy files into C:\Page\files\ and open your page localhost:8081\files your should be able to see all of them listed. If you need your files quick this is the easiest way to do it.

I didn't like the fact you need to click on each link, I had few hundreds files to copy so I needed something smarter. I had to write some javascript to style folder tree and implement bulk downloading.

Lets start from index.html I've added bootstrap just in case to style it quickly.

<!doctype html>
<html>
<head>
  <script  src="https://code.jquery.com/jquery-3.3.1.min.js"  integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="  crossorigin="anonymous"></script>
  <link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
  <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.bundle.min.js" integrity="sha384-feJI7QwhOS+hwpX2zkaeJQjeiwlhOP+SdQDqhgvvo1DsjtiSQByFdThsxO669S2D" crossorigin="anonymous"></script>
  <script src="main.js" type="text/javascript"></script>
<body id="page">
</body>
</html>


Time for main.js

var endpoint='http://localhost:8081';
var processing=false;
$.get(endpoint+'/files/', (data) =>
{
	let listing = loadTree(parseDirectoryListing(data));
	formatArray(listing);
	bindEvents();
});

For testing purposes lets keep endpoint as localhost:8081, at the end you will have to change it to your router IP, and you will need to setup port forwarding. var processing is used for security reasons which I will later explain. 

The code first loads the files folder in html so we have to put it through parseDirectoryListing which extracts href value from anchor tags. loadTree does almost the same as its parent method it loads subfolders with the small difference it is synchronous. formatArray it's just wrapping those hrefs in checkboxes and indenting subfolders. It also sorts the list of files and removes duplicates.

function loadTree(list){
	var subList=[];
	for(var i=1;i<list.length;i++){
		if(list[i].endsWith("/")){
			var subFolder = getSubFolder(list[i]);
			subList.append(subFolder);
		}
	}
	list.append(subList);
	return list;
}
function getSubFolder(path){
	var ajaxReturn=[];
    jQuery.ajax({
        url: endpoint+path,
        success: function (data) {
            ajaxReturn = loadTree(parseDirectoryListing(data));
        },
        async: false
    });
	return ajaxReturn;
}
Array.prototype.append = function(array)
{
    this.push.apply(this, array)
}
function parseDirectoryListing(text)
{
    let hrefs = text.match(/A\s+(?:[^>]*?\s+)?HREF=(["'])(.*?)\1/g).map((x) => x.replace('A HREF="', '').replace('"',''));
	return hrefs;
}

Array.prototype.append is extention for array for concatenating two arrays see line 14

function formatArray(data){
	data.sort();
	data=removeDuplicates(data);
	var indent=0;
	var lastFolder="";
	for(var i=0;i<data.length;i++){
		indent=(data[i].match(/\//g) || []).length*20;
		var item='<div class="item form-check" style="margin-left:'+indent+'px"><input class="form-check-input" type="checkbox" name="file" value="'+data[i]+'">'+decodeURI(data[i])+'</div>';
		$('body').append(item);
	}
	$('body').append('');
}
function removeDuplicates(arr) {
    var obj = {};
    var ret_arr = [];
    for (var i = 0; i < arr.length; i++) {
        obj[arr[i]] = true;
    }
    for (var key in obj) {
        ret_arr.push(key);
    }
    return ret_arr;
}

I want to bind two events, ticking chechbox which if we click on folder it will go down the tree and select all the files and subfolders. Second event will trigger dowloading.

function bindEvents(){
	$('.item').click(function(){
		var val=$(this).children(":first").val();
		var checked=$(this).children(":first").is(':checked');
		selectByName(val,checked);
	});
  $('.btn-download').click(function(){
    downloadSelected();
  });
}
function selectByName(name,toggle){
	$('.item').each(function(){
		var val=$(this).children(":first").val();
		if(val.startsWith(name)){
			$(this).children(":first").prop('checked', toggle);
		}
	});
}

Method below is asynchronous because it contains sleep method giveMeASecond. I had to implement delay otherwise downloading didn't work properly, it was only downloading the last selected file.

async function downloadSelected(){
  var checked = $('input:checked');
  for(var i=0;i<checked.length;i++){
    var val=$(checked[i]).val();
    if(!val.endsWith('/')){
      var result = await giveMeASecond(1000);
      download(val,i);
    }
  }
}
function giveMeASecond(ms) {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(ms);
    }, ms);
  });
}

To avoid downloading two files at once I put some kind of mutex to slow it down and retry later.

function download(file,idx){
  if(processing){
     download(file,idx);
  }else{
    processing=true;
    downloadElement(file);
    processing=false;
  }
  return idx;
}

function downloadElement(filename) {
  var element = document.createElement('a');
  element.setAttribute('href', filename);
  filename=decodeURI(filename);
  element.setAttribute('download', filename);
  element.style.display = 'none';
  document.body.appendChild(element);
  element.click();
  document.body.removeChild(element);
}

Almost over, now we need to setup our router for port forwarding in my example it is 8081. I also had to change endpoint to my router IP otherwise I would get Cross-Browser Exception and because I am using only javascript I couldn't easly solve that problem. Now you can go on your mobile to your WiFi IP and download your files in bulk. If some files don't want to download that is probably lacking MIME type in your IIS, I had to add mkv to mine.

See github for source code Github Project