需求描述
1.在window环境下
2.使用qt
3.从mtp设备中获取文件。
很显然这么奇葩的需求,肯定不是我自己想做的,没错的,你猜对了。是一个项目的需求。首先分析这个需求,我认为最难的一点就是基于window来做。(当然那些专业搞c#的人,整天研究大微软的人例外)。这个东西放在linux下简直太easy了。正因为在window下来做,所以难点就来了:首先你需要能获取到mtp设备下的文件,你要明白mtp设备是没有盘符的,这就意味着你指望qt中的方法调用肯定是没法实现了,所以这就成为了第一个难点,第二个难点:就是你得集成进qt里边去。当然这个第二个难点取决于你是怎么来解决第一个难点的。下面我就来说说我是如何一步一步干掉这些个问题的。
1.第一个坑:从mtp设备中获取文件
1.1 第一个大坑中的第一个小坑:采取何种方式获取mtp设备中的文件。
首先,必须得着手解决掉这个问题,于是我开始我的万里长征第一步,开始漫无目的的查资料。其实在网上翻过一遍之后,方法无非就是俩种:一种就是微软的那一套,直接去调用微软的API,各种C#,不过我一想到VISUAL STUDIO安装包就是几个G,而且作为一个平时只使用Ubuntu的码农来说,这种方法一开始在我的脑海中就被毙掉了。第二种就是基于libusb实现的libmtp。说实话,我一开始的时候计划按照这种方法来做的,不过资料全是基于linux的,我再看这个资料的过程中,无意中发现了一个从来没有听说过的东西Powershell.
一看到这个神奇的东西中带了shell,我就心里莫名的开心,因为我知道它和linux中的shell肯定多多少少会有联系。只要有联系,让我去搞这个东西,我就有把握。果断决定采取powershell这种方法。
powershell这种东西就是微软搞得一套自己的shell.(个人感觉是因为微软自己感觉Linux的shell太好用了,所以眼红了,就模仿人家搞了一套,不过我想说微软的这个powershell简直就是辣鸡!!!)。而且有一点很重要win7之后的windows都内置了powershell。看到这一点我就决定了要用这一套,因为它在windows上起码不用做多余的配置。
1.2 第一个大坑中的第二个小坑:利用Powershell去复制mtp设备中的文件。
写到这儿,(我必须吐槽一下:windows的powershell.既然你要模仿,你就给我好好地模仿…)作为一个习惯穿梭在linux下shell脚本的码农来说,powershell的语法我用晦涩难懂,简直就是辣鸡,来形容一点儿也不为过。接下来进入正题吧,下面是我调试通的从mtp设备中拷贝文件的shell脚本代码。
function Get-ShellProxy
{
if( -not $global:ShellProxy) {
$global:ShellProxy = new-object -com Shell.Application
}
$global:ShellProxy
}
function Get-ShellItem
{
param($Path=17)
$shell=Get-ShellProxy
trap [System.Management.Automation.MethodInvocationException]
{
$Path = $Path.ToString()
$dir = $Path.Substring( 0 ,$Path.LastIndexOf('\') )
return $shell.NameSpace($dir).items() |
where { (-not $_.IsFolder ) -and $_.path -eq $Path} |
select -First 1
continue
}
$shell.NameSpace($Path).self
}
function Get-ChildShellItem
{
param(
$Path=17,
[switch]$Recurse,
$Filter=$null)
Filter myFilter
{
if($Filter){
$_ | where { $_.Name -match $Filter }
}
else{
$_
}
}
$shellItem = Get-ShellItem $Path
$shellItem | myFilter
if( $shellItem.IsFolder ){
if($Recurse) {
$shellItem | myFilter
$stack=New-Object System.Collections.Stack
$stack.Push($shellItem)
while($stack.Count -gt 0)
{
$top = $stack.Pop()
$top | myFilter
$top.GetFolder.items() | foreach {
if( $_.IsFolder )
{ $stack.Push($_) }
else { $_ | myFilter }
}
}
}
else{
$shellItem.GetFolder.items() | myFilter }
}
}
function Copy-ShellItem
{
param($Path,$Destination)
$shell=Get-ShellProxy
$shell.NameSpace($Destination).Copyhere($Path,16)
}
$des = '你需要拷贝到PC中的位置'
$phone = Get-ChildShellItem | where { $_.name -eq '你的设备名称' }
Get-ChildShellItem -Path "$($phone.Path)\sdcard" -Filter '(.json)|(.js)$' | foreach {
echo $_
Copy-ShellItem -Path $_ -Destination $des
}
第二个坑:集成进qt中
当我成功的跳过第一个坑之后,在powershell中执行完脚本之后,看到文件成功复制到了我pc上的文件夹之后,别提我有多高兴了。根据我对linux调用脚本的easy程度,我以为在windos进行集成也很容易。结果我又一次的错了。第二个坑丝毫不亚于第一个坑。我只能硬着头皮开始了跨越第二坑的过程。
2.1第二个坑中的第一个小坑:采用何种方法进行集成
我开始了各种尝试在qt中去运行powershell脚本,不料我认为我都能用的方法,结果调试都没通了。最后的最后,想了一个大招,因为知道,exe可执行文件是可以在qt中被跑起来的,所以我就想到把脚本编成可执行文件的形式(其实这一步走弯路了,不过实在是没有办法)。下面是我调试可用的powershell脚本是用来生成可执行文件的。
function Convert-PS1ToExe
{
param(
[Parameter(Mandatory=$true)]
[ValidateScript({$true})]
[ValidateNotNullOrEmpty()]
[IO.FileInfo]$ScriptFile
)
if( -not $ScriptFile.Exists)
{
Write-Warning "$ScriptFile not exits."
return
}
[string]$csharpCode = @'
using System;
using System.IO;
using System.Reflection;
using System.Diagnostics;
namespace LoadXmlTestConsole
{
public class ConsoleWriter
{
private static void Proc_OutputDataReceived(object sender, System.Diagnostics.DataReceivedEventArgs e)
{
Process pro = sender as Process;
Console.WriteLine(e.Data);
}
static void Main(string[] args)
{
// Set title of console
Console.Title = "Powered by PSTips.Net";
// read script from resource
Assembly ase = Assembly.GetExecutingAssembly();
string scriptName = ase.GetManifestResourceNames()[0];
string scriptContent = string.Empty;
using (Stream stream = ase.GetManifestResourceStream(scriptName))
using (StreamReader reader = new StreamReader(stream))
{
scriptContent = reader.ReadToEnd();
}
string scriptFile = Environment.ExpandEnvironmentVariables(string.Format("%temp%\\{0}", scriptName));
try
{
// output script file to temp path
File.WriteAllText(scriptFile, scriptContent);
ProcessStartInfo proInfo = new ProcessStartInfo();
proInfo.FileName = "PowerShell.exe";
proInfo.CreateNoWindow = true;
proInfo.RedirectStandardOutput = true;
proInfo.UseShellExecute = false;
proInfo.Arguments = string.Format(" -File {0}",scriptFile);
var proc = Process.Start(proInfo);
proc.OutputDataReceived += Proc_OutputDataReceived;
proc.BeginOutputReadLine();
proc.WaitForExit();
Console.WriteLine("Hit any key to continue...");
Console.ReadKey();
}
catch (Exception ex)
{
Console.WriteLine("Hit Exception: {0}", ex.Message);
}
finally
{
// delete temp file
if (File.Exists(scriptFile))
{
File.Delete(scriptFile);
}
}
}
}
}
'@
# $providerDict
$providerDict = New-Object 'System.Collections.Generic.Dictionary[[string],[string]]'
$providerDict.Add('CompilerVersion','v4.0')
$codeCompiler = [Microsoft.CSharp.CSharpCodeProvider]$providerDict
# Create the optional compiler parameters
$compilerParameters = New-Object 'System.CodeDom.Compiler.CompilerParameters'
$compilerParameters.GenerateExecutable = $true
$compilerParameters.GenerateInMemory = $true
$compilerParameters.WarningLevel = 3
$compilerParameters.TreatWarningsAsErrors = $false
$compilerParameters.CompilerOptions = '/optimize'
$outputExe = Join-Path $ScriptFile.Directory "$($ScriptFile.BaseName).exe"
$compilerParameters.OutputAssembly = $outputExe
$compilerParameters.EmbeddedResources.Add($ScriptFile.FullName) > $null
$compilerParameters.ReferencedAssemblies.Add( [System.Diagnostics.Process].Assembly.Location ) > $null
# Compile Assembly
$compilerResult = $codeCompiler.CompileAssemblyFromSource($compilerParameters,$csharpCode)
# Print compiler errors
if($compilerResult.Errors.HasErrors)
{
Write-Host 'Compile faield. See error message as below:' -ForegroundColor Red
$compilerResult.Errors | foreach {
Write-Warning ('{0},[{1},{2}],{3}' -f $_.ErrorNumber,$_.Line,$_.Column,$_.ErrorText )
}
}
else
{
Write-Host 'Compile succeed.' -ForegroundColor Green
"Output executable file to '$outputExe'"
}
}