【Swift】FileManager を使いたい

最初、無駄にややこしくて使いづらい気がした。

しかし、整理できるとそうでもない。


let fileManager = FileManager.default
let documents = URL.documentsDirectory

Button("create file") {
  // new.txt
  let text = "Hello!"
  let to = documents.appending(component: "new.txt")
  try? text.write(to: to, atomically: true, encoding: .utf8)
}

Button("read file") {
  // new.txt
  let file = documents.appending(component: "new.txt")
  let text = (try? String(contentsOf: file, encoding: .utf8)) ?? "ERROR"
  print(text)
}

Button("create directory") {
  // some/
  let directory = documents.appending(component: "some/")
  try? fileManager.createDirectory(at: directory, withIntermediateDirectories: true)
}

Button("copy file") {
  // new.txt - copy -> some/copied.txt
  let at = documents.appending(component: "new.txt")
  let to = documents.appending(component: "some/copied.txt")
  try? fileManager.copyItem(at: at, to: to)
}

Button("move file") {
  // new.txt - move -> some/moved.txt
  let at = documents.appending(component: "new.txt")
  let to = documents.appending(component: "some/moved.txt")
  try? fileManager.moveItem(at: at, to: to)
}

Button("copy directory") {
  // some/ - copy -> another/
  let at = documents.appending(component: "some/")
  let to = documents.appending(component: "another/")
  try? fileManager.copyItem(at: at, to: to)
}

Button("delete file") {
  // some/moved.txt
  let dir = documents.appending(component: "some/")
  let file = dir.appending(component: "moved.txt")
  try? fileManager.removeItem(at: file)
}

Button("delete directory") {
  // some/ and another/
  let dir1 = documents.appending(component: "some/")
  let dir2 = documents.appending(component: "another/")
  try? fileManager.removeItem(at: dir1)
  try? fileManager.removeItem(at: dir2)
}

つづいて、ディレクトリを指定するだけで、

そのディレクトリ内に存在するディレクトリとファイルの状態を表示できるようにしておきます。

実体の確認を頻繁にしやすくしておくこと大事。



extension FileManager {
  private func contents(directory url: URL) -> [URL] {
    (try? contentsOfDirectory(at: url, includingPropertiesForKeys: nil, options: [])) ?? []
  }

  func showContents(_ url: URL = .documentsDirectory) {
    print(
      String(format: "%@ %@", url.shortPath(), url.isFile ? "[\(url.fileSize)]" : "")
    )

    if url.isDirectory {
      contents(directory: url)
        .sorted(by: {
          let lr = [$0, $1].map {
            ($0.path().components(separatedBy: "/").dropLast().joined(), $0.path())
          }
          return lr[0] < lr[1]
        })
        .forEach { content in
          showContents(content)
        }
    }
  }
}


FileManager.default.showContents(.documentsDirectory)

// /HOME/Documents/ 
// /HOME/Documents/new.txt [6 bytes]
// /HOME/Documents/another/ 
// /HOME/Documents/another/copied.txt [6 bytes]
// /HOME/Documents/some/ 
// /HOME/Documents/some/copied.txt [6 bytes]
// /HOME/Documents/some/moved.txt [6 bytes]

👉 contentsOfDirectory(at:includingPropertiesForKeys:options:) | Apple Developer Documentation hatena-bookmark

これぐらいでいいか。

Permission とか mask の話しはまたそのうちやりたいです。



 

🤔 まとめ

コツとしては、URL の取り扱い。


ファイルやディレクトリは URL を使って指す。


URL を path() などを使って String に変換するのは表示直前のみ。


URL は実体の情報を保持していない。

という当たり前の言葉が思いつく。

しかし、最初は混乱したし、すると思う。

String のほうが直感的で人間に近いし、

古い API を見てると String ベースでファイルを操作してる。

フツーに検索しても古い記述がヒットすることが多い。

 

🤔 参考

👉 【Swift】ファイルやディレクトリ操作するための extension をまずは作った hatena-bookmark
👉 【Swift】URL で特定のディレクトリやファイルを指す hatena-bookmark


【macOS】ルーターの注意喚起が出たら arp-scan がいいよ

ああ、ルーターの機種がもろ該当。

 

🛜 Buffalo バッファロー のルーターで注意喚起が出た

こういうの良く出てますけども。

対象の製品を利用していること、そしてWeb設定画面のパスワードが工場出荷時のまま、もしくは推測しやすい文字列を設定していることの両方を満たす場合には、bot感染の可能性がある。

👉 バッファローが一部Wi-Fiルーターで注意喚起、bot感染増加を受け パスワード変更やファームウェア更新を - ITmedia NEWS hatena-bookmark

