HAKADORI Script開発 その他

DirectCloudでフォルダ構造を一括変更

2022.06.01 水

フォルダ構造を変更サムネ

こんにちは CMBの藤田です。

弊社ではDirectCloudというオンラインストレージサービスを活用しているのですが、このDirectCloudでクライアントとファイルをやり取りをすることも多くあります。
DirectCloudロゴ

 

オンラインストレージでのファイルやり取りは便利なのですが、数が多くなるとなかなか大変です。

そんな中、以下のようなやり取りをする案件があるとのことで相談されました。

  • クライアントさんが多数(100以上)ある
  • ストレージ上にはクライアントの数だけディレクトリがある
  • アップする前にディレクトリの中をひとつのディレクトリにまとめる
  • それぞれのクライアントごとにそれぞれアップするファイルがある
  • 数が多いのでこれらを手作業で繰り返すのはつらい…

 

アップロードする流れ

という案件についてプログラムで効率化できましたので、備忘録も兼ねて記載しておきます。

 

DirectCloudにはAPIがある。が、しかし!

まず最初に思いついたのはDirectCloudのAPI(Application Programming Interface)連携です。
DirectCloudにはAPIが用意されており、プログラムを組めば一括でファイルのアップロードぐらい楽勝だろう… と思っていたのですが、社内の事情でAPIが使えないプランと契約していたことが判明。目論んでいた手法が使えないことで退路を断たれいきなり途方に暮れてしまいました。

 

DirectCloudドライブを使ってみる

しかし、APIが使えないプランであってもDirectCloudドライブなる機能があることが判明。一筋の光が見えました。

DirectCloudドライブとは

エクスプローラーやFinderでクラウド上のファイルにアクセスし、編集・保存・移動・コピー・削除などのファイル操作ができるので安全かつ便利です。
参照:PCにデータを残さずセキュアにファイル共有・編集

Windowsでいうエクスプローラー上でDirectCloudのディレクトリがアクセスできるようになる機能です。こちらはプランによる制限はありませんので使用できました。また、以前はWindows用のみでしたが2022年2月よりmacOS用(β版)がリリースされています。
このDirectCloudドライブであればOS上でのファイル操作でアップロードができそうです。

 

大量ファイルをアップロードする流れ

今回はファイルをアップロードするためのフローを2回に分けました。

1.アップする前にディレクトリの中をひとつのディレクトリにまとめる

まず、ディレクトリの中のファイル/フォルダをひとつのディレクトリにまとめる作業があります。

ファイル/フォルダをひとつのディレクトリにまとめる

 

2.それぞれのクライアントごとにそれぞれアップするファイルがある

そして、ローカルからストレージ上の各ディレクトリへファイルをアップロードしていきます。

各ディレクトリにファイルをアップロード

 

ファイル処理をおこなうプログラム

上記の操作(ディレクトリの中をひとつのディレクトリにまとめる処理およびディレクトリにファイルをアップロードする処理)をいろいろな言語で書いてみました。いずれもサンプルのコードなので1つのディレクトリを操作するだけとしていますが、実際はこのコードをループさせています。

AppleScript

use AppleScript version "2.4"
use framework "Foundation"
use scripting additions

property NSArray : a reference to current application's NSArray
property NSString : a reference to current application's NSString
property NSPredicate : a reference to current application's NSPredicate
property NSFileManager : a reference to current application's NSFileManager

-- フォルダ内に作成するフォルダの名前
property putFolderName : "001"

-- 移動元フォルダ
set pathA to POSIX path of (choose folder with prompt ("移動元フォルダを選択"))

-- 移動元フォルダのパス
set aPath to NSString's stringWithString:pathA

-- 移動先フォルダのパス
set aPath_ to aPath's stringByAppendingPathComponent:(NSString's stringWithString:putFolderName)

-- コピー元
set pathB to POSIX path of (choose folder with prompt ("コピー元フォルダを選択"))

-- コピー元フォルダのパス
set bPath to NSString's stringWithString:pathB

-- NSFileManagerを取得
set aManager to NSFileManager's defaultManager()

-- 移動元フォルダの中身をリストアップ
set aItems to aManager's contentsOfDirectoryAtPath:pathA |error|:(missing value)

-- コピー元フォルダの中身をリストアップ
set bItems to aManager's contentsOfDirectoryAtPath:pathB |error|:(missing value)

-- フォルダ「001」があるかどうか
if aManager's fileExistsAtPath:aPath_ isDirectory:true then
	-- フォルダ「001」を省く
	set aPredicate to NSPredicate's predicateWithFormat_("self != %@", putFolderName)
	set aItems to aItems's filteredArrayUsingPredicate:aPredicate
end if

-- フォルダを作成
set aRes to aManager's createDirectoryAtPath:aPath_ withIntermediateDirectories:true attributes:(missing value) |error|:(reference)

-- 移動元フォルダの第一階層パス
set aItemArray to aPath's stringsByAppendingPaths:aItems

