背景
私、iPhone使いです。
音楽聞く習慣がないので、パソコンにiTunes入れる必要性を感じません。と云うか入れたくありません。
私、秘密主義者です。
誰に覗かれるか分からないクラウドストレージはイマイチ不安。と云うか全く信用していません。
私、激烈心配性です。
離席中にひょっとして誰かにファイル抜かれたら嫌だなぁ。と云う事でWPDも無効化しちゃいました。
……………………
iPhoneに溜まり続ける写真。
何とかしてパソコンに吸い上げられないかなぁ…
このドラマは大筋に於いてフィクションであり、
登場する人物・団体・シチュエーションとかは
現実世界での出来事とは、ほぼほぼ無関係…
と思って下さい
方針
なんて我儘で贅沢な悩みでしょうか。
こんだけ周りに壁を立てておいて、写真だけは特別扱いしたいなんて。
ま、実際問題としてそんな事って良くありますけどね。
でも、そんな状況に出くわしてしまうと、思わずめらめらと何かが燃え上がってしまう自分がいます。
さて、そんな時取り敢えず思いつくのはやっぱりWebアプリですね。クラウドに向かっては出て行きたくないので、パソコンとiPhone間のプライベートネットワークって感じでしょうか。
となると、万人が「あーこりゃ簡単・便利!」って感じにはなりそうにもありませんが、まぁ「こんな事もできるんだねぇ…」位に思って貰えれば嬉しいかな。
構成
全体的な想定構成を決めておきましょう。
基本的に、動作検証の取れた自前の環境って事ですが…
- iPhone
- Windowsパソコン
OSはWin10 - 宅内の無線LAN環境が必要です
- パソコン側にIISが必要ですね
事前に設定しておきましょう
※ここら辺が参考になるかも
対応
一番オーソドックスな方法です。パソコンをWebサーバに見立てて(?いや、しっかりとWebサーバですけど)、iPhoneからそこにアクセスする感じです。
まず、iPhoneでパソコンにアクセスできるかどうか確認してみましょう。
パソコンのIPアドレスを確認した処、現時点ではWiFi
アダプタのIPアドレスは192.168.0.11
でした。
んじゃ、iPhone
でsafari
立ち上げてhttp://192.168.0.11
とすると…
よしよし。
じゃぁ、画像をアップロードする為に、c:\inetpub\wwwroot
の下に以下のファイルを置きましょう。(UTF-8
で保存してね)
<!DOCTYPE HTML>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=300">
<title>画像転送</title>
</head>
<body>
<h1>画像転送</h1><br/>
<form action="upload.aspx" method="post" enctype="multipart/form-data">
<input type="file" name="upfile" multiple="multiple"/><br/><br/>
<input type="submit" value="転送"/>
</form>
</body>
</html>
<%@ Page Language="C#" CodeFile="upload.aspx.cs" Inherits="MyPage" %>
<% Proc(); %>
using System;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Web;
public partial class MyPage : System.Web.UI.Page {
const string imgPath = @"c:\users\■■■■\desktop\upImage";
protected void Proc() {
for(int i=0; i<Request.Files.Count; i++) {
string fileName = Path.GetFileName(Request.Files[i].FileName);
fileName = Regex.Replace(FileName, @"(?=\.\w+$)|(?<!\.\w+)$", DateTime.Now.ToString("yyyyMMddHHmmssfff"));
Request.Files[i].SaveAs(Path.Combine(imgPath, fileName));
Thread.Sleep(1);
}
Response.Write(@"<!DOCTYPE HTML>");
Response.Write(@"<html lang=""ja"">");
Response.Write(@"<head>");
Response.Write(@"<meta charset=""UTF-8"">");
Response.Write(@"<meta name=""viewport"" content=""width=300"">");
Response.Write(@"<title>転送完了</title>");
Response.Write(@"</head>");
Response.Write(@"<body>");
Response.Write(@"<a href=""select.html""><button>戻る</button></a>");
Response.Write(@"</body>");
Response.Write(@"</html>");
}
}
あ、■■■■
の所は自分のユーザ名にして下さい。
そして、デスクトップにupImage
フォルダを作って、EveryOne
に対してFullControl
を与えておく必要があります。ここら辺が、IIS
のセットアップと共に面倒臭いハードルですかねぇ…
で、safari
でhttp://192.168.0.11/select.html
にアクセスすると…
ファイル選択ボタンを押すと…
よしよしよし…
画像を選択して、転送ボタンを押すと
デスクトップのupImageに画像ファイルが転送されました。
よしよしよしよし…
別解
できるのはできたけど、やっぱりIIS
立てたり、転送先を絶対パスで固定したり、妙なアクセス権付けたり…
と、イマイチ感がハンパないです。
なんとかなんないのー、どらヱモ~〇っ!
— という事で、良いもの見つけました。
その名もOWIN
。
IIS
不要!
起動するだけでOK!!
自分の中にWeb
サーバを抱え込んでくれる、と云う優れモノ。
つまりexe
を起動するだけで外部からのWeb
アクセスを引き受けてくれると云う…
では、始まりー
- Visual Studio Community 2015(等) を準備して下さい。
- 新規プロジェクトを始めましょう
- ↓この画面は、今回
Azure
使わないので(もし出ても)Cancel
しちゃってOKです
- ↓プロジェクトに新しい項目を追加しましょう
- ↓OWIN Startup クラスを追加します
- ↓ツール→NuGet、経由でOWINHostを追加しましょう(色々確認されるけど、気にしない気にしない)
- ↓追加された
Startup1
クラスのConfiguration
メソッドを整備しましょう
こんな感じかなっ
(本当は多分コントローラとかで対応するんだろうけど、今回は自分の為だけなので楽をしてみました)
public class Startup1 {
public void Configuration(IAppBuilder app) {
// アプリケーションの設定方法の詳細については、http://go.microsoft.com/fwlink/?LinkID=316888 を参照してください
app.Run(context => {
StringBuilder sb = new StringBuilder();
switch(context.Request.Path.Value) {
case "/":
context.Response.Redirect("/select");
break;
case "/select":
sb.AppendLine(@"<!DOCTYPE HTML>");
sb.AppendLine(@"<html lang=""ja"">");
sb.AppendLine(@"<head>");
sb.AppendLine(@"<meta charset=""UTF-8"">");
sb.AppendLine(@"<meta name=""viewport"" content=""width = 300"">");
sb.AppendLine(@"<title>画像転送</title>");
sb.AppendLine(@"</head>");
sb.AppendLine(@"<body>");
sb.AppendLine(@"<h1>画像転送</h1><br/>");
sb.AppendLine(@"<form action=""upload"" method=""post"" enctype=""multipart/form-data"">");
sb.AppendLine(@"<input type=""file"" name=""upfile"" multiple=""multiple""/><br/><br/>");
sb.AppendLine(@"<input type=""submit"" value=""転送""/>");
sb.AppendLine(@"</form>");
sb.AppendLine(@"</body>");
sb.AppendLine(@"</html>");
context.Response.Write(sb.ToString());
break;
case "/upload":
for(int i=0; i<context.Request.Files.Count; i++) {
string fileName = Path.GetFileName(context.Request.Files[i].FileName),;
fileName = Regex.Replace(fileName, @"(?=\.\w+$)|(?<!\.\w+)$", DateTime.Now.ToString("yyyyMMddHHmmssfff"));
context.Request.Files[i].SaveAs(Path.Combine(imgPath, fileName));
Thread.Sleep(1);
}
sb.AppendLine(@"<!DOCTYPE HTML>");
sb.AppendLine(@"<html lang=""ja"">");
sb.AppendLine(@"<head>");
sb.AppendLine(@"<meta charset=""UTF-8"">");
sb.AppendLine(@"<meta name=""viewport"" content=""width = 300"">");
sb.AppendLine(@"<title>画像転送</title>");
sb.AppendLine(@"</head>");
sb.AppendLine(@"<body>");
sb.AppendLine(@"<a href=""select""><button>戻る</button></a>");
sb.AppendLine(@"</body>");
sb.AppendLine(@"</html>");
context.Response.Write(sb.ToString());
break;
default:
context.Response.StatusCode = 404;
break;
}
return context.Response.WriteAsync(string.Empty);
});
}
}
あ~~~でもぉ、コンパイルが通らないぃ~ orz
OWIN
のRequest
にはFiles
がなかったのかー
いや、でも何か解決策がある筈!
:
ある筈…
:
力技
見つからない…
Nancy
ってのではFiles
を扱っている例は見つけたんですが、残念ながらナンシー嬢、私の環境へのお誘いにはつれない感じ…
コンパイルまでも辿り着けない体たらく。
已むを得ん!
この際無理矢理にでもファイルを取り出してやるっ!
って事で、無理矢理感満載の解決を見ました。
HTTP
のInputStream
から直接ファイルを切り出す方式です。
※ エラーになる事を基本的に想定していない実装なので、とても打たれ弱いです
今日の所はこれ位で勘弁して下さい…
ざっくりと方向性を説明すると—
-
HttpFileCollection
相当のクラスを作る -
HttpPostedFile
相当のクラスを作る
あれ?
終わってしまった…
では、まず上記クラスから—
class FileCollection : NameObjectCollectionBase, IDisposable {
private FileCollection() : base() { }
public void Dispose() { ((IDisposable)ms).Dispose(); }
public PostedFile this[int index] { get { return (PostedFile)this.BaseGet(index); } }
public PostedFile this[string key] {
get { return (PostedFile)this.BaseGet(key); }
set { this.BaseSet(key, value); }
}
public string[] AllStringValues { get { return (string[])this.BaseGetAllValues(typeof(string)); } }
public bool HasKeys { get { return this.BaseHasKeys(); } }
public void Add(string key, PostedFile value) { this.BaseAdd(key, value); }
public void Remove(string key) { this.BaseRemove(key); }
public void Remove(int index) { this.BaseRemoveAt(index); }
public void Clear() { this.BaseClear(); }
MemoryStream ms = null;
static public FileCollection GetFiles(IOwinContext context) {
FileCollection fc = new FileCollection();
HttpListenerRequest req = ((context.Request.Environment["System.Net.HttpListenerContext"]) as HttpListenerContext).Request;
string boundary = "--" + Regex.Match(req.ContentType, @"(?<=boundary=)(.+(?=; )|.+$)").Value;
fc.ms = new MemoryStream((int)req.ContentLength64);
byte[] work = new byte[1024 * 1024];
int cnt = 0;
using(Stream stream = req.InputStream) {
while((cnt = stream.Read(work, 0, work.Length)) > 0)
fc.ms.Write(work, 0, cnt);
}
fc.ms.Seek(0, SeekOrigin.Begin);
if(fc.ms.GetString(req.ContentEncoding, boundary.Length) != boundary) return fc;
while(fc.ms.GetString(req.ContentEncoding, 2) == "\r\n") {
string filename = null;
string filetype = null;
long spos = 0;
long epos = 0;
string line = null;
while((line = fc.ms.GetLine(req.ContentEncoding)) != string.Empty) {
if(line.StartsWith("Content-Disposition: ")) {
filename = Regex.Match(line, @"(?<=filename=)(.+(?=; )|.+$)").Value.Trim('"');
continue;
}
if(line.StartsWith("Content-Type: ")) {
filetype = Regex.Match(line, @"(?<= ).+$").Value;
continue;
}
}
spos = fc.ms.Position;
do {
epos = fc.ms.SkipLine();
} while(!fc.ms.IsFileEnd(req.ContentEncoding, boundary));
fc.Add(Path.GetFileName(filename), new PostedFile(fc.ms, filetype, filename, spos, epos));
}
return fc;
}
}
class PostedFile {
public PostedFile(Stream baseStream, string type, string name, long start, long end) {
this.Base = baseStream;
this.StartPos = start;
this.ContentLength = (int)(end - start);
this.ContentType = type;
this.FileName = name;
}
Stream Base = null;
private long StartPos = 0;
public int ContentLength { get; private set; }
public string ContentType { get; private set; }
public string FileName { get; private set; }
public Stream InputStream { get { return new SubStream(Base, StartPos, ContentLength); } }
public void SaveAs(string filename) {
byte[] work = new byte[1024 * 1024];
int c = 0;
using(Stream st = InputStream)
using(FileStream fs = new FileStream(filename, FileMode.Create, FileAccess.Write)) {
while((c = st.Read(work, 0, work.Length)) > 0)
fs.Write(work, 0, c);
}
}
}
後はヘルパさん達…
class SubStream : Stream {
public SubStream(Stream s, long start, long length) {
this.stream = s;
this.start = start;
this.length = length;
this.stream.Seek(this.start, SeekOrigin.Begin);
}
Stream stream;
long start;
long length;
public override long Length { get { return length; } }
public override bool CanRead { get { return this.Position < length; } }
public override bool CanWrite { get { return false; } }
public override bool CanSeek { get { return true; } }
public override void Flush() { }
public override long Position {
get { return this.stream.Position - this.start; }
set { this.stream.Position = this.start + value; }
}
public override int Read(byte[] buffer, int offset, int count) {
if(!CanRead) return 0;
count = (int)Math.Min(count, this.Length - this.Position);
return this.stream.Read(buffer, offset, count);
}
public override long Seek(long offset, SeekOrigin origin) {
switch(origin) {
case SeekOrigin.Begin:
this.stream.Seek(this.start + offset, origin);
break;
case SeekOrigin.Current:
this.stream.Seek(offset, origin);
break;
case SeekOrigin.End:
this.Seek(this.start + this.length + offset, origin);
break;
}
return this.Position;
}
public override void Write(byte[] buffer, int offset, int count) {
throw new NotImplementedException();
}
public override void SetLength(long value) {
throw new NotImplementedException();
}
}
static class ExtClass {
static public string GetString(this Stream Me, Encoding encoding, int length) {
byte[] work = new byte[length];
Me.Read(work, 0, length);
return encoding.GetString(work);
}
static public string GetLine(this Stream Me, Encoding encoding) {
List<byte> work = new List<byte>();
while(!work.IsLineEnd())
work.Add((byte)Me.ReadByte());
return encoding.GetString(work.ToArray()).TrimEnd('\r', '\n');
}
static public long SkipLine(this Stream Me) {
for(byte b1 = (byte)Me.ReadByte(), b2 = 0; !(b2 == '\r' && b1 == '\n'); b1 = (byte)Me.ReadByte())
b2 = b1;
return Me.Position - 2;
}
static public bool IsLineEnd(this List<byte> Me) {
if(Me.Count < 2) return false;
return (Me[Me.Count - 2] == '\r') && (Me[Me.Count - 1] == '\n');
}
static public bool IsFileEnd(this Stream Me, Encoding encoding, string boundary) {
byte[] work = encoding.GetBytes(boundary);
foreach(byte b in work)
if(b != Me.ReadByte()) {
Me.Seek(-1, SeekOrigin.Current);
return false;
}
return true;
}
}
で、`Startup1.cs`の一部を—
:
switch(context.Request.Path.Value) {
:
:
case "/upload":
FileCollection fc = FileCollection.GetFiles(context);
for(int i = 0; i < fc.Count; i++) {
string fileName = Path.GetFileName(fc[i].FileName);
fileName = Regex.Replace(fileName, @"(?=\.\w+$)|(?<!\.\w+)$", DateTime.Now.ToString("yyyyMMddHHmmssfff"));
fc[i].SaveAs(Path.Combine(@"pic", fileName));
Thread.Sleep(1);
}
:
:
}
こんな感じ…
実際にiPhoneと連携したい時は、デバッグ実行では無理っぽい感じなので、以下の手順で実行完了を整えてください。
- まず、ビルドしときましょう
- ソリューションフォルダ(
TestOwin.sln
がある処)からみた、packages\OwinHost.3.0.1\tools
フォルダを丸ごとデスクトップにコピーします
※PS
のスクリプトファイルは気になる人は消してもいいです - プロジェクトフォルダ(
TestOwin.csproj
がある処)配下にあるbin
フォルダをデスクトップに作ったtools
フォルダにコピーします -
tools
フォルダの中にpic
フォルダを作っておいて下さい - あ、
tools
って名前がなんだかなー
って時は好きな名前に変えて貰って大丈夫
で、実行環境は整いました。
今度は実行手順です。(tools
フォルダで話を続けます)
- 管理者でコマンドウィドウを開きます
-
cd c:\Users\■■■\Desktop\tools
でカレントディレクトリを移動します -
OwinHost.exe -u http://192.168.3.3:5000
で、以下のような画面が出てアクセス待ちになります
尚、上記コマンドのIP
アドレスは実行しているパソコンのアドレスを指定して、ポートは… まぁ5000を指定しとけば良いと思います
因みに、前の方とIPアドレス体系が変わってしまっているのは、プロバイダ乗り換えたからです…(^_^;
- アクセス待ちになったら、
iPhone
でsafari
立ち上げて、http://192.168.3.3:5000
にアクセスすると…
IIS
パターンと同じ画面が出るだけなので画面貼るのは省略しますが、tools\pic
の下に画像が転送されるのが確認出来る筈です - 転送が一通り終わったら、コマンド画面で
Enter
キーを押しましょう
一捻り
画像ファイルが転送できる事は前ケースで確認できました。
でも、前提が色々なぁ…
つまり、家にいてホームネットワークが構築されている状況が仮定されています。
ホームネットワークがない時にはどうしたら良いでしょうか?
例えば、旅行中とか、もにょもにょとか…
そういう時は、パソコンとiPhone間で直接ネットワークを構築するって技があります。
これならば、外出していても写真を吸い上げることができます。
直接ネットワークを構築する時には、パソコン側をWiFi親機にしてあげる必要があります。
SoftAPって奴ですかねぇ。
まぁ、ここら辺を確認してみて下さい。
以下ざっとした手順。(cmd.exe
を管理者モードで起動しておいて下さい)
1.netsh wlan set hostednetwork mode=allow
2.netsh wlan set hostednetwork ssid=■■■
(■■■
欄は希望のSSID)
3.netsh wlan set hostednetwork key=■■■ keyusage=persistent
(■■■
欄は希望のパスワード)
※ここまでは一度設定したら、毎回入力は不要
4.netsh wlan start hostednetwork
※転送を開始するときに入力
5.netsh wlan stop hostednetwork
※転送が完了したら入力
………
なんですけど、ここまでやって衝撃の事実にぶち当たりました。
私のパソコンhostednetwork
モードに対応していない…
(ぼっこいネットワークカード使いやがって、もー)
という事で、一応情報の提供までという事で。
御粗末様でした…