<commit_before>package com.nutomic.syncthingandroid.activities;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.IBinder;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.content.ContextCompat;
import android.text.TextUtils;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

import com.google.common.collect.Sets;
import com.nutomic.syncthingandroid.R;
import com.nutomic.syncthingandroid.SyncthingApp;
import com.nutomic.syncthingandroid.service.SyncthingService;
import com.nutomic.syncthingandroid.service.SyncthingServiceBinder;
import com.nutomic.syncthingandroid.util.Util;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;

/**
 * Activity that allows selecting a directory in the local file system.
 */
public class FolderPickerActivity extends SyncthingActivity
        implements AdapterView.OnItemClickListener, SyncthingService.OnServiceStateChangeListener {

    private static final String EXTRA_INITIAL_DIRECTORY =
            "com.nutomic.syncthingandroid.activities.FolderPickerActivity.INITIAL_DIRECTORY";

    private static final String EXTRA_ROOT_DIRECTORY =
            "com.nutomic.syncthingandroid.activities.FolderPickerActivity.ROOT_DIRECTORY";

    public static final String EXTRA_RESULT_DIRECTORY =
            "com.nutomic.syncthingandroid.activities.FolderPickerActivity.RESULT_DIRECTORY";

    public static final int DIRECTORY_REQUEST_CODE = 234;

    private ListView mListView;
    private FileAdapter mFilesAdapter;
    private RootsAdapter mRootsAdapter;

    /**
     * Location of null means that the list of roots is displayed.
     */
    private File mLocation;

    public static Intent createIntent(Context context, String initialDirectory, @Nullable String rootDirectory) {
        Intent intent = new Intent(context, FolderPickerActivity.class);

        if (!TextUtils.isEmpty(initialDirectory)) {
            intent.putExtra(EXTRA_INITIAL_DIRECTORY, initialDirectory);
        }

        if (!TextUtils.isEmpty(rootDirectory)) {
            intent.putExtra(EXTRA_ROOT_DIRECTORY, rootDirectory);
        }

        return intent;
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ((SyncthingApp) getApplication()).component().inject(this);

        setContentView(R.layout.activity_folder_picker);
        mListView = findViewById(android.R.id.list);
        mListView.setOnItemClickListener(this);
        mListView.setEmptyView(findViewById(android.R.id.empty));
        mFilesAdapter = new FileAdapter(this);
        mRootsAdapter = new RootsAdapter(this);
        mListView.setAdapter(mFilesAdapter);

        populateRoots();

        if (getIntent().hasExtra(EXTRA_INITIAL_DIRECTORY)) {
            displayFolder(new File(getIntent().getStringExtra(EXTRA_INITIAL_DIRECTORY)));
        } else {
            displayRoot();
        }

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            Toast.makeText(this, R.string.kitkat_external_storage_warning, Toast.LENGTH_LONG)
                    .show();
        }
    }

    /**
     * If a root directory is specified it is added to {@link #mRootsAdapter} otherwise
     * all available storage devices/folders from various APIs are inserted into
     * {@link #mRootsAdapter}.
     */
    @SuppressLint("NewApi")
    private void populateRoots() {
        ArrayList<File> roots = new ArrayList<>();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            roots.addAll(Arrays.asList(getExternalFilesDirs(null)));
            roots.remove(getExternalFilesDir(null));
        }

        String rootDir = getIntent().getStringExtra(EXTRA_ROOT_DIRECTORY);
        if (getIntent().hasExtra(EXTRA_ROOT_DIRECTORY) && !TextUtils.isEmpty(rootDir)) {
            roots.add(new File(rootDir));
        } else {
            roots.add(Environment.getExternalStorageDirectory());
            roots.add(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC));
            roots.add(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES));
            roots.add(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES));
            roots.add(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS));
            roots.add(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM));
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                roots.add(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS));
            }

            // Add paths that might not be accessible to Syncthing.
            SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
            if (sp.getBoolean("advanced_folder_picker", false)) {
                Collections.addAll(roots, new File("/storage/").listFiles());
                roots.add(new File("/"));
            }
        }
        // Remove any invalid directories.
        Iterator<File> it = roots.iterator();
        while (it.hasNext()) {
            File f = it.next();
            if (f == null || !f.exists() || !f.isDirectory()) {
                it.remove();
            }
        }

        mRootsAdapter.addAll(Sets.newTreeSet(roots));
    }

    @Override
    public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
        super.onServiceConnected(componentName, iBinder);
        SyncthingServiceBinder syncthingServiceBinder = (SyncthingServiceBinder) iBinder;
        syncthingServiceBinder.getService().registerOnServiceStateChangeListener(this);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        SyncthingService syncthingService = getService();
        if (syncthingService != null) {
            syncthingService.unregisterOnServiceStateChangeListener(this::onServiceStateChange);
        }
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        if (mListView.getAdapter() == mRootsAdapter)
            return true;

        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.folder_picker, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case R.id.create_folder:
                final EditText et = new EditText(this);
                AlertDialog dialog = new AlertDialog.Builder(this)
                        .setTitle(R.string.create_folder)
                        .setView(et)
                        .setPositiveButton(android.R.string.ok,
                                (dialogInterface, i) -> createFolder(et.getText().toString())
                        )
                        .setNegativeButton(android.R.string.cancel, null)
                        .create();
                dialog.setOnShowListener(dialogInterface -> ((InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE))
                        .showSoftInput(et, InputMethodManager.SHOW_IMPLICIT));
                dialog.show();
                return true;
            case R.id.select:
                Intent intent = new Intent()
                        .putExtra(EXTRA_RESULT_DIRECTORY, Util.formatPath(mLocation.getAbsolutePath()));
                setResult(Activity.RESULT_OK, intent);
                finish();
                return true;
            case android.R.id.home:
                finish();
                return true;
            default:
                return super.onOptionsItemSelected(item);
        }
    }

    /**
     * Creates a new folder with the given name and enters it.
     */
    private void createFolder(String name) {
        File newFolder = new File(mLocation, name);
        if (newFolder.mkdir()) {
            displayFolder(newFolder);
        } else {
            Toast.makeText(this, R.string.create_folder_failed, Toast.LENGTH_SHORT).show();
        }
    }

    /**
     * Refreshes the ListView to show the contents of the folder in {@code }mLocation.peek()}.
     */
    private void displayFolder(File folder) {
        mLocation = folder;
        mFilesAdapter.clear();
        File[] contents = mLocation.listFiles();
        // In case we don't have read access to the folder, just display nothing.
        if (contents == null)
            contents = new File[]{};

        Arrays.sort(contents, (f1, f2) -> {
            if (f1.isDirectory() && f2.isFile())
                return -1;
            if (f1.isFile() && f2.isDirectory())
                return 1;
            return f1.getName().compareTo(f2.getName());
        });

        for (File f : contents) {
            mFilesAdapter.add(f);
        }
        mListView.setAdapter(mFilesAdapter);
    }

    @Override
    public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
        @SuppressWarnings("unchecked")
        ArrayAdapter<File> adapter = (ArrayAdapter<File>) mListView.getAdapter();
        File f = adapter.getItem(i);
        if (f.isDirectory()) {
            displayFolder(f);
            invalidateOptions();
        }
    }

    private void invalidateOptions() {
        invalidateOptionsMenu();
    }

    private class FileAdapter extends ArrayAdapter<File> {

        public FileAdapter(Context context) {
            super(context, R.layout.item_folder_picker);
        }

        @Override
        @NonNull
        public View getView(int position, View convertView, @NonNull ViewGroup parent) {
            convertView = super.getView(position, convertView, parent);
            TextView title = convertView.findViewById(android.R.id.text1);
            File f = getItem(position);
            title.setText(f.getName());
            int textColor = (f.isDirectory())
                    ? android.R.color.primary_text_light
                    : android.R.color.tertiary_text_light;
            title.setTextColor(ContextCompat.getColor(getContext(), textColor));

            return convertView;
        }
    }

    private class RootsAdapter extends ArrayAdapter<File> {

        public RootsAdapter(Context context) {
            super(context, android.R.layout.simple_list_item_1);
        }

        @Override
        @NonNull
        public View getView(int position, View convertView, @NonNull ViewGroup parent) {
            convertView = super.getView(position, convertView, parent);
            TextView title = convertView.findViewById(android.R.id.text1);
            title.setText(getItem(position).getAbsolutePath());
            return convertView;
        }

        public boolean contains(File file) {
            for (int i = 0; i < getCount(); i++) {
                if (getItem(i).equals(file))
                    return true;
            }
            return false;
        }
    }

    /**
     * Goes up a directory, up to the list of roots if there are multiple roots.
     * <p>
     * If we already are in the list of roots, or if we are directly in the only
     * root folder, we cancel.
     */
    @Override
    public void onBackPressed() {
        if (!mRootsAdapter.contains(mLocation) && mLocation != null) {
            displayFolder(mLocation.getParentFile());
        } else if (mRootsAdapter.contains(mLocation) && mRootsAdapter.getCount() > 1) {
            displayRoot();
        } else {
            setResult(Activity.RESULT_CANCELED);
            finish();
        }
    }

    @Override
    public void onServiceStateChange(SyncthingService.State currentState) {
        if (!isFinishing() && currentState != SyncthingService.State.ACTIVE) {
            setResult(Activity.RESULT_CANCELED);
            finish();
        }
    }

    /**
     * Displays a list of all available roots, or if there is only one root, the
     * contents of that folder.
     */
    private void displayRoot() {
        mFilesAdapter.clear();
        if (mRootsAdapter.getCount() == 1) {
            displayFolder(mRootsAdapter.getItem(0));
        } else {
            mListView.setAdapter(mRootsAdapter);
            mLocation = null;
        }
        invalidateOptions();
    }

}
<commit_msg>Fix warning about missing write perm in root mode (fixes #1165) (#1166)<commit_after>package com.nutomic.syncthingandroid.activities;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.IBinder;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.content.ContextCompat;
import android.text.TextUtils;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

import com.google.common.collect.Sets;
import com.nutomic.syncthingandroid.R;
import com.nutomic.syncthingandroid.SyncthingApp;
import com.nutomic.syncthingandroid.service.Constants;
import com.nutomic.syncthingandroid.service.SyncthingService;
import com.nutomic.syncthingandroid.service.SyncthingServiceBinder;
import com.nutomic.syncthingandroid.util.Util;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;

/**
 * Activity that allows selecting a directory in the local file system.
 */
public class FolderPickerActivity extends SyncthingActivity
        implements AdapterView.OnItemClickListener, SyncthingService.OnServiceStateChangeListener {

    private static final String EXTRA_INITIAL_DIRECTORY =
            "com.nutomic.syncthingandroid.activities.FolderPickerActivity.INITIAL_DIRECTORY";

    private static final String EXTRA_ROOT_DIRECTORY =
            "com.nutomic.syncthingandroid.activities.FolderPickerActivity.ROOT_DIRECTORY";

    public static final String EXTRA_RESULT_DIRECTORY =
            "com.nutomic.syncthingandroid.activities.FolderPickerActivity.RESULT_DIRECTORY";

    public static final int DIRECTORY_REQUEST_CODE = 234;

    private ListView mListView;
    private FileAdapter mFilesAdapter;
    private RootsAdapter mRootsAdapter;

    /**
     * Location of null means that the list of roots is displayed.
     */
    private File mLocation;

    public static Intent createIntent(Context context, String initialDirectory, @Nullable String rootDirectory) {
        Intent intent = new Intent(context, FolderPickerActivity.class);

        if (!TextUtils.isEmpty(initialDirectory)) {
            intent.putExtra(EXTRA_INITIAL_DIRECTORY, initialDirectory);
        }

        if (!TextUtils.isEmpty(rootDirectory)) {
            intent.putExtra(EXTRA_ROOT_DIRECTORY, rootDirectory);
        }

        return intent;
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ((SyncthingApp) getApplication()).component().inject(this);

        setContentView(R.layout.activity_folder_picker);
        mListView = findViewById(android.R.id.list);
        mListView.setOnItemClickListener(this);
        mListView.setEmptyView(findViewById(android.R.id.empty));
        mFilesAdapter = new FileAdapter(this);
        mRootsAdapter = new RootsAdapter(this);
        mListView.setAdapter(mFilesAdapter);

        populateRoots();

        if (getIntent().hasExtra(EXTRA_INITIAL_DIRECTORY)) {
            displayFolder(new File(getIntent().getStringExtra(EXTRA_INITIAL_DIRECTORY)));
        } else {
            displayRoot();
        }

        Boolean prefUseRoot = PreferenceManager.getDefaultSharedPreferences(this).getBoolean(Constants.PREF_USE_ROOT, false);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && !prefUseRoot) {
            Toast.makeText(this, R.string.kitkat_external_storage_warning, Toast.LENGTH_LONG)
                    .show();
        }
    }

    /**
     * If a root directory is specified it is added to {@link #mRootsAdapter} otherwise
     * all available storage devices/folders from various APIs are inserted into
     * {@link #mRootsAdapter}.
     */
    @SuppressLint("NewApi")
    private void populateRoots() {
        ArrayList<File> roots = new ArrayList<>();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            roots.addAll(Arrays.asList(getExternalFilesDirs(null)));
            roots.remove(getExternalFilesDir(null));
        }

        String rootDir = getIntent().getStringExtra(EXTRA_ROOT_DIRECTORY);
        if (getIntent().hasExtra(EXTRA_ROOT_DIRECTORY) && !TextUtils.isEmpty(rootDir)) {
            roots.add(new File(rootDir));
        } else {
            roots.add(Environment.getExternalStorageDirectory());
            roots.add(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC));
            roots.add(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES));
            roots.add(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES));
            roots.add(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS));
            roots.add(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM));
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                roots.add(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS));
            }

            // Add paths that might not be accessible to Syncthing.
            SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
            if (sp.getBoolean("advanced_folder_picker", false)) {
                Collections.addAll(roots, new File("/storage/").listFiles());
                roots.add(new File("/"));
            }
        }
        // Remove any invalid directories.
        Iterator<File> it = roots.iterator();
        while (it.hasNext()) {
            File f = it.next();
            if (f == null || !f.exists() || !f.isDirectory()) {
                it.remove();
            }
        }

        mRootsAdapter.addAll(Sets.newTreeSet(roots));
    }

    @Override
    public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
        super.onServiceConnected(componentName, iBinder);
        SyncthingServiceBinder syncthingServiceBinder = (SyncthingServiceBinder) iBinder;
        syncthingServiceBinder.getService().registerOnServiceStateChangeListener(this);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        SyncthingService syncthingService = getService();
        if (syncthingService != null) {
            syncthingService.unregisterOnServiceStateChangeListener(this::onServiceStateChange);
        }
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        if (mListView.getAdapter() == mRootsAdapter)
            return true;

        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.folder_picker, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case R.id.create_folder:
                final EditText et = new EditText(this);
                AlertDialog dialog = new AlertDialog.Builder(this)
                        .setTitle(R.string.create_folder)
                        .setView(et)
                        .setPositiveButton(android.R.string.ok,
                                (dialogInterface, i) -> createFolder(et.getText().toString())
                        )
                        .setNegativeButton(android.R.string.cancel, null)
                        .create();
                dialog.setOnShowListener(dialogInterface -> ((InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE))
                        .showSoftInput(et, InputMethodManager.SHOW_IMPLICIT));
                dialog.show();
                return true;
            case R.id.select:
                Intent intent = new Intent()
                        .putExtra(EXTRA_RESULT_DIRECTORY, Util.formatPath(mLocation.getAbsolutePath()));
                setResult(Activity.RESULT_OK, intent);
                finish();
                return true;
            case android.R.id.home:
                finish();
                return true;
            default:
                return super.onOptionsItemSelected(item);
        }
    }

    /**
     * Creates a new folder with the given name and enters it.
     */
    private void createFolder(String name) {
        File newFolder = new File(mLocation, name);
        if (newFolder.mkdir()) {
            displayFolder(newFolder);
        } else {
            Toast.makeText(this, R.string.create_folder_failed, Toast.LENGTH_SHORT).show();
        }
    }

    /**
     * Refreshes the ListView to show the contents of the folder in {@code }mLocation.peek()}.
     */
    private void displayFolder(File folder) {
        mLocation = folder;
        mFilesAdapter.clear();
        File[] contents = mLocation.listFiles();
        // In case we don't have read access to the folder, just display nothing.
        if (contents == null)
            contents = new File[]{};

        Arrays.sort(contents, (f1, f2) -> {
            if (f1.isDirectory() && f2.isFile())
                return -1;
            if (f1.isFile() && f2.isDirectory())
                return 1;
            return f1.getName().compareTo(f2.getName());
        });

        for (File f : contents) {
            mFilesAdapter.add(f);
        }
        mListView.setAdapter(mFilesAdapter);
    }

    @Override
    public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
        @SuppressWarnings("unchecked")
        ArrayAdapter<File> adapter = (ArrayAdapter<File>) mListView.getAdapter();
        File f = adapter.getItem(i);
        if (f.isDirectory()) {
            displayFolder(f);
            invalidateOptions();
        }
    }

    private void invalidateOptions() {
        invalidateOptionsMenu();
    }

    private class FileAdapter extends ArrayAdapter<File> {

        public FileAdapter(Context context) {
            super(context, R.layout.item_folder_picker);
        }

        @Override
        @NonNull
        public View getView(int position, View convertView, @NonNull ViewGroup parent) {
            convertView = super.getView(position, convertView, parent);
            TextView title = convertView.findViewById(android.R.id.text1);
            File f = getItem(position);
            title.setText(f.getName());
            int textColor = (f.isDirectory())
                    ? android.R.color.primary_text_light
                    : android.R.color.tertiary_text_light;
            title.setTextColor(ContextCompat.getColor(getContext(), textColor));

            return convertView;
        }
    }

    private class RootsAdapter extends ArrayAdapter<File> {

        public RootsAdapter(Context context) {
            super(context, android.R.layout.simple_list_item_1);
        }

        @Override
        @NonNull
        public View getView(int position, View convertView, @NonNull ViewGroup parent) {
            convertView = super.getView(position, convertView, parent);
            TextView title = convertView.findViewById(android.R.id.text1);
            title.setText(getItem(position).getAbsolutePath());
            return convertView;
        }

        public boolean contains(File file) {
            for (int i = 0; i < getCount(); i++) {
                if (getItem(i).equals(file))
                    return true;
            }
            return false;
        }
    }

    /**
     * Goes up a directory, up to the list of roots if there are multiple roots.
     * <p>
     * If we already are in the list of roots, or if we are directly in the only
     * root folder, we cancel.
     */
    @Override
    public void onBackPressed() {
        if (!mRootsAdapter.contains(mLocation) && mLocation != null) {
            displayFolder(mLocation.getParentFile());
        } else if (mRootsAdapter.contains(mLocation) && mRootsAdapter.getCount() > 1) {
            displayRoot();
        } else {
            setResult(Activity.RESULT_CANCELED);
            finish();
        }
    }

    @Override
    public void onServiceStateChange(SyncthingService.State currentState) {
        if (!isFinishing() && currentState != SyncthingService.State.ACTIVE) {
            setResult(Activity.RESULT_CANCELED);
            finish();
        }
    }

    /**
     * Displays a list of all available roots, or if there is only one root, the
     * contents of that folder.
     */
    private void displayRoot() {
        mFilesAdapter.clear();
        if (mRootsAdapter.getCount() == 1) {
            displayFolder(mRootsAdapter.getItem(0));
        } else {
            mListView.setAdapter(mRootsAdapter);
            mLocation = null;
        }
        invalidateOptions();
    }

}

{'fraction_non_alphanumeric': 0.09699927555572502, 'fraction_numerical': 0.001906432302588935, 'mean_word_length': 2.4979994665244063, 'pattern_counts': {'":': 0, '<': 23, '<?xml version=': 0, '>': 35, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, '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': '379885', 'n_tokens_mistral': 7083, 'n_tokens_neox': 6866, 'n_words': 1444}