1つは、一旦設定を初期化すること。「インターネット設定等を工場出荷時のまま利用されている場合は、可能な限り設定を初期化しご利用ください」との表現がされているが、工場出荷時の状態から利用し続けている場合、意図しない設定変更が行われているおそれがあるので、一度初期化したのち、以下の対策を行ってほしい、との意図だという。

 次に、パスワード(ここで指しているのは、Wi-Fi接続用のパスワードではなく、Web設定画面にログインするための管理パスワード)を推測されにくい複雑なものに変更するとともに、ファームウェアを更新すること。なお、WSR-600DHPは工場出荷時の状態でファームウェアが自動更新されるようになっているが、ほかの4製品は自動更新機能を持たないため、手動での更新が必要となる。

 最後に、Web設定画面の[詳細設定]-[管理]-[システム設定]にて、「Internet側リモートアクセス設定を許可する」が無効になっていることを確認すること。なお、設定の初期化を行った場合は、この設定は無効になるため、確認の必要はないという。

👉 【追記あり】バッファローのWi-Fiルーター「WSR-1166DHP」シリーズほかのボット感染が増加、NICTER解析チームが警告 - INTERNET Watch hatena-bookmark

脆弱性の話はいつもあいまいなはっきりしない対応策が多いので分かりづらい。

注意はすればするほど安全なのだろう。

バッファロー公式のアナウンスを見る。

👉 NICTERの投稿に関する重要なお知らせ(5/23更新) | バッファロー hatena-bookmark

調査中の雰囲気ですが、

「外部から管理画面にアクセスしてパスワードを突破されて何かを仕込まれる。」

ということですよね。

対処法としては、

- 外部からのアクセス機能をOFFにする。
- パスワードをややこしいものにする。
- ファームウェアを最新のものにする。

ということのようです。

「初期化」という言葉が話をややこしくしていますが、

どちらにしても、ルーターの設定を触る必要がありますね。

 

🛜 ルーターの管理画面

パソコンやスマホに公式設定アプリを入れて操作するのが簡単ですのでおすすめです。

👉 ソフトウェア | バッファロー hatena-bookmark

しかし、いちいちアプリを入れたくないですよね !

今どきの個人向けルーターは、

ほぼブラウザでアクセスできる管理画面を持っています。

そして、それぞれのOS上で動く設定アプリ内でも

そのネットワーク上に存在しているルーターの

ホスト名やIPアドレスを検出しているはずです。

ブラウザからIPアドレス直打ちで管理画面開きたいですよね!

 

🛜 IPアドレスを見つける

「arp-scan」を使います。


❯ brew install arp-scan


👉 royhills/arp-scan: The ARP Scanner hatena-bookmark

root 必要です。


❯ sudo arp-scan -l
Interface: en0, type: EN10MB, MAC: 63:7e:67:a2:cd:1f, IPv4: 192.168.151.16
Starting arp-scan 1.10.0 with 512 hosts (https://github.com/royhills/arp-scan)
192.168.151.11	84:a3:ec:9c:16:08	BUFFALO.INC
192.168.151.14	f8:3f:c2:31:d2:ee	Apple, Inc.

「BUFFALO」ありましたので、

まずは、コンソールからアクセスします。


❯ curl 192.168.151.11
<html>
<head>

<title>Redirect Page</title>
<meta http-equiv="expires" content="-1">
<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="cache-control" content="no-cache">
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
<SCRIPT language=JavaScript>
function init()
{
	top.location.replace("../login.html");
}
</SCRIPT>
</head>
<body onload='init();'>
<table><tr><td></td></tr></table>
</body>
</html>

❯ curl 192.168.151.11:443
curl: (7) Failed to connect to 192.168.151.11 port 443 after 16 ms: Couldn't connect to server

❯ curl 192.168.151.11:80
<html>
<head>

<title>Redirect Page</title>
<meta http-equiv="expires" content="-1">
<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="cache-control" content="no-cache">
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
<SCRIPT language=JavaScript>
function init()
{
	top.location.replace("../login.html");
}
</SCRIPT>
</head>
<body onload='init();'>
<table><tr><td></td></tr></table>
</body>
</html>

80 のみなので http のみです。

ブラウザから見ると login.html にリダイレクトされます。

あとは、ログインして公式アナウンスの推奨する対応策をやれば OK。

 

🛜 参考



【SwiftUI】iOS と macOS で互換したいコードの一つの解法

なるほどこうするのか。

iOS ↔ macOS と切り替えると、

ビルドで落ちて萎えるときの

一つの何かのきっかけに。

最新の SwiftUI ならシンプルです。


import SwiftUI

public extension Color {

    #if os(macOS)
    static let background = Color(NSColor.windowBackgroundColor)
    static let secondaryBackground = Color(NSColor.underPageBackgroundColor)
    static let tertiaryBackground = Color(NSColor.controlBackgroundColor)
    #else
    static let background = Color(UIColor.systemBackground)
    static let secondaryBackground = Color(UIColor.secondarySystemBackground)
    static let tertiaryBackground = Color(UIColor.tertiarySystemBackground)
    #endif
}

👉 ios - SwiftUI: Get the Dynamic Background Color (Dark Mode or Light Mode) - Stack Overflow hatena-bookmark

マルチなプラットフォーム作成時には、押さえておきたい記述です。