skadategems-dev 0.0.9
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +16 -0
- data/.travis.yml +12 -0
- data/Gemfile +13 -0
- data/Guardfile +8 -0
- data/LICENSE.txt +22 -0
- data/README.md +74 -0
- data/Rakefile +6 -0
- data/bin/skadate +9 -0
- data/bin/skadategems-dev +18 -0
- data/integration/README.md +97 -0
- data/integration/cli/skadate_init_spec.rb +86 -0
- data/integration/cli/skadate_spec.rb +50 -0
- data/integration/cli/spec_helper.rb +23 -0
- data/integration/lib/remote/download_file_spec.rb +136 -0
- data/integration/spec_helper.rb +101 -0
- data/lib/skadategems/dev/bundle.rb +115 -0
- data/lib/skadategems/dev/cli/remote.rb +364 -0
- data/lib/skadategems/dev/cli/skadate.rb +184 -0
- data/lib/skadategems/dev/remote.rb +170 -0
- data/lib/skadategems/dev/remote/clone_logger.rb +118 -0
- data/lib/skadategems/dev/remote/configs.rb +97 -0
- data/lib/skadategems/dev/remote/directory.rb +88 -0
- data/lib/skadategems/dev/remote/file.rb +157 -0
- data/lib/skadategems/dev/remote/includes/mysql_connect.php +8 -0
- data/lib/skadategems/dev/skadate.rb +30 -0
- data/lib/skadategems/dev/software_version.rb +66 -0
- data/lib/skadategems/dev/source_controller.rb +101 -0
- data/lib/skadategems/dev/version.rb +5 -0
- data/skadategems-dev.gemspec +33 -0
- data/spec/skadategems/dev/bundle_spec.rb +268 -0
- data/spec/skadategems/dev/cli/remote_spec.rb +623 -0
- data/spec/skadategems/dev/cli/skadate_spec.rb +182 -0
- data/spec/skadategems/dev/remote/clone_logger_spec.rb +324 -0
- data/spec/skadategems/dev/remote/directory_spec.rb +117 -0
- data/spec/skadategems/dev/remote/file_spec.rb +74 -0
- data/spec/skadategems/dev/remote_spec.rb +95 -0
- data/spec/skadategems/dev/skadate_spec.rb +62 -0
- data/spec/skadategems/dev/software_version_spec.rb +92 -0
- data/spec/skadategems/dev/source_controller_spec.rb +243 -0
- data/spec/spec_helper.rb +32 -0
- metadata +153 -0
@@ -0,0 +1,97 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module SkadateGems
|
4
|
+
module Dev
|
5
|
+
|
6
|
+
class Remote
|
7
|
+
|
8
|
+
# Represents remote application configurations accessor.
|
9
|
+
class Configs
|
10
|
+
|
11
|
+
# @param remote [Remote] skadate remote app
|
12
|
+
def initialize(remote)
|
13
|
+
@remote = remote
|
14
|
+
end
|
15
|
+
|
16
|
+
# Configuration value accessor method.
|
17
|
+
# @param section_path_or_id [Fixnum|String] config section path or `config_id`
|
18
|
+
def [](section_path_or_id)
|
19
|
+
case section_path_or_id.class.to_s
|
20
|
+
when 'String'
|
21
|
+
raise 'Getting remote configs by path is currently not implemented'
|
22
|
+
when 'Fixnum'
|
23
|
+
get_by_id(section_path_or_id)
|
24
|
+
else
|
25
|
+
raise ArgumentError, "unexpected `section_path_or_id` type #{section_path_or_id.class}"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# @param config_id [Fixnum] configuration id
|
30
|
+
def get_by_id(config_id)
|
31
|
+
response = @remote.exec include: 'mysql_connect' do |script|
|
32
|
+
script << <<-PHPSCRIPT
|
33
|
+
$result = $mysql->query("SELECT `name`, `value` FROM `".TBL_CONFIG."` WHERE `config_id`=#{config_id}");
|
34
|
+
$config = $result->fetch_object();
|
35
|
+
echo '{ ';
|
36
|
+
echo '"name": "'.$config->name.'", ';
|
37
|
+
echo '"value": '.$config->value;
|
38
|
+
echo ' }';
|
39
|
+
PHPSCRIPT
|
40
|
+
end
|
41
|
+
|
42
|
+
result = JSON.parse(response.body)
|
43
|
+
|
44
|
+
Config.new(@remote, config_id, result['name'], result['value'])
|
45
|
+
end
|
46
|
+
|
47
|
+
# Represents remote application config entry.
|
48
|
+
class Config
|
49
|
+
|
50
|
+
# @return [Fixnum] config entry id
|
51
|
+
attr_reader :id
|
52
|
+
|
53
|
+
# @return [String] config entry name
|
54
|
+
attr_reader :name
|
55
|
+
|
56
|
+
# @return [mixed] config entry value
|
57
|
+
attr_reader :value
|
58
|
+
|
59
|
+
# @return [nil | :updated | :not_modified] config value update status
|
60
|
+
attr_reader :update_status
|
61
|
+
|
62
|
+
# @param remote [Remote] remote skadate app
|
63
|
+
# @param id [Fixnum] config id
|
64
|
+
# @param name [String] config name
|
65
|
+
# @param value [mixed] config value
|
66
|
+
def initialize(remote, id, name, value)
|
67
|
+
@remote, @id, @name, @value = remote, id, name, value
|
68
|
+
end
|
69
|
+
|
70
|
+
# Perform remote application config value update.
|
71
|
+
# Changes the #update_status value.
|
72
|
+
# @param value [#to_json] json serializable value
|
73
|
+
def value=(value)
|
74
|
+
if value == @value
|
75
|
+
@update_status = :not_modified
|
76
|
+
return
|
77
|
+
end
|
78
|
+
|
79
|
+
response = @remote.exec(include: 'mysql_connect') do |script|
|
80
|
+
script << <<-PHPSCRIPT
|
81
|
+
$value = $mysql->real_escape_string('#{value.to_json.gsub(/['\\]/, '\\\\\0')}');
|
82
|
+
$mysql->query("UPDATE `".TBL_CONFIG."` SET `value`='$value' WHERE `config_id`=#{@id}");
|
83
|
+
echo $mysql->affected_rows;
|
84
|
+
PHPSCRIPT
|
85
|
+
end
|
86
|
+
|
87
|
+
@update_status = (response.body == '1') ? :updated : :not_modified
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'skadategems/dev/remote/file'
|
3
|
+
|
4
|
+
module SkadateGems
|
5
|
+
module Dev
|
6
|
+
|
7
|
+
class Remote
|
8
|
+
|
9
|
+
# Represents a remote directory.
|
10
|
+
class Directory < File
|
11
|
+
|
12
|
+
# @attr list_hidden [boolean]
|
13
|
+
attr_accessor :list_hidden
|
14
|
+
|
15
|
+
# @return [boolean] always true
|
16
|
+
def directory?
|
17
|
+
true
|
18
|
+
end
|
19
|
+
|
20
|
+
# @return [String] always '/'
|
21
|
+
def extname
|
22
|
+
'/'
|
23
|
+
end
|
24
|
+
|
25
|
+
# List remote directory files.
|
26
|
+
# @return [Array < Directory | File >] where each entry
|
27
|
+
# may be either a `Remote::Directory` or a `Remote::File` accessor object.
|
28
|
+
def list
|
29
|
+
path = self.path.dup
|
30
|
+
path[0] = '' if path[0] == '/'
|
31
|
+
path[-1] = '' if path[-1] == '/'
|
32
|
+
|
33
|
+
response = @remote.exec do |batch|
|
34
|
+
batch << <<-PHPSCRIPT
|
35
|
+
$dir = dir(DIR_SITE_ROOT . '#{path.gsub(/['\\]/, '\\\\\0')}');
|
36
|
+
|
37
|
+
$list = array();
|
38
|
+
while (false !== ($file = $dir->read())) {
|
39
|
+
if (#{if list_hidden
|
40
|
+
'$file === \'.\' || $file === \'..\''
|
41
|
+
else
|
42
|
+
'$file{0} === \'.\' && $file !== \'.htaccess\''
|
43
|
+
end}) {
|
44
|
+
continue;
|
45
|
+
}
|
46
|
+
|
47
|
+
$path = "$dir->path/$file";
|
48
|
+
|
49
|
+
if ($path === $_SERVER['SCRIPT_FILENAME']) {
|
50
|
+
continue;
|
51
|
+
}
|
52
|
+
|
53
|
+
if (is_file($path)) {
|
54
|
+
$type = 'f';
|
55
|
+
} elseif (is_dir($path)) {
|
56
|
+
$type = 'd';
|
57
|
+
} else {
|
58
|
+
continue;
|
59
|
+
}
|
60
|
+
|
61
|
+
$list[] = array($file, $type, filesize($path));
|
62
|
+
}
|
63
|
+
|
64
|
+
echo json_encode($list);
|
65
|
+
PHPSCRIPT
|
66
|
+
end
|
67
|
+
|
68
|
+
result = []
|
69
|
+
|
70
|
+
JSON.parse(response.body).each do |entry|
|
71
|
+
name, type, size = entry
|
72
|
+
_path = ::File.join(self.path, name)
|
73
|
+
|
74
|
+
result << if type == 'd'
|
75
|
+
Directory.new(@remote, _path, size)
|
76
|
+
else
|
77
|
+
File.new(@remote, _path, size)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
result.sort! { |a, b| a.basename <=> b.basename }
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,157 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'digest/md5'
|
3
|
+
require 'tempfile'
|
4
|
+
|
5
|
+
module SkadateGems
|
6
|
+
module Dev
|
7
|
+
|
8
|
+
class Remote
|
9
|
+
|
10
|
+
# Remote file accessor.
|
11
|
+
class File
|
12
|
+
|
13
|
+
# @return [String] relative path to a file
|
14
|
+
attr_reader :path
|
15
|
+
|
16
|
+
# @return [Fixnum] remote file size in bytes
|
17
|
+
attr_reader :size
|
18
|
+
|
19
|
+
def initialize(remote, path, size = nil)
|
20
|
+
@remote, @path, @size = remote, path, size
|
21
|
+
end
|
22
|
+
|
23
|
+
def directory?
|
24
|
+
false
|
25
|
+
end
|
26
|
+
|
27
|
+
# @return [String] remote file basename
|
28
|
+
def basename
|
29
|
+
::File.basename(@path)
|
30
|
+
end
|
31
|
+
|
32
|
+
# @return [String] remote file extension
|
33
|
+
def extname
|
34
|
+
::File.extname(@path)
|
35
|
+
end
|
36
|
+
|
37
|
+
# @return [String] human readable file size
|
38
|
+
def friendly_size
|
39
|
+
units = %W(B KiB MiB GiB TiB)
|
40
|
+
|
41
|
+
size, unit = units.reduce(@size.to_f) do |(fsize, _), type|
|
42
|
+
fsize > 512 ? [fsize / 1024, type] : (break [fsize, type])
|
43
|
+
end
|
44
|
+
|
45
|
+
"#{size > 9 || size.modulo(1) < 0.1 ? '%d' : '%.1f'} %s" % [size, unit]
|
46
|
+
end
|
47
|
+
|
48
|
+
# Request remote file to read it's contents.
|
49
|
+
# @yield [response] passes a http response to the block
|
50
|
+
def request(md5_checksum: nil, &block)
|
51
|
+
path = self.path.dup
|
52
|
+
path[0] = '' if path[0] == '/'
|
53
|
+
|
54
|
+
batch = ScriptBatch.new
|
55
|
+
|
56
|
+
batch << <<-PHPSCRIPT
|
57
|
+
$filename = DIR_SITE_ROOT . '#{path.gsub(/['\\]/, '\\\\\0')}';
|
58
|
+
|
59
|
+
if (!file_exists($filename)) {
|
60
|
+
header('HTTP/1.1 404 Not Found');
|
61
|
+
exit;
|
62
|
+
}
|
63
|
+
|
64
|
+
$md5_checksum = md5_file($filename);
|
65
|
+
PHPSCRIPT
|
66
|
+
|
67
|
+
if md5_checksum
|
68
|
+
raise ':md5_checksum must be a String' unless md5_checksum.is_a? String
|
69
|
+
|
70
|
+
batch << <<-PHPSCRIPT
|
71
|
+
if ($md5_checksum === '#{md5_checksum.gsub(/['\\]/, '\\\\\0')}') {
|
72
|
+
header('HTTP/1.1 304 Not Modified');
|
73
|
+
exit;
|
74
|
+
}
|
75
|
+
PHPSCRIPT
|
76
|
+
end
|
77
|
+
|
78
|
+
batch << <<-PHPSCRIPT
|
79
|
+
header('Content-Description: File Transfer');
|
80
|
+
header('Content-Type: application/octet-stream');
|
81
|
+
header('Content-Disposition: attachment; filename=' . basename($filename));
|
82
|
+
header('Content-Length: ' . filesize($filename));
|
83
|
+
header('X-Content-MD5-Checksum: ' . $md5_checksum);
|
84
|
+
ob_clean();
|
85
|
+
flush();
|
86
|
+
readfile($filename);
|
87
|
+
exit;
|
88
|
+
PHPSCRIPT
|
89
|
+
|
90
|
+
@remote.accessor.exec(batch) do |response|
|
91
|
+
if response.is_a?(Net::HTTPNotModified)
|
92
|
+
block.call(response)
|
93
|
+
elsif response.is_a?(Net::HTTPOK)
|
94
|
+
if response['content-disposition'] == "attachment; filename=#{basename}" &&
|
95
|
+
response['x-content-md5-checksum']
|
96
|
+
block.call(response)
|
97
|
+
else
|
98
|
+
raise 'unexpected response headers'
|
99
|
+
end
|
100
|
+
else
|
101
|
+
raise "[#{response.code} \"#{response.message}\"]"
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# Download and save a remote file as a given filename.
|
107
|
+
# @param filename [String] filename to save file as
|
108
|
+
# @yield [chunk_size] on each segment receive
|
109
|
+
# @yieldparam chunk_size [Fixnum] received segment size
|
110
|
+
def save_as(filename)
|
111
|
+
current_checksum = if ::File.exists?(filename)
|
112
|
+
Digest::MD5.file(filename).hexdigest
|
113
|
+
end
|
114
|
+
|
115
|
+
request(md5_checksum: current_checksum) do |response|
|
116
|
+
if response.is_a?(Net::HTTPNotModified)
|
117
|
+
return false
|
118
|
+
elsif response.is_a?(Net::HTTPOK)
|
119
|
+
@size = response['content-length'].to_i if @size.nil?
|
120
|
+
new_checksum = response['x-content-md5-checksum']
|
121
|
+
md5 = Digest::MD5.new
|
122
|
+
|
123
|
+
begin
|
124
|
+
tempfile = Tempfile.create([basename, extname])
|
125
|
+
response.read_body do |chunk|
|
126
|
+
tempfile << chunk
|
127
|
+
md5 << chunk
|
128
|
+
yield chunk.size if block_given?
|
129
|
+
end
|
130
|
+
tempfile.close
|
131
|
+
|
132
|
+
if md5.hexdigest != new_checksum
|
133
|
+
raise "md5 checksum mismatch:\n [#{md5.hexdigest}]\n [#{new_checksum}]"
|
134
|
+
end
|
135
|
+
|
136
|
+
parent_dir = ::File.dirname(filename)
|
137
|
+
FileUtils.mkdir_p(parent_dir) unless ::File.exists?(parent_dir)
|
138
|
+
|
139
|
+
FileUtils.mv(tempfile.path, filename)
|
140
|
+
FileUtils.chmod(0644, filename)
|
141
|
+
|
142
|
+
return true
|
143
|
+
ensure
|
144
|
+
tempfile.close unless tempfile.closed?
|
145
|
+
end
|
146
|
+
else
|
147
|
+
raise "unexpected response #{response}"
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
end
|
153
|
+
|
154
|
+
end
|
155
|
+
|
156
|
+
end
|
157
|
+
end
|
@@ -0,0 +1,8 @@
|
|
1
|
+
<?php
|
2
|
+
|
3
|
+
$mysql = new mysqli(DB_HOST, DB_USER, DB_PASS, DB_NAME) or die("Error " . mysqli_error($link));
|
4
|
+
if ($mysql->connect_error) {
|
5
|
+
die('Connect Error (' . $mysql->connect_errno . ') ' . $mysql->connect_error);
|
6
|
+
}
|
7
|
+
|
8
|
+
require_once DIR_INTERNALS . 'db_tbl.const.php';
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'skadategems/dev/source_controller'
|
2
|
+
require 'skadategems/dev/remote'
|
3
|
+
|
4
|
+
module SkadateGems
|
5
|
+
module Dev
|
6
|
+
|
7
|
+
# Represents Skadate development project that is located in a specified `:root_dir`.
|
8
|
+
class Skadate
|
9
|
+
|
10
|
+
attr_reader :root_dir
|
11
|
+
|
12
|
+
# @param root_dir [String] path to skadate source root directory
|
13
|
+
def initialize(root_dir)
|
14
|
+
@root_dir = root_dir
|
15
|
+
end
|
16
|
+
|
17
|
+
# @return [SourceController] skadate source controller object
|
18
|
+
def source
|
19
|
+
@source ||= SourceController.new(@root_dir)
|
20
|
+
end
|
21
|
+
|
22
|
+
# @return [Remote] remote application accessor
|
23
|
+
def remote
|
24
|
+
@remote ||= Remote.new(source.filename '.dev/remote-server.json')
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'date'
|
2
|
+
|
3
|
+
module SkadateGems
|
4
|
+
module Dev
|
5
|
+
|
6
|
+
# Represents Skadate software version
|
7
|
+
class SoftwareVersion
|
8
|
+
LIST, MAP = [], {}
|
9
|
+
|
10
|
+
attr_reader :major
|
11
|
+
attr_reader :minor
|
12
|
+
attr_reader :build
|
13
|
+
attr_reader :release_date
|
14
|
+
attr_reader :codename
|
15
|
+
|
16
|
+
# @param version [String] string version representation
|
17
|
+
# @param release_date [Date] version release date
|
18
|
+
# @param codename [String] software version codename
|
19
|
+
def initialize(version, release_date = nil, codename = nil)
|
20
|
+
@major, @minor, @build = version.split('.', 3).map { |n| n.to_i }
|
21
|
+
@release_date = release_date
|
22
|
+
@codename = codename
|
23
|
+
end
|
24
|
+
|
25
|
+
# @return [String] version string representation
|
26
|
+
def to_s
|
27
|
+
"#{@major}.#{@minor}.#{@build}"
|
28
|
+
end
|
29
|
+
|
30
|
+
# Versions equality operator
|
31
|
+
def == (other)
|
32
|
+
return other == to_s if other.instance_of?(String)
|
33
|
+
|
34
|
+
return false unless other.instance_of?(SoftwareVersion)
|
35
|
+
|
36
|
+
@major == other.major &&
|
37
|
+
@minor == other.minor &&
|
38
|
+
@build == other.build
|
39
|
+
end
|
40
|
+
|
41
|
+
# @return [boolean] true if current version is supported by `skadategems-dev`
|
42
|
+
def supported?
|
43
|
+
MAP.key?(to_s)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Raised in case of software version compatibility error.
|
47
|
+
# @see Dev::SourceController#version!
|
48
|
+
class CompatibilityError < StandardError; end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Skadate software supported versions.
|
52
|
+
[ ['9.0.2500', Date.new(2012, 1, 30)],
|
53
|
+
['9.0.2555', Date.new(2012, 5, 23)],
|
54
|
+
['9.2.2657', Date.new(2012, 10, 12)],
|
55
|
+
['9.2.2960', Date.new(2013, 4, 16), 'Master'],
|
56
|
+
['9.3.3144', Date.new(2013, 10, 8), 'Master II']
|
57
|
+
].each do |params|
|
58
|
+
version = SoftwareVersion.new(*params)
|
59
|
+
SoftwareVersion::LIST << version
|
60
|
+
SoftwareVersion::MAP[params[0]] = version
|
61
|
+
end
|
62
|
+
|
63
|
+
SoftwareVersion::LIST.freeze
|
64
|
+
SoftwareVersion::MAP.freeze
|
65
|
+
end
|
66
|
+
end
|