/*
XOWA: the XOWA Offline Wiki Application
Copyright (C) 2012 gnosygnu@gmail.com

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
package gplx.core.ios; import gplx.*; import gplx.core.*;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.nio.*;
import java.nio.channels.*;
import java.util.Date;

import javax.print.FlavorException;
import javax.tools.JavaCompiler;
import gplx.core.criterias.*; import gplx.core.bits.*; import gplx.core.envs.*;
import gplx.core.ios.streams.*;
import gplx.core.progs.*;
public class IoEngine_system extends IoEngine_base {
	@Override public String Key() {return IoEngine_.SysKey;}
	@Override public void DeleteDirDeep(IoEngine_xrg_deleteDir args) {utl.DeleteDirDeep(this, args.Url(), args);}
	@Override public void XferDir(IoEngine_xrg_xferDir args) {Io_url trg = args.Trg(); utl.XferDir(this, args.Src(), IoEnginePool.Instance.Get_by(trg.Info().EngineKey()), trg, args);}
	@Override public void XferFil(IoEngine_xrg_xferFil args) {utl.XferFil(this, args);}
	@Override public IoItmDir QueryDirDeep(IoEngine_xrg_queryDir args) {return utl.QueryDirDeep(this, args);}
	@Override public void CopyDir(Io_url src, Io_url trg) {IoEngine_xrg_xferDir.copy_(src, trg).Recur_().Exec();}
	@Override public void MoveDirDeep(IoEngine_xrg_xferDir args) {Io_url trg = args.Trg(); utl.XferDir(this, args.Src(), IoEnginePool.Instance.Get_by(trg.Info().EngineKey()), trg, args);}
	@Override public void DeleteFil_api(IoEngine_xrg_deleteFil args) {
		Io_url url = args.Url();
		File fil = Fil_(url);	
		if (!Fil_Exists(fil)) return;
		MarkFileWritable(fil, url, args.ReadOnlyFails(), "DeleteFile");
		DeleteFil_lang(fil, url);
	}
		@Override public boolean ExistsFil_api(Io_url url) {
		File f = new File(url.Xto_api());
		return f.exists();
	}
	@Override public void SaveFilText_api(IoEngine_xrg_saveFilStr mpo) {
		Io_url url = mpo.Url();

		// encode string
		byte[] textBytes = null;
		textBytes = Bry_.new_u8(mpo.Text());

		FileChannel fc = null; FileOutputStream fos = null;
		if (!ExistsDir(url.OwnerDir())) CreateDir(url.OwnerDir());
		try {
			// open file
			try 	{fos = new FileOutputStream(url.Xto_api(), mpo.Append());}
			catch 	(FileNotFoundException e) {throw Err_Fil_NotFound(e, url);}
			fc = fos.getChannel();
		
			// write text
			try 	{fc.write(ByteBuffer.wrap(textBytes));}
			catch	(IOException e) {
				Closeable_close(fc, url, false);
				Closeable_close(fos, url, false);
				throw Err_.new_exc(e, "io", "write data to file failed", "url", url.Xto_api());
			}
			if (!Op_sys.Cur().Tid_is_drd()) {
				File fil = new File(url.Xto_api());
				IoEngine_system_xtn.SetExecutable(fil, true);
			}
		}
		finally {		
			// cleanup
			Closeable_close(fc, url, false);
			Closeable_close(fos, url, false);
		}
	}
	@Override public String LoadFilStr(IoEngine_xrg_loadFilStr args) {
		Io_url url = args.Url(); String url_str = url.Xto_api();
		boolean file_exists = ExistsFil_api(url);			// check if file exists first to avoid throwing exception; note that most callers pass Missing_ignored; DATE:2015-02-24
		if (!file_exists) {
			if (args.MissingIgnored()) 	return "";
			else 						throw Err_Fil_NotFound(url);
		}		
		// get reader for file
		InputStream stream = null;		
		try 	{stream = new FileInputStream(url_str);}
		catch 	(FileNotFoundException e) {
			if (args.MissingIgnored()) return "";
			throw Err_Fil_NotFound(e, url);
		}
		return Load_from_stream_as_str(stream, url_str);
	}
	public static String Load_from_stream_as_str(InputStream stream, String url_str) {
		InputStreamReader reader = null;
		try 	{reader = new InputStreamReader(stream, IoEngineArgs.Instance.LoadFilStr_Encoding);}
		catch 	(UnsupportedEncodingException e) {
			Closeable_close(stream, url_str, false);
			throw Err_text_unsupported_encoding(IoEngineArgs.Instance.LoadFilStr_Encoding, "", url_str, e);
		}
		
		// make other objects
	    char[] readerBuffer = new char[IoEngineArgs.Instance.LoadFilStr_BufferSize];
	    int pos = 0;
	    StringWriter sw = new StringWriter();
	    
	    // transfer data 
		while (true) {
		    try 	{pos = reader.read(readerBuffer);}
		    catch	(IOException e) {
	    		try 	{
	    			stream.close();
	    			reader.close();
	    		}
	    		catch 	(IOException e2) {}		    	
		    	throw Err_.new_exc(e, "io", "read data from file failed", "url", url_str, "pos", pos);
		    }
		    if (pos == -1) break;
		    sw.write(readerBuffer, 0, pos);
		}
		
		// cleanup
	    Closeable_close(stream, url_str, false);
	    Closeable_close(reader, url_str, false);
		return sw.toString();
	}
	public static byte[] Load_from_stream_as_bry(InputStream stream, String url_str) {
		ByteArrayOutputStream buffer = new ByteArrayOutputStream();
		byte[] data = new byte[4096];
		int read = 0;
		try {
			while ((read = stream.read(data, 0, data.length)) != -1) {
			  buffer.write(data, 0, read);
			}
			buffer.flush();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return buffer.toByteArray();
	}
	@Override public boolean ExistsDir(Io_url url) {return new File(url.Xto_api()).exists();}
	@Override public void CreateDir(Io_url url) {new File(url.Xto_api()).mkdirs();}
	@Override public void DeleteDir(Io_url url) {
		File dir = new File(url.Xto_api());
		if (!dir.exists()) return;
		boolean rv = dir.delete();
		if (!rv) throw Err_.new_(IoEngineArgs.Instance.Err_IoException, "delete dir failed", "url", url.Xto_api());
	}
	@Override public IoItmDir QueryDir(Io_url url) {
		IoItmDir rv = IoItmDir_.scan_(url);
		String url_api = url.Xto_api();
		if (	gplx.core.envs.Op_sys.Cur().Tid_is_wnt()			// op_sys is wnt 
			&& 	String_.Eq(url.OwnerDir().Raw(), String_.Empty)		// folder is drive; EX: "C:"
			)
			url_api = url_api + "\\";								// add "\\"; else listFiles will return working folder's files, not C:; DATE:2016-04-07
		File dirInfo = new File(url_api);
		if (!dirInfo.exists()) {
			rv.Exists_set(false);
			return rv;
		}
		IoUrlInfo urlInfo = url.Info();
		File[] subItmAry = dirInfo.listFiles();
		if (subItmAry == null) return rv;	// directory has no files
		for (int i = 0; i < subItmAry.length; i++) {
			File subItm = subItmAry[i];
			if (subItm.isFile()) {
				IoItmFil subFil = QueryMkr_fil(urlInfo, subItm);
				rv.SubFils().Add(subFil);
			}
			else {
				IoItmDir subDir = QueryMkr_dir(urlInfo, subItm);				
				rv.SubDirs().Add(subDir);
			}
		}		
		return rv;
	}
	IoItmFil QueryMkr_fil(IoUrlInfo urlInfo, File apiFil) {
		Io_url filUrl = Io_url_.new_inf_(apiFil.getPath(), urlInfo);	// NOTE: may throw PathTooLongException when url is > 248 (exception messages states 260)
		long fil_len = apiFil.exists() ? apiFil.length() : IoItmFil.Size_invalid;	// NOTE: if file doesn't exist, set len to -1; needed for "boolean Exists() {return size != Size_Invalid;}"; DATE:2014-06-21
		IoItmFil rv = IoItmFil_.new_(filUrl, fil_len, DateAdp_.MinValue, DateAdp_.unixtime_lcl_ms_(apiFil.lastModified()));
		rv.ReadOnly_(!apiFil.canWrite());
		return rv;
	}
	IoItmDir QueryMkr_dir(IoUrlInfo urlInfo, File apiDir) {
		Io_url dirUrl = Io_url_.new_inf_(apiDir.getPath() + urlInfo.DirSpr(), urlInfo);	// NOTE: may throw PathTooLongException when url is > 248 (exception messages states 260)
		return IoItmDir_.scan_(dirUrl);
	}
	@Override public IoItmFil QueryFil(Io_url url) {
		File fil = new File(url.Xto_api());
		return QueryMkr_fil(url.Info(), fil);
	}
	@Override public void UpdateFilAttrib(Io_url url, IoItmAttrib atr) {
		File f = new File(url.Xto_api());
		boolean rv = true;
		if (atr.ReadOnly() != Fil_ReadOnly(f)) {
			if (atr.ReadOnly())
				rv = f.setReadOnly();
			else {
				if (!Op_sys.Cur().Tid_is_drd())
					IoEngine_system_xtn.SetWritable(f, true);
			}
			if (!rv) throw Err_.new_(IoEngineArgs.Instance.Err_IoException, "set file attribute failed", "attribute", "readOnly", "cur", Fil_ReadOnly(f), "new", atr.ReadOnly(), "url", url.Xto_api());
		}
		if (atr.Hidden() != f.isHidden()) {
			//Runtime.getRuntime().exec("attrib +H myHiddenFile.java");			
		}
	}
	@Override public void UpdateFilModifiedTime(Io_url url, DateAdp modified) {
		File f = new File(url.Xto_api());
		long timeInt = modified.UnderDateTime().getTimeInMillis();
//		if (timeInt < 0) {
//			UsrDlg_._.Notify("{0} {1}", url.Xto_api(), timeInt);
//			return;
//		}
		if (!f.setLastModified(timeInt)) {
			if (Fil_ReadOnly(f)) {
				boolean success = false;
				try {
					UpdateFilAttrib(url, IoItmAttrib.normal_());
					success = f.setLastModified(timeInt);
				}
				finally {
					UpdateFilAttrib(url, IoItmAttrib.readOnly_());
				}
				if (!success) throw Err_.new_wo_type("could not update file modified time", "url", url.Xto_api(), "modifiedTime", modified.XtoStr_gplx_long());
			}
		}
	}
	@Override public IoStream OpenStreamRead(Io_url url) {return IoStream_base.new_(url, IoStream_.Mode_rdr);}
	@Override public IoStream OpenStreamWrite(IoEngine_xrg_openWrite args) {
		Io_url url = args.Url();
		if (!ExistsFil_api(url)) SaveFilText_api(IoEngine_xrg_saveFilStr.new_(url, ""));
		return IoStream_base.new_(url, args.Mode());
	}
	@Override public void CopyFil(IoEngine_xrg_xferFil args) {
		// TODO:JAVA6 hidden property ignored; 1.6 does not allow OS-independent way of setting isHidden (wnt only possible through jni)
		boolean overwrite = args.Overwrite();
		Io_url srcUrl = args.Src(), trgUrl = args.Trg();
		File srcFil = new File(srcUrl.Xto_api()), trgFil = new File(trgUrl.Xto_api());
		if (trgFil.isFile()) {		// trgFil exists; check if overwrite set and trgFil is writable
			Chk_TrgFil_Overwrite(overwrite, trgUrl);
			MarkFileWritable(trgFil, trgUrl, args.ReadOnlyFails(), "copy");
		}		
		else {						// trgFil doesn't exist; must create file first else fileNotFound exception thrown
			boolean rv = true;
			if (!ExistsDir(trgUrl.OwnerDir())) CreateDir(trgUrl.OwnerDir());
			try 	{
				trgFil.createNewFile();
				if (!Op_sys.Cur().Tid_is_drd())
					IoEngine_system_xtn.SetExecutable(trgFil, true);
			}
			catch 	(IOException e) {
				rv = false;
			}
			if (!rv)
				throw Err_.new_wo_type("create file failed", "trg", trgUrl.Xto_api());
		}
		FileInputStream srcStream = null; FileOutputStream trgStream = null;
		FileChannel srcChannel = null, trgChannel = null;
		try {
			// make objects
			try 	{srcStream = new FileInputStream(srcFil);}
			catch 	(FileNotFoundException e) {throw IoErr.FileNotFound("copy", srcUrl);}
			try 	{trgStream = new FileOutputStream(trgFil);}
			catch 	(FileNotFoundException e) {
				trgStream = TryToUnHideFile(trgFil, trgUrl);
				if (trgStream == null)
					throw IoErr.FileNotFound("copy", trgUrl);
//				else
//					wasHidden = true;
			}		
			srcChannel = srcStream.getChannel();		
			trgChannel = trgStream.getChannel();
			
			// transfer data
			long pos = 0, count = 0, read = 0;
			try 	{count = srcChannel.size();}
			catch 	(IOException e) {throw Err_.new_exc(e, "io", "size failed", "src", srcUrl.Xto_api());}
			int totalBufferSize = IoEngineArgs.Instance.LoadFilStr_BufferSize;
			long transferSize = (count > totalBufferSize) ? totalBufferSize : count;	// transfer as much as fileSize, but limit to LoadFilStr_BufferSize 
			while 	(pos < count) {
				try 	{read = trgChannel.transferFrom(srcChannel, pos, transferSize);}
				catch 	(IOException e) {
					Closeable_close(srcChannel, srcUrl, false);
					Closeable_close(trgChannel, trgUrl, false);
					Closeable_close(srcStream, srcUrl, false);
					Closeable_close(trgStream, srcUrl, false);
					throw Err_.new_exc(e, "io", "transfer data failed", "src", srcUrl.Xto_api(), "trg", trgUrl.Xto_api());
				}
			    if (read == -1) break;
			    pos += read;
			}
//			if (wasHidden)
//				
		}
		finally {
			// cleanup
			Closeable_close(srcChannel, srcUrl, false);
			Closeable_close(trgChannel, trgUrl, false);
			Closeable_close(srcStream, srcUrl, false);
			Closeable_close(trgStream, srcUrl, false);
		}
		UpdateFilModifiedTime(trgUrl, QueryFil(srcUrl).ModifiedTime());	// must happen after file is closed
	}
	public void CopyFil(gplx.core.progs.Gfo_prog_ui prog_ui, Io_url src_url, Io_url trg_url, boolean overwrite, boolean readonly_fails) {
		// TODO:JAVA6 hidden property ignored; 1.6 does not allow OS-independent way of setting isHidden (wnt only possible through jni)
		File src_fil = new File(src_url.Xto_api()), trg_fil = new File(trg_url.Xto_api());
		if (trg_fil.isFile()) {		// trg_fil exists; check if overwrite set and trg_fil is writable
			Chk_TrgFil_Overwrite(overwrite, trg_url);
			MarkFileWritable(trg_fil, trg_url, readonly_fails, "copy");
		}		
		else {						// trg_fil doesn't exist; must create file first else fileNotFound exception thrown
			boolean rv = true;
			if (!ExistsDir(trg_url.OwnerDir())) CreateDir(trg_url.OwnerDir());
			try 	{
				trg_fil.createNewFile();
				if (!Op_sys.Cur().Tid_is_drd())
					IoEngine_system_xtn.SetExecutable(trg_fil, true);
			}
			catch 	(IOException e) {
				rv = false;
			}
			if (!rv)
				throw Err_.new_wo_type("create file failed", "trg", trg_url.Xto_api());
		}
		FileInputStream src_stream = null; FileOutputStream trg_stream = null;
		FileChannel src_channel = null, trg_channel = null;
		try {
			// make objects
			try 	{src_stream = new FileInputStream(src_fil);}
			catch 	(FileNotFoundException e) {throw IoErr.FileNotFound("copy", src_url);}
			try 	{trg_stream = new FileOutputStream(trg_fil);}
			catch 	(FileNotFoundException e) {
				trg_stream = TryToUnHideFile(trg_fil, trg_url);
				if (trg_stream == null)
					throw IoErr.FileNotFound("copy", trg_url);
//				else
//					wasHidden = true;
			}		
			src_channel = src_stream.getChannel();		
			trg_channel = trg_stream.getChannel();
			
			// transfer data
			long pos = 0, count = 0, read = 0;
			try 	{count = src_channel.size();}
			catch 	(IOException e) {throw Err_.new_exc(e, "io", "size failed", "src", src_url.Xto_api());}
			int buffer_size = IoEngineArgs.Instance.LoadFilStr_BufferSize;
			long transfer_size = (count > buffer_size) ? buffer_size : count;	// transfer as much as fileSize, but limit to LoadFilStr_BufferSize 
			while 	(pos < count) {
				try 	{read = trg_channel.transferFrom(src_channel, pos, transfer_size);}
				catch 	(IOException e) {
					Closeable_close(src_channel, src_url, false);
					Closeable_close(trg_channel, trg_url, false);
					Closeable_close(src_stream, src_url, false);
					Closeable_close(trg_stream, src_url, false);
					throw Err_.new_exc(e, "io", "transfer data failed", "src", src_url.Xto_api(), "trg", trg_url.Xto_api());
				}
			    if (read == -1) break;
				if (prog_ui.Prog_notify_and_chk_if_suspended(pos, count)) return;
			    pos += read;
			}
//			if (wasHidden)
//				
		}
		finally {
			// cleanup
			Closeable_close(src_channel, src_url, false);
			Closeable_close(trg_channel, trg_url, false);
			Closeable_close(src_stream, src_url, false);
			Closeable_close(trg_stream, src_url, false);
		}
		UpdateFilModifiedTime(trg_url, QueryFil(src_url).ModifiedTime());	// must happen after file is closed
	}
	FileOutputStream TryToUnHideFile(File trgFil, Io_url trgUrl) {
		FileOutputStream trgStream = null;
		if (trgFil.exists()) {	// WORKAROUND: java fails when writing to hidden files; unmark hidden and try again
			Process p = null;
			try {
				String d = "attrib -H \"" + trgUrl.Xto_api() + "\"";
				p = Runtime.getRuntime().exec(d);
			} catch (IOException e1) {
				e1.printStackTrace();
			}					
			try {
				p.waitFor();
			} catch (InterruptedException e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			}
			try 	{trgStream = new FileOutputStream(trgFil);}
			catch 	(FileNotFoundException e) {
				return null;
			}
		}		
		return trgStream;
	} 
	@Override public void MoveFil(IoEngine_xrg_xferFil args) {
		Io_url srcUrl = args.Src(), trgUrl = args.Trg();
		String src_api = srcUrl.Xto_api(), trg_api = trgUrl.Xto_api();
		if (String_.Eq(src_api, trg_api)) return; 	// ignore command if src and trg is same; EX: C:\a.txt -> C:\a.txt should be noop
		File srcFil = new File(src_api), trgFil = new File(trg_api);
		
		// if drive is same, then rename file
		if (String_.Eq(srcUrl.OwnerRoot().Raw(), trgUrl.OwnerRoot().Raw())) {
			boolean overwrite = args.Overwrite();
			if (!srcFil.exists() && args.MissingFails()) throw IoErr.FileNotFound("move", srcUrl);
			if (trgFil.exists()) {
				Chk_TrgFil_Overwrite(overwrite, trgUrl);
				MarkFileWritable(trgFil, trgUrl, args.ReadOnlyFails(), "move");
				DeleteFil_lang(trgFil, args.Trg());	// overwrite is specified and file is writable -> delete
			}
			if (!ExistsDir(trgUrl.OwnerDir())) CreateDir(trgUrl.OwnerDir());
			srcFil.renameTo(trgFil);
		}
		// else copy fil and delete
		else {
			if (!srcFil.exists() && !args.MissingFails()) return;
			CopyFil(args);
			DeleteFil_lang(srcFil, srcUrl);
		}
	}
	void Chk_TrgFil_Overwrite(boolean overwrite, Io_url trg) {
		if (!overwrite)
			throw Err_.new_invalid_op("trgFile exists but overwriteFlag not set").Args_add("trg", trg.Xto_api());		
	}
	@Override public void MoveDir(Io_url src, Io_url trg) {
		String srcStr = src.Xto_api(), trgStr = trg.Xto_api();
		File srcFil = new File(srcStr), trgFil = new File(trgStr);
		if (trgFil.exists()) 	{throw Err_.new_invalid_op("cannot move dir if trg exists").Args_add("src", src, "trg", trg);}
		if (String_.Eq(src.OwnerRoot().Raw(), trg.OwnerRoot().Raw())) {			
			srcFil.renameTo(trgFil);
		}
		else {
			XferDir(IoEngine_xrg_xferDir.copy_(src, trg));
		}
	}
	public static void Closeable_close(Closeable closeable, Io_url url, boolean throwErr) {Closeable_close(closeable, url.Xto_api(), throwErr);}
	public static void Closeable_close(Closeable closeable, String url_str, boolean throwErr) {
		if 		(closeable == null) return;
		try 	{closeable.close();}
		catch 	(IOException e) {
			if (throwErr)
				throw Err_.new_exc(e, "io", "close object failed", "class", Type_adp_.NameOf_obj(closeable), "url", url_str);
//			else
//				UsrDlg_._.Finally("failed to close FileChannel", "url", url, "apiErr", Err_.Message_err_arg(e));
		}		
	}

	File Fil_(Io_url url) {return new File(url.Xto_api());}
	boolean Fil_Exists(File fil) {return fil.exists();}
	boolean Fil_ReadOnly(File fil) {return !fil.canWrite();}
	boolean Fil_Delete(File fil) {return fil.delete();}
	void Fil_Writable(File fil) {
		if (!Op_sys.Cur().Tid_is_drd())
			IoEngine_system_xtn.SetWritable(fil, true);
	}
	private static Err Err_text_unsupported_encoding(String encodingName, String text, String url_str, Exception e) {
		return Err_.new_exc(e, "io", "text is in unsupported encoding").Args_add("encodingName", encodingName, "text", text, "url", url_str);
	}
	boolean user_agent_needs_resetting = true;
	@Override public Io_stream_rdr DownloadFil_as_rdr(IoEngine_xrg_downloadFil xrg) {
		Io_stream_rdr_http rdr = new Io_stream_rdr_http(xrg);
		rdr.Open();
		return rdr;
	}
	@Override public boolean DownloadFil(IoEngine_xrg_downloadFil xrg) {
		IoStream trg_stream = null;
		java.io.BufferedInputStream src_stream = null;
		java.net.URL src_url = null;
		HttpURLConnection src_conn = null;
		if (user_agent_needs_resetting) {user_agent_needs_resetting = false; System.setProperty("http.agent", "");}
		boolean exists = Io_mgr.Instance.ExistsDir(xrg.Trg().OwnerDir());
		Gfo_usr_dlg prog_dlg = null;
		String src_str = xrg.Src();
		Io_download_fmt xfer_fmt = xrg.Download_fmt();
		prog_dlg = xfer_fmt.Usr_dlg();
		if (!Web_access_enabled) {
			if (prog_dlg != null) {
				if (session_fil == null) session_fil = prog_dlg.Log_wkr().Session_dir().GenSubFil("internet.txt");
				prog_dlg.Log_wkr().Log_msg_to_url_fmt(session_fil, "download disabled: src='~{0}' trg='~{1}'", xrg.Src(), xrg.Trg().Raw());				
			}
			return false;
		}
		try {
			trg_stream = Io_mgr.Instance.OpenStreamWrite(xrg.Trg());
			src_url = new java.net.URL(src_str);
			src_conn = (HttpURLConnection)src_url.openConnection();
//			src_conn.setReadTimeout(5000);	// do not set; if file does not exist, will wait 5 seconds before timing out; want to fail immediately
			String user_agent = xrg.User_agent(); if (user_agent != null) src_conn.setRequestProperty("User-Agent", user_agent);
			long content_length = Long_.parse_or(src_conn.getHeaderField("Content-Length"), IoItmFil.Size_invalid_int);
			xrg.Src_content_length_(content_length);
			if (xrg.Src_last_modified_query())	// NOTE: only files will have last modified (api calls will not); if no last_modified, then src_conn will throw get nullRef; avoid nullRef 
				xrg.Src_last_modified_(DateAdp_.unixtime_lcl_ms_(src_conn.getLastModified()));
			if (xrg.Exec_meta_only()) return true;
	        src_stream = new java.io.BufferedInputStream(src_conn.getInputStream());
	        if (!exists) {
	        	Io_mgr.Instance.CreateDir(xrg.Trg().OwnerDir());	// dir must exist for OpenStreamWrite; create dir at last possible moment in case stream does not exist.
	        }
    		byte[] download_bfr = new byte[Download_bfr_len];	// NOTE: download_bfr was originally member variable; DATE:2013-05-03
    		xfer_fmt.Bgn(content_length);
    		int count = 0;
    		while ((count = src_stream.read(download_bfr, 0, Download_bfr_len)) != -1) {
    			if (xrg.Prog_cancel()) {
    				src_stream.close();
    				trg_stream.Rls();
    				Io_mgr.Instance.DeleteFil(xrg.Trg());
    			}
    			xfer_fmt.Prog(count);
    			trg_stream.Write(download_bfr, 0, count);
    		}
    		if (prog_dlg != null) {
    			xfer_fmt.Term();
    			if (session_fil == null) session_fil = prog_dlg.Log_wkr().Session_dir().GenSubFil("internet.txt");
    			prog_dlg.Log_wkr().Log_msg_to_url_fmt(session_fil, "download pass: src='~{0}' trg='~{1}'", src_str, xrg.Trg().Raw());
    		}
    		return true;
		}
		catch (Exception exc) {
			xrg.Rslt_err_(exc);
			if 		(Type_adp_.Eq_typeSafe(exc, java.net.UnknownHostException.class)) 	xrg.Rslt_(IoEngine_xrg_downloadFil.Rslt_fail_host_not_found);
			else if (Type_adp_.Eq_typeSafe(exc, java.io.FileNotFoundException.class))	xrg.Rslt_(IoEngine_xrg_downloadFil.Rslt_fail_file_not_found);
			else																		xrg.Rslt_(IoEngine_xrg_downloadFil.Rslt_fail_unknown);
			if (prog_dlg != null && !xrg.Prog_cancel()) {
    			if (session_fil == null) session_fil = prog_dlg.Log_wkr().Session_dir().GenSubFil("internet.txt");
    			prog_dlg.Log_wkr().Log_msg_to_url_fmt(session_fil, "download fail: src='~{0}' trg='~{1}' error='~{2}'", src_str, xrg.Trg().Raw(), Err_.Message_lang(exc));
			}
			if (trg_stream != null) {
				try {
					trg_stream.Rls();
					DeleteFil_api(IoEngine_xrg_deleteFil.new_(xrg.Trg()));
				}
				catch (Exception e2) {Err_.Noop(e2);}
			}
			return false;
		}
    	finally {
    		xrg.Prog_running_(false);
    		try {
    			if (src_stream != null) src_stream.close();
    			if (src_conn != null) {
    				src_conn.disconnect();
    				src_conn.getInputStream().close();
    			}
    		} 	catch (Exception exc) {
    			Err_.Noop(exc);
    		}
    		if (trg_stream != null) trg_stream.Rls();
    	}
	}	Io_url session_fil; Bry_bfr prog_fmt_bfr;
	@Override public boolean Truncate_fil(Io_url url, long size) {
		FileOutputStream stream = null;
		FileChannel channel = null;
        try     {stream = new FileOutputStream(url.Xto_api(), true);}
        catch   (FileNotFoundException e) {throw Err_.new_("io", "truncate: open failed", "url", url.Xto_api(), "err", Err_.Message_gplx_log(e));}
        channel = stream.getChannel();
	    try 	{channel.truncate(size); return true;}
	    catch 	(IOException e) {return false;}
	    finally {
	    	try {
		    	if (stream != null) stream.close();
		    	if (channel != null) channel.close();
	    	} catch 	(IOException e) {return false;}
	    }
	}
		byte[] download_bfr; static final int Download_bfr_len = Io_mgr.Len_kb * 128;
	public static Err Err_Fil_NotFound(Io_url url) {
		return Err_.new_(IoEngineArgs.Instance.Err_FileNotFound, "file not found", "url", url.Xto_api()).Trace_ignore_add_1_();
	}
	public static Err Err_Fil_NotFound(Exception e, Io_url url) {
		return Err_.new_exc(e, "io", "file not found", "url", url.Xto_api()).Trace_ignore_add_1_();
	}
	void MarkFileWritable(File fil, Io_url url, boolean readOnlyFails, String op) {	
		if (Fil_ReadOnly(fil)) {
			if (readOnlyFails)	// NOTE: java will always allow final    files to be deleted; programmer api is responsible for check
				throw Err_.new_(IoEngineArgs.Instance.Err_ReadonlyFileNotWritable, "writable operation attempted on readOnly file", "op", op, "url", url.Xto_api());
			else
				Fil_Writable(fil);
		}
	}
	void DeleteFil_lang(File fil, Io_url url) {	
		boolean rv = Fil_Delete(fil);
		if (!rv)
			throw Err_.new_(IoEngineArgs.Instance.Err_IoException, "file not deleted", "url", url.Xto_api());
	}
	IoEngineUtl utl = IoEngineUtl.new_();
	public static IoEngine_system new_() {return new IoEngine_system();} IoEngine_system() {}
	static final String GRP_KEY = "Io_engine";
	public static boolean Web_access_enabled = true;
}
class IoEngineArgs {
	public int		LoadFilStr_BufferSize = 4096 * 256;
	public String	LoadFilStr_Encoding = "UTF-8";
	public String	Err_ReadonlyFileNotWritable = "gplx.core.ios.ReadonlyFileNotWritable";
	public String	Err_FileNotFound 			= "gplx.core.ios.FileNotFound";
	public String	Err_IoException				= "gplx.core.ios.IoException";
	public static final    IoEngineArgs Instance = new IoEngineArgs();
}
class IoEngine_system_xtn {
	// PATCH.DROID:VerifyError if file.setExecutable is referenced directly in IoEngine_system. However, if placed in separate class
	public static void SetExecutable(java.io.File file, boolean v) 	{file.setExecutable(v);}
	public static void SetWritable(java.io.File file, boolean v) 	{file.setWritable(v);}
}
class Io_download_http {
	public static boolean User_agent_reset_needed = true;
	public static void User_agent_reset() {
		User_agent_reset_needed = false;
		System.setProperty("http.agent", "");	// need to set http.agent to '' in order for "User-agent" to take effect
	}
	public static void Save_to_fsys(IoEngine_xrg_downloadFil xrg) {
		Io_stream_rdr_http rdr = new Io_stream_rdr_http(xrg); 
		IoStream trg_stream = null;
		try {			
			boolean exists = Io_mgr.Instance.ExistsDir(xrg.Trg().OwnerDir());
		    if (!exists)
		    	Io_mgr.Instance.CreateDir(xrg.Trg().OwnerDir());	// dir must exist for OpenStreamWrite; create dir at last possible moment in case stream does not exist.
		    trg_stream = Io_mgr.Instance.OpenStreamWrite(xrg.Trg());
		    byte[] bfr = new byte[Download_bfr_len];
		    rdr.Open();
		    while (rdr.Read(bfr, 0, Download_bfr_len) != Read_done) {		    	
		    }
		}
		finally {
			rdr.Rls();
			if (trg_stream != null) trg_stream.Rls();
		}
		if (xrg.Rslt() != IoEngine_xrg_downloadFil.Rslt_pass)
			Io_mgr.Instance.DeleteFil_args(xrg.Trg()).MissingFails_off().Exec();
	}
	public static final int Read_done = -1;
	public static final int Download_bfr_len = Io_mgr.Len_kb * 128;
}
class Io_stream_rdr_http implements Io_stream_rdr {
	public Io_stream_rdr_http(IoEngine_xrg_downloadFil xrg) {
		this.xrg = xrg;
	}	private IoEngine_xrg_downloadFil xrg;
	public byte Tid() {return Io_stream_tid_.Tid__raw;}
	public boolean Exists() {return exists;} private boolean exists = false;
	public Io_url Url() {return url;} public Io_stream_rdr Url_(Io_url v) {url = v; return this;} private Io_url url;
	public long Len() {return len;} public Io_stream_rdr Len_(long v) {len = v; return this;} private long len = IoItmFil.Size_invalid;	// NOTE: must default size to -1; DATE:2014-06-21	
	private String src_str; private HttpURLConnection src_conn; private java.io.BufferedInputStream src_stream;
	private Io_download_fmt xfer_fmt; private Gfo_usr_dlg prog_dlg;
	private boolean read_done = true, read_failed = false;
	public Io_stream_rdr Open() {
		if (Io_download_http.User_agent_reset_needed) Io_download_http.User_agent_reset();
		if (!IoEngine_system.Web_access_enabled) {
			read_done = read_failed = true;
			if (prog_dlg != null)
				prog_dlg.Log_wkr().Log_msg_to_url_fmt(session_fil, "download disabled: src='~{0}' trg='~{1}'", xrg.Src(), xrg.Trg().Raw());
			return this;
		}
		src_str = xrg.Src();
		xfer_fmt = xrg.Download_fmt(); prog_dlg = xfer_fmt.Usr_dlg();
		try {
			src_conn = (HttpURLConnection)new java.net.URL(src_str).openConnection();
			String user_agent = xrg.User_agent();
			if (user_agent != null) src_conn.setRequestProperty("User-Agent", user_agent);	// NOTE: must be set right after openConnection
//			src_conn.setReadTimeout(5000);	// do not set; if file does not exist, will wait 5 seconds before timing out; want to fail immediately
			long content_length = Long_.parse_or(src_conn.getHeaderField("Content-Length"), IoItmFil.Size_invalid_int);
			xrg.Src_content_length_(content_length);
			this.len = content_length;
			if (xrg.Src_last_modified_query())	// NOTE: only files will have last modified (api calls will not); if no last_modified, then src_conn will throw get nullRef; avoid nullRef
				xrg.Src_last_modified_(DateAdp_.unixtime_lcl_ms_(src_conn.getLastModified()));
			if (xrg.Exec_meta_only()) {
				read_done = true;
				return this;
			}
	        read_done = false;
			this.exists = Int_.In(src_conn.getResponseCode(), 200, 301);	// ASSUME: response code of 200 (OK) or 301 (Redirect) means that file exists; note that content_length seems to always be -1; DATE:2015-05-20
	        src_stream = new java.io.BufferedInputStream(src_conn.getInputStream());
    		xfer_fmt.Bgn(content_length);
		}
		catch (Exception e) {Err_handle(e);}
		return this;
	}
	public void Open_mem(byte[] v) {}
	public Object Under() {return src_stream;}
	public int Read(byte[] bry, int bgn, int len) {
		if (read_done) return Io_download_http.Read_done;
		if (xrg.Prog_cancel()) {read_failed = true; return Io_download_http.Read_done;}
		try {
			int read = src_stream.read(bry, bgn, len); 
			xfer_fmt.Prog(read);
			return read;
		}
		catch (Exception e) {
			Err_handle(e);
			return Io_download_http.Read_done;
		}
	}
	private Io_url session_fil = null;
	private boolean rls_done = false;
	public long Skip(long len) {return 0;}
	public void Rls() {
		if (rls_done) return;
		try {
			read_done = true;
			if (prog_dlg != null) {
				xfer_fmt.Term();
			}
			if (session_fil == null && prog_dlg != null) session_fil = prog_dlg.Log_wkr().Session_dir().GenSubFil("internet.txt");
			if (read_failed) {
			}
			else {
				if (prog_dlg != null)
					prog_dlg.Log_wkr().Log_msg_to_url_fmt(session_fil, "download pass: src='~{0}' trg='~{1}'", src_str, xrg.Trg().Raw());
				xrg.Rslt_(IoEngine_xrg_downloadFil.Rslt_pass);
			}
			xrg.Prog_running_(false);
		}
		catch (Exception e) {Err_.Noop(e);}	// ignore close errors; also Err_handle calls Rls() so it would be circular
		finally {
			try {if (src_stream != null) src_stream.close();} 
			catch (Exception e) {Err_.Noop(e);}	// ignore failures when cleaning up
			if (src_conn != null) src_conn.disconnect();
			src_stream = null;
			src_conn = null;
			rls_done = true;
		}
	}
	private void Err_handle(Exception exc) {
		read_done = read_failed = true;
		len = -1;
		xrg.Rslt_err_(exc);
		if 		(Type_adp_.Eq_typeSafe(exc, java.net.UnknownHostException.class)) 	xrg.Rslt_(IoEngine_xrg_downloadFil.Rslt_fail_host_not_found);
		else if (Type_adp_.Eq_typeSafe(exc, java.io.FileNotFoundException.class))	xrg.Rslt_(IoEngine_xrg_downloadFil.Rslt_fail_file_not_found);
		else																		xrg.Rslt_(IoEngine_xrg_downloadFil.Rslt_fail_unknown);
		if (prog_dlg != null && !xrg.Prog_cancel()) {
			if (session_fil == null) session_fil = prog_dlg.Log_wkr().Session_dir().GenSubFil("internet.txt");
			prog_dlg.Log_wkr().Log_msg_to_url_fmt(session_fil, "download fail: src='~{0}' trg='~{1}' error='~{2}'", src_str, xrg.Trg().Raw(), Err_.Message_lang(exc));
		}
		this.Rls();
	}
}
