<commit_before>﻿#if !__MonoCS__
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using PodcastUtilities.PortableDevices;

namespace Bloom.Publish.Android.usb
{
	enum DeviceNotFoundReportType
	{
		Unknown,
		NoDeviceFound,
		NoBloomDirectory,
		MoreThanOneReadyDevice
	}

	class OneReadyDeviceNotFoundEventArgs
	{
		public OneReadyDeviceNotFoundEventArgs(DeviceNotFoundReportType reportType, List<string> deviceNames)
		{
			ReportType = reportType;
			DeviceNames = deviceNames;
		}

		public DeviceNotFoundReportType ReportType { get; }

		public List<string> DeviceNames { get; }
	}

	/// <summary>
	/// Handles non-UI functions of connecting to an Android device via USB and working with the file system there
	/// </summary>
	class AndroidDeviceUsbConnection
	{
		public Action<Book.Book> OneReadyDeviceFound;
		public Action<DeviceNotFoundReportType, List<string>> OneReadyDeviceNotFound;

		private const string kBloomFolderOnDevice = "Bloom";
		private IDevice _device;
		private string _bloomFolderPath;
		private bool _stopLookingForDevice;

		/// <summary>
		/// Attempt to establish a connection with a device which has the Bloom Reader app,
		/// then send it the book.
		/// </summary>
		public void ConnectAndSendToOneDevice(Book.Book book)
		{
			_stopLookingForDevice = false;
			_device = null;

			//The UX here is to only allow one device plugged in a time.
			while (!_stopLookingForDevice && _device == null)
			{
				var devices = EnumerateAllDevices();
				if(!ConnectAndSendToOneDeviceInternal(devices, book))
				{
					Thread.Sleep(1000);
				}
			}
		}

		public void StopFindingDevice()
		{
			_stopLookingForDevice = true;
		}

		public bool BookExists(string fileName)
		{
			if (_device == null || _bloomFolderPath == null)
				throw new InvalidOperationException("Must connect before calling BookExists");

			return _device.GetObjectFromPath(Path.Combine(_bloomFolderPath, fileName)) != null;
		}

		public void SendBook(string bloomdPath)
		{
			if (bloomdPath == null)
				throw new ArgumentNullException(nameof(bloomdPath));

			if (_device == null || _bloomFolderPath == null)
				throw new InvalidOperationException("Must connect before calling SendBookAsync");

			var sourceStream = File.OpenRead(bloomdPath);
			var targetStream = _device.OpenWrite(Path.Combine(_bloomFolderPath, Path.GetFileName(bloomdPath)), new FileInfo(bloomdPath).Length, true);
			Copy(sourceStream, targetStream);
		}

		public string GetDeviceName()
		{
			if (_device == null)
				throw new InvalidOperationException("Must connect before calling GetDeviceName");

			return _device.Name;
		}

		private void Copy(Stream source, Stream destination)
		{
			const int kCopyBufferSize = 262144;

			var buffer = new byte[kCopyBufferSize];<commit_msg>BL-4755 push marker file to help BR detect changes<commit_after>﻿#if !__MonoCS__
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using PodcastUtilities.PortableDevices;

namespace Bloom.Publish.Android.usb
{
	enum DeviceNotFoundReportType
	{
		Unknown,
		NoDeviceFound,
		NoBloomDirectory,
		MoreThanOneReadyDevice
	}

	class OneReadyDeviceNotFoundEventArgs
	{
		public OneReadyDeviceNotFoundEventArgs(DeviceNotFoundReportType reportType, List<string> deviceNames)
		{
			ReportType = reportType;
			DeviceNames = deviceNames;
		}

		public DeviceNotFoundReportType ReportType { get; }

		public List<string> DeviceNames { get; }
	}

	/// <summary>
	/// Handles non-UI functions of connecting to an Android device via USB and working with the file system there
	/// </summary>
	class AndroidDeviceUsbConnection
	{
		public Action<Book.Book> OneReadyDeviceFound;
		public Action<DeviceNotFoundReportType, List<string>> OneReadyDeviceNotFound;

		private const string kBloomFolderOnDevice = "Bloom";
		private IDevice _device;
		private string _bloomFolderPath;
		private bool _stopLookingForDevice;

		/// <summary>
		/// Attempt to establish a connection with a device which has the Bloom Reader app,
		/// then send it the book.
		/// </summary>
		public void ConnectAndSendToOneDevice(Book.Book book)
		{
			_stopLookingForDevice = false;
			_device = null;

			//The UX here is to only allow one device plugged in a time.
			while (!_stopLookingForDevice && _device == null)
			{
				var devices = EnumerateAllDevices();
				if(!ConnectAndSendToOneDeviceInternal(devices, book))
				{
					Thread.Sleep(1000);
				}
			}
		}

		public void StopFindingDevice()
		{
			_stopLookingForDevice = true;
		}

		public bool BookExists(string fileName)
		{
			if (_device == null || _bloomFolderPath == null)
				throw new InvalidOperationException("Must connect before calling BookExists");

			return _device.GetObjectFromPath(Path.Combine(_bloomFolderPath, fileName)) != null;
		}

		public void SendBook(string bloomdPath)
		{
			if (bloomdPath == null)
				throw new ArgumentNullException(nameof(bloomdPath));

			if (_device == null || _bloomFolderPath == null)
				throw new InvalidOperationException("Must connect before calling SendBookAsync");

			using (var sourceStream = File.OpenRead(bloomdPath))
			using (var targetStream = _device.OpenWrite(Path.Combine(_bloomFolderPath, Path.GetFileName(bloomdPath)),
				new FileInfo(bloomdPath).Length, true))
			{
				Copy(sourceStream, targetStream);
			}

			// Also send a little marker file. BloomReader can tell "something has been updated" by just checking the modify time on this.
			var buffer = Encoding.UTF8.GetBytes("Just for change detection");
			using (var targetStream = _device.OpenWrite(Path.Combine(_bloomFolderPath, "something.modified"), buffer.Length, true))
			{
				targetStream.Write(buffer, 0, buffer.Length);
				targetStream.Flush();
			}

		}

		public string GetDeviceName()
		{
			if (_device == null)
				throw new InvalidOperationException("Must connect before calling GetDeviceName");

			return _device.Name;
		}

		private void Copy(Stream source, Stream destination)
		{
			const int kCopyBufferSize = 262144;

			var buffer = new byte[kCopyBufferSize];
{'fraction_non_alphanumeric': 0.08861378074604985, 'fraction_numerical': 0.004235217462127383, 'mean_word_length': 4.297670405522002, 'pattern_counts': {'":': 0, '<': 21, '<?xml version=': 0, '>': 21, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5106196', 'n_tokens_mistral': 2063, 'n_tokens_neox': 1765, 'n_words': 497}