folder_stash 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.rdoc +66 -0
- data/lib/folder_stash/errors/branch_error.rb +18 -0
- data/lib/folder_stash/errors/no_directory_error.rb +17 -0
- data/lib/folder_stash/errors/tree_limit_exceeded_error.rb +28 -0
- data/lib/folder_stash/errors.rb +11 -0
- data/lib/folder_stash/file_usher.rb +164 -0
- data/lib/folder_stash/folder.rb +64 -0
- data/lib/folder_stash/folder_tree.rb +140 -0
- data/lib/folder_stash.rb +13 -0
- metadata +60 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 8d57c9ab806041af992961e83a9472a27836b63f0d46b42adfc9e2e04b35d860
|
4
|
+
data.tar.gz: 10d1be204348138ab0027ffedd519d0ab3cdd56a0733732ddb6d9f4bfef590fc
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 3f2810d1dd64f6e67dd7e5afa513bc0b71d2b00dfd306f028e2ad1ac598aea49b2906fdc0c9b342cc3e9a6fe3ccd87f85c9a87354f2eee850d95fb33f0a5e6fe
|
7
|
+
data.tar.gz: a9cb901842fc1052f09e620961cc95cee8314c9f2e7f1984bb9282698859cc49352f06769f1d8596ad5f19604ecfe6f45d8452443aa2f1dae8556edc068f66d2
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2019 Martin Stein
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
= folder_stash
|
2
|
+
|
3
|
+
The <tt>folder_stash</tt> gem will store files in a directory with a user
|
4
|
+
definable number of nested subdirectories in a given path and a maximum number
|
5
|
+
of items allowed per subdirectory.
|
6
|
+
|
7
|
+
New nested subdirectories will be created on demand as a given subdirectory
|
8
|
+
reaches the specified limit of items. All created subdirectories will have
|
9
|
+
randomized base names.
|
10
|
+
|
11
|
+
<tt>folder_stash</tt> uses a symlink (<tt>.current_store_path</tt>) to the
|
12
|
+
currently available directory. By default the symlink will be in the top level
|
13
|
+
storage directory, but it can optionally be placed in any directory.
|
14
|
+
|
15
|
+
== Installation
|
16
|
+
|
17
|
+
gem install folder_stash
|
18
|
+
|
19
|
+
== Usage
|
20
|
+
|
21
|
+
The basic usage is to create a new instance of FileUsher with the directory in
|
22
|
+
which files are to be stored in; the top level storage directory.
|
23
|
+
|
24
|
+
require 'folder_stash'
|
25
|
+
|
26
|
+
# create a new FileUsher instance with defaults (2 levels of subdirectories,
|
27
|
+
# 10000 items per subdirectory)
|
28
|
+
usher = FolderStash::FileUsher.new('~/storage_dir')
|
29
|
+
|
30
|
+
FileUsher will try to locate the <tt>.current_store_path</tt> symlink, either in
|
31
|
+
the top level directory, or, if any other location for the link passed as the
|
32
|
+
<tt>link_location</tt> option passed to the
|
33
|
+
initializer[rdoc-ref:FolderStash::FileUsher.new].
|
34
|
+
|
35
|
+
If the symlink does not exist, it will create a new branch (nested path) with
|
36
|
+
the number of nested subdirectories given in the <tt>nesting_levels</tt> option
|
37
|
+
passed to the initializer[rdoc-ref:FolderStash::FileUsher.new] and create the
|
38
|
+
symlink which will point to the terminal (most deeply nested) subdirectory.
|
39
|
+
|
40
|
+
storage_directory
|
41
|
+
├── .current_store_path -> ~/storage_dir/a1bd81a073a78025/2d9dfcd7a6c329b4
|
42
|
+
└── a1bd81a073a78025
|
43
|
+
└── 2d9dfcd7a6c329b4
|
44
|
+
|
45
|
+
If the symlink exists, FileUsher will use the existing subdirectory hierarchy.
|
46
|
+
|
47
|
+
Files can be copied or moved to the directory the symlink currently points to
|
48
|
+
using the {#copy}[rdoc-ref:FolderStash::FileUsher#copy] and
|
49
|
+
{#move}[rdoc-ref:FolderStash::FileUsher#move] methods respectively, which both
|
50
|
+
will return the path the file was stored to.
|
51
|
+
|
52
|
+
usher.copy('~/image1.jpg')
|
53
|
+
# => "storage_dir/a1bd81a073a78025/2d9dfcd7a6c329b4/image1.jpg"
|
54
|
+
|
55
|
+
usher.move('~/image2.jpg')
|
56
|
+
# => "storage_dir/a1bd81a073a78025/2d9dfcd7a6c329b4/image2.jpg"
|
57
|
+
|
58
|
+
The path returned will by default start with the top level storage directory. It
|
59
|
+
is possible to return the relative path or absolute path by passing the values
|
60
|
+
+:relative+ or +:absolute+ as the +pathtype+ option:
|
61
|
+
|
62
|
+
usher.copy('~/image3.jpg', :pathtype => :relative)
|
63
|
+
# => "path/to/storage_dir/a1bd81a073a78025/2d9dfcd7a6c329b4/image3.jpg"
|
64
|
+
|
65
|
+
usher.copy('~/image4.jpg', :pathtype => :absolute)
|
66
|
+
# => "/path/to/storage_dir/a1bd81a073a78025/2d9dfcd7a6c329b4/image4.jpg"
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FolderStash
|
4
|
+
module Errors
|
5
|
+
# Error that is raises when attempting to create a branch in a folder that
|
6
|
+
# can not be branched (typically the terminal).
|
7
|
+
class BranchError < StandardError
|
8
|
+
# Directory for the folder where the branch was attempted.
|
9
|
+
attr_reader :dir
|
10
|
+
|
11
|
+
def initialize(msg = nil, dir: nil)
|
12
|
+
@dir = dir
|
13
|
+
msg ||= "Can not branch in #{dir} because it is a tree terminal."
|
14
|
+
super msg
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FolderStash
|
4
|
+
module Errors
|
5
|
+
# Error that is raised if a directory does not exist.
|
6
|
+
class NoDirectoryError < StandardError
|
7
|
+
# Path for the directory that does not exist.
|
8
|
+
attr_reader :dir
|
9
|
+
|
10
|
+
def initialize(msg = nil, dir: nil)
|
11
|
+
@dir = dir
|
12
|
+
msg ||= "The directory #{dir} does not exist or is not a directory"
|
13
|
+
super msg
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FolderStash
|
4
|
+
module Errors
|
5
|
+
# Error that is raised when the number of items in a tree has exceeded the
|
6
|
+
# maximum number allowed in a tree.
|
7
|
+
class TreeLimitExceededError < StandardError
|
8
|
+
# Number of subdirectories in a given path (branch) of the tree.
|
9
|
+
attr_reader :subdirs
|
10
|
+
|
11
|
+
# Number of items allowed in a subdirectory.
|
12
|
+
attr_reader :subdir_limit
|
13
|
+
|
14
|
+
# Total number of items allowed in a tree.
|
15
|
+
attr_reader :tree_limit
|
16
|
+
|
17
|
+
def initialize(msg = nil, tree: nil)
|
18
|
+
@subdirs = tree.path_length
|
19
|
+
@subdir_limit = tree.folder_limit
|
20
|
+
@tree_limit = tree.tree_limit
|
21
|
+
msg ||= 'The storage tree has reached the limit of allowed items:'\
|
22
|
+
" #{subdir_limit} items in #{subdirs} subdirectories"\
|
23
|
+
" (#{tree_limit} allowd items in total)."
|
24
|
+
super msg
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,164 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FolderStash
|
4
|
+
# FileUsher stores files in a directory tree with a defined maximum number of
|
5
|
+
# #items per directory.
|
6
|
+
#
|
7
|
+
# It will create new subdirectories to store files in if a subdirectory has
|
8
|
+
# reached the maximum number of items.
|
9
|
+
class FileUsher
|
10
|
+
CURRENT_STORE_PATH = '.current_store_path'
|
11
|
+
|
12
|
+
# The working directory where all subdirectories and files are stored.
|
13
|
+
attr_reader :directory
|
14
|
+
|
15
|
+
# An instance of FolderTree.
|
16
|
+
attr_reader :tree
|
17
|
+
|
18
|
+
# Returns a new instance.
|
19
|
+
#
|
20
|
+
# ===== Arguments
|
21
|
+
#
|
22
|
+
# * +dir+ (_String_) - path for the #directory.
|
23
|
+
#
|
24
|
+
# ===== Options
|
25
|
+
#
|
26
|
+
# * <tt>nesting_levels</tt> - the number of subdirectories below #directory
|
27
|
+
# in the path of files that are stored (_default_: +2+).
|
28
|
+
# * <tt>folder_limit</tt> - the maximum number of items allowed per
|
29
|
+
# directory (_default_: +10000+).
|
30
|
+
# * <tt>link_location</tt> - the directory where the #current_directory
|
31
|
+
# symlink is stored. When not specified, the symlink will be in #directory
|
32
|
+
#
|
33
|
+
# Setting <tt>nesting_levels</tt> to +nil+ will also set the
|
34
|
+
# <tt>folder_limit</tt>. Conversely, setting <tt>folder_limit</tt> to +nil+
|
35
|
+
# also set <tt>nesting_levels</tt> to +nil+.
|
36
|
+
def initialize(dir, **opts)
|
37
|
+
raise Errors::NoDirectoryError, dir: dir unless File.directory? dir
|
38
|
+
|
39
|
+
@options = { nesting_levels: 2, folder_limit: 10_000 }.update(opts)
|
40
|
+
@directory = dir
|
41
|
+
@current_directory = File.join @options.fetch(:link_location, directory),
|
42
|
+
CURRENT_STORE_PATH
|
43
|
+
|
44
|
+
@tree = init_existing || init_new(nesting_levels)
|
45
|
+
link_target
|
46
|
+
end
|
47
|
+
|
48
|
+
# Copies +file+ to linked path.
|
49
|
+
def copy(file, pathtype: :tree)
|
50
|
+
path = store_path(file)
|
51
|
+
File.open(path, 'wb') { |f| f.write(File.new(file).read) }
|
52
|
+
file_path path, pathtype
|
53
|
+
end
|
54
|
+
|
55
|
+
# Returns the full path (_String_) to the current directory symlink.
|
56
|
+
def current_directory
|
57
|
+
File.expand_path @current_directory
|
58
|
+
end
|
59
|
+
|
60
|
+
# Returns the full directory path for #current_folder
|
61
|
+
def current_path
|
62
|
+
current_folder.path
|
63
|
+
end
|
64
|
+
|
65
|
+
# The number of items allowed in any directory in a nested directory path.
|
66
|
+
#
|
67
|
+
# Will be +nil+ if #nesting_levels is +nil+.
|
68
|
+
def folder_limit
|
69
|
+
return unless @options.fetch :nesting_levels
|
70
|
+
|
71
|
+
@options.fetch :folder_limit
|
72
|
+
end
|
73
|
+
|
74
|
+
# Returns the directory path the #current_directory symlink points to.
|
75
|
+
def linked_path
|
76
|
+
File.readlink current_directory
|
77
|
+
end
|
78
|
+
|
79
|
+
# Moves +file+ to the #linked_path.
|
80
|
+
def move(file, pathtype: :tree)
|
81
|
+
path = store_path(file)
|
82
|
+
FileUtils.mv File.expand_path(file), store_path(file)
|
83
|
+
file_path path, pathtype
|
84
|
+
end
|
85
|
+
|
86
|
+
# The number of nested subdirectories.
|
87
|
+
def nesting_levels
|
88
|
+
return unless @options.fetch :folder_limit
|
89
|
+
|
90
|
+
tree&.path_length || @options.fetch(:nesting_levels)
|
91
|
+
end
|
92
|
+
|
93
|
+
private
|
94
|
+
|
95
|
+
# Returns the next available folder in #tree. Will raise OutOfStorageError
|
96
|
+
# if none is available.
|
97
|
+
def available_folder
|
98
|
+
folder = tree.available_folder
|
99
|
+
raise Errors::TreeLimitExceededError, tree: tree unless folder
|
100
|
+
|
101
|
+
folder
|
102
|
+
end
|
103
|
+
|
104
|
+
# Returns +true+ if the current directory symlink exists in #directory.
|
105
|
+
def current_directory?
|
106
|
+
File.exist? current_directory
|
107
|
+
end
|
108
|
+
|
109
|
+
# Returns a Folder that is currently the FolderTree#terminal.
|
110
|
+
def current_folder
|
111
|
+
tree.terminal
|
112
|
+
end
|
113
|
+
|
114
|
+
# Returns the path for +path+ as +:absolute+, +:relative+, or only the
|
115
|
+
# nested subdirectories in the +:tree+.
|
116
|
+
def file_path(path, pathtype)
|
117
|
+
treepath = tree.branch_path.append(File.basename(path))
|
118
|
+
case pathtype
|
119
|
+
when :absolute
|
120
|
+
File.realpath path
|
121
|
+
when :relative
|
122
|
+
treepath[0] = directory
|
123
|
+
treepath.join('/')
|
124
|
+
when :tree
|
125
|
+
treepath.join('/')
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
# Creates the folder #tree in an existing directory with #current_directory
|
130
|
+
# symlink.
|
131
|
+
def init_existing
|
132
|
+
return unless current_directory?
|
133
|
+
|
134
|
+
FolderTree.for_path linked_path, root: directory, limit: folder_limit
|
135
|
+
end
|
136
|
+
|
137
|
+
# Creates the folder #tree for a new direcrory without #current_directory
|
138
|
+
# symlink.
|
139
|
+
def init_new(levels)
|
140
|
+
FolderTree.empty directory, levels: levels, limit: folder_limit
|
141
|
+
end
|
142
|
+
|
143
|
+
# Creates the current_directory symlink, pointing to the #current_path.
|
144
|
+
def link_target
|
145
|
+
return if current_directory? && linked_path == current_path
|
146
|
+
|
147
|
+
FileUtils.ln_s File.expand_path(current_path), current_directory
|
148
|
+
end
|
149
|
+
|
150
|
+
# Returns the next available path (_String_) for a file to be stored under.
|
151
|
+
def store_path(file)
|
152
|
+
update_link if folder_limit && current_folder.count >= folder_limit
|
153
|
+
File.join current_directory, File.basename(file)
|
154
|
+
end
|
155
|
+
|
156
|
+
# Creates new subdirectories points the #current_directory symlink to the
|
157
|
+
# new #current_folder.
|
158
|
+
def update_link
|
159
|
+
tree.new_branch_in available_folder
|
160
|
+
FileUtils.rm current_directory
|
161
|
+
link_target
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FolderStash
|
4
|
+
# A Folder represents a directory in the filesystem.
|
5
|
+
class Folder
|
6
|
+
# Basename for the folder.
|
7
|
+
attr_reader :basename
|
8
|
+
|
9
|
+
# Absolute path for the folder.
|
10
|
+
attr_reader :path
|
11
|
+
|
12
|
+
# Returns a new instance.
|
13
|
+
#
|
14
|
+
# ===== Arguments
|
15
|
+
#
|
16
|
+
# * +path+ (String) - path to the directory for the folder.
|
17
|
+
def initialize(path)
|
18
|
+
@path = File.expand_path path
|
19
|
+
@basename = File.basename path
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.folders_for_path_segment(root, segment)
|
23
|
+
root_folder = Folder.new root
|
24
|
+
segment.inject([root_folder]) do |dirs, dir|
|
25
|
+
path = File.join dirs.last.path, dir
|
26
|
+
dirs << Folder.new(path)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Returns the number of visible files in the folder.
|
31
|
+
def count
|
32
|
+
entries.count
|
33
|
+
end
|
34
|
+
|
35
|
+
# Creates the directory path in the immediate parent.
|
36
|
+
def create
|
37
|
+
FileUtils.mkdir path unless exist?
|
38
|
+
end
|
39
|
+
|
40
|
+
# Creates the directory #path with all parents.
|
41
|
+
def create!
|
42
|
+
FileUtils.mkdir_p path unless exist?
|
43
|
+
end
|
44
|
+
|
45
|
+
# Returns +true+ if the directory #path exists.
|
46
|
+
def exist?
|
47
|
+
File.exist? path
|
48
|
+
end
|
49
|
+
|
50
|
+
# Returns a list of entries (files or folders) in the folder.
|
51
|
+
#
|
52
|
+
# ===== Options
|
53
|
+
#
|
54
|
+
# * <tt>include_hidden</tt>
|
55
|
+
# * +true+ - list visible and hidden entries.
|
56
|
+
# * +false+ (_default_) - list only visible entries.
|
57
|
+
def entries(include_hidden: false)
|
58
|
+
children = Dir.children path
|
59
|
+
return children if include_hidden == true
|
60
|
+
|
61
|
+
children.reject { |entry| entry.start_with? '.' }
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FolderStash
|
4
|
+
# A FolderTree represents a nested directory path.
|
5
|
+
class FolderTree
|
6
|
+
# An array with instances of Folder, one for each directory in a nested
|
7
|
+
# directory path from the #root to the #terminal.
|
8
|
+
attr_accessor :folders
|
9
|
+
|
10
|
+
# The maximum number of itmes that may be stored in any folder in #folders.
|
11
|
+
attr_reader :folder_limit
|
12
|
+
|
13
|
+
# The number of items (directories) in a nested directory path, from the
|
14
|
+
# #root to the #terminal.
|
15
|
+
attr_reader :path_length
|
16
|
+
|
17
|
+
attr_reader :tree_limit
|
18
|
+
|
19
|
+
# Returns a new instance.
|
20
|
+
#
|
21
|
+
# ===== Arguments
|
22
|
+
#
|
23
|
+
# * +folders+ (String) - Array of Folder instances.
|
24
|
+
# * +levels+ (Integer) - Number of nested subdirectories in a path.
|
25
|
+
# * +limit+ (Integer) - Number of items allowed in any folder in the
|
26
|
+
# tree's directory path.
|
27
|
+
def initialize(folders, levels, limit)
|
28
|
+
@folders = folders
|
29
|
+
@path_length = levels
|
30
|
+
@folder_limit = limit
|
31
|
+
@tree_limit = folder_limit ? folder_limit**(path_length + 1) : nil
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.empty(root, levels:, limit:)
|
35
|
+
folders = [Folder.new(root)]
|
36
|
+
tree = new(folders, levels, limit)
|
37
|
+
tree.new_branch_in tree.root, levels
|
38
|
+
tree
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.for_path(path, root:, limit:)
|
42
|
+
path_items = path_segment path, root
|
43
|
+
folders = Folder.folders_for_path_segment root, path_items
|
44
|
+
new folders, path_items.count, limit
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.path_segment(terminal, root)
|
48
|
+
File.expand_path(terminal).split('/') - File.expand_path(root).split('/')
|
49
|
+
end
|
50
|
+
|
51
|
+
# Returns the number of folder in the nested path currently available.
|
52
|
+
def actual_path_length
|
53
|
+
folders.count
|
54
|
+
end
|
55
|
+
|
56
|
+
# Returns the next available folder, searching upstream from the terminal
|
57
|
+
# folder to the #root.
|
58
|
+
#
|
59
|
+
# Returns #root if root is the only folder.
|
60
|
+
def available_folder
|
61
|
+
return root if flat?
|
62
|
+
|
63
|
+
folders.reverse.find { |folder| folder.count < folder_limit }
|
64
|
+
end
|
65
|
+
|
66
|
+
def branch_path
|
67
|
+
folders.map(&:basename)
|
68
|
+
end
|
69
|
+
|
70
|
+
def flat?
|
71
|
+
actual_path_length == 1 && path_length.nil?
|
72
|
+
end
|
73
|
+
|
74
|
+
# Returns the number (integer) of levels of folders nested in +folder+.
|
75
|
+
def levels_below(folder)
|
76
|
+
return if flat?
|
77
|
+
|
78
|
+
path_length - folders.index(folder)
|
79
|
+
end
|
80
|
+
|
81
|
+
# Creates a new branch of folders in +folder+ and updates #folders to the
|
82
|
+
# new branch.
|
83
|
+
#
|
84
|
+
# Returns an array with the full path for the terminal folder in the branch
|
85
|
+
# created.
|
86
|
+
def new_branch_in(folder, levels = nil)
|
87
|
+
return if flat?
|
88
|
+
|
89
|
+
raise Errors::BranchError, dir: folder.path if folder == terminal
|
90
|
+
|
91
|
+
raise TreeLimitExceededError, tree: self if folder.count >= folder_limit
|
92
|
+
|
93
|
+
levels ||= levels_below folder
|
94
|
+
new_branch = new_paths_in folder, levels
|
95
|
+
@folders = folders[0..folders.index(folder)].concat new_branch
|
96
|
+
folders.last.create!
|
97
|
+
end
|
98
|
+
|
99
|
+
# Returns the root folder.
|
100
|
+
def root
|
101
|
+
folders.first
|
102
|
+
end
|
103
|
+
|
104
|
+
# Returns the terminal (most deeply nested) folder.
|
105
|
+
#
|
106
|
+
# Returns +nil+ if the tree has not been fully initialized with a branch.
|
107
|
+
def terminal
|
108
|
+
return root if flat?
|
109
|
+
|
110
|
+
return if actual_path_length < path_length
|
111
|
+
|
112
|
+
folders.last
|
113
|
+
end
|
114
|
+
|
115
|
+
private
|
116
|
+
|
117
|
+
# If the file <tt>path/name</tt> exists, randomize the name until a new
|
118
|
+
# random is found that does not exist.
|
119
|
+
#
|
120
|
+
# Returns the new unique path name.
|
121
|
+
#
|
122
|
+
# This only needs to be called for the first new directory to be created,
|
123
|
+
# all others will be created in empty directories and therefroe always be
|
124
|
+
# unique.
|
125
|
+
def ensure_unique_node(path, name)
|
126
|
+
name = SecureRandom.hex(8) while File.exist? File.join(path, name)
|
127
|
+
Folder.new File.join(path, name)
|
128
|
+
end
|
129
|
+
|
130
|
+
# Returns an array of new Folder instances.
|
131
|
+
def new_paths_in(folder, count)
|
132
|
+
first_node = ensure_unique_node(folder.path, SecureRandom.hex(8))
|
133
|
+
remainder = count - 1
|
134
|
+
remainder.times.inject([first_node]) do |nodes|
|
135
|
+
path = File.join nodes.last.path, SecureRandom.hex(8)
|
136
|
+
nodes << Folder.new(path)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
data/lib/folder_stash.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'fileutils'
|
4
|
+
require 'securerandom'
|
5
|
+
|
6
|
+
require_relative 'folder_stash/errors'
|
7
|
+
require_relative 'folder_stash/file_usher'
|
8
|
+
require_relative 'folder_stash/folder'
|
9
|
+
require_relative 'folder_stash/folder_tree'
|
10
|
+
|
11
|
+
# Module that contains the FileUsher, FolderTree, and FolderTree classes.
|
12
|
+
module FolderStash
|
13
|
+
end
|
metadata
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: folder_stash
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Martin Stein
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2019-08-15 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: |
|
14
|
+
The <tt>folder_stash</tt> gem will store files in a directory with a user
|
15
|
+
definable number of nested subdirectories in a given path and a maximum
|
16
|
+
number of items allowed per subdirectory.
|
17
|
+
|
18
|
+
New nested subdirectories will be created on demand as a given subdirectory
|
19
|
+
reaches the specified limit of items. All created subdirectories will have
|
20
|
+
randomized base names.
|
21
|
+
email: loveablelobster@fastmail.fm
|
22
|
+
executables: []
|
23
|
+
extensions: []
|
24
|
+
extra_rdoc_files: []
|
25
|
+
files:
|
26
|
+
- LICENSE
|
27
|
+
- README.rdoc
|
28
|
+
- lib/folder_stash.rb
|
29
|
+
- lib/folder_stash/errors.rb
|
30
|
+
- lib/folder_stash/errors/branch_error.rb
|
31
|
+
- lib/folder_stash/errors/no_directory_error.rb
|
32
|
+
- lib/folder_stash/errors/tree_limit_exceeded_error.rb
|
33
|
+
- lib/folder_stash/file_usher.rb
|
34
|
+
- lib/folder_stash/folder.rb
|
35
|
+
- lib/folder_stash/folder_tree.rb
|
36
|
+
homepage: https://github.com/loveablelobster/folder_stash
|
37
|
+
licenses:
|
38
|
+
- MIT
|
39
|
+
metadata: {}
|
40
|
+
post_install_message:
|
41
|
+
rdoc_options: []
|
42
|
+
require_paths:
|
43
|
+
- lib
|
44
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - ">="
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '0'
|
49
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
requirements: []
|
55
|
+
rubygems_version: 3.0.3
|
56
|
+
signing_key:
|
57
|
+
specification_version: 4
|
58
|
+
summary: Keeps the number of files per directory within a limit by autogenerating
|
59
|
+
subdirectories.
|
60
|
+
test_files: []
|