-- 移動元から移動
repeat with aItem in aItemArray
	set aPath2 to (aPath_'s stringByAppendingPathComponent:(aItem's lastPathComponent()))
	-- アイテムを移動
	set moveRes to (aManager's moveItemAtPath:aItem toPath:aPath2 |error|:(missing value))
end repeat

-- コピー元フォルダの第一階層パス
set bItemArray to bPath's stringsByAppendingPaths:bItems

-- コピー元からコピー
repeat with bItem in bItemArray
	set bPath2 to (aPath's stringByAppendingPathComponent:(bItem's lastPathComponent()))
	-- アイテムをコピー
	set copyRes to (aManager's copyItemAtPath:bItem toPath:bPath2 |error|:(missing value))
end repeat
return true

上記のコードはAppleScriptObjCです。実はノーマルなAppleScriptでもファイル移動やコピーはできる(しかもコードは短い)のですが、大量にファイルやフォルダがあると処理速度がちがいます。また、ファイルもフォルダも区別せずコピーできるのはコードを書くには楽です。
こちらはAppleScriptなのでmacOSでのみ動作します。

 

Excel VBA

Sub CopyItemAtPath()
    '変数を宣言する
    Dim ps, PutFolderName, PathA, PathA_, PathB As String
    
    'パスセレクター:通常は\
    ps = Application.PathSeparator
    'フォルダ名
    PutFolderName = "001"
    
    '移動元フォルダ
    With Application.FileDialog(msoFileDialogFolderPicker)
        .Title = "移動元フォルダを選択"
        '選択したフォルダ
        If .Show = True Then
            PathA = .SelectedItems(1) + ps
        Else
            Exit Sub
        End If
    End With
    
    'コピー元フォルダ
    With Application.FileDialog(msoFileDialogFolderPicker)
        .Title = "コピー元フォルダを選択"
        '選択したフォルダ
        If .Show = True Then
            PathB = .SelectedItems(1) + ps
        Else
            Exit Sub
        End If
    End With
    
    'インスタンス
    Set Fso = CreateObject("Scripting.FileSystemObject")
    
    'フォルダをオブジェクト化
    Set FolderObjA = Fso.GetFolder(PathA)
    
    'フォルダのパス
    PathA_ = PathA + PutFolderName + ps
    
    'フォルダがなければ
    If Not Fso.FolderExists(PathA_) Then
        'フォルダを作成
        Fso.CreateFolder PathA_
    End If

    'フォルダ内のファイル
    For Each FileA In FolderObjA.Files
        'ファイルを移動
        Fso.MoveFile FileA.Path, PathA_
    Next

    'フォルダ内のサブフォルダ
    For Each FolderA In FolderObjA.subFolders
        '移動先フォルダではないかどうか
        If FolderA.Name <> PutFolderName Then
            'サブフォルダを移動
            Fso.MoveFolder FolderA.Path, PathA_
        End If
    Next
    
    'フォルダをオブジェクト化
    Set FolderObjB = Fso.GetFolder(PathB)

    'フォルダ内のファイル
    For Each FileB In FolderObjB.Files
        'ファイルをコピー
        Fso.CopyFile FileB.Path, PathA
    Next
        
    'フォルダ内のサブフォルダ
    For Each FolderB In FolderObjB.subFolders
        'サブフォルダをコピー
        Fso.CopyFolder FolderB.Path, PathA
    Next
    
    'お片付け
    Set FolderObj = Nothing
    Set Fso = Nothing
End Sub

Excelでファイル操作というのは一般的なのでしょうか? FSO(FileSystemObject)を使えばできるということで書いてみましたが、あまりExcelに適した作業ではないように思えますが…? ただしもうちょっと肉付けして操作したファイル名をセルに入れていくなどすればExcelがログ代わりになりそうです。
こちらはFSOを使ったVBAなのでWindowsでのみ動作します。

 

Python

import os
import shutil
from tkinter import filedialog

def get_file_path(msg):
    desktop_path = os.path.expanduser('~/Desktop')
    path = filedialog.askdirectory(initialdir=desktop_path, title=msg)
    return path


path_a = get_file_path('移動元フォルダを選択') # 移動元フォルダ
if not path_a:
    exit()

path_b = get_file_path('コピー元フォルダを選択') # コピー元フォルダ
if not path_b:
    exit()

items_a = os.listdir(path_a) # 移動元フォルダの中身をリストアップ
items_b = os.listdir(path_b) # コピー元フォルダの中身をリストアップ

put_folder_name = "001" # フォルダ内に作成するフォルダの名前

if put_folder_name in items_a: # 「001」フォルダーがあったらリストから取り除く
    items_a.remove(put_folder_name)

path_a_ = os.path.join(path_a, put_folder_name) # 「001」フォルダーのパス
os.makedirs(path_a_, exist_ok=True) # 「001」フォルダーを作成(既存でもOK)

for item_a in items_a: # ファイル・フォルダを移動
    shutil.move(os.path.join(path_a, item_a), os.path.join(path_a_, item_a))

for item_b in items_b: # ファイル・フォルダをコピー
    item_b_ = os.path.join(path_b, item_b)
    if os.path.isfile(item_b_): # ファイルだった場合
        shutil.copy2(item_b_, path_a)
        continue
    if os.path.isdir(item_b_): # フォルダだった場合
        shutil.copytree(item_b_, os.path.join(path_a, item_b))
        continue

Pythonはコードを書くのがいちばん楽でした。コピーするのにファイルかフォルダを判別してメソッドを使い分ける必要はありますが、それぐらいです。全体的に短いコードでサクッと書けるのがいいですね。
パス指定は tkinter というライブラリでフォルダ指定できますが、OSに合わせたパスをハードコーディングしてもいいと思います。
このコードはmacOSでもWindowsでも動作します。

実際の処理もWindows版のDirectCloudドライブで上記のコードを拡張したPythonプログラムで実行しました。

 

オンラインストレージのフォルダ構造構築、承ります。

クライアントとのファイルやり取りに便利なオンラインストレージですが、今回のように大量ファイルをディレクトリごとにアップロードするといった作業は手間がかかったりするものです。

タクトシステムではこういったオンラインストレージのフォルダ構造構築や自動アップロード作業の効率化など承っております。

また、オンラインストレージDirectCloudをお試しで使ってみたいというご要望あればお応えすることも可能ですのでこちらもよろしければお問い合わせください。

-HAKADORI, Script開発, その他
-, , , ,