大量のファイルをタイムスタンプの日付毎にフォルダ分け
大量の写真ファイルを整理したかったのでRubyでサクッと書き殴り。 とはいえ、また使いそうな気もするのでメモしておきます。
require 'fileutils' Dir.chdir("xxx") # ファイルが入っているディレクトリ Dir.each_child(".") { |file| timestamp = File.mtime(file) dirname = timestamp.strftime("%F") # "YYYY-MM-DD"形式(ISO-8601) if File.directory?(file) then next # ディレクトリは無視 end if not Dir.exist?(dirname) then Dir.mkdir(dirname) end FileUtils.mv(file, dirname) }
Firebaseのプロジェクト上限数の追加リクエストをしてみました
最近は技術検証等でFirebaseプロジェクトを作ることが多く、上限数に引っかかってしまいました。
不要なプロジェクトを削除しても30日間は復旧可能な論理削除のような状態で残っているようで、削除後もプロジェクト追加が出来なかったので、以下のページから上限数追加のリクエストを送ってみました。ちなみに以下のページはプロジェクト作成時に上限数に達している場合は誘導のリンクが表示されるはずです。(スクショ撮り忘れ)
名前とかメールアドレスを適当に記入し、無料アプリを作りたかったので「Free Services」にチェック、プロジェクト数はとりあえず「5」にしておいてSubmitで良いだろと思いきや、最後の項目も必須とのこと。
Any other things we need to be aware of to help us understand the request? *
和訳: その他、要望を理解するために気をつけるべきことがあれば教えてください。by DeepL
気をつけるべきことって何だ...と思いつつも以下のように適当に記載してSubmit。
We want to develop iOS and Android app using Firebase Auth, Firestore, Cloud Messaging, and Functions. We also want to do some technical research on Firebase for our clients.
「Firebase Auth、Firestore、Cloud Messaging、Functionsを使ったiOS、Androidアプリを開発したいと考えています。また、クライアントのためにFirebaseの技術的な調査も行いたいです。」的な内容です。
で、Submitするやいなや数秒でメールを受信。
Quota Granted
一瞬で承認されました。 これ間違いなくメッセージ読んでないなと思いつつ、めでたしめでたし。
Firebaseエミュレータが起動しない
以前、Zennのスクラップ機能にメモを残しながらFlutter/Firebaseアプリを作成していましたが約1年が経過。
長らくFlutter/Firebaseから離れてしまっていましたが再開。 そしてFirebaseエミュレータを起動しようとしたら↓のようなエラーが。
⚠ hosting: Port 5000 is not open on localhost, could not start Hosting Emulator. {"metadata":{"emulator":{"name":"hosting"},"message":"Port 5000 is not open on localhost, could not start Hosting Em ulator."}} ⚠ hosting: To select a different host/port, specify that host/port in a firebase.json config file: { // ... "emulators": { "hosting": { "host": "HOST", "port": "PORT" } } } {"metadata":{"emulator":{"name":"hosting"},"message":"To select a different host/port, specify that host/port in a firebase.json config file:\n {\n // ...\n \"emulators\": {\n \"hosting\": {\n \"host\": \"\u001b[33mHOST\u001b[39m\",\n \"port\": \"\u001b[33mPORT\u001b[39m\"\n }\n }\n }"}} i emulators: Shutting down emulators. {"metadata":{"emulator":{"name":"hub"},"message":"Shutting down emulators."}} Error: Could not start Hosting Emulator, port taken.
Firebase Hostingで使用する5000番ポートが開かないと。 どうやらmacOS MontereyからAirPlayが5000番を使うようになったらしく、ポートの衝突が起きているらしい。 firebase.jsonを開き、"hosting"のポート番号を変更すると起動出来るようになりました。
"hosting": { - "port": 5000 + "port": 5002 },
参考
Azure DevOps(Boards)のWork ItemをOrganization間で移行する
はじめに
あまり無いことだとは思いますが、Azure DevOpsの情報を別Organization(企業)へ移行する必要が生じました。
Azure Reposのソースコードの移行はGitなので簡単に出来たのですが、Azure BoardsのWork Itemについてはなかなか情報が見つかりませんでした。
Azure DevOps CLIを使ってみたり、移行元でQueriesを使い全WorkItemをExcelにエクスポートし、移行先でインポートする方法などもありましたが、添付ファイル(Attachment)の移行が出来なかったり等、色々問題がありそうなので採用出来ず。
いろいろ調べた結果、Azure DevOps Migration Tools
というOSSツールを使用することである程度の移行が出来たため、手順を記録しておきます。
これらの手順を実行する場合は自己責任にてお願いします。
Azure DevOps Migration Tools とは
名前の通り、Azure DevOpsの移行用ツールです。C#で記述されているオープンソースのツールになります。移行対象はAzure Boards(Work Item)がメインですが、Azure Test Plansの移行もbeta版ですが提供されているようです。(2020年10月現在)
Windows専用ツールであり、Windows向けパッケージマネージャであるChocolatey
からインストール出来ます。
公式サイトはこちらです。
注意点
このツールは公式サイトに以下の記載がある通り、初心者向けのツールではありません。Azure DevOpsの構造に精通していないと運用は難しいかと思います。
WARNING: This tool is not designed for a novice. This tool was developed to support the scenarios below, and the edge cases that have been encountered by the 30+ contributors from around the Azure DevOps community. You should be comfortable with the TFS/Azure DevOps object model, as well as debugging code in Visual Studio.
こんな記事を書いている私ですが、およそ精通してるとは言えないため、使い方が誤っている可能性は大いにあります...ご了承ください...
何が移行出来た?
- タイトル
- State(New, Active, Closed, etc.)
- 親子関係
- ただし、移行出来たものもあれば何故か出来なかった(Unparentになった)ものも...
- コメント
- 添付ファイル(Attachment)
- Area
- Iteration
- Tags
etc...おおよそのものは移行できるのではないかと思います。
何が移行できなかった?
- アサイン先(Assigned To)
- 移行元のユーザのまま。(当たり前ですが)
- あとでQueriesを使い移行元ユーザを抽出 → 一括で変更は出来たため、そこまで問題無し。
- ブランチ、Pull Request等のLink情報
- 移行自体は出来るけど、移行先のLink対象と一致せずエラーになり、結局は自分で設定し直すことに。
設定次第ではもしかしたら出来たかもしれないけど、私の知識では不可能でした。
移行手順
移行先プロジェクトのWork Itemを拡張する
移行先のWork Itemに移行状態を記載するというツールの仕様上、これを記載するためにWork ItemにCustom Fieldを追加する必要があります(※移行先のみ)。 まずはこのCustom Fieldを追加します。
Work Itemを拡張するためのInherited Processを作成する
- Organization Settings > Boards > Processを開く。
- Processesタブから、ベースとなるProcess(私の場合は”Agile")の右側の"..."をクリックし、"Create inherited process"を開く。
- Process name, Descriptionを記述し、”Create process"を選択し、新しいProcessを作成する。
作成したInherited ProcessにField追加
- 作成したProcessを選択し、"Bug"を選択する。
- "Layout"タブから"New field"を選択する。
- 表示されたポップアップで"Create a field"を選択し、以下を入力して"Add field"を選択する。
- Name: "ReflectedWorkItemID"
- Type: "Text (single line)"
- 以上をWork Item Type全て(Epic, Feature, etc.)に適用する(何故か"Bug"に追加しただけで動作してしまったけど、恐らく全部に適用した方が良さそう?)
ここで設定した名前("ReflectedWorkItemID")は後述する設定ファイルに記述する名前になります。
移行先プロジェクトのProcessを作成したInherited Processに変更する
- Organization Settings > Boards > Processを開く。
- Processesタブから、移行先のプロジェクトが適用しているProcessを開く。
- Projectsタブを開き、プロジェクト名右側の"..."をクリックし、"→ Change process"を選択する。
- 新しく作成したProcessを選択し、保存する。
ちなみにProcessについては以下の記事が分かりやすく書いていただいており参考になります。
https://qiita.com/mstakaha1113/items/2c857e85ed6203d93028
Azure DevOps Migration Toolsのインストール
以下の二通りがあるようです。
- (recommended)Install from Chocolatey
- Download the latest release from GitHub and unzip
私は前者のChocolateyを使用しました。
- Chocolateyのインストール(https://chocolatey.org/install)
- コマンドプロンプトから以下を実行
choco install vsts-sync-migrator
成功するとc:\tools\vstssyncmigration\
にインストールされ、この中のmigrate.exe
を使用して移行します。
設定ファイルの作成
基本的には公式サイトのGetting Startedにサンプルの設定ファイルを含めて記載されています。
- コマンドプロンプトを開き、
c:\tools\vstssyncmigration\
へ移動する ./migration.exe init
を実行configuration.json
が作成されるので、それを編集する
私の場合はサンプルの設定ファイルを色々いじった結果、以下のようになりました。
{ "Version": "11.0", "TelemetryEnableTrace": false, "workaroundForQuerySOAPBugEnabled": false, "Source": { "Collection": "https://dev.azure.com/{移行元Organization}", "Project": "{移行元プロジェクト名}", "ReflectedWorkItemIDFieldName": "ReflectedWorkItemID", "AllowCrossProjectLinking": false, "PersonalAccessToken": "{移行元で作成したPersonal Access Token(PAT)}", "LanguageMaps": { "AreaPath": "Area", "IterationPath": "Iteration" } }, "Target": { "Collection": "https://dev.azure.com/{移行先Organization}", "Project": "{移行先プロジェクト名}", "ReflectedWorkItemIDFieldName": "ReflectedWorkItemID", "AllowCrossProjectLinking": false, "PersonalAccessToken": "{移行元で作成したPersonal Access Token(PAT)}", "LanguageMaps": { "AreaPath": "Area", "IterationPath": "Iteration" } }, "GitRepoMapping": null, "Processors": [ { "ObjectType": "NodeStructuresMigrationConfig", "PrefixProjectToNodes": false, "Enabled": true }, { "ObjectType": "WorkItemMigrationConfig", "QueryBit": "AND [Microsoft.VSTS.Common.ClosedDate] = '' AND [System.WorkItemType] NOT IN ('Test Suite', 'Test Plan')", "OrderBit": "[System.ChangedDate] desc", "Enabled": true, "LinkMigration": true, "AttachmentMigration": true, "AttachmentWorkingPath": "c:\\temp\\WorkItemAttachmentWorkingFolder\\", "FixHtmlAttachmentLinks": false, "SkipToFinalRevisedWorkItemType": false, "WorkItemCreateRetryLimit": 5, "FilterWorkItemsThatAlreadyExistInTarget": true, "PauseAfterEachWorkItem": false, "AttachmentMazSize": 480000000, "CollapseRevisions": false, "LinkMigrationSaveEachAsAdded": false } ] }
- "Version"はインストールしたAzure DevOps Migration Toolsのバージョンを記述
./migration.exe --version
で確認
- "Source"以下に移行元の情報を記述
- "Target"以下に移行先の情報を記述
- "ReflectedWorkItemIDFieldName"にはWork Itemを拡張した際に追加したCustom Fieldに指定した名前("ReflectedWorkItemID")を記述
- "FieldMaps", "WorkItemTypeDefinition"は不要そうだったので削除
- 移行元と移行先でFieldやWorkItemTypeが異なる場合のマッピング設定?
- "Processors"内にそれぞれの"Enabled"はtrueに変更(trueにしないとそのProcessorが起動しない)
- Migration Toolsの処理は、その内容に応じてProcessorというものに分かれており、これを複数実行することで移行処理を行なっていく仕組みのようです。
- "ObjectType"に設定されたProcessorが実行されます。
- "ObjectType": "WorkItemMigrationConfig" → WorkItemMigration Processorを実行
- Processor一覧: https://nkdagility.github.io/azure-devops-migration-tools/#processors
移行開始
以下のコマンドで移行ツールを起動します。
./migration.exe --config configuration.json
途中、移行元/移行先のMicrosoftアカウントへのログインが表示されるかもしれませんが、必要に応じてログインすれば続行するかと思います。
まとめ
私は以上の手順で、不完全ではありますが移行が出来ました。
最初に書きましたが、私自身あまりAzure DevOpsに詳しくなく、他のプロジェクトでも同様に移行できるかは全く不明なため、これらの手順を実行する場合はあくまで自己責任にてお願いします。
iOS 端末のモデル識別子を取得する
開発中のアプリで端末の機種を判定したかったのですが、
UIDevice.current.model は iPhone
や iPod touch
としか返さないため、もう少し詳細な情報を取得する方法を調べました。
今回のコードにより iPhone12,3
等の形式の識別子を取得することが出来ます。
実装コード
今回はUIDeviceのextensionとして実装してみます。
extension UIDevice { var modelIdentifier: String { var systemInfo = utsname() uname(&systemInfo) let machineMirror = Mirror(reflecting: systemInfo.machine) let identifier = machineMirror.children.reduce("") { identifier, element in guard let value = element.value as? Int8, value != 0 else { return identifier } return identifier + String(UnicodeScalar(UInt8(value))) } return identifier } }
検索すると、ここから更にモデル名(iPhone12,3
→iPhone 11 Pro
)にマッピングする記事を見かけますが、個人的にはこの状態で十分だったため止めておきます。
マッピング自体は以下が参考になるかと思います。
Models - The iPhone Wiki (Identifier列を参照)
Expressサーバ上にGatsbyサイトをデプロイしてみた
最近Gatsbyをチマチマ触っているので、Expressサーバ上に載せてみました。 基本的にはExpressの静的ファイル配布機能を使用しているだけですが。
※モバイル畑なので、Web関連はあまり詳しくありません。
プロジェクトの作成
$ gatsby new foo $ cd foo $ npm install express
(余談ですが、npm installに --save
オプションって不要になってたんですね。)
サーバ側の実装
プロジェクト直下にindex.js
を作成
$ touch index.js
index.js
の編集
const express = require("express") const app = express() const port = 3000 // `gatsby build` の結果がpublicディレクトリに出力される app.use(express.static("public/")) app.listen(port, () => { console.log(`Listening at http://localhost:${port}`) })
起動スクリプト
package.json
の編集
... "scripts": { "prestart": "gatsby build", "start": "node index.js" } ...
サーバ起動
$ npm start
以下にアクセスして、サイトが表示されることを確認。
http://localhost:3000
設定アプリ(Settings Bundle)にUUIDを表示する方法
たまにですが、設定アプリに「ユーザーID」といった項目名でUUIDを表示しているアプリを見かけます。 開発中のアプリでも端末識別用のUUIDを設定アプリに表示する必要があったため方法を調べてみたのですが、 予め決まっている固定値を表示する方法がほとんどで、アプリ側で生成したUUIDを表示させるといった情報が意外と見つからなかったため、自分が試して実現した方法を書き記しておこうと思います。
結論
Settings Bundleの値はUserDefaultsで取得出来る、ということはアプリ側から書き込みすれば設定アプリに表示出来るのではと考え、予め設定項目だけ作成しておき、アプリ起動時にUserDefaultsに書き込むことで表示することが出来ました。
※ただ、当然ながらアプリインストール直後で起動していない時点では書き込みが出来ておらずデフォルト値が表示されます。この辺りの解決方法は見つかっていません。
実装手順
Settings.bundle を作成
通常通り、ターゲットのルートディレクトリにSettings.bundleを作成します。
公式の情報はこの辺りでしょうか。 → Implementing an iOS Settings Bundle
設定値の作成
以下のように設定値をRoot.plist
に作成します。
- Item 0: Group
- Title: "UUID"
- Item 1: Title
- Default Value: "xxx"
- Identifier: "uuid"
- UUIDは長い文字列のため、項目名は専用のGroup(
Item 0
)のTitleに表示するようにし、Item 1
にTitleを表示させずに横幅を伸ばすようにしています。 Default Value
は何かしら記載しておかないと項目自体が設定アプリに表示されないようです。(半角スペース(" ")でも項目の表示はされましたが、これ良いのかな...)Item 1
のIdentifierの値は後ほどUserDefaultsに書き込む際に使用します。
UserDefaultsに書き込み
今回はUUIDとして UIDevice.current.identifierForVendor
を使用しています。
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { ... // UUID取得 let uuid = UIDevice.current.identifierForVendor! // UserDefaultsに書き込み。 // Settings.bundleの"Identifier"で指定した値をKeyにして書き込む。 UserDefaults.standard.set(uuid.uuidString, forKey: "uuid") ... }
このようにしてアプリを起動後、設定アプリを確認するとUUIDが表示されているかと思います。
まとめ
所々微妙な部分があるのですが、この方法でとりあえずは実現出来ました。
またここまでの内容を簡単なサンプルコードとしてGitHubにアップしています。