<commit_before>//
//  Copyright 2021 Square, Inc.
//
//  Licensed under the Apache License, Version 2.0 (the "License");
//  you may not use this file except in compliance with the License.
//  You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.
//

import Foundation

@objc(ARKFileSystemAttachmentGenerator)
public final class FileSystemAttachmentGenerator: NSObject {

    // MARK: - Public Static Methods

    /// Generates bug report attachments showing a breakdown of the file system.
    ///
    /// The attachment consists of:
    /// * A list of files in each of the specified `searchPathDirectories` with their file size and content modification
    ///   date.
    /// * Information about the total and available capacity on the main device volume.
    ///
    /// - parameter searchPathDirectories: The search paths within the app's container that should be scanned for files.
    public static func attachment(
        searchPathDirectories: [FileManager.SearchPathDirectory] = [
            .documentDirectory,
            .applicationSupportDirectory,
            .cachesDirectory,
        ]
    ) throws -> ARKBugReportAttachment {
        enum Error: Swift.Error {
            case missingDocumentDirectory
            case missingVolumeResourceValues
        }

        let fileManager = FileManager.default

        let urls = searchPathDirectories.reduce([]) {
            $0 + fileManager.urls(for: $1, in: .userDomainMask)
        }

        var description = ""

        // We need an arbitrary URL on the main device volume in order to check the capacity and determine the URL for
        // the container. We'll use the document directory since it should always exist.
        guard let volumeURL = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first else {
            throw Error.missingDocumentDirectory
        }

        let appContainerPrefixes = try self.appContainerPrefixes(for: urls.first!)

        if let primaryContainerPath = appContainerPrefixes.first {
            description += "Showing files from search paths in app container at \(primaryContainerPath)\n\n"
        }

        let propertyKeys: [URLResourceKey] = [
            .fileSizeKey,
            .contentModificationDateKey,
        ]

        let fileSizeFormatter = ByteCountFormatter()
        fileSizeFormatter.zeroPadsFractionDigits = true
        fileSizeFormatter.allowsNonnumericFormatting = false
        fileSizeFormatter.formattingContext = .listItem
        fileSizeFormatter.isAdaptive = true

        let maxPaddedFileSizeCharacterLength = 12

        let dateFormatter = ISO8601DateFormatter()
        dateFormatter.timeZone = .current

        for directoryURL in urls {
            guard let enumerator = fileManager.enumerator(
                at: directoryURL,
                includingPropertiesForKeys: propertyKeys
            ) else {
                continue
            }

            let directoryDisplayPath: String
            if let appContainerPrefix = appContainerPrefixes.first(where: { directoryURL.path.hasPrefix($0) }) {
                directoryDisplayPath = String(directoryURL.path.dropFirst(appContainerPrefix.count))
            } else {
                directoryDisplayPath = directoryURL.path
            }

            description += directoryDisplayPath + "\n"

            var containedFiles = false
            for case let fileURL as URL in enumerator {
                containedFiles = true

                let resourceValues = try fileURL.resourceValues(forKeys: Set(propertyKeys))<commit_msg>Add comment explaining max file size char count<commit_after>//
//  Copyright 2021 Square, Inc.
//
//  Licensed under the Apache License, Version 2.0 (the "License");
//  you may not use this file except in compliance with the License.
//  You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.
//

import Foundation

@objc(ARKFileSystemAttachmentGenerator)
public final class FileSystemAttachmentGenerator: NSObject {

    // MARK: - Public Static Methods

    /// Generates bug report attachments showing a breakdown of the file system.
    ///
    /// The attachment consists of:
    /// * A list of files in each of the specified `searchPathDirectories` with their file size and content modification
    ///   date.
    /// * Information about the total and available capacity on the main device volume.
    ///
    /// - parameter searchPathDirectories: The search paths within the app's container that should be scanned for files.
    public static func attachment(
        searchPathDirectories: [FileManager.SearchPathDirectory] = [
            .documentDirectory,
            .applicationSupportDirectory,
            .cachesDirectory,
        ]
    ) throws -> ARKBugReportAttachment {
        enum Error: Swift.Error {
            case missingDocumentDirectory
            case missingVolumeResourceValues
        }

        let fileManager = FileManager.default

        let urls = searchPathDirectories.reduce([]) {
            $0 + fileManager.urls(for: $1, in: .userDomainMask)
        }

        var description = ""

        // We need an arbitrary URL on the main device volume in order to check the capacity and determine the URL for
        // the container. We'll use the document directory since it should always exist.
        guard let volumeURL = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first else {
            throw Error.missingDocumentDirectory
        }

        let appContainerPrefixes = try self.appContainerPrefixes(for: urls.first!)

        if let primaryContainerPath = appContainerPrefixes.first {
            description += "Showing files from search paths in app container at \(primaryContainerPath)\n\n"
        }

        let propertyKeys: [URLResourceKey] = [
            .fileSizeKey,
            .contentModificationDateKey,
        ]

        let fileSizeFormatter = ByteCountFormatter()
        fileSizeFormatter.zeroPadsFractionDigits = true
        fileSizeFormatter.allowsNonnumericFormatting = false
        fileSizeFormatter.formattingContext = .listItem
        fileSizeFormatter.isAdaptive = true

        /// The maximum padded file size character length is the longest formatted byte count string (three digit number
        /// plus " bytes" is nine characters) plus the minimum three spaces of padding between columns.
        let maxPaddedFileSizeCharacterLength = 12

        let dateFormatter = ISO8601DateFormatter()
        dateFormatter.timeZone = .current

        for directoryURL in urls {
            guard let enumerator = fileManager.enumerator(
                at: directoryURL,
                includingPropertiesForKeys: propertyKeys
            ) else {
                continue
            }

            let directoryDisplayPath: String
            if let appContainerPrefix = appContainerPrefixes.first(where: { directoryURL.path.hasPrefix($0) }) {
                directoryDisplayPath = String(directoryURL.path.dropFirst(appContainerPrefix.count))
            } else {
                directoryDisplayPath = directoryURL.path
            }

            description += directoryDisplayPath + "\n"

            var containedFiles = false
            for case let fileURL as URL in enumerator {
                containedFiles = true

                let resourceValues = try fileURL.resourceValues(forKeys: Set(propertyKeys))
{'fraction_non_alphanumeric': 0.06809506218446004, 'fraction_numerical': 0.004186676517670238, 'mean_word_length': 2.3997488488907495, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3750274', 'n_tokens_mistral': 1946, 'n_tokens_neox': 1868, 'n_words': 679}